Kubernetes

Spickzettel für Container-Orchestrier Kubernetes (kurz: K8s).

Übersicht über Container-Orchestrier:

Links:

TODO Terraform, siehe: https://youtu.be/gpmerrSpbHg?t=809

Befehle

Siehe:

Status sehen

Cluster-Informationen sehen:

kubectl cluster-info

Deployments auflisten:

kubectl get deployments

Cluster-Nodes auflisten:

kubectl get nodes

Pods auflisten:

kubectl get pods [-L<label-name>] [-o wide]

Volumes auflisten (pv = persisted volume):

kubectl get pv

Alles auflisten:

kubectl get all --all-namespaces

Detail-Info zu einem Deployment:

kubectl describe deployments <deployment-name>

Detail-Info zu einem Pod:

kubectl describe pods <pod-id>

Noch mehr Infos zu einem Pod:

kubectl get pod <pod-id> --output=yaml | less

Log eines Pods anschauen:

kubectl logs <pod-id>

Log eines Containers in einem Deployment anschauen (geht auch wenn Start failed):

kubectl logs deployment/<deployment-id> <container-name>

Aktuelle Konsolenausgabe eines Pods anschauen:

kubectl attach <pod-id>

Detail-Info zu einem Service:

kubectl describe services <service-name>

Status von Deployment-Rollouts zeigen:

kubectl rollout status deployment <deployment-name>

Historie von Deployment-Rollouts zeigen (zeigt Versionen, zu denen ein Rollback möglich ist):

kubectl rollout history deployment/<deployment-name> [--revision=<revision-number>]

Deployment neustarten:

kubectl rollout restart deployment <deployment-name>

Debugging

Port-Forward (Cluster-Port auf lokalen Port forwarden):

kubectl port-forward <pod-id> <local port>:<container port>

Kommando in einem Container ausführen:

kubectl exec [-it] <pod-id> [-c <container name>] <command> [<args...>]

Deployments ändern

Deployment in Cluster hinzufügen oder ändern:

kubectl apply -f ./deployment.yaml

Deployment löschen:

kubectl delete deployment <deployment-name>

Cluster direkt ändern (ohne Deployment-Beschreibung)

Service erzeugen (Deployment für externes Netzwerk verfügbar machen):

kubectl expose deployment <deployment-name> --type=<NodePort|LoadBalancer|...> [--port=<external port>] [--target-port=<container port>] [--name <service-name>]

Pod labeln:

kubectl label [--overwrite] <pod-id|...> <key>=<value> ...

Node labeln:

kubectl label node <node-name> <label-key>=<label-value>

Image als Pods starten (schnell und ohne Deployment-Beschreibung):

kubectl run <pod-name> --image=<image-name> --replicas=1000 [--port=<container port>]

Bash als Pods starten (schnell und ohne Deployment-Beschreibung):

kubectl run -it --rm busybox --image=busybox -- bash

Image eines Containers in einem Deployment ändern (z.B. Upgrade auf neue Version):

kubectl set image deployment/<deployment-name> <container-name>=<image-name>:<image-version>

Replicas skalieren:

kubectl scale --replicas=2000 <pod-name>
kubectl scale --replicas=0 deployment/<deployment-name>

Rolling Update:

kubectl rolling-update <pod-name> --image:<image-name>:<image-version>

Rollback eines Updates:

kubectl rolling-update <pod-name> --rollback

Terminologie

Übersicht (von Khtan66, CC BY-SA 4.0, via Wikimedia Commons): K8s architecture

Begriff Definition
Node (Minion, Worker) Ein Rechner auf dem K8s läuft, um Pods auszuführen.
Cluster Mehrere Nodes die zu einem Cluster verbunden wurden.
Master Ein Rechner auf dem die K8s Control Components (TODO) installiert sind. Der Master überwacht die Nodes eines Clusters und orchestriert diese (verteilt Container auf die einzelnen Nodes und verbindet Container, die von einander abhängen). 
kubectl Kommandozeilen-Tool um Cluster zu steuern oder seinen Status abzufragen. Auch "Kube control" oder "Kube cuddle" (TODO) genannt
Deployment High-Level-Definition einer Applikation
Pod Kleinste lauffähige Einheit eines Deployments. Also ein Container oder eine Gruppe von Containern, die zusammen auf einem Minion laufen.
Service Fasst mehrere Pods des Clusters als ein Service zusammen. Je nach Typ erbeitet ein Service z.B. als Cluster-interner Load-Balancer (Default, type: ClusterIP) oder als externer Load-Balancer (type: LoadBalancer). Siehe Abschnitt "Service beschreiben"
Label Ein Key/Value-Paar, das einem Objekt in einem Deployment (z.B. einem Pod) zugeordnet ist. Labels helfen Objekte zu organisieren und leichter zu identifizieren.
Selector Ein Ausdruck, über den Objekte anhand ihrer Labels ausgewählt werden. Details siehe Abschnitt "Labels und Selektoren".

