1. Container Grundlagen

Die Aufgaben in diesem Abschnitt beziehen sich auf das erste Folienkapitel “Container Grundlagen”.

1.1. Den ersten Docker Container ausführen

Starten Sie einen Docker-Willkommens-Container mit dem folgenden Befehl:

docker run -p 8080:80 docker/welcome-to-docker

Um zu bestätigen, dass der Container läuft besuchen Sie http://localhost:8080. Der Container kann nun gestoppt werden, indem der laufende Befehl mit CTRL + C abgebrochen wird.

2. Docker

Die Aufgaben in diesem Abschnitt beziehen sich auf das zweite Folienkapitel “Docker”.

2.1. Installation von Docker

  1. Prüfen Sie ob eine Verbindung zu ihrem Virtual Private Server (VPS) hergestellt werden kann. Die Server-Adresse und das dazugehörige Passwort wurde ihnen zusammen mit dem Aufgabenmaterial zugeschickt.

    ssh student@<server-domain>
  2. Optional: Laden Sie ihren Public Key auf den Server um in Zukunft schneller darauf zugreifen zu können:

    # Fall noch kein public key vorhanden ist
    ssh-keygen

    ssh-copy-id student@<server-domain>
  3. Installieren Sie auf dem Server Docker mit dem Convenience Script.

    https://docs.docker.com/engine/install/ubuntu/#install-using-the-convenience-script

  4. Machen Sie Docker auch für Ihren lokalen Nutzer student verfügbar. Indem Sie ihn zu der docker Gruppe hinzufügen:

    sudo groupadd docker
    sudo usermod -aG docker $USER

    # Ausloggen und wieder einloggen um die Gruppe zu erhalten
    exit
    ssh student@<server-domain>
  5. Testen Sie die installierte Docker-Version mit:

    docker version

2.2. Einrichtung des entfernten Docker Sockets

  1. Erstellen Sie einen Docker Kontext, der es erlaubt die Docker Befehle direkt von Ihrer lokalen Maschine auszuführen, ohne dabei extra den SSH Befehl aufrufen zu müssen:

    docker context create student --docker "host=ssh://student@<server_domain>"
  2. Lassen Sie sich eine Liste der verfügbaren Docker-Kontexte ausgeben und wechseln Sie auf den neuen Kontext:

    docker context ls
    docker context use student
  3. Lassen Sie sich die Docker-Servers mit dem folgenden Befehl ausgeben:

    docker version

    Unterscheidet sich die Server-Version mit der Version ihrer CLI?

  4. Starten Sie einen einfachen Caddy Server über den Befehl:

    docker run -p 443:443 -p 80:80 caddy caddy file-server \
    --listen ":443" \
    --root /usr/share/caddy \
    --domain <server_domain>

    Verifizieren Sie, dass Sie über Ihren Browser die Domain https://<server_domain> erreichen können.

  5. Stoppen Sie nun den Caddy Server indem Sie den Prozess mit CTRL + C beenden und wechseln Sie zurück auf den “default”-Kontext:

    docker context use default

3. Images

Die Aufgaben in diesem Abschnitt beziehen sich auf das dritte Folienkapitel “Images”.

3.1. Images erkunden

Pullen Sie die folgenden Images und beantworten Sie die Fragen:

docker pull nginx:1.27-alpine
docker pull nginx:latest
docker pull python:3.12-slim
  1. Wie groß sind die drei Images? Nutzen Sie docker image ls um die Größen zu vergleichen.
  2. Inspizieren Sie das Image nginx:1.27-alpine mit docker image inspect. Welches Betriebssystem und welche Architektur hat das Image?
  3. Lassen Sie sich die Layer-Historie von nginx:1.27-alpine mit docker image history anzeigen. Wie viele Layers hat das Image?
  4. Was ist der Unterschied zwischen nginx:1.27-alpine und nginx:latest? Tipp: Vergleichen Sie die Ausgabe von docker image inspect beider Images (z.B. das Feld Os, Architecture, RootFS.Layers).

3.2. Container-Lifecycle

In dieser Aufgabe üben Sie das Erstellen, Steuern und Aufräumen von Containern.

  1. Starten Sie einen Nginx-Container im Hintergrund mit Port-Mapping und einem eigenen Namen:

    docker run -d --name mein-nginx -p 8080:80 nginx:1.27-alpine

    Öffnen Sie http://localhost:8080 im Browser um zu prüfen, dass der Container läuft.

  2. Lassen Sie sich alle laufenden Container anzeigen (docker ps)

  3. Öffnen Sie eine Shell im laufenden Container und erstellen Sie eine eigene index.html:

    docker exec -it mein-nginx sh
    echo "<h1>Hallo aus dem Container!</h1>" > /usr/share/nginx/html/index.html
    exit

    Laden Sie die Seite im Browser neu. Was sehen Sie?

  4. Stoppen Sie den Container mit docker stop mein-nginx. Prüfen Sie mit docker ps -a, dass der Container im Zustand Exited ist.

  5. Starten Sie den Container erneut mit docker start mein-nginx. Ist Ihre angepasste index.html noch da?

  6. Löschen Sie den Container mit docker rm -f mein-nginx. Starten Sie einen neuen Container mit dem gleichen Image. Ist die Änderung an der index.html noch vorhanden? Warum (nicht)?

  7. Räumen Sie auf: Entfernen Sie alle gestoppten Container mit docker container prune.

3.3. Eigenes Image bauen

In dieser Aufgabe erstellen Sie ein eigenes Docker Image mit einem Dockerfile.

  1. Erstellen Sie ein neues Verzeichnis und öffnen Sie es mit dem Editor ihrer Wahl.

  2. Erstellen Sie eine Datei index.html mit beliebigem Inhalt, zum Beispiel:

    <!DOCTYPE html>
    <html>
    <head><title>Meine Seite</title></head>
    <body>
    <h1>Willkommen auf meiner containerisierten Webseite!</h1>
    <p>Erstellt mit Docker.</p>
    </body>
    </html>
  3. Erstellen Sie ein Dockerfile mit folgendem Inhalt:

    FROM nginx:1.27-alpine
    COPY index.html /usr/share/nginx/html/index.html
    EXPOSE 80
  4. Erstellen Sie eine .dockerignore-Datei, die die Datei Dockerfile und eventuell vorhandene .git-Verzeichnisse ausschließt.

  5. Bauen Sie das Image und vergeben Sie einen Tag:

    docker build -t meine-webseite:1.0 .
  6. Starten Sie einen Container aus Ihrem neuen Image und prüfen Sie das Ergebnis im Browser:

    docker run -d --name webseite -p 8080:80 meine-webseite:1.0
  7. Ändern Sie nun die index.html und bauen Sie das Image erneut als meine-webseite:2.0. Beobachten Sie die Build-Ausgabe: Welche Layers werden aus dem Cache genommen und welche neu gebaut?

3.4. Images taggen, pushen und lokal speichern

