services

Kubernetes

Von Clouds und anderen zentralen Infrastrukturen

In letzter Zeit kam das Thema Cloud häufiger in meiner Umgebung vor, als Virus oder Bit.

Wenn man dann erstmal anfängt, darüber nachzudenken, was das Wort bedeutet, kommt man schnell auf die Frage, wie ist Cloud eigentlich definiert. Und da fängt es schnell an, wolkig zu werden. Es gibt zwar Definitionen, aber die sind schwammig und bedeuten alles und nichts. Die einen kennen eine Private Cloud und meinen damit zumeist eine Umgebung mit Hosts und VMs. Die andere Seite spricht gerne auch mal von “Cloud-nativ” und meint damit Serverless-Systeme, bei denen jeder Request einen Script ausführt und dessen Ausführungszeit haargenau abgerechnet wird. Was eine Cloud ist - ist also beliebig wolkig zu sehen.

Ich bin nicht wirklich glücklich damit, zumal mich das Marketingdeutsch gewaltig stört. Mein Auto hat ‘ne Batterie. Ist es deshalb nicht auch ein E-Auto?…

Kubernetes

Taucht man etwas tiefer in die Welt der Cloud-afinen Menschen ein, beginnt sich der Wald etwas zu lichten. In der Realität muss man nämlich konkreter werden und da geht es dann schnell um die Themen, die wir auch einfach beim richtigen Namen nennen können. Nur sind die nicht so griffig. Ziel ist es, die Services maximal von einander zu trennen. Beim Thema Host+VMs handelt es sich um Virtualisierung. Hier kann man relativ einfach pro VM einen Service definieren. Der Vorteil ist. Der Service hat seine gesamte Landschaft, die ihm behagt, um sich herum gestrickt. Der Nachteil ist, dass der gesamte Overhead eines kompletten Betriebssystem jedesmal mitkommt. So wird aus einem simplen Java-Prozess gleich ein Thema um Kernel Updates, Firewalls und ich weiß nicht was.

Da der Overhead für große Umgebungen irgendwann unwirtschaftlich war und die Linuxwelt schon lange eine Alternative anbot, entwickelte sich mit der Zeit das Thema Paravirtualisierung zu einer besseren Lösung. Als Docker Inc. dann noch mit verhältnismäßig simplen Tools für CLI um die Ecke kam, war das Thema Cloud langsam geboren und benannt. Paravirtualisierung trennt die Services durch Prozessisolation. Ein Programm A kann in seiner Isolationsschicht nicht auf Programm B zugreifen, wenn dieser sich (leienhaft ausgedrückt) nicht im gleichen Context befindet. Was also eine CPU bei der Virtualisierung mit VMs macht, kann auch der Linux-Kernel auf Prozessebene. Er trennt die Programme hart voneinander. Durch den Einsatz von Images (eigentlich TAR-Balls) kann jeder Service in einen Container verpackt werden und liefert sein Ökosystem mit (also alle Libs, die ein Programm so während der Laufzeit braucht). Das OS wird aber nicht wirklich gestartet. Nur das Programm. Nun ist <a href="Docker ansich schon schön - aber auch hier gilt wieder das Problem - je mehr Services man betreibt, desto unübersichtlicher wird es.

Also fingen einige Leute an, ein System um Docker zu stricken. Sie nutzen die Ausführungsmöglichkeiten und die Tools um eine Management Umgebung drumherum zu bauen und fertig war Kubernetes.

Installation

Gefühlt ist die Installation von Kubernetes einfach… wenn man weiß, wie. In der Realität gibt es diverse Haken und Ösen. Benutzt man Docker als Unterbau, ist das Leben relativ einfach, weil sich das Ökosystem Kubernetes um Docker aufbaut. Da Docker aber langsam stirbt und die diversen Probleme ziemlich nerven, würde man gerne auf was anderes setzen. Mein erster Gedanke war Podman. Leider braucht Kubernetes einen Dienst, gegen den er sich verbinden kann und da kam Podman anfangs nicht in Frage. Deshalb hab ich die Alternative Basis CRI-O eingesetzt. Letztlich wrappen Docker und Podman CRI-O. Da Docker nun auch schon langsam sinkt, bedient man sich dem daraus geschaffenen Industriestandard containerd.