Komponenten von K8s:

Komponente Aufgabe
API server Rest/JSON-Service über den das Cluster von außen überwacht und gesteuert werden kann
etcd Eine verteilte Key/Value-Datenbank in der K8s seinen Zustand persistiert. Wird über alle Nodes im Cluster repliziert (TODO Stimmt das so?)
Scheduler Verteilt Arbeit oder Container auf die einzelnen Minions.
Controller Überwachen das Cluster und reagieren, wenn Nodes, Container oder Endpoints nicht mehr laufen.
Container runtime Führt Container aus. Das ist üblicherweise Docker, K8s unterstützt auch andere container runtimes wie z.B. Rocket
kubelet Führt Pods aus. Läuft auf jedem Node und dient als Agent des Nodes. kubelet führt die Befehle des Masters aus und startet, stoppt und überwacht die lokalen Pods
cAdvisor  Teil von kubelet. Misst CPU-Last und Speicherverbrauch von Pods auf seinem Node und berichtet an den Master.
Kube-Proxy Läuft auf jedem Node. Macht Ports von Pods für andere Pods oder Services (z.B. Load-Balancer) zugänglich. (TODO Wie geht das genau? Siehe Doku)

Deployment beschreiben

Einfaches Deployment nginx-deploymen.yaml:

apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

Service beschreiben

Siehe:

Beispiel - Cluster-interner Service:

apiVersion: v1
kind: Service
metadata:
  name: my-mysql
  labels:
    app: wordpress
spec:
  ports:
    - port: 3306
  selector:
    app: wordpress
    tier: mysql
  clusterIP: None    # Nur verwenden, wenn es nur einen Pod gibt (s.u.)

Beispiel - Service mit externem Load-Balancer:

apiVersion: v1
kind: Service
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  ports:
    - port: 80
  selector:
    app: wordpress
    tier: frontend
  type: LoadBalancer

Installation

kubectl installieren (Mac OS):

brew install kubectl

minikube

Distributionen:

minikube installieren:

brew install minikube

minikube starten:

minikube start

minikube stoppen:

minikube stop

Beispiel-Deployment installieren und wieder löschen:

#minikube starten
minikube start
# Deployment mit Echo-Service deployen
kubectl run hello-minikube --image=gcr.io/google_containers/echoserver:1.4 --port=8080
# Deployment für externes Netzwerk verfügbar machen
kubectl expose deployment hello-minikube --type=NodePort
# Pods auflisten
kubectl get pods
# Echo-Service aufrufen
curl $(minikube service hello-minikube --url)
# Deployment löschen
kubectl delete deployment hello-minikube
# minikube stoppen
minikube stop

Skalierung von Stateless Apps

Replicas kann man definieren über:

Replicas in Deployment-Beschreibung definieren

Einfach die Anzahl in deployment.yaml angeben:

spec:
  ...
  replicas: 4

Per kubectl für laufendes Deployment ändern:

kubectl scale --replicas=4 deployment/<deployment-name>

Skalierung von Stateful Apps

TODO

Auto-Scaling

Horizontal Pod Autoscaler (HPA)

Beispiel - HPA per CLI anlegen:

kubectl autoscale deployment wordpress --cpu-percent=50 --min=1 --max=10

Auto-Scaling testen

CPU-Zuweisung eines Pods künstlich reduzieren, damit man ihn leichter Stress-Testen kann:

apiVersion: apps/v1beta2
kind: Deployment
spec:
  template:
    spec:
      containers:
      - image: ...
        resources:
          requests:
            cpu: "100m"   # =10%
          limits:
            cpu: "200m"

busybox-Container starten für Stress-Test:

kubectl run -i --tty load-generator --image=busybox --generator=run-pod/v1 /bin/sh

In busybox-Container den Stress-Test starten:

while true; do wget -q -O- http://my-service.default.svc.cluster.local; done

HPA-Status anzeigen:

kubectl get hpa

Labels und Selektoren

Siehe:

Beispiel - Pod so einstellen, dass er nur auf einem Node mit einer SSD ausgeführt wird:

Label storageType=ssd zu Node hinzufügen:

kubectl label node <node-name> storageType=ssd

Deployment-Beschreibung des Pods ändern:

spec:
  ...
  template:
    ...
    spec:
      containers:
        ...
      nodeSelector:
        storageType: ssd

Deployment-Beschreibung in Cluster aktualisieren:

kubectl apply -f ./deployment.yaml

Health Checks (aka Probes)

Health Checks:

Beispiel:

spec:
  ...
  template:
    ...
    spec:
      containers:
      - name: my-container
        ...
        readinessProbe:
          httpGet:
            path: /
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 3
        livenessProbe:
          httpGet:
            path: /
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 30

Dashboard UI

Siehe Doku Web UI (Dashboard)

Dashboard installieren (wenn nicht bereits vorinstalliert):

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta8/aio/deploy/recommended.yaml

Token ermitteln:

$ kubectl -n kube-system get secret
# All secrets with type 'kubernetes.io/service-account-token' will allow to log in.
# Note that they have different privileges.
NAME                                     TYPE                                  DATA      AGE
deployment-controller-token-frsqj        kubernetes.io/service-account-token   3         22h

$ kubectl -n kube-system describe secret deployment-controller-token-frsqj
# ...
token:      abcdefg..

Lokalen Proxy starten:

kubectl proxy

Dashboard UI im Browser öffnen (Token zur Anmeldung verwenden): http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/

Quelle: Stackoverflow-Artikel

DNS

DNS:

Namespaces:

Volumes

Verwendung von Volumes:

Beispiel: Definition eines Volumes:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-pv-1
  labels:
    type: local
spec:
  storageClassName: ssd
  capacity:
    storage: 20Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /tmp/data/pv-1

Beispiel: Volume-Claim:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: my-pv-claim
spec:
  storageClassName: ssd
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 3Gi

Beispiel: Volume-Claim einem Container zuweisen:

apiVersion: apps/v1beta2
kind: Deployment
...
spec:
  ...
  template:
    spec:
      containers:
      - image: ...
        volumeMounts:
        - name: my-persistent-storage
          mountPath: /path/in/container
          subPath: /path/in/volume       # Default: "" (volume's root)
          readOnly: true                 # Default: false
      volumes:
      - name: my-persistent-storage
        persistentVolumeClaim:
          claimName: my-pv-claim

Volumes in AWS

Secrets

Secret per CLI aus Dateien anlegen:

kubectl create secret generic <secret-name> --from-file=<file-path-1> --from-file=<file-path-2>

Secret per CLI aus Wert anlegen:

kubectl create secret generic <secret-name> --from-literal=<key>=<value>

Beispiel: Secret mit zufälligem Passwort anlegen:

kubectl create secret generic <secret-name> --from-literal=password=`date +%s | shasum | base64 | head -c 32 ; echo`

Secret als Umgebungsvariable verwenden:

apiVersion: apps/v1beta2
kind: Deployment
spec:
  template:
    spec:
      containers:
      - image: ...
        env:
        - name: MY_ENV_VARIABLE
          valueFrom:
            secretKeyRef:
              name: my-secret-name
              key: my-key

Secret als Volume verwenden:

apiVersion: apps/v1beta2
kind: Deployment
spec:
  template:
    spec:
      containers:
      - image: ...
        volumeMounts:
        - name: my-secret-storage
          mountPath: /path/in/container
      volumes:
      - name: my-secret-storage
        secret:
          secretName: my-secret-name

Monitoring

Heapster:

TODO Heapster ist deprecated und seit K8s 1.12 aus K8s entfernt.

Heapster auf minikube einrichten (TODO geht nicht, Fehler heapster is not a valid addon):

# Add-On "Heapster" aktivieren
minikube addons enable heapster
# Das dauert eine Weile - So sieht man den Status:
kubectl get pods --namespace=kube-system
# Wenn die Pods laufen: Grafana Dashboard im Browser öffnen
minikube addons open heapster

Auditing

Auditing in K8s:

Audit Policy:

Audit Backend:

Beispiel - Änderungen an Metadaten in minikube in eine Datei loggen

