Übung: Kubernetes
- Werkzeuge installieren
- Cluster anlegen
- kubectl-Konfiguration herunterladen
- Pod deployen
- Applikation im Internet freigeben
- Gesundheitszustand der App in Betracht ziehen
- App konfigurieren
- Geheimnisse
- Mehrere Instanzen
- Aufräumen
- Fehlermeldungen
- Sonstiges
- Fußnoten
Werkzeuge installieren
-
ibmcloud
zur Interaktion mit der IBM Cloud: cloud.ibm.com -
ibmcloud
Container Service plugin:$ ibmcloud plugin install container-service
-
kubectl
zur Interaktion mit Ihrem k8s-Cluster: kubernetes.io (nurkubectl
) -
jq
zur Verarbeitung von JSON auf der Kommandozeile: stedolan.github.io/jq
Cluster anlegen
Benutzen Sie Ihren IBM Cloud Account, den Sie in der Vorbereitung zur Übung angelegt haben:
$ ibmcloud login
Jetzt können Sie einen neuen Kubernetes-Cluster anlegen, den Sie für die folgenden Übungen verwenden können:
$ ibmcloud ks cluster create classic --name webservices-lab
Erfahrungsgemäß dauert dieser Schritt ca. 15 min.
Applikation kennenlernen
Während Ihr Cluster angelegt wird, können Sie sich mit der App plaintoot
, die wir in dieser Übung deployen wollen, vertraut machen:
- Lesen Sie das README
-
Starten Sie
plaintoot
in einer lokalen Docker-Umgebung:$ docker run --rm -it suhlig/plaintoot /app/plaintoot print https://chaos.social/@nixCraft@mastodon.social/111108182085516402 Reminder | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄| | Don't Push To Production On Friday | |_________________| \ (•◡•) / \ / —— | | |_ |_ -- nixCraft@chaos.social
Sie können jede anderen öffentliche Mastoton-URL verwenden.
kubectl
-Konfiguration herunterladen
Warten Sie, bis der Status Ihres Clusters normal
ist:
$ ibmcloud ks cluster ls
OK
Name ID State …
webservices-lab c908493f0hqeguesbo3g normal …
Jetzt können Sie die Cluster-Konfiguration herunterladen. kubectl
verwendet diese, um mit dem Cluster (von Ihrem Rechner aus) zu interagieren:
$ ibmcloud ks cluster config --cluster webservices-lab
OK
The configuration for webservices-lab was downloaded successfully.
Falls Sie zur Verwaltung lieber eine Web-Applikation verwenden, können Sie Ihren frisch angelegten Cluster auswählen und dem Link “Kubernetes Dashboard” 1 folgen.
Pod deployen
Ein Pod als kleinste in Kubernetes handhabbare Einheit muss mindestens einen Container beinhalten, der den Applikationsprozeß darstellt. Beschreiben Sie das gewünschte Ergebnis in pod.yml
:
apiVersion: v1
kind: Pod
metadata:
name: plaintoot
labels: { app: plaintoot }
spec:
containers:
- name: plaintoot
image: suhlig/plaintoot
command: [ /plaintoot ]
args: [ serve ]
ports: [ containerPort: 8080 ]
Die Angabe
containerPort
dokumentiert lediglich den Port, auf dem die Applikation (innerhalb des Containers) auf Verbindungen wartet. Entscheidend ist, auf welchem Port der Anwendungsprozeß auf Anfragen wartet.
Gewünschten Zustand beschreiben
Teilen Sie Ihrem Cluster den gewünschten Zustand mit:
$ kubectl apply -f pod.yml
pod/plaintoot created
Wirklichen Zustand beobachten
Der von Ihnen gewünschte Zustand wird sich nicht sofort einstellen - je nach Komplexität der Beschreibung und Auslastung des Clusters können einige Sekunden vergehen, bis aus dem Wunsch Wirklichkeit geworden ist.
Beobachten Sie den wirklichen Zustand (incl. Übergang zwischen den Pod-Phasen):
$ kubectl get pods
NAME READY STATUS …
plaintoot 1/1 Running …
Sie können das gleiche Kommando auch mit dem Parameter --watch
aufrufen, um die Veränderungen kontinuierlich zu beobachten.
Fragen zur Kontrolle
-
Was passiert, wenn Sie einen Schreibfehler in der Image-Referenz
plaintoot
haben, z.B.image: suhlig/keintweet
und das mitkubectl apply
anwenden? -
Wie lautet die Versionsnummer von
plaintoot
, die Sie deployt haben?
Applikation im Internet freigeben
Jetzt ist die App zwar gestartet, aber noch nicht von außen (per Internet) erreichbar. Dafür wird eine Resource vom Typ Service
benötigt, die sie in service.yml
beschreiben können:
apiVersion: v1
kind: Service
metadata:
name: plaintoot-service
spec:
type: NodePort
ports:
- name: http
port: 9090
targetPort: 8080
selector:
app: plaintoot
$ kubectl apply -f service.yml
Die Verbindung zwischen einem “freigegebenen” (exposed) Port und den Pods, die die Anfragen beantworten, wird über den selector
gesteuert. Hier werden alle eingehenden Anfragen an Port 9090
zu den Pods (auf deren Port 8080
) weitergeleitet, die das Label app
mit dem Wert plaintoot
haben. Sie können diese Entscheidung mit folgendem Befehl nachvollziehen:
$ kubectl get pods --selector app=plaintoot
NAME READY STATUS …
plaintoot 1/1 Running …
Um die App zu erreichen, benötigen Sie die öffentliche IP Ihres Clusters sowie den extern sichtbaren Port (nodePort
):
-
Öffentliche IP Ihres Clusters abfragen:
$ ibmcloud ks worker ls --cluster webservices-lab --output json \ | jq --raw-output '.[].publicIP'
-
nodePort
Ihres Services herausfinden:$ kubectl get service plaintoot-service -o json \ | jq --raw-output '.spec.ports[].nodePort'
Jetzt können Sie die öffentliche URL Ihrer App aufrufen:
$ curl http://$public-ip:$port
Beispiel:
$ curl http://159.122.183.244:32733
Serves a plain-text representation of a single tweet via HTTP
🎉 Herzlichen Glückwunsch 🎉, Sie haben erfolgreich eine Web-Applikation auf Kubernetes deployt und für die Öffentlichkeit zur Verfügung gestellt!
Gesundheitszustand der App in Betracht ziehen
Kubernetes kann Ihre Applikation in regelmäßigen Abständen auf ihre Gesundheit (liveness probe
) überprüfen und mögliche Probleme durch einen Neustart des Pods (und damit der Container) lösen.
Es gibt mehrere Wege für eine App, diese Frage zu beantworten. Für eine Web-Applikation wie plaintoot
bietet sich eine Abfrage mit HTTP an.
liveness probe
deklarieren
apiVersion: v1
kind: Pod
metadata:
name: plaintoot
labels:
app: plaintoot
spec:
containers:
- name: plaintoot
image: suhlig/plaintoot
command: [ /plaintoot ]
args: [ serve ]
ports: [ containerPort: 8080 ]
livenessProbe:
httpGet:
path: /liveness
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
Teilen Sie Ihrem Cluster den gewünschten neuen Zustand mit dem obigen Inhalt in pod-with-liveness-probe.yml
mit. Kubernetes wird die Aktualisierung des vorhandenen Pods aber verweigern:
$ kubectl apply -f pod-with-liveness-probe.yml
The Pod "plaintoot" is invalid…
Eine Aktualisierung von Pods ist nur eingeschränkt möglich (pets vs. cattle2). Eine bessere Lösung wäre z.B. ein Deployment
, mit dem dieses und noch andere Probleme gelöst werden. Das ist Thema eines späteren Kapitels.
Vorerst können Sie den Pod manuell löschen und erneut anlegen:
$ kubectl delete pod plaintoot
$ kubectl apply -f pod-with-liveness-probe.yml
Pod-Fehler simulieren
Um einen Fehlerzustand innerhalb der Applikation zu simulieren, kann plaintoot
mit der Umgebungsvariable MAX_UPTIME
angewiesen werden, nach Ablauf dieser Zeitspanne einen Fehler zu berichten. Kubernetes wird einen Neustart des Pods erzwingen, falls die Antwort der liveness probe
einen Statuscode ergibt, der nicht 200
ist.
apiVersion: v1
kind: Pod
metadata:
name: plaintoot
labels:
app: plaintoot
spec:
containers:
- name: plaintoot
image: suhlig/plaintoot
command: [ /plaintoot ]
args: [ serve ]
ports: [ containerPort: 8080 ]
livenessProbe:
httpGet:
path: /liveness
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
env:
- name: MAX_UPTIME
value: 60s
-
Löschen Sie den Pod und legen Sie ihn mit der oben stehenden aktualisierten Konfiguration erneut an:
$ kubectl delete pod plaintoot $ kubectl apply -f pod-with-liveness-probe-failing.yml
-
Beobachten Sie die Zustandsübergänge des
plaintoot
-Pods:$ kubectl get pods --selector app=plaintoot --watch
-
Beobachten Sie parallel dazu (in einem zweiten Terminal-Fenster oder -Tab) die Log-Nachrichten des Pods:
$ kubectl logs --selector app=plaintoot --follow
Achtung: Da die plaintoot
-App deterministisch nach der konfigurierten Zeit in den Zustand unhealthy
geht, wird Kubernetes die Neustarts immer länger hinauszögern:
After containers in a Pod exit, the kubelet restarts them with an exponential back-off delay (10s, 20s, 40s, …), that is capped at five minutes. Once a container has executed for 10 minutes without any problems, the kubelet resets the restart backoff timer for that container.
Sie können weitere Möglichkeiten nutzen, um sich über den Zustand des Pods zu informieren:
-
Schauen Sie sich die Beschreibung des Pods an:
$ kubectl describe pod --selector app=plaintoot
-
Nur Ereignisse:
$ kubectl get events --field-selector involvedObject.name=plaintoot
App konfigurieren
Um ihren Sinn zu erfüllen und die Text-Version eines Tweets mithilfe der Twitter-API abzurufen, muss die Applikation konfiguriert werden. Im bisherigen Zustand gibt die Applikation eine Fehlermeldung aus, sobald Sie versuchen, einen Tweet (hier mit der ID 20
) zu erfragen:
$ curl http://$public-ip:$port/20
Error: oauth2: cannot fetch token: 403 Forbidden
Unsere Application ist zwar healthy
(antwortet grundsätzlich per HTTP), aber nicht ready
(kann ihre Aufgabe erfüllen), weil die Twitter-API nur mit API-Key und -Secret erreichbar ist. Diese Konfiguration erwartet die plaintoot
-Anwendung in der Umgebungsvariablen $TWITTER_BEARER_TOKEN
; bisher haben wir aber nichts dergleichen konfiguriert.
Bevor wir den eigentlichen Fehler (fehlende Konfigurationsparameter) beheben, müssen wir zunächst dafür sorgen, daß die Applikation im unkonfigurierten Zustand den richtigen Fehler zeigt und Kubernetes darauf aufmerksam macht, daß die Applikation noch keine Anfragen erhalten soll.
readiness probe
hinzufügen
Eine Applikation in einem Fehlerzustand, der nicht durch einen Neustart behoben werden kann, sollte keine Anfragen per HTTP erhalten3. Um Kubernetes diesen Zustand mitzuteilen, können Sie eine readiness probe
konfigurieren:
apiVersion: v1
kind: Pod
metadata:
name: plaintoot
labels:
app: plaintoot
spec:
containers:
- name: plaintoot
image: suhlig/plaintoot
command: [ /plaintoot ]
args: [ serve ]
ports: [ containerPort: 8080 ]
readinessProbe:
httpGet:
path: /readiness
port: 8080
initialDelaySeconds: 5
timeoutSeconds: 1
Beschreiben Sie den gewünschten Zustand:
$ kubectl apply -f pod-with-readiness-probe.yml
Beachten Sie, daß wir die liveness-Probe vorerst wieder entfernt haben.
Zustandsübergänge beobachten
-
Beobachten Sie die Zustandsübergänge des
plaintoot
-Pods:$ kubectl get pods --selector app=plaintoot --watch
-
Beobachten Sie parallel dazu (in einem zweiten Terminal-Fenster oder -Tab) die Log-Nachrichten des Pods:
$ kubectl logs --selector app=plaintoot --follow
Fragen zur Kontrolle
-
Welchen Zustand erreicht der oben beschriebene Pod (mit
readiness probe
, aber ohne die nötigen Umgebungsvariablen), und warum?
Geheimnisse
Der Wert der oben besprochenen Umgebungsvariablen TWITTER_BEARER_TOKEN
soll möglichst geheim bleiben; im Idealfall hat nur der Pod selbst darauf Zugriff. Statt die Werte im Klartext zu konfigurieren (siehe oben), können wir die wertvollen Geheimnisse separat verwalten.
Geheimnis anlegen
$ kubectl create secret generic twitter-credentials \
--from-literal twitter_bearer_token=deadbeef
Konzeptionell legen Sie damit eine Resource vom Typ Secret
an. Dafür wäre eigentlich das gleiche kubectl apply
wie für einen Pod möglich:
apiVersion: v1
kind: Secret
metadata:
name: twitter-credentials
type: Opaque
stringData:
TWITTER_BEARER_TOKEN: deadbeef
Machen Sie sich aber bitte bewusst, daß Sie in diesem Fall ein wertvolles Geheimnis in eine Datei auf Ihrer lokalen Festplatte schreiben.
Weitere Möglichkeiten, um die Werte für Secrets zu lesen, finden Sie mit der Hilfe: kubectl create secret generic -h
.
Geheimnis verwenden
Jetzt ist das Geheimnis im Cluster4 bekannt, und wir können es in der Definition des Pods verwenden:
apiVersion: v1
kind: Pod
metadata:
name: plaintoot
labels:
app: plaintoot
spec:
containers:
- name: plaintoot
image: suhlig/plaintoot
command: [ /plaintoot ]
args: [ serve ]
ports: [ containerPort: 8080 ]
env:
- name: TWITTER_BEARER_TOKEN
valueFrom:
secretKeyRef:
name: twitter-credentials
key: twitter_bearer_token
livenessProbe:
httpGet:
path: /liveness
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /readiness
port: 8080
initialDelaySeconds: 5
timeoutSeconds: 1
Fragen zur Kontrolle
-
Warum ist das Ablegen eines Geheimnisses in einer lokalen Datei keine gute Idee? Was könnte schiefgehen?
-
Wie schützt Kubernetes Geheimnisse in einem Cluster vor unberechtigtem Zugriff?
Mehrere Instanzen
Jetzt haben wir eine Instanz unserer Applikation. Kubernetes wird sie neu starten, wenn sie unerwartet beendet wird, aber nicht unendlich oft. Außerdem ist die Anwendung zwischen den Neustarts nicht verfügbar.
In der Vorlesung haben wir das Konzept von Deployments kennengelernt, womit sich diese Nachteile beheben lassen.
Gewünschten Zustand beschreiben
apiVersion: apps/v1
kind: Deployment
metadata:
name: plaintoot
spec:
replicas: 5
selector:
matchLabels:
app: plaintoot
template:
metadata:
labels:
app: plaintoot
spec:
containers:
- name: plaintoot
image: suhlig/plaintoot
command: [ /plaintoot ]
args: [ serve ]
ports: [ containerPort: 8080 ]
livenessProbe:
httpGet:
path: /liveness
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /readiness
port: 8080
initialDelaySeconds: 5
timeoutSeconds: 1
resources:
requests:
memory: "10Mi"
cpu: "10m"
limits:
memory: "20Mi"
cpu: "100m"
$ kubectl apply -f deployment.yml
Wirklichen Zustand beobachten
$ kubectl get deployments
$ kubectl get pods
Fragen zur Kontrolle
-
Was ist am Betreiben eines einzelnen Pods problematisch?
-
Beschreiben Sie stichwortartig die Vorgänge, die nach der Änderung des Deployments
- von
replicas: 3
aufreplicas: 5
sowie - von
replicas: 3
aufreplicas: 1
ablaufen:
- von
Aufräumen
Wenn Sie den Cluster nicht mehr benötigen, sollten Sie ihn löschen:
$ ibmcloud ks cluster rm --cluster webservices-lab
Beachten Sie bitte, daß Cluster, die Ihnen im Rahmen der Vorlesung zur Verfügung gestellt wurden, nach spätestens 30 Tagen automatisch gelöscht werden.
Fehlermeldungen
-
Unable to connect to the server: failed to refresh token: oauth2: cannot fetch token: 400 Bad Request
Die auf Ihrem Rechner gespeicherte
$KUBECONFIG
(~/.kube/config
) ist nicht mehr gültig (bzw. das darin enthaltene Token). Aktualisieren Sie diese mit dem Kommando:$ ibmcloud ks cluster config --cluster webservices-lab
Gegebenenfalls müssen Sie sich erneut mit
ibmcloud login
authentifizieren. -
Etwas eleganteres Warten, bis der Cluster bereit ist (macOS):
$ while [[ "$(ibmcloud ks cluster get --cluster webservices-lab --output json | jq --raw-output .state | tee .cluster-state)" != "normal" ]]; do echo "$(gdate --rfc-3339=s) $(< .cluster-state)" sleep 1 done osascript -e 'display notification "Los gehts!" with title "Cluster ist bereit"' rm .cluster-state
-
Statt Benutzername und Passwort können Sie sich auch mit einem API-Key bei der IBM Cloud anmelden:
-
Anlegen (einmalig):
$ ibmcloud iam api-key-create "$USER@$(hostname)" Creating API key suhlig@lux under ac1c1612b0ef457692995a7bbcc67709 as s+webservices20211006@uhlig.it... OK
-
Verwenden:
$ export IBMCLOUD_API_KEY=******** $ ibmcloud login # Benutzername und Passwort nicht nötig
Natürlich sollten Sie auch diesen Schlüssel geheimhalten!
-
-
Was ist so besonders am Tweet mit der ID
20
? -
Lesenswert:
Fußnoten
-
siehe auch die Dokumentation des Kubernetes Dashboards ↩
-
Alternativ können Sie diesen Zustand auch innerhalb der Applikation behandeln. ↩
-
eigentlich nur innerhalb des Namespaces, aber in dieser Übung arbeiten wir der Einfachheit halber immer im
default
-Namespace. ↩