In dieser Aufgabe üben Sie den Umgang mit Image-Tags, einer lokalen Registry und dem Export/Import von Images.

  1. Starten Sie eine lokale Docker-Registry:

    docker run -d -p 5000:5000 --name registry \
    -v registry_data:/var/lib/registry \
    --restart always \
    registry:2
  2. Taggen Sie Ihr zuvor gebautes Image für die lokale Registry und pushen Sie es:

    docker tag meine-webseite:1.0 localhost:5000/meine-webseite:1.0
    docker push localhost:5000/meine-webseite:1.0
  3. Löschen Sie das lokale Image und pullen Sie es wieder aus Ihrer lokalen Registry:

    docker image rm localhost:5000/meine-webseite:1.0
    docker pull localhost:5000/meine-webseite:1.0

    Starten Sie einen Container daraus, um zu prüfen, dass alles funktioniert.

  4. Exportieren Sie ein Image als .tar-Datei und importieren Sie es wieder:

    docker save -o meine-webseite.tar meine-webseite:1.0
    docker image rm meine-webseite:1.0
    docker load -i meine-webseite.tar
    docker image ls | grep meine-webseite
  5. Prüfen Sie den Inhalt Ihrer lokalen Registry über die REST API:

    curl http://localhost:5000/v2/_catalog
    curl http://localhost:5000/v2/meine-webseite/tags/list
  6. Bonus: Starten Sie die Docker-Registry auf ihrem Server. Können Sie einfach Images pushen?

3.5. Isolation und Sicherheit entdecken

In dieser Aufgabe erkunden Sie Kernel-Namespaces und Capabilities in der Praxis.

  1. Starten Sie einen Container und inspizieren Sie die Prozessisolation:

    docker run -d --name isolated nginx:1.27-alpine

    Zeigen Sie die Prozesse innerhalb des Containers an:

    docker exec isolated ps aux

    Zeigen Sie nun die Host-PID des Container-Hauptprozesses:

    docker inspect --format '{{.State.Pid}}' isolated

    Vergleichen Sie: Im Container sieht nginx sich als PID 1, auf dem Host hat der Prozess eine andere PID.

  2. Starten Sie einen Container mit Host-PID-Namespace und vergleichen Sie:

    docker run --rm --pid=host busybox ps aux

    Was fällt auf? Welche Prozesse sehen Sie jetzt?

  3. Testen Sie Kernel-Capabilities. Starten Sie einen Container und versuchen Sie, den Hostnamen zu ändern:

    docker run --rm -it busybox hostname neuer-name

    Starten Sie nun den gleichen Container mit entfernter Capability:

    docker run --rm --cap-add=SYS_ADMIN -it busybox hostname neuer-name
  4. Starten Sie einen Container, bei dem alle Capabilities entfernt werden. Beobachten Sie, was noch funktioniert und was nicht:

    docker run --rm --cap-drop=ALL -it busybox sh
    # Versuchen Sie im Container:
    # ping 8.8.8.8
    # id

    Frage: Warum funktioniert ping nicht? Welche Capability würden Sie hinzufügen müssen?

3.6. Multi-Architektur Image bauen

In dieser Aufgabe bauen Sie ein Image für mehrere CPU-Architekturen.

  1. Prüfen Sie, welche Architektur Ihr Rechner hat:

    uname -m
    docker version --format '{{.Server.Arch}}'
  2. Erstellen Sie einen neuen Buildx-Builder und aktivieren Sie ihn:

    docker buildx create --name multiarch --driver docker-container --bootstrap --use
    docker buildx inspect

    Welche Plattformen werden unterstützt?

  3. Erstellen Sie ein neues Verzeichnis mit einem einfachen Dockerfile:

    mkdir ~/multi-arch-test && cd ~/multi-arch-test
    FROM alpine:3.21
    RUN echo "Gebaut auf: $(uname -m)" > /info.txt
    CMD ["cat", "/info.txt"]
  4. Bauen Sie das Image für Ihre lokale Plattform und testen Sie es:

    docker buildx build -t multi-test:local --load .
    docker run --rm multi-test:local
  5. Bauen Sie jetzt das Image für eine andere Architektur (z.B. linux/arm64 wenn Sie auf amd64 sind) und laden Sie es lokal:

    docker buildx build --platform linux/arm64 -t multi-test:arm64 --load .
    docker run --rm multi-test:arm64

4. Resources

Die Aufgaben in diesem Abschnitt beziehen sich auf das vierte Folienkapitel “Docker Resources”.

4.1. Volumes und Bind Mounts

In dieser Aufgabe speichern Sie Daten persistent und vergleichen verschiedene Mount-Typen.

  1. Erstellen Sie ein Docker-Volume und prüfen Sie, dass es existiert:

    docker volume create workshop_data
    docker volume ls
    docker volume inspect workshop_data
  2. Starten Sie einen Container, der in dieses Volume schreibt:

    docker run --rm --name writer \
    -v workshop_data:/data \
    alpine:3.21 sh -c "echo 'Hallo von $(date)' > /data/nachricht.txt && cat /data/nachricht.txt"
  3. Starten Sie einen zweiten Container und lesen Sie die gleiche Datei aus dem gleichen Volume:

    docker run --rm --name reader \
    -v workshop_data:/data \
    alpine:3.21 cat /data/nachricht.txt

    Frage: Warum ist die Datei noch da, obwohl der erste Container mit --rm gelöscht wurde?

  4. Vergleichen Sie nun mit einem Bind Mount. Erstellen Sie auf dem Host ein Arbeitsverzeichnis:

    mkdir -p ~/docker-ressources/bind-demo
    echo "Version 1" > ~/docker-ressources/bind-demo/info.txt
  5. Starten Sie einen Container mit Bind Mount und lesen Sie die Datei:

    docker run --rm \
    -v $HOME/docker-ressources/bind-demo:/workspace \
    alpine:3.21 cat /workspace/info.txt
  6. Ändern Sie die Datei auf dem Host und prüfen Sie im Container erneut:

    echo "Version 2" > ~/docker-ressources/bind-demo/info.txt
    docker run --rm \
    -v $HOME/docker-ressources/bind-demo:/workspace \
    alpine:3.21 cat /workspace/info.txt

4.2. Container Networking

In dieser Aufgabe bauen Sie ein kleines Zwei-Container-Netzwerk aus Web-App und Client.

  1. Erstellen Sie ein eigenes Netzwerk:

    docker network create playground_net
    docker network ls
  2. Starten Sie den Hello-Docker-Container als internes Backend (ohne Port-Mapping auf den Host):

    docker run -d --name hello-docker --network playground_net docker/welcome-to-docker
  3. Prüfen Sie, welche Container im Netzwerk hängen:

    docker network inspect playground_net
  4. Starten Sie einen Test-Container im gleichen Netzwerk und prüfen Sie, ob das Backend über den Container-Namen erreichbar ist:

    docker run --rm --network playground_net curlimages/curl "curl -I http://hello-docker"

    Frage: Warum funktioniert der Aufruf über http://hello-docker ohne IP-Adresse?

4.3. Docker API und Portainer

In dieser Aufgabe sehen Sie, dass die CLI intern auch nur mit der Docker Engine API spricht.

  1. Fragen Sie die Docker-Version direkt über die API ab:

    curl --unix-socket /var/run/docker.sock http://localhost/version
  2. Lassen Sie sich laufende Container per API anzeigen:

    curl --unix-socket /var/run/docker.sock http://localhost/containers/json
  3. Nutzen Sie die API-Antwort und suchen Sie in der Ausgabe die Namen hello-docker und caddy-proxy.

  4. Starten Sie Portainer:

    docker run -d -p 8000:8000 -p 9443:9443 \
    --name portainer --restart unless-stopped \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v portainer_data:/data \
    portainer/portainer-ce:latest
  5. Öffnen Sie https://<server_domain>:9443 (oder lokal https://localhost:9443), erstellen Sie einen Admin-Account und verbinden Sie sich mit der lokalen Docker-Umgebung.

  6. Erstellen Sie in Portainer einen neuen Container hello-ui mit Image docker/welcome-to-docker und Port-Mapping 8090:80.

  7. Prüfen Sie anschließend in der Shell:

    docker ps
    curl -I http://localhost:8090
  8. Stoppen Sie hello-ui einmal in Portainer und starten Sie ihn wieder über die CLI mit docker start hello-ui.