Im Beispiel werden nur Änderungen an Metadaten geloggt. Man kann auch andere Ereignisse loggen, aber das kann schnell sehr viel werden.

Audit Policy audit-policy.yaml erstellen:

# Log all requests at the Metadata level.
apiVersion: audit.k8s.io/v1beta1
kind: Policy
rules:
- level: Metadata

Audit Policy in minikube-Addons-Verzeichnis kopieren:

cp audit-policy.yaml ~/.minikube/addons/

minikube neu starten:

minikube stop
minikube start --extra-config=apiserver.Authorization.Mode=RBAC --extra-config=apiserver.Audit.LogOptions.Path=/var/logs/audit.log --extra-config=apiserver.Audit.PolicyFile=/etc/kubernetes/addons/audit-policy.yaml

Aktion ausführen, das die Metadaten betrifft:

kubectl get pods

Per SSH auf minikube-VM verbinden:

minikube ssh

Log auf minikube-VM anschauen (JSON-Einträge werden per jq lesbar formatiert):

sudo bash
cat /var/logs/audit.log | jq .

Cluster in AWS aufsetzen

Siehe:

AWS EKS vs kops:

Cluster in AWS mit kops aufsetzen

Diese Anleitung nutzt AWS EC2 (Elastic Cloud) und AWS S3 (Simple Storage)

  1. In AWS-Konsole einloggen und Security Credentials herunterladen (rechts oben in Nutzer-Menü, dort Access Key hinzufügen)

  2. kops lokal installieren (Mac OS)

    brew update && brew install kops
  3. aws CLI installieren (kops ruft aws CLI auf)

    brew install aws
  4. aws CLI konfigurieren

    aws configure
    • Bei Frage nach "Default region" z.B. eu-central-1 (für Frankfurt) verwenden oder in AWS-Doku Regionen und Availability Zones nachschauen.
    • Bei Frage nach "Default output format" einfach Enter drücken für "None"
  5. AWS-S3-Bucket erstellen als Speicher für kops state store

    aws s3api create-bucket --bucket <bucket-name> --region <region-name> --create-bucket-configuration LocationConstraint=<region-name>
    • Der Bucket-Name ist global eindeutig und öffentlich sichtbar. Daher am besten mit einem Prefix der eigenen Organisation arbeiten - z.B. mycompany-myclustername-kops-state.
  6. Cluster mit kops erzeugen

    export KOPS_STATE_STORE=s3://mycompany-myclustername-kops-state
    kops create cluster <cluster-name> --zones <zone-name> --yes
    • Der cluster-name sollte auf .k8s.local enden. Dann erstellt kops einen gossip-basierten Cluster, so wir nicht DNS konfiguriert haben müssen, damit das Cluster-interne DNS funktioniert.
    • Als zone-name muss man eine Zone angeben, d.h. ein Region-Name plus einen Buchstaben. Also z.B. eu-central-1a.
    • Wenn Fehler error reading SSH key file auftritt, dann hat man noch lokal noch keinen Client-SSH-Key angelegt.

      Das kann man folgendermaßen nachholen:

      ssh-keygen
    • kubectl ist nun mit dem neuen Cluster verbunden
    • Per Default legt kops einen Master (Instanz-Typen m3.medium) und zwei Nodes (Instanz-Typ t2.medium) an.
  7. Cluster-Status sehen

    kops validate cluster
  8. Nodes auflisten

    kubectl get nodes --show-labels
  9. SSH auf Master öffnen

    ssh -i ~/.ssh/id_rsa admin@<master-host>
    • Den master-host kann man in der AWS-Konsole nachschauen:
      • Rechts oben Region auswählen
      • S3 -> Instances -> Master-VM selektieren -> "Public DNS"
    • Auf dem Master ist auch kubectl verfügbar

Cluster löschen:

kops delete cluster <cluster-name>

kops-etcd-Volumes verkleinern

Quelle: Blog-Artikel Resize etcd volumes on kops

Lokal - Volumes erzeugen:

$ aws ec2 create-volume --availability-zone eu-central-1b --size 2 --volume-type gp2 --tag-specifications 'ResourceType=volume,Tags=[{Key=Name,Value=b.etcd-main.my-cluster-name}]'
{
    ...
    "VolumeId": "vol-02a7048643e42a68d",
}
$ aws ec2 create-volume --availability-zone eu-central-1b --size 2 --volume-type gp2 --tag-specifications 'ResourceType=volume,Tags=[{Key=Name,Value=b.etcd-events.my-cluster-name}]'
{
    ...
    "VolumeId": "vol-04c26929ea5e3a527",
}

