Software container management
Je webshop draait in 40 containers, verspreid over 6 servers.
Om 3 uur 's nachts valt server 4 uit.
Wie start die containers opnieuw op? Op welke server? En hoe vinden de andere containers ze daarna terug?
Na deze les kan je:
kubectl

Een Dockerfile beschrijft stap voor stap hoe een image gebouwd wordt.
FROM ubuntu:24.04
LABEL maintainer="roel.vansteenberghe@odisee.be"
RUN apt-get update \
&& apt-get install -y nginx \
&& rm -rf /var/lib/apt/lists/*
COPY index.html /var/www/html/
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
FROM: de image waarop je verder bouwtRUN, COPY en ADD maakt een nieuwe laag. Lagen worden gecachet en gedeeld tussen images.COPY: bestanden in de image plaatsenCMD / ENTRYPOINT: het ene proces dat de container startEXPOSE documenteert de poort. Publiceren gebeurt pas bij het starten (docker run -p 8080:80).En ook: self-healing, updates zonder downtime, secrets, ...
compose.yaml)docker compose (de oude Python-versie docker-compose wordt niet meer ondersteund)Ideaal voor development, tests en kleine setups
services:
database:
image: mariadb:11.4
volumes:
- db_data:/var/lib/mysql
restart: always
environment:
MARIADB_ROOT_PASSWORD: somewordpress
MARIADB_DATABASE: wordpress
MARIADB_USER: wordpress
MARIADB_PASSWORD: wordpress
wordpress:
image: wordpress:latest
volumes:
- wp_data:/var/www/html
ports:
- "80:80"
restart: always
environment:
WORDPRESS_DB_HOST: database
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
WORDPRESS_DB_NAME: wordpress
volumes:
db_data:
wp_data:
WORDPRESS_DB_HOST: database. Je hoeft dus geen ip-adressen te kennen.ports: hoe de container van buitenaf bereikbaar is. Dit is de -p-parameter van docker run. De databank heeft geen ports: die is enkel intern bereikbaar.volumes: data die het herstarten (en verwijderen) van een container overleeft.restart: always: crasht een container, dan start Docker hem opnieuw. Maar enkel op deze host.
docker compose up -d # opstarten op de achtergrond (= daemon)
docker compose ps # welke containers draaien er?
docker compose logs -f # logs volgen
docker compose down # alles stoppen en opruimen (volumes blijven)
En als je 3 servers hebt in plaats van 1? En als er één uitvalt?
kubectl voor Windows, Linux en Mac), beperkte GUI

Jij beschrijft de gewenste toestand, controllers zorgen er continu voor dat de werkelijkheid volgt: self-healing.
| Object | Wat? |
|---|---|
| Node | (virtuele) machine die deel uitmaakt van de cluster en pods uitvoert |
| Pod | kleinste eenheid: één of meer containers met gedeeld netwerk en volumes, met een intern ip-adres |
| Deployment | gewenste toestand van een applicatie: welke image, hoeveel pods, hoe updaten |
| Service | stabiel adres (en load balancing) voor een groep pods |
| Ingress / Gateway | inkomend http(s)-verkeer van buiten de cluster naar services routeren. Gateway is de opvolger van Ingress. |
| Namespace | logische scheiding binnen de cluster (project, klant, omgeving) |
| Secret / ConfigMap | configuratie en gevoelige gegevens, los van de image |
| PersistentVolumeClaim | aanvraag voor persistente opslag |
Configuratie via kubectl, YAML (of JSON) en deels via de GUI van je cloudprovider
Allocatable in kubectl describe node.
localhost) en de volumes.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.29
ports:
- containerPort: 80
apiVersion + kind: welk type object. Workloads zitten in apps/v1.replicas: hoeveel pods er moeten draaienselector: welke pods horen bij deze deployment? Moet overeenkomen met de labels in de template.template: de blauwdruk van elke podlatest: dan weet je wat er draait, en werken updates en rollbacks voorspelbaar
containers:
- name: nginx
image: nginx:1.29
resources:
requests:
memory: "64Mi" # 64 MiB RAM
cpu: "100m" # 0,1 core
limits:
memory: "128Mi"
cpu: "500m"
Voeg altijd requests (en limits) toe
| Probe | Vraag | Als ze faalt… |
|---|---|---|
| readinessProbe | Mag deze pod verkeer krijgen? | pod tijdelijk uit de load balancing |
| livenessProbe | Leeft de container nog? | container wordt herstart |
| startupProbe | Is de container al opgestart? | de andere probes wachten tot die slaagt |
readinessProbe:
httpGet:
path: /healthz
port: 80
periodSeconds: 5
| Object | Gebruik | Voorbeeld |
|---|---|---|
| Deployment | stateless, pods zijn inwisselbaar | webapp, API |
| StatefulSet | pods met een vaste identiteit en eigen opslag | databank, Kafka |
| DaemonSet | precies één pod op elke node | log- of monitoring-agent |
| Job | loopt tot het werk klaar is | datamigratie |
| CronJob | een Job op vaste tijdstippen | nachtelijke backup |
Variant op een deployment, met sticky identity:
Unlike a Deployment, a StatefulSet maintains a sticky identity for each of its Pods. These pods are created from the same spec, but are not interchangeable: each has a persistent identifier that it maintains across any rescheduling.
mysql-0, mysql-1… met een vaste DNS-naamvolumeClaimTemplates), dat hem volgt bij een herstart

apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: ClusterIP
selector:
app: nginx
ports:
- name: http
protocol: TCP
port: 80
targetPort: 8080
selector: naar welke pods gaat het verkeer? Alle pods met het label app: nginx.port: de poort van de servicetargetPort: de poort waarop de container luistertmy-service (in dezelfde namespace)gke-l7-global-external-managedBest practice: naar buiten via een Gateway, niet via een LoadBalancer per service
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: shop-gateway
spec:
gatewayClassName: gke-l7-global-external-managed
listeners:
- name: http
protocol: HTTP
port: 80
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: shop
spec:
parentRefs:
- name: shop-gateway
rules:
- matches:
- path: { type: PathPrefix, value: /api }
backendRefs:
- { name: api, port: 80 }
- backendRefs: # al de rest
- { name: web-v1, port: 80, weight: 90 }
- { name: web-v2, port: 80, weight: 10 }
parentRefs aan een Gatewayweight splits je het verkeer: hier gaat 10% naar een nieuwe versie (canary release)
service-naam.namespace.svc.cluster.local

apiVersion: v1
kind: Pod
metadata:
name: cuda-test
spec:
containers:
- name: cuda-demo
image: registry.k8s.io/cuda-vector-add:v0.1
resources:
limits:
nvidia.com/gpu: 1
nodeSelector:
cloud.google.com/gke-accelerator: nvidia-l4
nodeSelector bepaalt op welke nodes deze pod mag komen
kubectl create secret generic db-user-pass \
--from-literal=username=admin \
--from-literal=password='1f2d1e2e67df'
secret/db-user-pass created
apiVersion: v1
kind: Secret
metadata:
name: db-credentials
type: Opaque
stringData: # leesbaar invoeren, anders base64
username: "db-admin"
password: "S3cr3tP@ssw0rd!"
apiVersion: v1
kind: Pod
metadata:
name: app-pod-env
spec:
containers:
- name: my-app
image: busybox
command: ["sleep", "3600"]
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
kubectl, het commando om Kubernetes te beheren, en bij GKE ook de gke-gcloud-auth-plugin
gcloud components install kubectl gke-gcloud-auth-plugin
gcloud container clusters get-credentials cluster --location europe-west1-d
kubectl get nodes
kubectl create namespace demo
kubectl create deployment testdeploy \
--image=nginx:1.29 --port=80 -n demo
--dry-run=client -o yaml krijg je de YAML als vertrekpunt
kubectl apply -f deployment.yaml
kubectl apply -f ./k8s/ # hele map
apply opnieuw uitvoeren past enkel aan wat veranderd is| Commando | Wat? |
|---|---|
kubectl get pods -n test | lijst van de pods in de namespace test |
kubectl describe pods -n test | alle details, met onderaan de events |
kubectl logs pod -n test | output van de container (-f om te volgen) |
kubectl exec -it pod -n test -- sh | een shell in de container |
kubectl port-forward svc/service 8080:80 -n test | tunnel van je pc naar de cluster |
kubectl get events -n test | wat gebeurde er recent? |
'pods' kan je uiteraard vervangen door andere objecten: deployments, services, gateways, pvc…
kubectl scale deployment web --replicas=5
kubectl autoscale deployment web \
--min=2 --max=10 --cpu=60%
kubectl get hpa
kubectl set image deployment/web web=registry/app:v2 # nieuwe versie
kubectl rollout status deployment/web # meekijken
kubectl rollout undo deployment/web # terug naar v1
| ReadWriteOnce | lezen/schrijven vanaf één node (meest voorkomend, bv. persistent disk) |
| ReadWriteOncePod | lezen/schrijven door één pod |
| ReadOnlyMany | enkel lezen, vanaf meerdere nodes |
| ReadWriteMany | lezen/schrijven vanaf meerdere nodes: netwerkbestandssysteem nodig (bv. Filestore, EFS) |
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
storageClassName: fastdisks
resources:
requests:
storage: 8Gi
Zonder storageClassName krijg je de default class (kubectl get storageclass).
Met StorageClasses bied je zelf meerdere tiers aan:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fastdisks
provisioner: pd.csi.storage.gke.io
parameters:
type: pd-ssd
volumeBindingMode: WaitForFirstConsumer
Demo!
helm repo add prometheus-community \
https://prometheus-community.github.io/helm-charts
helm install monitoring \
prometheus-community/kube-prometheus-stack


| Docker Compose | Kubernetes (GKE Standard) | GKE Autopilot | Cloud Run | |
|---|---|---|---|---|
| Hosts | 1 | zoveel je wil | beheerd door Google | onzichtbaar |
| Self-healing | herstart op dezelfde host | ja | ja | ja |
| Autoscaling | nee | pods en nodes | pods, nodes automatisch | ook naar nul |
| Jij beheert | de server | nodes, upgrades, capaciteit | enkel je workloads | enkel je container |
| Je betaalt per… | server | node (VM) + cluster | aangevraagde pod-resources | gebruik |
| Flexibiliteit | beperkt | maximaal | groot | http-containers en jobs |
Kubernetes is een platform om platformen te bouwen
Begin je zoektocht naar info steeds in de officiële documentatie. Die is, in tegenstelling tot de honderden blogs over het onderwerp, steeds up-to-date. Geen overbodige luxe met een framework dat elke paar maanden nieuwe features krijgt…
Volgende stap: aan de slag in het labo!