5. Services

Die Aufgaben in diesem Abschnitt beziehen sich auf das sechste Folienkapitel “Docker Compose”.

5.1. Übertragung Docker-CLI zu Docker-Compose

In dieser Aufgabe lernen Sie, einen einfachen docker run Befehl in das Docker Compose Format zu übersetzen.

  1. Schauen Sie sich das folgende “docker run”-Kommando an:

    docker run --name compose-nginx -p 8080:80 -p 8443:443 -v /some/content:/usr/share/nginx/html:ro -d nginx:1.27-alpine
  2. Erstellen Sie ein neues Verzeichnis:

    mkdir -p ~/compose-nginx && cd ~/compose-nginx
  3. Erstellen Sie eine docker-compose.yml Datei und übersetzen Sie das obige Kommando ins Compose-Format. Beachten Sie dabei:

  4. Starten Sie den Service mit:

    docker-compose up -d
  5. Prüfen Sie, dass der Container läuft:

    docker-compose ps
    curl http://localhost:8080
  6. Stoppen Sie den Service:

    docker-compose down

5.2. Wordpress mit Reverse-Proxy

In dieser Aufgabe erstellen Sie ein Multi-Service Docker Compose Setup mit Wordpress, Datenbank und einem TLS-terminating Reverse Proxy auf ihrem Server:

  1. Erstellen Sie ein neues Verzeichnis für das Projekt:

    mkdir -p ~/wordpress-compose && cd ~/wordpress-compose
  2. Erstellen Sie eine docker-compose.yml Datei mit dem folgenden Template:

    version: '3.8'

    services:
    db:
    image: mariadb:10.6.4-focal
    command: '--default-authentication-plugin=mysql_native_password'
    volumes:
    - db_data:/var/lib/mysql
    restart: always
    environment:
    - MYSQL_ROOT_PASSWORD=somewordpress
    - MYSQL_DATABASE=wordpress
    - MYSQL_USER=wordpress
    - MYSQL_PASSWORD=wordpress
    expose:
    - 3306
    - 33060

    wordpress:
    image: wordpress:latest
    volumes:
    - wp_data:/var/www/html
    restart: always
    environment:
    - WORDPRESS_DB_HOST=db
    - WORDPRESS_DB_USER=wordpress
    - WORDPRESS_DB_PASSWORD=wordpress
    - WORDPRESS_DB_NAME=wordpress
    depends_on:
    - db

    volumes:
    db_data:
    wp_data:
  3. Ergänzen Sie einen Reverse-Proxy Service mit Caddy, der Wordpress über HTTPS verfügbar macht. Der Reverse Proxy sollte:

    Nutzen Sie als Referenz den folgenden “docker run”-Befehl:

    docker run -p 80:80 -p 443:443 -v caddy_data:/data -v caddy_config:/config caddy caddy reverse-proxy --from <server_domain> --to wordpress:80
  4. Sorgen Sie dafür, dass der Wordpress Service nicht direkt von außen zugegriffen werden kann. Nur der Reverse Proxy sollte direkt auf den Host-Ports erreichbar sein:

    wordpress:
    # ... rest of config ...
    expose: # statt ports
    - 80
  5. Starten Sie alle Services:

    docker-compose up -d
  6. Prüfen Sie, dass alle Services laufen:

    docker-compose ps
    docker-compose logs -f caddy
  7. Öffnen Sie im Browser Ihre Domain und prüfen Sie die HTTPS-Verbindung.

  8. Stoppen und räumen Sie auf:

    docker-compose down

5.3. Python-Entwicklungsumgebung mit Docker Compose

In dieser Aufgabe erstellen Sie eine lokale Dev-Umgebung für eine einfache Python-Anwendung mit Docker Compose und Live-Reload.

  1. Erstellen Sie ein neues Verzeichnis für das Projekt:

    mkdir -p ~/python-dev-env && cd ~/python-dev-env
  2. Erstellen Sie eine einfache Python-Anwendung mit dem Namen app.py:

    from flask import Flask
    import os
    from datetime import datetime

    app = Flask(__name__)

    @app.route('/')
    def hello():
    return f'''
    <!DOCTYPE html>
    <html>
    <head><title>Python Dev App</title></head>
    <body>
    <h1>Willkommen zur Python Dev-Umgebung!</h1>
    <p>Aktuelle Zeit: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
    <p>Hostname: {os.getenv('HOSTNAME', 'unknown')}</p>
    </body>
    </html>
    '''

    @app.route('/api/status')
    def status():
    return {'status': 'running', 'timestamp': datetime.now().isoformat()}

    if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)
  3. Erstellen Sie eine requirements.txt mit den notwendigen Abhängigkeiten:

    Flask==3.0.0
    Werkzeug==3.0.0
  4. Erstellen Sie ein Dockerfile für die Entwicklung:

    FROM python:3.12-slim

    WORKDIR /app

    COPY requirements.txt .

    RUN pip install --no-cache-dir -r requirements.txt

    CMD ["python", "app.py"]
  5. Erstellen Sie ein docker-compose.yml File:

    services:
    app:
    build: .
    container_name: python-dev-app
    ports:
    - "5000:5000"
    volumes:
    - .:/app
    environment:
    - FLASK_ENV=development
    - FLASK_DEBUG=1
    - PYTHONUNBUFFERED=1
    command: python -m flask run --host=0.0.0.0
  6. Starten Sie die Anwendung mit Docker Compose:

    docker-compose up
  7. Öffnen Sie im Browser http://localhost:5000 und beobachten Sie die Ausgabe.

  8. Ändern Sie nun die Datei app.py und ergänzen Sie einen neuen Endpoint. Beobachten Sie, dass die Anwendung aufgrund von Flask Debug-Mode automatisch neu startet, ohne den Container neu zu bauen.

    @app.route('/hello/<name>')
    def hello_name(name):
    return f'<h1>Hallo {name}!</h1>'

    Testen Sie den neuen Endpoint im Browser: http://localhost:5000/hello/Docker

  9. Beobachten Sie die Logs der Anwendung mit:

    docker-compose logs -f
  10. Stoppen Sie die Services mit:

    docker-compose down

6. Kubernetes Grundlagen

Die Aufgaben in diesem Abschnitt beziehen sich auf das erste Folienkapitel “Grundlagen”.

6.1. Ein Kubernetes Cluster in Docker Desktop starten

In dieser Aufgabe lernen Sie ein Kubernetes-Cluster in Docker Desktop zu starten.

  1. Öffnen Sie Docker Desktop.

  2. Wählen Sie in der rechten Navigation den Punkt “Kubernetes” aus.

  3. Klicken Sie auf “Create Cluster” (oder “Cluster erstellen”) und wählen Sie die folgenden Optionen an:

  4. Öffnen Sie die Konsole und verifizieren Sie, dass es einen Kubernetes Kontext mit dem Namen “docker-desktop” gibt:

    kubectl config get-contexts
  5. Starten Sie den ersten Kubernetes Pod:

    kubectl run nginx --image nginx
  6. Verifizieren Sie das der Pod läuft:

    kubectl get pods
    # Pod sollte im Status running sein
    kubectl describe pod nginx
    # Letztes Event sollte sein "Started container nginx"
  7. Leiten Sie den Port aus dem Pod auf ihr Hostsystem um:

    kubectl port-forward pods/nginx 8080:80

    Besuchen Sie die Seite und verifizieren Sie, dass der Pod erreicht werden kann.

  8. Beenden Sie das Port-Forwarding mit CTRL + C.