Lokal - SSH auf Master öffnen:

$ ssh admin@<master-ip>

Auf Master - Master-Services stoppen und aktuelle Plattenbelegung anzeigen:

$ sudo systemctl stop docker-healthcheck.timer
$ sudo systemctl stop docker
$ sudo systemctl stop kubelet
$ sudo systemctl stop protokube
$ df
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/xvda2      15660904 3882264  11007384  27% /
/dev/xvdu       20511312  170624  20324304   1% /mnt/master-vol-00768b179c346f1f8
/dev/xvdv       20511312  423268  20071660   3% /mnt/master-vol-06517921f1de67628

Lokal - Neues etcd-main-Volume an Master attachen:

$ aws ec2 attach-volume --device /dev/sdf --instance-id i-07637f16bd2dd0a72 --volume-id vol-02a7048643e42a68d

Auf Master - Neues etcd-main-Volume formatieren und mounten:

$ lsblk | grep 2G
xvdf    202:80    0      2G  0 disk
$ sudo mkfs.ext4 /dev/xvdf
$ sudo mkdir /mnt/etcd-main
$ sudo mount /dev/xvdf /mnt/etcd-main

Lokal - Neues etcd-events-Volume an Master attachen:

$ aws ec2 attach-volume --device /dev/sdg --instance-id i-07637f16bd2dd0a72 --volume-id vol-04c26929ea5e3a527

Auf Master - Neues etcd-events-Volume formatieren und mounten:

$ lsblk | grep 2G
xvdf    202:80    0      2G  0 disk /mnt/etcd-main
xvdg    202:96    0      2G  0 disk
$ sudo mkfs.ext4 /dev/xvdg
$ sudo mkdir /mnt/etcd-events
$ sudo mount /dev/xvdg /mnt/etcd-events
$ df
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/xvda2      15660904 3882272  11007376  27% /
/dev/xvdu       20511312  170624  20324304   1% /mnt/master-vol-00768b179c346f1f8
/dev/xvdv       20511312  423268  20071660   3% /mnt/master-vol-06517921f1de67628
/dev/xvdf        1998672    6144   1871288   1% /mnt/etcd-main
/dev/xvdg        1998672    6144   1871288   1% /mnt/etcd-events

Alte Volumes in AWS-Konsole nachschauen:

Auf Master - Daten kopieren und Volumes unmounten:

$ cd /mnt/master-vol-06517921f1de67628
$ sudo cp -av * /mnt/etcd-main
$ cd /mnt/master-vol-00768b179c346f1f8
$ sudo cp -av * /mnt/etcd-events
$ sudo umount /mnt/etcd-main
$ sudo umount /mnt/etcd-events

Lokal - Tags zu neuen Volumes umziehen:

$ aws ec2 create-tags --resources vol-02a7048643e42a68d --tags Key=KubernetesCluster,Value=my-cluster-name Key=k8s.io/etcd/main,Value=b/b Key=k8s.io/role/master,Value=1 Key=kubernetes.io/cluster/my-cluster-name,Value=owned
$ aws ec2 create-tags --resources vol-04c26929ea5e38709 --tags Key=KubernetesCluster,Value=my-cluster-name Key=k8s.io/etcd/events,Value=b/b Key=k8s.io/role/master,Value=1 Key=kubernetes.io/cluster/my-cluster-name,Value=owned
$ aws ec2 delete-tags --resources vol-00768b179c346f1f8 vol-06517921f1de67628 --tags Key=KubernetesCluster Key=k8s.io/etcd/main Key=k8s.io/etcd/events Key=k8s.io/role/master Key=kubernetes.io/cluster/my-cluster-name

Auf Master - Master herunterfahren:

$ sudo poweroff

Warten bis neuer Master erzeugt wurde.

Auf neuem Master - Prüfen:

$ df
Filesystem     1K-blocks    Used Available Use% Mounted on
/dev/xvda2      15660904 3906140  10983508  27% /
/dev/xvdu        1998672  131680   1745752   8% /mnt/master-vol-04c26929ea5e3a527
/dev/xvdv        1998672  384332   1493100  21% /mnt/master-vol-02a7048643e42a68d