Inhoudsopgave
- Wat je leert
- Startpunt
- Hoe Docker Compose mapt op Kubernetes
- Stap 1: audit je docker-compose.yml
- Stap 2: converteer met Kompose
- Stap 3: maak een namespace en secrets aan
- Stap 4: fix storage met goede PersistentVolumeClaims
- Stap 5: voeg resource requests en limits toe
- Stap 6: voeg health probes toe
- Stap 7: vervang depends_on door init containers
- Stap 8: kies de juiste deployment strategy
- Stap 9: apply en verifieer
- Drie misvattingen die mensen in de problemen brengen
- Productie-hardening checklist
- Wat je geleerd hebt
Wat je leert
Aan het eind van deze tutorial heb je een Docker Compose-bestand omgezet naar Kubernetes-manifesten en die versterkt met resource limits, health probes, fatsoenlijke secrets en de juiste deployment strategy. Je begrijpt dan waarom Kompose-output een startpunt is, geen eindproduct.
Startpunt
Deze tutorial gaat ervan uit dat je het volgende hebt:
- Een werkend
docker-compose.ymlin Compose V3-formaat, met minimaal twee services (een webapplicatie en een database) - Een Kubernetes-cluster met
kubectlgeconfigureerd en verbonden (Minikube, kind, of een managed cloudcluster) kubectlversie 1.28 of nieuwer- Basiskennis van YAML en de commandline
- Container-images die al gebuild en gepusht zijn naar een registry (Kompose bouwt standaard geen images voor je)
De voorbeelden gebruiken een WordPress + MySQL-stack omdat die alle migratie-uitdagingen afdekt: volumes, secrets, dependency-volgorde en health checks. Dezelfde principes gelden voor elk multi-service Compose-bestand.
Hoe Docker Compose mapt op Kubernetes
Snap eerst wat wel en niet vertaalt voordat je een tool aanraakt. Deze tabel voorkomt de meest voorkomende verrassingen.
| Docker Compose-concept | Kubernetes-equivalent | Mapt netjes? |
|---|---|---|
services: |
Deployment + Service per service | Ja |
image: |
Container image in pod spec |
Ja |
ports: |
Service met port en targetPort |
Ja |
environment: |
env: in container spec (plaintext) |
Technisch wel, maar een beveiligingsprobleem |
volumes: (named) |
PersistentVolumeClaim | Gedeeltelijk: vereist StorageClass, access mode, grootte |
depends_on: |
Niks. Gebruik init containers | Nee |
networks: |
Genegeerd. Kubernetes gebruikt flat networking | Nee |
build: |
Genegeerd. Bouw je images van tevoren | Nee |
restart: always |
restartPolicy: Always (standaard) |
Ja |
Bron: Kompose-conversiematrix
De velden die niet netjes mappen zijn precies de velden die productie-incidenten veroorzaken als je ze niet aanpakt.
Stap 1: audit je docker-compose.yml
Loop voor de conversie door je Compose-bestand en categoriseer elke configuratiewaarde.
Neem deze typische WordPress-stack:
# docker-compose.yml (Compose V3)
services:
wordpress:
image: wordpress:6.7-apache
ports:
- "80:80"
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: verander-me-in-productie # gevoelig
WORDPRESS_DB_NAME: wordpress
volumes:
- wp-content:/var/www/html/wp-content
depends_on:
- db
db:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root-verander-me # gevoelig
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: verander-me-in-productie # gevoelig
volumes:
- db-data:/var/lib/mysql
volumes:
wp-content:
db-data:
Classificeer elk element:
- Gevoelige environment variables (
WORDPRESS_DB_PASSWORD,MYSQL_ROOT_PASSWORD,MYSQL_PASSWORD): worden Kubernetes Secrets - Niet-gevoelige environment variables (
WORDPRESS_DB_HOST,WORDPRESS_DB_NAME): worden een ConfigMap - Named volumes (
wp-content,db-data): worden PersistentVolumeClaims met een expliciete StorageClass en grootte depends_on: vereist een init container op de WordPress Deploymentnetworks(hier niet aanwezig, maar vaak wel): worden door Kompose genegeerd
Checkpoint: je kunt elke gevoelige waarde, elk volume en elke dependency in je Compose-bestand benoemen voordat je verder gaat.
Stap 2: converteer met Kompose
Kompose is een officieel Kubernetes-incubatorproject dat Docker Compose-bestanden omzet naar Kubernetes-manifesten.
Installeer het:
# Linux (Kompose v1.34.0)
curl -L https://github.com/kubernetes/kompose/releases/download/v1.34.0/kompose-linux-amd64 -o kompose
chmod +x kompose
sudo mv ./kompose /usr/local/bin/kompose
# macOS
brew install kompose
Draai de conversie:
kompose convert -f docker-compose.yml
Je krijgt waarschuwingen als deze:
WARN Unsupported depends_on key - ignoring
WARN Unsupported networks key - ignoring
Die waarschuwingen betekenen dat die velden stilletjes uit de output zijn gelaten. Kompose is niet stuk; het vertelt je dat de output incompleet is.
Bekijk wat Kompose heeft gegenereerd:
ls -la *.yaml
# Verwachte output:
# db-deployment.yaml
# db-service.yaml
# db-data-persistentvolumeclaim.yaml
# wordpress-deployment.yaml
# wordpress-service.yaml
# wp-content-persistentvolumeclaim.yaml
Pas deze bestanden nog niet toe. Ze moeten eerst gehard worden. Ook de officiële Kubernetes-documentatie zegt dat je moet "review and edit" voordat je apply draait.
Checkpoint: je hebt zes YAML-bestanden. Geen ervan hoort as-is te worden toegepast.
Stap 3: maak een namespace en secrets aan
Begin met een dedicated namespace en verplaats gevoelige waarden uit plaintext.
kubectl create namespace wordpress
Maak Secrets aan voor alle gevoelige waarden uit Stap 1:
kubectl create secret generic wordpress-secrets \
--from-literal=db-password="$(openssl rand -base64 24)" \
--from-literal=db-root-password="$(openssl rand -base64 24)" \
--namespace=wordpress
Maak een ConfigMap voor niet-gevoelige configuratie:
# wordpress-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: wordpress-config
namespace: wordpress
data:
WORDPRESS_DB_HOST: "db"
WORDPRESS_DB_NAME: "wordpress"
WORDPRESS_DB_USER: "wordpress"
kubectl apply -f wordpress-config.yaml
Bewerk nu beide Deployment-bestanden om hiernaar te verwijzen in plaats van naar plaintext env:-waarden. Vervang in wordpress-deployment.yaml het env:-blok dat Kompose genereerde:
# Vervang Kompose's plaintext env: blok hiermee
envFrom:
- configMapRef:
name: wordpress-config
env:
- name: WORDPRESS_DB_PASSWORD
valueFrom:
secretKeyRef:
name: wordpress-secrets
key: db-password
En in de MySQL Deployment:
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: wordpress-secrets
key: db-root-password
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: wordpress-secrets
key: db-password
- name: MYSQL_DATABASE
value: "wordpress"
- name: MYSQL_USER
value: "wordpress"
Base64-encoding (zoals gebruikt in Secret YAML-manifesten) is encoding, geen encryptie. Voor productie-GitOps-workflows kun je kijken naar Sealed Secrets of HashiCorp Vault.
Checkpoint: er staan geen gevoelige waarden meer als plaintext in je YAML-bestanden.
Stap 4: fix storage met goede PersistentVolumeClaims
Kompose maakt wel PVC-manifesten, maar zonder de belangrijkste velden. De gegenereerde PVCs verwijzen naar geen StorageClass en geen opslaggrootte. Voor een dieper begrip van PVs, PVCs en StorageClasses, zie Kubernetes PersistentVolumes en PersistentVolumeClaims.
Check eerst welke StorageClasses je cluster aanbiedt:
kubectl get storageclass
# Voorbeeldoutput op een cloudcluster:
# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE
# standard (default) kubernetes.io/gce-pd Delete WaitForFirstConsumer
# premium-rwo pd.csi.storage.gke.io Delete WaitForFirstConsumer
Vervang de gegenereerde PVC voor de database door een goed gespecificeerde:
# db-data-persistentvolumeclaim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-data
namespace: wordpress
spec:
accessModes:
- ReadWriteOnce # single pod read/write, correct voor databases
storageClassName: standard # moet overeenkomen met een StorageClass in je cluster
resources:
requests:
storage: 20Gi
Doe hetzelfde voor wp-content:
# wp-content-persistentvolumeclaim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wp-content
namespace: wordpress
spec:
accessModes:
- ReadWriteOnce # prima voor single-replica WordPress
storageClassName: standard
resources:
requests:
storage: 10Gi
Als je StorageClass de hostPath-provisioner gebruikt (standaard op Minikube), dan staat data in een temp-directory op de node en gaat die verloren bij rescheduling. Dat is alleen voor development.
Checkpoint: draai kubectl get storageclass en bevestig dat je gekozen StorageClass bestaat. Elke PVC specificeert een accessMode, een storageClassName en een storage-grootte.
Stap 5: voeg resource requests en limits toe
Kompose genereert geen resources-blok. Zonder resource requests heeft de scheduler geen capaciteitsgarantie voor je pods. Zonder limits kan een op hol geslagen container een hele node opeten. Voor de volledige mechanica, zie Kubernetes resource requests en limits.
Voeg resource-blokken toe aan elke container in elke Deployment. Dit zijn startwaarden; stem ze af met kubectl top pods of VPA in aanbevelingsmodus zodra je productiedata hebt.
WordPress-container:
resources:
requests:
cpu: "100m" # scheduling-garantie
memory: "128Mi"
limits:
cpu: "500m" # kernel throttlet hierboven
memory: "256Mi" # kernel OOMKillt hierboven
MySQL-container:
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "512Mi"
CPU throttlet (het proces wordt trager). Memory OOMKillt (het proces wordt gekilld). Stel memory limits conservatief maar realistisch in. Een te krappe limit veroorzaakt frequente restarts; een te ruime verspilt node-capaciteit.
Checkpoint: elke container in elke Deployment heeft zowel requests als limits.
Stap 6: voeg health probes toe
Kompose genereert geen health probes. Zonder probes kan Kubernetes een vastgelopen container niet detecteren, kan het geen verkeer omleiden bij ongezonde pods, en kan het nieuwe pods niet veilig verifiëren tijdens rolling updates. Voor probe-types, mechanismen en timing, zie hoe je Kubernetes health probes configureert.
Voeg probes toe aan de WordPress-container:
# WordPress health probes
startupProbe: # blokkeert liveness + readiness tot boot klaar is
httpGet:
path: /wp-login.php
port: 80
failureThreshold: 30 # 30 x 10 = 300 seconden maximaal opstarttijd
periodSeconds: 10
livenessProbe: # herstart container als hij vastloopt
httpGet:
path: /wp-login.php
port: 80
periodSeconds: 30
failureThreshold: 3
readinessProbe: # verwijdert pod uit Service endpoints als ongezond
httpGet:
path: /wp-login.php
port: 80
periodSeconds: 10
failureThreshold: 3
Plugin-zware WordPress-installaties kunnen meer dan 60 seconden nodig hebben bij de eerste boot. De startup probe met een 300-seconden-venster houdt daar rekening mee zonder snelle boots af te straffen.
Voeg een TCP-probe toe aan de MySQL-container (MySQL heeft geen HTTP-endpoint):
# MySQL health probes
startupProbe:
tcpSocket:
port: 3306
failureThreshold: 30
periodSeconds: 10
livenessProbe:
tcpSocket:
port: 3306
periodSeconds: 10
failureThreshold: 3
readinessProbe:
tcpSocket:
port: 3306
periodSeconds: 10
failureThreshold: 3
Checkpoint: elke container heeft een startupProbe, livenessProbe en readinessProbe.
Stap 7: vervang depends_on door init containers
Docker Compose depends_on zorgt ervoor dat de database-container start voor WordPress. Kubernetes heeft geen equivalent veld. Kompose laat het stilletjes vallen. Zonder vervanging probeert WordPress verbinding te maken met MySQL voordat MySQL klaar is, wat resulteert in Error establishing a database connection bij de eerste deploy.
De oplossing: voeg een init container toe aan de WordPress Deployment die wacht tot MySQL verbindingen accepteert.
# Voeg toe aan de WordPress Deployment spec.template.spec
initContainers:
- name: wait-for-mysql
image: mysql:8.0 # hergebruik het MySQL-image, dat heeft mysqladmin
command:
- sh
- -c
- |
until mysqladmin ping -h db --silent; do
echo "Waiting for MySQL..."
sleep 3
done
env:
- name: MYSQL_PWD
valueFrom:
secretKeyRef:
name: wordpress-secrets
key: db-root-password
Init containers draaien sequentieel, tot voltooiing, voordat de main containers starten. Zodra mysqladmin ping slaagt, start WordPress met een werkende databaseverbinding.
Checkpoint: deploy de WordPress-pod en check kubectl describe pod <wordpress-pod> -n wordpress. Je zou de wait-for-mysql init container moeten zien met status Completed voordat de main container start.
Stap 8: kies de juiste deployment strategy
Dit is de stap die mensen overslaan, om er vervolgens uren aan debugging te besteden.
De standaard Kubernetes deployment strategy is RollingUpdate: maak een nieuwe pod aan voordat de oude wordt gestopt. Met ReadWriteOnce-PVCs (het meest voorkomende type voor cloud block storage) kan maar één pod tegelijk het volume mounten. Tijdens een rolling update hangt de nieuwe pod in Pending met een Multi-Attach error omdat de oude pod het volume nog vasthoudt.
Voor single-replica workloads met ReadWriteOnce-PVCs, gebruik Recreate:
# In zowel wordpress-deployment.yaml als db-deployment.yaml
spec:
strategy:
type: Recreate # stop oude pod voordat nieuwe pod wordt aangemaakt
Dit veroorzaakt korte downtime tijdens updates. Voor een single-replica WordPress + MySQL-setup is die afweging acceptabel en voorkomt het de Multi-Attach-deadlock volledig.
Wil je zero-downtime voor WordPress, dan heb je ReadWriteMany-storage nodig (NFS, AWS EFS, Azure Files) zodat meerdere pods hetzelfde volume kunnen mounten. Dat is een andere architectuur dan wat Docker Compose je geeft.
Checkpoint: beide Deployments hebben strategy.type: Recreate.
Stap 9: apply en verifieer
Pas alle manifesten toe op de wordpress-namespace:
kubectl apply -f . -n wordpress
Monitor de pod-status:
kubectl get pods -n wordpress -w
# Verwacht: init container draait eerst, daarna bereiken beide pods Running/Ready
Check PVC-binding:
kubectl get pvc -n wordpress
# Verwacht: STATUS = Bound voor beide PVCs
Als een PVC in Pending blijft hangen, is de meest waarschijnlijke oorzaak een ontbrekende of niet-matchende StorageClass. Verifieer met kubectl describe pvc <naam> -n wordpress en vergelijk de storageClassName met kubectl get storageclass.
Wacht op de WordPress-rollout:
kubectl rollout status deployment/wordpress -n wordpress
# Verwacht: "deployment 'wordpress' successfully rolled out"
Open de WordPress-setup-wizard:
kubectl port-forward svc/wordpress 8080:80 -n wordpress
# Open http://localhost:8080 in je browser
Checkpoint: je ziet de WordPress-installatiewizard. De databaseverbinding werkt. Beide PVCs zijn Bound.
Drie misvattingen die mensen in de problemen brengen
"Kompose-output is productiewaardig"
Kompose is scaffolding. De officiële Kompose-documentatie stelt dat conversies "not always 1-to-1" zijn. Gegenereerde manifesten missen resource limits (risico: pod-eviction en node-uithongering), health probes (risico: verkeer naar dode pods), en secrets-afhandeling (risico: wachtwoorden in Git). Stappen 3 tot en met 8 van deze tutorial bestaan volledig omdat Kompose-output incompleet is.
"Docker Compose-volumes mappen direct op Kubernetes-volumes"
Docker Compose named volumes zijn host-lokale storage. Kubernetes biedt meerdere volume-types, en Kompose maakt PVCs aan die mogelijk verwijzen naar een StorageClass die niet bestaat in je cluster. Zonder geldige StorageClass hangt de PVC voor altijd in Pending zonder duidelijke foutmelding. Op Minikube slaat de standaard hostPath-provisioner data op in /tmp, wat verloren gaat bij rescheduling.
"Docker Compose-networks mappen op Kubernetes-namespaces"
Ze zijn fundamenteel verschillend. Docker Compose-networks zijn per-project bridge networks voor DNS-isolatie. Kubernetes gebruikt flat networking: elke pod kan elke andere pod bereiken via IP-adres over het hele cluster. Namespaces zijn administratieve grenzen (RBAC, resource quota's), geen netwerkisolatie. Heb je netwerkisolatie nodig na de migratie, dan heb je NetworkPolicy-objecten nodig.
Productie-hardening checklist
Verifieer elk punt voordat je dit in productie draait:
- [ ] Resource requests en limits op elke container
- [ ] Liveness, readiness en startup probes op elke container
- [ ] Alle gevoelige waarden in Kubernetes Secrets, geen plaintext in YAML gecommit naar Git
- [ ] StorageClass bevestigd met
kubectl get storageclass - [ ] PVC reclaim policy ingesteld op
Retainvoor database-volumes (kubectl patch pv <naam> -p '{"spec":{"persistentVolumeReclaimPolicy":"Retain"}}') - [ ] Deployment strategy komt overeen met PVC access mode (
RecreatevoorReadWriteOnce) - [ ] Dedicated ServiceAccount per applicatie met minimale RBAC
- [ ] Ingress met TLS-terminatie voor externe toegang (zie cert-manager NGINX-tutorial)
- [ ] NetworkPolicy als inter-service-isolatie vereist is
Wat je geleerd hebt
Je begon met een Docker Compose-bestand en eindigde met productiegeharde Kubernetes-manifesten. Het belangrijkste inzicht: Kompose handelt de structurele conversie af, maar de beveiliging, betrouwbaarheid en operationele gaten zijn voor jou om te dichten.
De concepten die je hier hebt toegepast (Secrets, PVCs, init containers, health probes, deployment strategy) zijn dezelfde die je gebruikt voor elke Kubernetes-workload, niet alleen voor Docker Compose-migraties. Specifiek voor productie-WordPress op Kubernetes bundelt de Bitnami WordPress Helm chart deze patronen in een enkel, onderhouden pakket. Het handmatige pad in deze tutorial leert je wat die chart onder de motorkap doet.