6.2. Das Kubernetes System inspizieren

In dieser Aufgabe untersuchen Sie die Nodes Ihres Kubernetes-Clusters und verstehen, wie die Control Plane den Cluster-Zustand verwaltet.

  1. Zeigen Sie alle Nodes im Cluster an und prüfen Sie deren Status:

    kubectl get nodes

    Welche Nodes sehen Sie? Welche Rolle hat jeder Node (Control Plane, Worker)?

  2. Zeigen Sie die Nodes mit zusätzlichen Informationen an (IP-Adresse, Kubernetes-Version):

    kubectl get nodes -o wide
  3. Untersuchen Sie einen Node im Detail:

    kubectl describe node <node-name>

    Beantworten Sie folgende Fragen anhand der Ausgabe:

  4. Zeigen Sie alle Pods an, die auf dem Node laufen – einschließlich der System-Pods:

    kubectl get pods --all-namespaces -o wide

    Auf welchem Node läuft Ihr nginx-Pod aus Aufgabe 1.1?

  5. Zeigen Sie nur die System-Pods im Namespace kube-system an:

    kubectl get pods -n kube-system

    Welche Kubernetes-Komponenten laufen dort als Pods? Erkennen Sie kube-apiserver, etcd oder coredns?

6.3. (Optional) Ein Bare Metal Cluster erstellen

In dieser Aufgabe installieren Sie k3s auf Ihrem bekannten Virtual Private Server und binden den Cluster anschließend in Ihre lokale kubectl-Konfiguration ein.

6.3.1. k3s auf dem Server installieren

  1. Stellen Sie eine SSH-Verbindung zu Ihrem Server her:

    ssh student@<server_domain>
  2. Installieren Sie k3s mit dem offiziellen Convenience Script:

    curl -sfL https://get.k3s.io | sh -

    Das Script lädt k3s herunter, richtet einen systemd-Service ein und startet den Cluster automatisch.

  3. Prüfen Sie, ob der k3s-Service läuft:

    sudo systemctl status k3s
  4. Verifizieren Sie, dass der Cluster bereit ist:

    sudo kubectl get nodes

    Der Node sollte nach kurzer Zeit den Status Ready haben.

6.3.2. Die Kubeconfig auf das lokale System übertragen

k3s legt die Kubeconfig unter /etc/rancher/k3s/k3s.yaml ab. Da sie standardmäßig nur für root lesbar ist, kopieren Sie sie zunächst auf dem Server:

  1. Kopieren Sie die Kubeconfig in Ihr Home-Verzeichnis und passen Sie die Berechtigungen an:

    sudo cp /etc/rancher/k3s/k3s.yaml ~/k3s.yaml
    sudo chown student:student ~/k3s.yaml
  2. Verlassen Sie die SSH-Verbindung:

    exit
  3. Übertragen Sie die Datei auf Ihren lokalen Rechner:

    scp student@<server_domain>:~/k3s.yaml ~/.kube/k3s.yaml
  4. Die Kubeconfig enthält als Server-Adresse 127.0.0.1. Ersetzen Sie diesen Wert durch die echte Domain Ihres Servers:

    sed -i 's/127.0.0.1/<server_domain>/g' ~/.kube/k3s.yaml
  5. Fügen Sie die neue Kubeconfig Ihrer lokalen Konfiguration hinzu, indem Sie die Umgebungsvariable KUBECONFIG setzen:

    export KUBECONFIG=~/.kube/config:~/.kube/k3s.yaml

    Um diese Einstellung dauerhaft zu machen, tragen Sie die Zeile in Ihre Shell-Konfigurationsdatei (z. B. ~/.bashrc oder ~/.zshrc) ein.

  6. Listen Sie alle verfügbaren Kontexte auf und wechseln Sie auf den k3s-Kontext:

    kubectl config get-contexts
    kubectl config use-context default

    Der k3s-Kontext heißt standardmäßig default. Benennen Sie ihn zur besseren Übersicht um:

    kubectl config rename-context default k3s-server
    kubectl config use-context k3s-server
  7. Verifizieren Sie die Verbindung zum Remote-Cluster:

    kubectl get nodes
    kubectl get pods --all-namespaces

    Sie sollten den Node Ihres Servers und die k3s-Systemkomponenten sehen (u. a. traefik als Ingress-Controller und coredns).

7. Kubernetes Objekte

Die Aufgaben in diesem Abschnitt beziehen sich auf das Folienkapitel “Kubernetes Objekte”.

7.1. Namespaces erkunden

In dieser Aufgabe lernen Sie die Kubernetes-Namespaces kennen und legen einen eigenen Namespace an.

  1. Listen Sie alle Namespaces im Cluster auf:

    kubectl get namespaces

    Sie sollten mindestens vier vorinstallierte Namespaces sehen:

  2. Zeigen Sie Detailinformationen zu einem einzelnen Namespace an:

    kubectl describe namespace default
  3. Erstellen Sie eine Datei my-namespace.yaml mit folgendem Inhalt:

    apiVersion: v1
    kind: Namespace
    metadata:
    name: training

    Legen Sie den Namespace an:

    kubectl apply -f my-namespace.yaml
  4. Verifizieren Sie, dass der Namespace angelegt wurde und prüfen Sie seinen Status:

    kubectl get namespaces
  5. Starten Sie einen Pod im neuen Namespace:

    kubectl run nginx-training --image nginx --namespace training

    Zeigen Sie die Pods im Namespace training an:

    kubectl get pods -n training
  6. Führen Sie kubectl get pods ohne -n training aus. Ist der Pod sichtbar? Warum nicht?

  7. Löschen Sie den Namespace inklusive aller darin enthaltenen Ressourcen:

    kubectl delete namespace training

    Beobachten Sie den Status mit kubectl get namespaces: Durchläuft der Namespace kurz den Zustand Terminating?

7.2. Eine Shell in einem laufenden Container öffnen

In dieser Aufgabe starten Sie einen Pod und öffnen eine interaktive Shell direkt im laufenden Container.

Die Begleitmaterialien enthalten die Datei objects-container-shell.yaml.

  1. Legen Sie den Pod an:

    kubectl apply -f objects-container-shell.yaml
  2. Verifizieren Sie, dass der Pod läuft:

    kubectl get pod shell-demo
  3. Öffnen Sie eine interaktive Shell im Container:

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

    Der -- trennt die kubectl-Argumente von dem Befehl, der im Container ausgeführt wird.

  4. Erkunden Sie den Container von innen. Führen Sie folgende Befehle innerhalb der Shell aus:

    ls /
    cat /proc/mounts
  5. Schreiben Sie eine eigene Startseite für nginx:

    echo 'Hello shell demo' > /usr/share/nginx/html/index.html
  6. Verlassen Sie die Shell:

    exit
  7. Leiten Sie den Port aus dem Pod auf ihr Hostsystem um:

    kubectl port-forward pods/nginx 8080:80

    Besuchen Sie die Seite und verifizieren Sie, dass Ihre Änderungen sichtbar sind.

  8. Beenden Sie das Port-Forwarding mit CTRL + C.

  9. Löschen Sie den Pod nach der Aufgabe:

    kubectl delete pod shell-demo