Ich hab zwei Wege ausprobiert, um Kubernetes zu installieren.

Do-it-yourself

Der Ansible Script, den ich gebaut habe, tut eigentlich nicht viel mehr als folgendes:

  • Repository http://apt.kubernetes.io/ hinzufügen
  • Repository http://ppa.launchpad.net/projectatomic/ppa/ubuntu hinzufügen
  • kubelet, kubectl, kubeadm, kubernetes-cni , cri-o-1.15 installieren
  • overlay und br_netfilter Module im Kernel laden (dauerhaft)
  • IPv4 und IPv6 Forward in der Firewall aktivieren (dauerhaft)
  • CRI-O so konfigurieren, dass es Overlay benutzt, den Pfad auf conmon richtig setzt
  • die Docker.Io und interne Registry erlaubt
  • außerdem muss für Kubelet noch konfiguriert werden, dass es CRI-O verwenden soll und Systemd

Auf enem dem Master-Node wird dann mit kubeadm init .. der Cluster gestartet und das Netzwerk vorbereitet. Die einzelnen Nodes joinen dann den Cluster mit kubeadmin join und einem Token.

Dieses Basissetup ist dann erstmal ein Cluster. Es gibt eine Reihe von Services, die dem kube-system hinzugefügt werden sollten.

Ich gebe zu, Kubernetes alleine ist im produktiven Einsatz schwierig zu handhaben. Der Umgang mit den Yaml-Configs, die man “apply”‘n muss, ist gewöhnungsbedürftig. Deshalb kamen später auch Managementtools, wie OpenShift auf, um einem Admin das Leben in produktiven Umgebungen zu erleichtern.

Kubespray

Da ich sowieso überall und nirgends Ansible verwende, liefert eine Suche im Netz relativ schnell kubespray ans Tageslicht. Kubespray ist ein spezielles Repository mit einem eigenen Ökosystem für Kubernetes. Es richtet nicht nur Kubernetes an sich ein, es bereitet die Systeme auch entsprechend vor. Leider lässt es sich schlecht in eigene Ansible Repositories integrieren. Das ist aber auch so ziemlich der einzige Nachteil.

Folgende Schritte sind zu erledigen.

Anschließend dann das Playbook cluster.yml ausführen. Es wird ein etcd installiert. Dann wird der ausgewählte container manager ausgeführt (in unserem Fall containerd). Dann wird Kubernetes konfiguriert

Konfiguration

Anschließend kann man das Kubernetes Dashboard installieren.

Die Konfigdatei mit curl https://raw.githubusercontent.com/kubernetes/dashboard/master/aio/deploy/recommended.yaml > kubernetes-dashboard-deployment.yml herunterladen. Die Datei enthält diverse fürs Dashboard notwendige Deklarationen. Eine davon ist der Service kubernetes-dashboard. An diesem stellt man den spec-type Nodeport ein.

...
kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  ports:
    - port: 443
      targetPort: 8443
  selector:
    k8s-app: kubernetes-dashboard
  type: NodePort
...

Der Sinn dahinter ist, dass damit das Dashboard auf allen Nodes im Kubernetes auf einen Port erreichbar ist und keinen Ingress braucht. Die Datei dann mit kubectl apply -f kubernetes-dashboard-deployment.yml einfügen.

Anschließend muss ein Admin-Account angelegt werden. Das ist etwas seltsam nur mit Tokens.

# acoby-admin.yml
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: acoby-admin
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: acoby-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
  - kind: ServiceAccount
    name: acoby-admin
    namespace: kube-system

