DevOps: CI/CD, Infrastructure as Code en Observability
DevOps is geen tool, maar een manier van werken die ontwikkeling (Dev) en beheer (Ops) samenbrengt om sneller, betrouwbaarder en herhaalbaar software te leveren. In deze tutorial bouwen we een praktisch en samenhangend beeld op van drie pijlers die in moderne DevOps-teams vrijwel altijd terugkomen:
- CI/CD (Continuous Integration / Continuous Delivery & Deployment) – geautomatiseerd bouwen, testen en uitrollen.
- Infrastructure as Code (IaC) – infrastructuur beschrijven als code voor herhaalbaarheid en auditability.
- Observability – inzicht in gedrag en gezondheid van systemen via metrics, logs en traces.
De focus ligt op diep begrip én echte commando’s die je kunt uitvoeren. Voorbeelden gebruiken Linux/macOS shell. Waar relevant noem ik alternatieven.
Inhoud
- 1. Basisprincipes van DevOps
- 2. CI: Continuous Integration in de praktijk
- 3. CD: Continuous Delivery & Deployment
- 4. Infrastructure as Code (IaC)
- 5. Observability
- 6. Alles samen: een end-to-end flow
- 7. Checklist en best practices
1. Basisprincipes van DevOps
DevOps draait om het verkorten van de feedbackloop: van code schrijven naar veilig in productie draaien, met meetbare kwaliteit. De belangrijkste principes:
- Automatisering: handmatige stappen zijn foutgevoelig en slecht schaalbaar.
- Herhaalbaarheid: dezelfde input moet altijd dezelfde output geven (builds, infra, deploys).
- Kleine batches: kleine wijzigingen zijn makkelijker te reviewen, testen en terug te draaien.
- Shared ownership: teams zijn verantwoordelijk voor “you build it, you run it”.
- Observability: zonder zicht op productie kun je niet betrouwbaar verbeteren.
Een nuttige mentale keten is:
Code → Build → Test → Package → Release → Deploy → Run → Observe → Improve
CI/CD automatiseert de keten, IaC maakt de “Deploy/Run”-laag reproduceerbaar, en Observability sluit de feedbackloop.
2. CI: Continuous Integration in de praktijk
Continuous Integration betekent: vaak integreren, automatisch bouwen, automatisch testen, en snel feedback. CI is pas waardevol als het betrouwbaar is: een “groene pipeline” moet echt betekenen dat de wijziging veilig is om te promoten.
2.1 Git-strategie en branch policies
Een simpele, effectieve setup:
main(ofmaster) is altijd deploybaar.- Feature branches via Pull Requests (PR).
- Verplichte checks: unit tests, lint, security scan (minimaal SCA), en code review.
Handige Git-commando’s:
# Nieuwe feature branch
git checkout -b feature/login-rate-limit
# Wijzigingen committen
git add .
git commit -m "Voeg rate limiting toe aan login endpoint"
# Push naar origin
git push -u origin feature/login-rate-limit
Belangrijk: houd PR’s klein. Als je PR 2000 regels wijzigt, is de kans groot dat review en testen gaten laten vallen.
2.2 Build, unit tests en linting
CI moet minimaal:
- Dependencies installeren
- Linting/formatting checken
- Unit tests draaien
- Build maken (binary, package of container)
- Resultaten publiceren (test reports, artifacts)
Voorbeeld met Node.js (conceptueel; je kunt dit vertalen naar Java/Maven, .NET, Go, Python):
# Schone install (reproduceerbaar)
npm ci
# Lint
npm run lint
# Unit tests met coverage
npm test -- --coverage
# Build
npm run build
Voor Java met Maven:
mvn -B -DskipTests=false clean test
mvn -B package
Diepgang: waarom “reproduceerbaar” belangrijk is
npm installkan lockfiles negeren of updaten;npm ciinstalleert exact wat inpackage-lock.jsonstaat.- Reproduceerbaarheid maakt builds auditbaar: je kunt later herleiden welke dependencies zijn gebruikt.
2.3 Artifact management
Een artifact is de output van CI: een JAR, wheel, container image, of static build. In CD wil je hetzelfde artifact promoten door omgevingen (dev → test → prod), niet steeds opnieuw bouwen. Dit voorkomt “works on staging” problemen door build-variatie.
Praktisch voorbeeld: een buildbestand archiveren:
mkdir -p dist
cp -r build/* dist/
tar -czf app-build.tar.gz dist
sha256sum app-build.tar.gz > app-build.tar.gz.sha256
Je kunt artifacts opslaan in:
- GitHub Releases / Actions artifacts
- Nexus / Artifactory
- Een container registry (voor images)
2.4 Container builds met Docker
Containers zijn populair omdat ze runtime-omgeving en dependencies bundelen.
Bouw een image:
docker build -t myapp:1.0.0 .
Inspecteer images:
docker images | head
docker history myapp:1.0.0
Run lokaal:
docker run --rm -p 8080:8080 myapp:1.0.0
Push naar een registry (voorbeeld met GitHub Container Registry):
# Login (gebruik bij voorkeur een token)
echo "$GITHUB_TOKEN" | docker login ghcr.io -u USERNAME --password-stdin
# Tag en push
docker tag myapp:1.0.0 ghcr.io/username/myapp:1.0.0
docker push ghcr.io/username/myapp:1.0.0
Best practice: immutable tags
- Gebruik een versie of git SHA (
1.0.0ofgit-<sha>). - Vermijd alleen
latestals “bron van waarheid”.
3. CD: Continuous Delivery & Deployment
CD gaat over het veilig en herhaalbaar uitrollen naar omgevingen. Er is een belangrijk onderscheid:
- Continuous Delivery: elke wijziging kan naar productie, maar er is vaak nog een handmatige “approve”.
- Continuous Deployment: elke wijziging die door de pipeline komt, gaat automatisch naar productie.
3.1 Omgevingen en promotie
Typische omgevingen:
- dev: snel, flexibel, mag soms “rommelig” zijn.
- test/qa: stabieler, bedoeld voor integratietests.
- staging: productie-achtig, laatste check.
- prod: echte gebruikers.
Belangrijk: promotie betekent hetzelfde artifact. Dus:
- CI bouwt
myapp:git-abc123 - CD deployt
myapp:git-abc123naar dev - Na checks deployt CD hetzelfde image naar staging en prod
3.2 Deploy-strategieën
Rolling update
- Vervangt pods/instances geleidelijk.
- Voordeel: simpel.
- Nadeel: tijdens rollout kunnen oude en nieuwe versies tegelijk draaien → compatibiliteit vereist.
Blue/Green
- Twee identieke omgevingen (blue = live, green = nieuw).
- Switch via load balancer.
- Voordeel: snelle rollback (terugschakelen).
- Nadeel: duurder (dubbele capaciteit).
Canary
- Kleine subset van traffic naar nieuwe versie.
- Voordeel: risico beperken, meten in productie.
- Nadeel: complexer (traffic splitting, metrics-driven decisions).
Praktisch: Kubernetes rollout commando’s (als je Kubernetes gebruikt):
# Deploy toepassen
kubectl apply -f k8s/deployment.yaml
# Rollout status volgen
kubectl rollout status deployment/myapp
# Terugdraaien
kubectl rollout undo deployment/myapp
# Huidige versies bekijken
kubectl get rs -l app=myapp
kubectl describe deployment myapp
3.3 Database changes en migraties
Database-migraties zijn vaak de bron van deploy-problemen. Een paar regels die veel ellende voorkomen:
- Backward compatible migrations: eerst uitbreiden (kolom toevoegen), dan code deployen die beide begrijpt, pas later opruimen.
- Migrations als onderdeel van de pipeline: gecontroleerd, gelogd, herhaalbaar.
- Locking en runtime: grote migraties kunnen tabellen locken; plan en test.
Voorbeeld met Flyway (Java-ecosysteem):
# Migraties uitvoeren (voorbeeld)
flyway -url=jdbc:postgresql://localhost:5432/app \
-user=app -password=secret \
migrate
# Status
flyway info
Voorbeeld met PostgreSQL direct:
psql "postgresql://app:secret@localhost:5432/app" -c "SELECT now();"
psql "postgresql://app:secret@localhost:5432/app" -f migrations/2026_04_28_add_index.sql
4. Infrastructure as Code (IaC)
4.1 Waarom IaC essentieel is
Zonder IaC krijg je “snowflake servers”: handmatig ingerichte machines die uniek en onherhaalbaar zijn. IaC maakt infrastructuur:
- Versiebeheerd (Git)
- Reviewbaar (PR’s)
- Herhaalbaar (zelfde code → zelfde infra)
- Auditbaar (wie wijzigde wat, wanneer)
- Automatiseerbaar (pipeline kan infra aanpassen)
IaC is niet alleen “servers”; ook:
- netwerken (VPC/VNet, subnets)
- IAM/permissions
- databases, queues, object storage
- DNS, TLS certificaten
- monitoring resources (dashboards, alerts)
4.2 Terraform: kernconcepten
Terraform is een veelgebruikte IaC-tool die declaratief beschrijft wat je wilt. Kernbegrippen:
- Providers: plugin voor AWS/Azure/GCP/etc.
- Resources: bouwblokken (bijv.
aws_s3_bucket) - State: Terraform’s administratie van wat er bestaat
- Plan: het verschil tussen gewenst en huidig
- Apply: wijzigingen doorvoeren
Hoewel Terraform-configuratie zelf in .tf staat, kun je de workflow volledig via commando’s begrijpen en beheersen.
4.3 Terraform workflow met echte commando’s
Installatie check:
terraform version
Initialiseer een project (download providers, initialiseer backend):
terraform init
Valideer syntaxis en basisregels:
terraform validate
Formatteer (consistentie in PR’s):
terraform fmt -recursive
Bekijk wat er gaat gebeuren:
terraform plan
Voer uit:
terraform apply
Met auto-approve (alleen in gecontroleerde CI-contexten):
terraform apply -auto-approve
Resources inspecteren:
terraform state list
terraform show
Een specifieke resource opnieuw aanmaken (voorzichtig!):
terraform taint aws_instance.web
terraform apply
Diepgang: plan/apply discipline
- In CI wil je vaak
terraform plandraaien en het planbestand opslaan. - In CD wil je exact dat plan uitvoeren om drift te voorkomen (hetzelfde “artifact”-idee, maar dan voor infra).
Plan opslaan:
terraform plan -out=tfplan
Apply exact dat plan:
terraform apply tfplan
4.4 State, locking en remote backends
Terraform state is cruciaal. Als je lokaal state bewaart en met meerdere mensen werkt, krijg je race conditions en corrupte state. Daarom:
- Gebruik een remote backend (bijv. S3 + DynamoDB lock, Azure Storage, GCS).
- Gebruik locking zodat maar één apply tegelijk kan draaien.
- Beperk toegang: state kan gevoelige data bevatten (resource IDs, soms secrets afhankelijk van tool/usage).
Handige commando’s:
# Trek state naar lokaal (alleen voor inspectie)
terraform state pull > state.json
# Push state (zeldzaam; alleen bij herstel)
terraform state push state.json
Drift detecteren:
terraform plan -refresh-only
4.5 Secrets en configuratie
Een klassieke valkuil is secrets in Git. Richtlijnen:
- Zet geen API keys, wachtwoorden of private keys in repo.
- Gebruik secret managers (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, Vault).
- Gebruik short-lived credentials waar mogelijk (OIDC, workload identity).
Praktisch: environment variables gebruiken voor tools:
export DB_PASSWORD="$(security find-generic-password -a app -s db_password -w 2>/dev/null || true)"
Of met pass (Linux password store):
export DB_PASSWORD="$(pass show prod/db_password)"
Voor Kubernetes secrets (let op: base64 is geen encryptie):
kubectl create secret generic db-secret \
--from-literal=password="$DB_PASSWORD" \
-n myapp
5. Observability
Observability is het vermogen om de interne toestand van een systeem af te leiden uit externe signalen. In praktijk draait het om drie soorten telemetry:
- Metrics: numerieke tijdreeksen (latency, error rate, CPU, queue depth)
- Logs: gebeurtenissen met context (requests, errors, business events)
- Traces: end-to-end pad van een request door meerdere services
5.1 Metrics, logs en traces (en waarom alle drie)
Metrics zijn goed voor:
- trends (latency stijgt langzaam)
- alerting (error rate > drempel)
- dashboards (SLO’s, capaciteit)
Logs zijn goed voor:
- details en context (stacktrace, input)
- forensics (“wat gebeurde er om 12:03?”)
- audit trails
Traces zijn goed voor:
- microservices: waar zit latency?
- dependency mapping
- correlatie tussen services
Alleen metrics geven geen details; alleen logs zijn te veel ruis; alleen traces missen soms brede trends. Samen vormen ze een compleet beeld.
5.2 SLI/SLO/SLA en error budgets
Een volwassen DevOps-organisatie stuurt op betrouwbaarheid met:
- SLI (Service Level Indicator): meetwaarde, bv. “% succesvolle HTTP responses”.
- SLO (Service Level Objective): doel, bv. “99,9% success per 30 dagen”.
- SLA (Service Level Agreement): contractueel, vaak met boetes.
Error budget: als je SLO 99,9% is, is je budget 0,1% fouten. Dat budget “mag je opmaken” voor deploys en experimenten. Is het op? Dan focus op stabiliteit.
Voorbeeld: 99,9% per 30 dagen betekent maximaal:
- 30 dagen = 43.200 minuten
- 0,1% downtime budget = 43,2 minuten
Dit maakt trade-offs concreet: een riskante migratie vlak voor een piekperiode kan je budget opblazen.
5.3 Praktisch: logs inspecteren en metrics opvragen
Logs met journald (Linux):
# Laatste 100 regels van een service
sudo journalctl -u myapp.service -n 100 --no-pager
# Volgen (tail -f)
sudo journalctl -u myapp.service -f
Nginx access logs (voorbeeld):
sudo tail -n 200 /var/log/nginx/access.log
sudo grep " 500 " /var/log/nginx/access.log | tail -n 50
Container logs:
docker logs --tail 200 -f <container_id>
Kubernetes logs:
kubectl logs -n myapp deploy/myapp --tail=200
kubectl logs -n myapp deploy/myapp -c myapp --since=10m
Metrics endpoint (Prometheus-stijl)
Veel apps exposen metrics op /metrics. Je kunt dit direct testen:
curl -s http://localhost:8080/metrics | head -n 40
Als je Prometheus draait, kun je targets debuggen door de endpoint te testen vanaf de Prometheus pod (voorbeeld):
kubectl -n monitoring exec -it deploy/prometheus -- sh -lc \
"apk add --no-cache curl >/dev/null 2>&1 || true; curl -s http://myapp.myapp.svc.cluster.local:8080/metrics | head"
5.4 Distributed tracing en correlation IDs
In microservices is één user request vaak 10+ interne calls. Zonder tracing zie je alleen losse logs. Met tracing voeg je een trace-id toe die overal terugkomt.
Correlation ID in HTTP
- Client stuurt
X-Request-ID(oftraceparentbij W3C Trace Context). - Elke service logt die ID en geeft hem door.
Praktisch testen met curl:
REQ_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
curl -v -H "X-Request-ID: $REQ_ID" http://localhost:8080/api/orders
Zoek vervolgens in logs:
kubectl logs -n myapp deploy/myapp --since=30m | grep "$REQ_ID"
Diepgang: waarom dit helpt
- Je reduceert MTTR (Mean Time To Recovery): sneller oorzaak vinden.
- Je kunt latency per hop analyseren (service A → B → DB).
5.5 Alerting zonder ruis
Alert fatigue is echt: te veel alerts → niemand reageert. Richtlijnen:
- Alert op symptomen, niet op oorzaken (bv. “error rate hoog” i.p.v. “CPU 80%”).
- Gebruik multi-window burn rate voor SLO-alerts (snel én langzaam venster).
- Maak alerts actionable: elke alert moet een duidelijke eerste actie hebben.
- Deduplicate en routeer (on-call, team ownership).
Praktisch: simpele drempel-check met shell (niet als vervanging, wel als debug):
# Check of endpoint gezond is
curl -fsS http://localhost:8080/health || echo "HEALTHCHECK FAIL"
Latency meten:
curl -o /dev/null -s -w "time_total=%{time_total}\nhttp_code=%{http_code}\n" \
http://localhost:8080/api/orders
6. Alles samen: een end-to-end flow
Hier is een realistische flow die CI/CD, IaC en Observability verbindt:
- Developer maakt feature branch
- Code + tests + (optioneel) migratie
- CI pipeline
npm ci/mvn test/go test- build container image
- security checks (dependency scan)
- push image
myapp:git-<sha>naar registry
- IaC pipeline (optioneel apart)
terraform fmt/validate/plan- review van plan in PR
terraform applyna approval
- CD pipeline
- deploy image naar dev namespace
- smoke tests (HTTP checks)
- promotie naar staging
- canary of blue/green naar prod
- Observability
- dashboards tonen latency/error rate
- traces bevestigen dat nieuwe code geen regressie introduceert
- alerts op SLO burn rate
Praktisch: “smoke test” script na deploy:
set -euo pipefail
BASE_URL="${1:-http://localhost:8080}"
echo "Healthcheck..."
curl -fsS "$BASE_URL/health" >/dev/null
echo "API check..."
curl -fsS "$BASE_URL/api/version"
echo "Latency sample..."
curl -o /dev/null -s -w "time_total=%{time_total}\n" "$BASE_URL/api/orders"
Run:
bash smoke.sh https://myapp-staging.example.com
7. Checklist en best practices
CI/CD
- Elke commit triggert CI (build + tests).
- Pipeline is snel (streef: < 10 minuten voor basis checks).
- Artifacts zijn immutable en worden gepromoot (niet opnieuw gebouwd).
- Rollback is getest en gedocumenteerd (
kubectl rollout undo, blue/green switch). - Migraties zijn backward compatible en getest op staging.
IaC
- Infra wijzigingen gaan via PR + review.
- Remote state + locking is ingericht.
-
terraform planwordt opgeslagen en exact toegepast. - Secrets staan in secret manager, niet in Git of state (waar mogelijk).
- Periodieke drift checks (
terraform plan -refresh-only).
Observability
- Metrics: latency, error rate, traffic, saturation (de “4 golden signals”).
- Logs zijn gestructureerd (JSON waar mogelijk) en bevatten request/trace IDs.
- Tracing is end-to-end voor kritieke flows.
- SLO’s zijn gedefinieerd; alerts zijn gebaseerd op burn rate of user impact.
- Runbooks bestaan voor top-10 alerts.
Afsluiting
CI/CD, IaC en Observability versterken elkaar: CI/CD levert snel en gecontroleerd, IaC maakt omgevingen voorspelbaar en reproduceerbaar, en Observability zorgt dat je met vertrouwen kunt veranderen omdat je direct ziet wat de impact is. Als je één onderdeel overslaat, wordt de hele keten fragiel: snelle deploys zonder observability zijn gokken, IaC zonder CI discipline wordt drift, en observability zonder betrouwbare releases maakt incidenten eindeloos.
Als je wilt, kan ik deze tutorial uitbreiden met een concreet voorbeeldproject (bijv. een kleine API), inclusief Dockerfile, Terraform-structuur, en commando’s voor een lokale observability stack (Prometheus/Grafana/Jaeger) — allemaal in Markdown.