7.3. Readiness Probe hinzufügen

In dieser Aufgabe lernen Sie, warum Readiness Probes wichtig sind, und fügen dem Pod slow-nginx eine solche Probe hinzu.

Die Begleitmaterialien enthalten die Datei objects-container-readiness.yaml. Der Pod simuliert mit sleep 30 eine langsame Startup-Phase.

  1. Legen Sie den Pod ohne Readiness Probe an und beobachten Sie den Status:

    kubectl apply -f objects-container-readiness.yaml
    kubectl get pod slow-nginx --watch

    Wird der Pod sofort als Running markiert? Ist nginx in dieser Zeit bereits erreichbar?

    Beenden Sie das Watching mit CTRL + C.

  2. Löschen Sie den Pod wieder:

    kubectl delete pod slow-nginx
  3. Öffnen Sie die Datei objects-container-readiness.yaml und ergänzen Sie eine httpGet Readiness Probe die auf den Pfad / mit dem Port 80 prüft.

  4. Wenden Sie die aktualisierte Datei an und beobachten Sie den Status erneut:

    kubectl apply -f objects-container-readiness.yaml
    kubectl get pod slow-nginx --watch

    Beenden Sie das Watching mit CTRL + C.

  5. Prüfen Sie die Readiness Probe in den Pod-Events:

    kubectl describe pod slow-nginx

    Sehen Sie Einträge wie Readiness probe failed? Nach wie vielen Sekunden wird der Pod als ready markiert?

  6. Löschen Sie den Pod nach der Aufgabe:

    kubectl delete pod slow-nginx

8. Konfiguration

Die Aufgaben in diesem Abschnitt beziehen sich auf das Folienkapitel “Configuration”.

8.1. ConfigMap für Environment-Variablen nutzen

In dieser Aufgabe erstellen Sie eine ConfigMap auf verschiedene Arten und binden deren Daten in einen Pod ein.

  1. Erstellen Sie eine ConfigMap mit einzelnen Schlüssel-Wert-Paaren:

    kubectl create configmap special-config \
    --from-literal=SPECIAL_LEVEL=very \
    --from-literal=SPECIAL_TYPE=charm
  2. Prüfen Sie den Inhalt der ConfigMap:

    kubectl get configmap special-config -o yaml

    Wie sind die Werte im data-Feld gespeichert?

  3. Bearbeiten Sie die Pod-Datei config-env-pod.yaml und laden Sie alle Werte der Config-Map als Environment-Variables.

  4. Legen Sie den Pod an:

    kubectl apply -f config-env-pod.yaml
  5. Prüfen Sie die Ausgabe des Pods:

    kubectl logs config-env-pod

    Sehen Sie SPECIAL_LEVEL=very und SPECIAL_TYPE=charm?

  6. Löschen Sie den Pod:

    kubectl delete pod config-env-pod

8.2. ConfigMap für Volumes nutzen

  1. Bearbeiten Sie die Pod-Datei config-volume-pod.yaml und mounten Sie alle Werte der Config-Map als Volumes in das Verzeichnis /etc/config

  2. Legen Sie den Pod an und prüfen Sie die Ausgabe:

    kubectl apply -f config-volume-pod.yaml
    kubectl logs config-volume-pod

    Welche Dateien liegen im Verzeichnis /etc/config? Was ist der Inhalt von SPECIAL_LEVEL?

  3. Löschen Sie den Pod:

    kubectl delete pod config-volume-pod
  4. Löschen Sie die ConfigMap:

    kubectl delete configmap special-config

8.3. Zugangsdaten sicher mit Secrets verwalten

In dieser Aufgabe erstellen Sie ein Kubernetes Secret, binden es als Volume in einen Pod ein.

Die Begleitmaterialien enthalten die Datei secret-pod.yaml.

  1. Erstellen Sie eine Datei die Passwort enthält:

    echo "sec@re%t" > password.txt
  2. Erstellen Sie ein Secret mit Benutzername und Passwort:

    kubectl create secret generic test-secret \
    --from-literal=username='my-app' \
    --from-file=password=./password.txt
  3. Prüfen Sie das angelegte Secret:

    kubectl get secret test-secret
    kubectl describe secret test-secret

    Warum werden die Werte in describe nicht im Klartext angezeigt?

  4. Öffnen Sie die Datei secret-pod.yaml und ergänzen Sie:

  5. Legen Sie den Pod an:

    kubectl apply -f secret-pod.yaml
  6. Öffnen Sie eine Shell im laufenden Container und lesen Sie die Secret-Dateien aus:

    kubectl exec -it secret-test-pod -- /bin/bash
    ls /etc/secret-volume
    cat /etc/secret-volume/username
    cat /etc/secret-volume/password

    Stimmen die Werte mit den Daten überein, die Sie beim Erstellen des Secrets angegeben haben?

  7. Verlassen Sie die Shell und löschen Sie den Pod:

    exit
    kubectl delete pod secret-test-pod

9. Volumes

Die Aufgaben in diesem Abschnitt beziehen sich auf das Folienkapitel “Volumes”.

9.1. StorageClasses im Cluster untersuchen

In dieser Aufgabe lernen Sie die verfügbaren StorageClasses Ihres Clusters kennen und ermitteln, welche als Standard (default) festgelegt ist.

  1. Listen Sie alle StorageClasses im Cluster auf:

    kubectl get storageclass

    Welche StorageClasses sehen Sie? Ist eine davon als (default) markiert?

  2. Zeigen Sie die Details der Default-StorageClass an:

    kubectl describe storageclass <name>

    Beantworten Sie folgende Fragen anhand der Ausgabe:

  3. Zeigen Sie die StorageClass als YAML aus, um die vollständige Konfiguration zu sehen:

    kubectl get storageclass <name> -o yaml

    Welche Annotation kennzeichnet die StorageClass als Default?

9.2. Einen Pod mit einem PersistentVolumeClaim betreiben

In dieser Aufgabe erstellen Sie einen PVC, binden ihn in einen Pod ein und schreiben Daten auf das persistente Volume.