Das auch einbinden mit kubectl apply -f acoby-admin.yml. Dann das Token extrahieren.

SA_NAME="acoby-admin"
kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep ${SA_NAME} | awk '{print $1}')

Denn relevanten Ports auf den Nodes findet man mit

kubectl get service -n kubernetes-dashboard

Dann brauchen wir einen Ingress Controller. Den gibt es hier

 kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.43.0/deploy/static/provider/baremetal/deploy.yaml

Ein Beispiel Service auf Basis von HTTPBin konfiguriert man wie folgt.

 kubectl apply -f https://github.com/istio/istio/raw/master/samples/httpbin/httpbin.yaml

Damit ist der Service im Kubernetes ausgerollt. Um ihn über den Ingress Controller erreichbar zu machen, muss folgende Definition eingefügt werden.

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: httpbin-ingress
  namespace: default
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: httpbin.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: httpbin
                port:
                  number: 8000

Relevant ist hier der Host und sein Name. Über die Domain httpbin.example.com werden alle Anfragen an den Ingress an den Service httpbin auf Port 8000 weitergeleitet.

Hier ein paar hilfreiche Kommandos

kubectl get ing
kubectl get svc -n ingress-nginx

Der letzte Befehl zeigt uns den Quellport für unseren Kubernetes Ingress. Dieser ist von außen erreichbar.

Am Ende noch ein Beispiel für NFS. Die Einbindung für persistente Volumes ist trivial.

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs
spec:
  capacity:
    storage: 200Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: nfs
  nfs:
    path: /mnt/md1/shares/kubernetes
    server: 192.168.10.2
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
  labels:
    demo: nfs-pvc
spec:
  storageClassName: nfs
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 200Gi

Dabei wird das NFS Share /mnt/md1/shares/kubernetes vom NFS Server 192.168.10.2 mit 200GB als PersistentVolume und Claim dem Cluster bekannt gemacht.

Nützliche Befehle

Hier eine Liste nützlicher Befehle

kubectl cluster-info

Zeigt einem den Status des Kubernetes Clusters an. Wichtig finde ich den Befehl, weil er den Einstiegspunkt (Master) aufzeigt.

kubectl completion bash > /etc/bash_completion.d/kubectl 

Erzeugt die Bash-Completion und erstellt eine entsprechende Datei für Bash (gibts auch für zsh). Danach einmal neu einloggen und das Leben mit kubectl ist deutlich einfacher.

kubectl get services --all-namespaces

Die Services in Kubernetes sind in Namespaces gruppiert. Mit diesem Befehl kann man sich alle Namespaces und die darin definierten Services anzeigen lassen. Sehr wichtig für den ersten Überblick.

kubectl get pods --all-namespaces

Mindestens genauso interessant ist die Liste aller Pods. In jedem Namespace, zu jedem Service gibt es eine Reihe von Pods. Jeder Pod enthält einen oder mehreren Container (also Docker-Prozess).

kubectl get secrets

Liefert eine Liste aller Tokens, die in dem Cluster im Namespace default konfiguriert sind. Kann man mit –all-namespace auch auf alle Namespaces anwenden.

kubectl get secrets default-token-....

Liefert dem Default-Token. Eigentlich das gleiche, wie get secret. Mit folgendem Befehl kann man sich aber das Zertifikat ziehen, was man zB. braucht, um Gitlab zu füttern.

kubectl get secret default-token-... -o jsonpath="{['data']['ca\.crt']}" | base64 --decode

Liefert das Zertifkat des Defaulttokens. Das wird zB. für Gitlab gebraucht.

kubectl apply -f patch.yaml

Mit dem Apply Parameter kann man den Kubernetes Cluster konfigurieren. Das ist ein hochgradig wichtiger Befehl, der so richtig nervig ist. Denn die Daten im YAML Format sind alles andere als hübsch und schlecht beschrieben. Beispiele folgen weiter unten.

kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab-admin | awk '{print $1}')

Damit bekommt man eine Token für den externen Zugriff aufs System. Der innere Kubectl Befehl listet alle Tokens auf, grepped den User bzw. Tokenname, den man will und der zweite äußere Befehl zeigt mit describe secret die Daten darin an.

kubectl logs --namespace gitlab-managed-apps install-helm

Damit kann man sich die Logs eines Pods anschauen.

kubectl exec -it --namespace default shell-demo /bin/bash

Mit dem Befehl kann man in einen Pod einsteigen und einen Befehl ausführen. Das -it verursacht, dass der Befehl nicht gleich wieder beendet wird (also wirklich ein TTY öffnet). Ist sehr ähnlich zu docker/podman exec.

heml repo add stable https://kubernetes-charts.storage.googleapis.com
helm repo update
helm repo remove stable

Helm ist eine Art Meta-CLI-Befehl. Damit kann man in den Cluster einige vorgefertigte Pakete installieren. Ziel ist die Vereinfachung der Installation und des Managements. Ist wie ein Paketmanager zu betrachten. Die Pakete liegen als Charts vor. Meiner Meinung nach ein verwirrender Begriff.

Linksammlung

Hier finden sich einige nützliche und interessante Links an, die sich im Laufe der Recherche angesammelt haben.

Kubernetes & Ansible

Mal genauer reinschauen:

bootcamp2.yaml

apiVersion: apps/v1 kind: Deployment metadata: name: kubernetes-bootcamp labels: app: bootcamp spec: selector: matchLabels: app: bootcamp template: metadata: labels: app: bootcamp spec: containers: - name: bootcamp image: gcr.io/google-samples/kubernetes-bootcamp:v1 ports: - containerPort: 8080

apiVersion: v1 kind: Service metadata: name: bootcamp-service spec: type: ClusterIP selector: app: bootcamp ports:

  • port: 8080 targetPort: 8080

04 helm repo list 07 helm repo update 20 kubectl get pods 64 kubectl get pods -o wide 22 kubectl get nodes 30 kubectl get deployments 56 kubectl get services 57 kubectl get svc 26 kubectl proxy 44 kubectl describe pods 33 kubectl apply -f bootcamp.yml 42 kubectl exec kubernetes-bootcamp-fcc5bfb48-48pvr - sh 52 curl http://localhost:8001/api 53 curl http://localhost:8001/api/v1/namespaces/default/pods/kubernetes-bootcamp-fcc5bfb48-48pvr 54 curl http://localhost:8001/api/v1/namespaces/default/pods/kubernetes-bootcamp-fcc5bfb48-48pvr/proxy 55 curl http://localhost:8001/api/v1/namespaces/default/pods/kubernetes-bootcamp-fcc5bfb48-48pvr/proxy/ 65 more /run/flannel/subnet.env

120 kubectl get services my-nginx 121 kubectl delete services my-nginx 122 kubectl delete services hellok8s-service 123 kubectl delete services kubernetes 124 pico test.yaml 125 kubectl delete deployments.apps hellok8s-deployment 126 kubectl create -f test.yaml 127 kubectl get all 128 kubectl get all 129 kubectl get all 130 kubectl port-forward service/hellok8s-service 8080:8080 131 more test.yaml 132 kubectl get all 133 kubectl port-forward service/bootcamp-service 8080:8080 134 ll 135 ls -al 136 curl https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/namespace.yaml 137 curl https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/metallb.yaml 138 kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/namespace.yaml 139 kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.9.3/manifests/metallb.yaml 140 kubectl create secret generic -n metallb-system memberlist –from-literal=secretkey="$(openssl rand -base64 128)" 141 kubectl get all -n metallb-system 142 kubectl get all -n metallb-system 143 pico metallbconfig.yaml 144 kubectl apply -f metallbconfig.yaml 145 ls -al 146 more bootcamp2.yml 147 history