Die Begleitmaterialien enthalten die Datei volumes-pvc-pod.yaml mit einem PVC und einem Pod.

  1. Wenden Sie die Datei an, um PVC und Pod anzulegen:

    kubectl apply -f volumes-pvc-pod.yaml
  2. Prüfen Sie den Status des PVC:

    kubectl get pvc nginx-pvc

    Welchen Status hat der PVC — Pending oder Bound? Welche StorageClass wurde automatisch zugewiesen?

  3. Prüfen Sie, ob der Pod läuft:

    kubectl get pod nginx-pvc-pod
  4. Öffnen Sie eine Shell im Container und schreiben Sie eine eigene index.html auf das persistente Volume:

    kubectl exec -it nginx-pvc-pod -- /bin/bash
    echo '<h1>Hallo aus dem PVC!</h1>' > /usr/share/nginx/html/index.html
    exit
  5. Leiten Sie den Port weiter und rufen Sie die Seite auf:

    kubectl port-forward pod/nginx-pvc-pod 8080:80

    Besuchen Sie http://localhost:8080 und verifizieren Sie, dass Ihre Seite angezeigt wird.

    Beenden Sie das Port-Forwarding mit CTRL + C.

  6. Löschen Sie den Pod und starten Sie ihn erneut:

    kubectl delete pod nginx-pvc-pod
    kubectl apply -f volumes-pvc-pod.yaml

    Leiten Sie den Port erneut weiter. Ist die index.html noch vorhanden? Was sagt das über den Lebenszyklus des PVC aus?

    Beenden Sie das Port-Forwarding mit CTRL + C.

  7. Zeigen Sie die Details des PVC an:

    kubectl describe pvc nginx-pvc

    Welches PersistentVolume wurde gebunden (Volume-Feld)? Zeigen Sie dieses PV ebenfalls an:

    kubectl get pv
    kubectl describe pv <name>
  8. Löschen Sie Pod und PVC:

    kubectl delete pod nginx-pvc-pod
    kubectl delete pvc nginx-pvc

    Beobachten Sie mit kubectl get pv, was mit dem PV passiert, nachdem der PVC gelöscht wurde. Was bewirkt die ReclaimPolicy?

10. Workloads

Die Aufgaben in diesem Abschnitt beziehen sich auf das Folienkapitel “Workloads”.

Liste der verwendeten Images: nginx:1.27 nginx:1.26

10.1. Ein Deployment erstellen und untersuchen

In dieser Aufgabe erstellen Sie ein Deployment und untersuchen, welche Kubernetes-Objekte dabei automatisch angelegt werden.

Die Begleitmaterialien enthalten die Datei deployment-nginx.yaml mit einem Deployment.

  1. Legen Sie das Deployment an:

    kubectl apply -f deployment-nginx.yaml
  2. Prüfen Sie den Status des Deployments:

    kubectl get deployment nginx-deployment

    Was bedeuten die Spalten READY, UP-TO-DATE und AVAILABLE?

  3. Lassen Sie sich die Details des Deployments anzeigen:

    kubectl describe deployment nginx-deployment
  4. Das Deployment erstellt automatisch ein ReplicaSet. Zeigen Sie es an:

    kubectl get replicaset

    Wie lautet der Name des ReplicaSets?

  5. Zeigen Sie die Pods an, die das ReplicaSet verwaltet:

    kubectl get pods

    Wie viele Pods laufen? Was fällt Ihnen am Pod-Namen auf?

  6. Untersuchen Sie einen der Pods im Detail:

    kubectl describe pod <pod-name>
  7. Überprüfen Sie die Labels der Pods und vergleichen Sie sie mit dem Selektor des Deployments:

    kubectl get pods --show-labels

    Sehen Sie das Label pod-template-hash? Was ist dessen Zweck?

  8. Skalieren Sie das Deployment auf 5 Replicas:

    kubectl scale deployment nginx-deployment --replicas=5

    Beobachten Sie, wie die neuen Pods hochfahren:

    kubectl get pods --watch

    Beenden Sie das Watching mit CTRL + C.

    Hat sich das ReplicaSet verändert?

    kubectl get replicaset
  9. Löschen Sie einen der Pods manuell:

    kubectl delete pod <pod-name>

    Beobachten Sie sofort danach:

    kubectl get pods --watch

    Verifizieren Sie dass ein neuer Pod gestartet wurde.

    Beenden Sie das Watching mit CTRL + C.

10.2. Rolling Update und Rollback

In dieser Aufgabe führen Sie ein Rolling Update durch, beobachten den Ablauf im Detail und machen das Update anschließend rückgängig.

  1. Stellen Sie sicher, dass das Deployment nginx-deployment aus der vorherigen Aufgabe noch läuft und 3 Replicas hat:

    kubectl scale deployment nginx-deployment --replicas=3
    kubectl get deployment nginx-deployment
  2. Setzen Sie eine Annotation, damit die Rollout-Historie aussagekräftig ist:

    kubectl annotate deployment nginx-deployment \
    kubernetes.io/change-cause="initiales Deployment mit nginx:1.27"
  3. Lösen Sie ein Rolling Update auf nginx:1.29 aus:

    kubectl set image deployment/nginx-deployment nginx=nginx:1.29
  4. Beobachten Sie den Rollout-Status in Echtzeit:

    kubectl rollout status deployment/nginx-deployment
  5. Beobachten Sie gleichzeitig, wie die ReplicaSets wechseln:

    kubectl get replicaset --watch

    Beenden Sie das Watching mit CTRL + C.

  6. Annotieren Sie die neue Revision direkt im Anschluss:

    kubectl annotate deployment nginx-deployment \
    kubernetes.io/change-cause="Update auf nginx:1.29" --overwrite
  7. Prüfen Sie die Rollout-Historie:

    kubectl rollout history deployment/nginx-deployment

    Wie viele Revisionen sehen Sie? Was steht unter CHANGE-CAUSE?

  8. Zeigen Sie die Details einer einzelnen Revision an:

    kubectl rollout history deployment/nginx-deployment --revision=1
    kubectl rollout history deployment/nginx-deployment --revision=2

    Welches Image wurde in Revision 1 verwendet, welches in Revision 2?

  9. Lösen Sie nun absichtlich ein fehlerhaftes Update aus (ungültiges Image-Tag):

    kubectl set image deployment/nginx-deployment nginx=nginx:does-not-exist
    kubectl annotate deployment nginx-deployment \
    kubernetes.io/change-cause="fehlerhaftes Update: nginx:does-not-exist" --overwrite
  10. Beobachten Sie, was passiert:

    kubectl rollout status deployment/nginx-deployment

    Bricht der Befehl nach einiger Zeit ab? Was wird gemeldet?

    Schauen Sie sich die Pods an:

    kubectl get pods

    Welche Pods haben den Status ImagePullBackOff oder ErrImagePull? Wie viele Pods aus der alten Version laufen noch?

    Lassen Sie sich die Events des fehlerhaften Pods anzeigen:

    kubectl describe pod <pod-name-des-fehlerhaften-pods>

    Was steht unter Events?

    Beenden Sie den rollout status-Befehl mit CTRL + C, falls er noch läuft.

  11. Machen Sie das fehlerhafte Update rückgängig:

    kubectl rollout undo deployment/nginx-deployment
  12. Überprüfen Sie, dass alle Pods wieder laufen:

    kubectl rollout status deployment/nginx-deployment
    kubectl get pods

    Auf welche Image-Version wurde zurückgerollt?

  13. Prüfen Sie die Rollout-Historie erneut:

    kubectl rollout history deployment/nginx-deployment

    Wie viele Revisionen gibt es jetzt? Was fällt Ihnen an der Revisionsnummer auf?

  14. Rollen Sie gezielt auf Revision 2 zurück (das funktionierende nginx:1.26-Update):

    kubectl rollout undo deployment/nginx-deployment --to-revision=2

    Verifizieren Sie das verwendete Image:

    kubectl describe deployment nginx-deployment | grep Image
  15. Löschen Sie das Deployment nach der Aufgabe:

    kubectl delete deployment nginx-deployment

    Beobachten Sie mit kubectl get replicaset und kubectl get pods: Werden alle abhängigen Objekte ebenfalls gelöscht?

10.3. DaemonSets im Cluster erkunden

In dieser Aufgabe untersuchen Sie die DaemonSets, die bereits auf Ihrem Cluster laufen, und verstehen deren Aufgabe.

  1. Listen Sie alle DaemonSets clusterübergreifend auf:

    kubectl get daemonset --all-namespaces
  2. Untersuchen Sie das DaemonSet kube-proxy im Detail:

    kubectl describe daemonset kube-proxy -n kube-system
  3. Untersuchen Sie die Toleration von kube-proxy genauer:

    kubectl get daemonset kube-proxy -n kube-system -o yaml | grep -A 5 tolerations

    Sie sehen eine Toleration der Form op: Exists ohne einen Key. Was bewirkt das? Auf welchen Nodes darf kube-proxy damit laufen?

  4. Untersuchen Sie das DaemonSet kindnet im Detail:

    kubectl describe daemonset kindnet -n kube-system

    kindnet ist das CNI-Plugin (Container Network Interface) von kind. Es stellt das Pod-Netzwerk bereit — ohne es könnten Pods nicht miteinander kommunizieren.

  5. Zeigen Sie die Pods der DaemonSets an und prüfen Sie, auf welchen Nodes sie laufen:

    kubectl get pods -n kube-system -o wide | grep -E 'kindnet|kube-proxy'

10.4. Einen Job ausführen und beobachten

In dieser Aufgabe erstellen Sie einen einfachen Kubernetes Job, beobachten seinen Ablauf und verstehen, wie Jobs sich von dauerhaft laufenden Workloads unterscheiden.

  1. Erstellen Sie den folgenden Job, der die Zahl Pi auf 2 Stellen ausgibt:

    kubectl create job pi --image=busybox:1.28 -- sh -c "sleep 30; echo 3.14"
  2. Beobachten Sie den Status des Jobs:

    kubectl get job pi --watch

    Beenden Sie das Watching mit CTRL + C.

  3. Zeigen Sie den Pod an, den der Job erstellt hat:

    kubectl get pods

    Welchen Status hat der Pod nach Abschluss? Was ist der Unterschied zu einem Pod, der von einem Deployment verwaltet wird?

  4. Lesen Sie die Ausgabe des Jobs:

    kubectl logs job/pi

    Sehen Sie die Dezimalstellen von Pi?

  5. Untersuchen Sie den abgeschlossenen Job im Detail:

    kubectl describe job pi
  6. Erstellen Sie nun einen Job, der absichtlich fehlschlägt, und beobachten Sie das Retry-Verhalten.

    Die Datei job-error.yaml finden Sie in den Begleitmaterialien.

    kubectl create -f job-error.yaml

    Beobachten Sie die Pods:

    kubectl get pods --watch

    Was passiert? Wie oft versucht Kubernetes den Job neu zu starten? Was bedeutet backoffLimit?

    Beenden Sie das Watching mit CTRL + C.

    kubectl describe job fail-job

    Was steht unter Pods Statuses und Events?

  7. Räumen Sie auf:

    kubectl delete job pi
    kubectl delete job fail-job

    Was passiert mit den zugehörigen Pods beim Löschen des Jobs?

11. Networking

Die Aufgaben in diesem Abschnitt beziehen sich auf das Folienkapitel “Networking”.

11.1. Deployment mit ClusterIP-Service exponieren

In dieser Aufgabe erstellen Sie ein Deployment mit mehreren Replicas, exponieren es über einen ClusterIP-Service und untersuchen, wie Kubernetes den Traffic per Round-Robin auf die Pods verteilt.

  1. Erstellen Sie einen neuen Namespace und wechseln Sie in diesen:

    kubectl create namespace web
    kubectl config set-context --current --namespace=web
  2. Erstellen Sie ein Deployment mit 3 Replicas:

    kubectl create deployment nginx-web --image=nginx:1.27 --replicas=3
  3. Exponieren Sie das Deployment als ClusterIP-Service auf Port 80:

    kubectl expose deployment nginx-web --port=80 --target-port=80 --name=nginx-svc
  4. Zeigen Sie den erstellten Service an:

    kubectl get service nginx-svc
  5. Starten Sie einen temporären BusyBox-Pod im selben Namespace (web), um den Service von innen zu testen:

    kubectl run debug --image=busybox:1.28 --rm -it -- sh

    Führen Sie im Container folgende Befehle aus:

    1. Lösen Sie den Service-Namen per DNS auf:

      nslookup nginx-svc
      • Welche IP-Adresse gibt nslookup zurück? Stimmt sie mit der CLUSTER-IP des Services überein?
      • Auf welchen DNS-Namen wird die Anfrage vollständig aufgelöst (FQDN)?

    Verlassen Sie den Container mit exit.

  6. Starten Sie nun einen BusyBox-Pod in einem anderen Namespace (default), um zu testen, wie der Service von außerhalb des Namespaces erreichbar ist:

    kubectl run debug --image=busybox:1.28 --rm -it --namespace=default -- sh

    Führen Sie im Container folgende Befehle aus:

    1. Versuchen Sie, den Service nur über den kurzen Namen zu erreichen:

      nslookup nginx-svc

      Was passiert? Warum schlägt die Auflösung fehl?

    2. Verwenden Sie stattdessen den vollständigen DNS-Namen (FQDN):

      nslookup nginx-svc.web.svc.cluster.local

      Was ist der Unterschied? Wann ist der kurze Name ausreichend?

    3. Rufen Sie den Service über den FQDN auf:

      wget -qO- http://nginx-svc.web.svc.cluster.local

    Verlassen Sie den Container mit exit.

  7. Räumen Sie auf:

    kubectl delete deployment nginx-web -n web
    kubectl delete service nginx-svc -n web
    kubectl delete namespace web
    kubectl config set-context --current --namespace=default

11.2. EndpointSlices untersuchen

In dieser Aufgabe untersuchen Sie die EndpointSlices, die Kubernetes automatisch hinter einem Service anlegt. Sie beobachten, wie sich die Einträge bei Skalierung und Pod-Ausfall ändern.

  1. Erstellen Sie ein Deployment und einen Service:

    kubectl create deployment nginx-ep --image=nginx:1.27 --replicas=2
    kubectl expose deployment nginx-ep --port=80 --name=nginx-ep-svc
  2. Zeigen Sie alle EndpointSlices im default-Namespace an:

    kubectl get endpointslices

    Welchen Namen hat der EndpointSlice für nginx-ep-svc? Über welches Label ist er mit dem Service verknüpft?

  3. Zeigen Sie den EndpointSlice im Detail an:

    kubectl get endpointslice <endpointslice-name> -o yaml

    Beantworten Sie folgende Fragen anhand der Ausgabe:

  4. Skalieren Sie das Deployment auf 4 Replicas:

    kubectl scale deployment nginx-ep --replicas=4

    Warten Sie kurz und zeigen Sie den EndpointSlice erneut an:

    kubectl get endpointslice <endpointslice-name> -o yaml

    Sind jetzt vier Endpunkte eingetragen?

  5. Räumen Sie auf:

    kubectl delete deployment nginx-ep
    kubectl delete service nginx-ep-svc

11.3. Gateway API und NGINX Gateway Fabric installieren

In dieser Aufgabe installieren Sie die Gateway API CRDs sowie den NGINX Gateway Fabric Controller und überprüfen die Installation.

  1. Installieren Sie die Standard-CRDs der Gateway API:

    kubectl kustomize \
    "https://github.com/nginx/nginx-gateway-fabric/config/crd/gateway-api/standard?ref=v2.5.1" \
    | kubectl apply -f -
  2. Erstellen Sie den Namespace für den Gateway-Controller:

    kubectl create namespace nginx-gateway
  3. Installieren Sie die NGINX-Gateway-Fabric-eigenen CRDs:

    kubectl apply --server-side \
    -f https://raw.githubusercontent.com/nginx/nginx-gateway-fabric/v2.5.1/deploy/crds.yaml
  4. Deployen Sie den NGINX Gateway Fabric Controller:

    kubectl apply \
    -f https://raw.githubusercontent.com/nginx/nginx-gateway-fabric/v2.5.1/deploy/default/deploy.yaml
  5. Warten Sie, bis der Controller-Pod bereit ist:

    kubectl wait --namespace nginx-gateway \
    --for=condition=ready pod \
    --selector=app.kubernetes.io/name=nginx-gateway-fabric \
    --timeout=120s
  6. Überprüfen Sie, dass die Gateway-API-CRDs installiert wurden:

    Unter Linux

    kubectl get crds | grep gateway.networking.k8s.io

    Unter Windows

    kubectl get crds | Select-String -Pattern gateway.networking.k8s.io

    Welche CRDs sehen Sie? Erkennen Sie gateways, httproutes und gatewayclasses?

  7. Überprüfen Sie, ob eine GatewayClass registriert wurde:

    kubectl get gatewayclass

11.4. Gewichtetes Blue/Green-Routing mit der Gateway API

In dieser Aufgabe deployen Sie zwei Versionen einer Anwendung (Blue und Green) und konfigurieren eine HTTPRoute, die den eingehenden Traffic im Verhältnis 30 % Blue zu 70 % Green aufteilt.

Die Begleitmaterialien enthalten die Dateien gateway-canary-prepare.yaml und gateway-canary.yaml.

  1. Legen Sie die Backend-Ressourcen an:

    kubectl apply -f gateway-canary-prepare.yaml
  2. Überprüfen Sie, was angelegt wurde:

    kubectl get deployments
    kubectl get services
    kubectl get configmaps
  3. Schauen Sie sich beide Anwendungen einzeln an. Leiten Sie zunächst den blue-service weiter und öffnen Sie http://localhost:8081 im Browser:

    kubectl port-forward svc/blue-service 8081:80

    Beenden Sie das Port-Forwarding mit CTRL + C.

  4. Leiten Sie anschließend den green-service weiter und öffnen Sie http://localhost:8082 im Browser:

    kubectl port-forward svc/green-service 8082:80

    Was unterscheidet die beiden Seiten optisch? Beenden Sie das Port-Forwarding mit CTRL + C.

  5. Öffnen Sie die Datei gateway-canary.yaml. Sie enthält bereits ein Gateway-Objekt. Ergänzen Sie in der HTTPRoute die rules, sodass 30 % des Traffics an blue-service und 70 % an green-service gehen.

  6. Wenden Sie die Datei an:

    kubectl apply -f gateway-canary.yaml
  7. Überprüfen Sie den Status des Gateways:

    kubectl get gateway my-gateway
    kubectl describe gateway my-gateway
  8. Zeigen Sie die HTTPRoute an:

    kubectl get httproute blue-green-route
    kubectl describe httproute blue-green-route
  9. Besuchen Sie http://localhost:8085. Was ist das Ergebnis? Bleibt der Besuch der Seite bei jeden Aufruf gleich?

  10. Senden Sie in mehrere Anfragen und beobachten Sie die Verteilung:

    Unter Linux

    for i in $(seq 1 20); do curl -s http://localhost:8085 | grep -o '<h1>.*</h1>'; done

    Unter Windows

    for ($i=1; $i -le 20; $i++) {
        (Invoke-WebRequest "http://localhost:8085").Content -match '<h1>.*</h1>' | Out-Null
        $matches[0]
    }
  11. Räumen Sie auf:

    kubectl delete -f gateway-canary.yaml
    kubectl delete -f gateway-canary-prepare.yaml

12. Helm

Die Aufgaben in diesem Abschnitt beziehen sich auf das Folienkapitel “Helm”.

12.1. WordPress mit Helm installieren und konfigurieren

In dieser Aufgabe installieren Sie WordPress über ein Helm-Chart aus einem öffentlichen Repository, passen die Installation über Values an und führen anschließend ein Upgrade durch.

  1. Fügen Sie das Bitnami-Repository hinzu und aktualisieren Sie den lokalen Index:

    helm repo add bitnami https://charts.bitnami.com/bitnami
    helm repo update
  2. Suchen Sie nach dem WordPress-Chart und zeigen Sie verfügbare Versionen an:

    helm search repo wordpress

    Welche Chart-Version und welche APP VERSION sind aktuell verfügbar?

  3. Zeigen Sie die konfigurierbaren Standardwerte des Charts an:

    Unter Linux

    helm show values bitnami/wordpress | head -80

    Unter Windows

    helm show values bitnami/wordpress | Select-Object -First 80

    Welche Werte steuern die Anzahl der Replicas und das Admin-Passwort?

  4. Erstellen Sie einen Namespace für die Installation:

    kubectl create namespace wordpress
  5. Installieren Sie WordPress mit einem eigenen Blog-Titel und einem festen Admin-Passwort:

    helm install my-wordpress bitnami/wordpress \
    --namespace wordpress \
    --set wordpressUsername=admin \
    --set wordpressPassword=schulung123 \
    --set blogName="Kubernetes Training" \
    --set service.type=ClusterIP \
    --set mariadb.auth.rootPassword=rootpass123
  6. Beobachten Sie, wie die Pods hochfahren:

    kubectl get pods -n wordpress --watch

    Beenden Sie das Watching mit CTRL + C, sobald alle Pods im Status Running sind.

  7. Zeigen Sie alle von Helm erzeugten Kubernetes-Ressourcen an:

    kubectl get all -n wordpress
  8. Zeigen Sie den Status und die Metadaten des Helm-Releases an:

    helm status my-wordpress -n wordpress
    helm list -n wordpress
  9. Leiten Sie den WordPress-Service auf Ihren Rechner weiter und öffnen Sie http://localhost:8080 im Browser:

    kubectl port-forward svc/my-wordpress 8080:80 -n wordpress

    Ist die WordPress-Startseite erreichbar? Beenden Sie das Port-Forwarding mit CTRL + C.

  10. Führen Sie ein Upgrade durch und erhöhen Sie die Anzahl der WordPress-Replicas auf 2:

    helm upgrade my-wordpress bitnami/wordpress \
    --namespace wordpress \
    --reuse-values \
    --set replicaCount=2

    Überprüfen Sie, dass zwei WordPress-Pods laufen:

    kubectl get pods -n wordpress
  11. Sehen Sie sich die Upgrade-Historie des Releases an:

    helm history my-wordpress -n wordpress

    Wie viele Revisionen gibt es? Welche DESCRIPTION hat Revision 1, welche Revision 2?

  12. Machen Sie das Upgrade rückgängig und kehren Sie zu Revision 1 zurück:

    helm rollback my-wordpress 1 -n wordpress

    Überprüfen Sie den Status nach dem Rollback:

    helm list -n wordpress
    helm history my-wordpress -n wordpress

    Auf wie viele Replicas läuft WordPress jetzt wieder?

  13. Räumen Sie auf:

    helm uninstall my-wordpress -n wordpress
    kubectl delete namespace wordpress

    Werden alle Ressourcen (Pods, Services, PVCs) gelöscht? Beobachten Sie mit kubectl get all -n wordpress.