DevOps: CI/CD, Infrastructure as Code en Observability
DevOps is geen tool en ook geen functietitel; het is een manier van werken waarin development en operations samenwerken om software sneller, betrouwbaarder en herhaalbaar te leveren. In deze tutorial bouwen we een end-to-end beeld van drie kernpijlers:
- CI/CD (Continuous Integration / Continuous Delivery): automatisch bouwen, testen en uitrollen.
- Infrastructure as Code (IaC): infrastructuur definieer je als code, versiebeheerbaar en reproduceerbaar.
- Observability: je systeem “begrijpen” via metrics, logs en traces, zodat je problemen sneller vindt en performance kunt verbeteren.
We gaan diep in op concepten én laten echte commando’s zien met gangbare tooling: Git, Docker, GitHub Actions, Terraform, Kubernetes (optioneel), Prometheus, Grafana, Loki en OpenTelemetry.
Inhoud
- 1. Voorbereiding en uitgangspunten
- 2. CI: Continuous Integration in de praktijk
- 3. CD: Continuous Delivery/Deployment
- 4. Infrastructure as Code met Terraform
- 5. Configuratie, secrets en omgevingen
- 6. Observability: metrics, logs en traces
- 7. SLO’s, incidenten en feedback loops
- 8. Praktische checklist en next steps
1. Voorbereiding en uitgangspunten
Wat je nodig hebt (lokaal)
- Git
- Docker
- (Optioneel) Kubernetes lokaal:
kindofminikube - Terraform (voor IaC)
- Een cloudaccount (optioneel, maar handig voor echte deployments)
Controleer je installaties:
git --version
docker --version
terraform version
Voorbeeldapplicatie (simpel maar realistisch)
We nemen een kleine webservice als voorbeeld (bijv. Node.js/Express of Python/FastAPI). De exacte code is minder belangrijk dan de pipeline eromheen. Stel dat je repo deze structuur heeft:
.
├── app/
│ ├── src/
│ ├── package.json
│ └── package-lock.json
├── Dockerfile
├── .github/workflows/
└── terraform/
Een simpele Dockerfile (Node.js):
FROM node:20-alpine
WORKDIR /app
COPY app/package*.json ./
RUN npm ci --omit=dev
COPY app/src ./src
EXPOSE 3000
CMD ["node", "src/index.js"]
Build lokaal:
docker build -t demo-app:local .
docker run --rm -p 3000:3000 demo-app:local
curl -i http://localhost:3000/health
2. CI: Continuous Integration in de praktijk
Continuous Integration betekent: elke wijziging wordt automatisch gebouwd en getest, liefst bij elke push en bij elke pull request. Doelen:
- Snelle feedback (binnen minuten).
- Kwaliteit borgen (tests, linting, security checks).
- Reproduceerbare builds (zelfde input → zelfde output).
CI-stappen die je vrijwel altijd wilt
- Checkout van de code.
- Dependencies installeren (met caching).
- Linting (stijl en eenvoudige fouten).
- Unit tests (snel, deterministisch).
- Build (artifact of container image).
- Security checks (SCA, secrets scanning).
- Publiceer artifact (bijv. Docker image naar registry).
Voorbeeld: GitHub Actions CI (commando’s zijn echt)
Maak .github/workflows/ci.yml met stappen zoals:
npm cinpm testdocker build
Belangrijke commando’s die je lokaal ook kunt draaien:
cd app
npm ci
npm run lint
npm test
Container build:
docker build -t ghcr.io/<org>/<repo>:sha-$(git rev-parse --short HEAD) .
Tests in containers (voordelen en valkuilen)
Voordeel: je test in een omgeving die dichter bij productie ligt.
Valkuil: tests kunnen trager worden en je moet goed omgaan met caching.
Een patroon is om tests “host-based” te doen (snel) en de container build apart.
Security in CI: dependency scanning en secrets
- Dependency scanning (SCA): controleer kwetsbaarheden in packages.
- Secrets scanning: voorkom dat API keys in je repo belanden.
Voor Node kun je minimaal draaien:
cd app
npm audit --audit-level=high
En voor container images (met Trivy):
trivy image demo-app:local
3. CD: Continuous Delivery/Deployment
Continuous Delivery: je kunt op elk moment deployen met één druk op de knop (of automatisch), omdat je pipeline betrouwbaar is.
Continuous Deployment: elke change die door CI komt gaat automatisch naar productie.
In de praktijk kiezen teams vaak voor:
- Automatisch naar dev/staging
- Handmatig (met approval) naar prod
Release-strategieën
- Rolling update: geleidelijk vervangen van pods/instances.
- Blue/Green: twee omgevingen, switch verkeer in één keer.
- Canary: klein percentage verkeer naar nieuwe versie, daarna opschalen.
Versioning en immutability
Gebruik immutable artifacts: een image-tag die exact één build representeert (bijv. git SHA). “latest” is handig, maar onbetrouwbaar voor rollback.
Voorbeeld tags:
ghcr.io/org/repo:sha-1a2b3c4ghcr.io/org/repo:1.4.2
Taggen:
git tag -a v1.4.2 -m "Release v1.4.2"
git push origin v1.4.2
Deploy commando’s (voorbeeld Kubernetes)
Stel je hebt een deployment:
kubectl get ns
kubectl -n demo get deploy,po,svc
Update image:
kubectl -n demo set image deployment/demo-app demo-app=ghcr.io/org/repo:sha-1a2b3c4
kubectl -n demo rollout status deployment/demo-app
Rollback:
kubectl -n demo rollout undo deployment/demo-app
kubectl -n demo rollout status deployment/demo-app
Database migraties in CD
Migrations zijn vaak de bron van downtime. Basisregels:
- Maak migraties backwards compatible (expand/contract).
- Run migraties als aparte stap vóór het switchen van verkeer.
- Monitor errors en latency tijdens migratie.
Voorbeeld (conceptueel) stap:
# voorbeeld: migration job in cluster
kubectl -n demo apply -f k8s/migrate-job.yaml
kubectl -n demo logs job/demo-migrate -f
4. Infrastructure as Code met Terraform
IaC betekent: je definieert infrastructuur (netwerken, VM’s, databases, IAM, load balancers) in code. Voordelen:
- Herhaalbaarheid: dezelfde infra in dev/staging/prod.
- Reviewbaar: pull requests voor infra changes.
- Audit trail: wie veranderde wat en wanneer.
- Automatisering: provisioning in pipelines.
Terraform kernconcepten
- Providers: cloud-API’s (AWS/Azure/GCP).
- Resources: concrete objecten (VPC, subnet, bucket).
- State: Terraform onthoudt wat er is uitgerold.
- Plan: diff tussen gewenste en huidige situatie.
- Apply: voer wijzigingen door.
Belangrijke commando’s:
terraform init
terraform fmt -recursive
terraform validate
terraform plan
terraform apply
terraform destroy
State management (kritisch!)
Gebruik in teams vrijwel altijd remote state met locking, anders krijg je race conditions.
- AWS: S3 + DynamoDB lock
- Azure: Storage Account + Blob lease
- GCP: GCS + locking via backend (afhankelijk van setup)
Zonder YAML te gebruiken, conceptueel:
- Zorg dat je
terraform initeen remote backend gebruikt. - Beperk toegang via IAM.
- Maak state backups.
Voorbeeld: simpele resource (AWS S3) (conceptueel HCL)
In terraform/main.tf (HCL, geen YAML):
terraform {
required_version = ">= 1.6.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
resource "aws_s3_bucket" "artifacts" {
bucket = "${var.project}-artifacts-${var.env}"
}
resource "aws_s3_bucket_versioning" "artifacts" {
bucket = aws_s3_bucket.artifacts.id
versioning_configuration {
status = "Enabled"
}
}
Variabelen in terraform/variables.tf:
variable "project" {
type = string
}
variable "env" {
type = string
}
variable "aws_region" {
type = string
default = "eu-west-1"
}
Run:
cd terraform
terraform init
terraform fmt -recursive
terraform validate
terraform plan -var="project=demo" -var="env=dev"
terraform apply -var="project=demo" -var="env=dev"
Modules en omgevingen
Een volwassen structuur:
modules/voor herbruikbare bouwblokken (vpc, cluster, db)envs/dev,envs/staging,envs/proddie modules aanroepen
Belangrijk: voorkom “copy-paste infra” die uiteen gaat lopen. Gebruik modules en variabelen.
Drift detection
Drift = infra is handmatig aangepast buiten Terraform om. Detecteer dit door regelmatig:
terraform plan
In CI kun je een scheduled job draaien die een plan maakt en waarschuwt bij drift.
5. Configuratie, secrets en omgevingen
Config ≠ code
- Code: in git, versiebeheer.
- Config: per omgeving anders (DB host, feature flags).
- Secrets: nooit in git, altijd via secret manager.
Secrets management
Opties:
- Cloud secret managers (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager)
- Kubernetes Secrets (liefst versleuteld in etcd + RBAC)
- Vault (HashiCorp) voor complexere setups
Praktische regels:
- Geef services minimale rechten (least privilege).
- Roteer secrets.
- Log nooit secrets.
- Maak onderscheid tussen “config” en “secret”.
Voorbeeld: secret als environment variable (runtime), niet baked in image:
docker run --rm -e DATABASE_URL="postgres://..." demo-app:local
Feature flags
Feature flags maken deployments veiliger:
- Deploy code → zet feature aan voor interne users → breid uit.
- Combineer met canary releases.
6. Observability: metrics, logs en traces
Monitoring vertelt je dat iets stuk is; observability helpt je begrijpen waarom. De drie pilaren:
- Metrics: tijdreeksen (CPU, latency, error rate).
- Logs: gebeurtenissen (structured logging).
- Traces: request flows door services (distributed tracing).
6.1 Metrics met Prometheus
Prometheus scrapt metrics endpoints (meestal /metrics) en slaat tijdreeksen op. Je definieert alerts op basis van queries (PromQL).
Voorbeeld: draai Prometheus lokaal met Docker (simpel):
docker network create obs || true
docker run -d --name prometheus --network obs -p 9090:9090 prom/prometheus
In productie gebruik je meestal Helm of een operator, maar het concept blijft:
- App expose’t metrics
- Prometheus scrapt
- Grafana visualiseert
Wat meet je minimaal?
- RED (voor request-based services):
- Rate (requests/sec)
- Errors (error rate)
- Duration (latency)
- USE (voor resources):
- Utilization
- Saturation
- Errors
6.2 Logs: structured logging en centralisatie
Structured logs (JSON) zijn beter doorzoekbaar dan vrije tekst. Basisvelden:
timestamplevelservicerequest_id/trace_idmessage- context (user, route, status_code, latency_ms)
Lokaal kun je logs bekijken:
docker logs -f <container>
Voor centralisatie kun je een stack gebruiken zoals Loki + Grafana of ELK/EFK.
Voorbeeld: Loki draaien:
docker run -d --name loki --network obs -p 3100:3100 grafana/loki:2.9.0
In Kubernetes stuur je logs vaak via Promtail/Fluent Bit naar Loki/Elastic.
6.3 Traces met OpenTelemetry
Distributed tracing is essentieel zodra je meerdere services hebt. Met OpenTelemetry (OTel) instrumenteer je je app zodat elke request een trace krijgt met spans.
Belangrijke begrippen:
- Trace: één end-to-end request.
- Span: een stap (bijv. HTTP call, DB query).
- Context propagation: trace-id gaat mee via headers.
Een OTel Collector kan traces ontvangen en doorsturen naar backends zoals Jaeger, Tempo of commerciële APM.
Voorbeeld: Jaeger all-in-one lokaal:
docker run -d --name jaeger --network obs \
-p 16686:16686 -p 4317:4317 -p 4318:4318 \
jaegertracing/all-in-one:1.57
Dan configureer je je app om OTLP te exporteren naar http://jaeger:4318 (HTTP) of jaeger:4317 (gRPC), afhankelijk van je SDK.
Correlatie: logs + traces + metrics
De echte winst komt als je kunt klikken:
- Alert (metrics) → relevante logs → trace van een specifieke request → exacte dependency die faalt.
Daarvoor heb je nodig:
trace_idin logs- consistente labels/tags (service name, env, version)
- dashboards die dezelfde dimensies gebruiken
Alerting: wat is een goede alert?
Een alert moet:
- actie vereisen
- een duidelijke eigenaar hebben
- een runbook hebben
- niet te noisy zijn
Voorbeelden van nuttige alerts:
- 5xx error rate boven drempel gedurende 5 minuten
- p95 latency boven SLO
- saturation: CPU throttling, memory pressure
- queue lag / consumer behind
7. SLO’s, incidenten en feedback loops
SLI/SLO/SLA in het kort
- SLI: indicator (bijv. “percentage succesvolle requests”).
- SLO: doelstelling (bijv. “99.9% success in 30 dagen”).
- SLA: contract (juridisch/extern).
Een praktisch SLO-voorbeeld:
- SLI:
success_rate = 1 - (5xx_requests / total_requests) - SLO:
success_rate >= 99.9%over 30 dagen
Error budgets
Als je SLO 99.9% is, heb je 0.1% “budget” voor fouten. Dat budget helpt beslissen:
- Budget op? Dan focus op stabiliteit.
- Budget over? Dan kun je sneller releasen.
Incident response
Minimale onderdelen:
- Detectie (alerts)
- Triage (impact, scope, mitigatie)
- Communicatie (status updates)
- Postmortem (blameless, actiepunten)
Runbooks zijn cruciaal: korte stappen die iemand om 03:00 kan volgen.
Voorbeeld runbook-stappen bij verhoogde 5xx:
1) Check dashboard: error rate per route en per versie
2) Check recente deploys: welke image SHA draait?
3) Rollback indien correlatie met deploy
4) Check dependency health (DB, cache, externe API)
5) Verzamel: trace_id voorbeelden + relevante logs
6) Maak incident timeline en actiepunten
8. Praktische checklist en next steps
CI/CD checklist
- Elke PR triggert build + tests + lint
- Artifacts zijn immutable (git SHA tags)
- Security scans (dependencies + container)
- Deploy naar staging automatisch
- Deploy naar prod met approval + rollback plan
- Database migraties veilig (expand/contract)
- Pipeline runt snel (caching, parallelisatie)
IaC checklist
- Remote state + locking
- Modules voor hergebruik
- Least privilege IAM
-
terraform fmt/validate/planin CI - Drift detection
- Tagging/labeling van resources (owner, env, cost center)
Observability checklist
- RED/USE metrics aanwezig
- Dashboards per service + per dependency
- Alerts met runbooks
- Structured logs met trace_id/request_id
- Distributed tracing via OpenTelemetry
- Release markers (versie labels) in metrics/logs/traces
Conclusie
CI/CD, IaC en observability versterken elkaar:
- CI/CD zorgt dat je snel en gecontroleerd kunt leveren.
- IaC maakt infrastructuur betrouwbaar, reviewbaar en reproduceerbaar.
- Observability geeft je de feedback om kwaliteit en performance structureel te verbeteren.
Als je dit als één systeem benadert (in plaats van losse tools), krijg je een delivery machine die niet alleen snel is, maar ook stabiel en goed te opereren.
Extra: handige commando’s (spiekbrief)
Git
git status
git diff
git log --oneline --decorate -n 20
git tag -l
Docker
docker build -t demo-app:local .
docker run --rm -p 3000:3000 demo-app:local
docker ps
docker logs -f <container>
docker exec -it <container> sh
Kubernetes
kubectl get nodes
kubectl get ns
kubectl -n demo get all
kubectl -n demo describe pod <pod>
kubectl -n demo logs -f deploy/demo-app
kubectl -n demo rollout status deploy/demo-app
kubectl -n demo rollout undo deploy/demo-app
Terraform
terraform init
terraform fmt -recursive
terraform validate
terraform plan -out tfplan
terraform apply tfplan
Als je wilt, kan ik deze tutorial uitbreiden met een volledig uitgewerkt voorbeeld (repo-structuur, concrete GitHub Actions workflow, Terraform modules, en een lokale observability stack met Prometheus + Grafana + Loki + Jaeger/Tempo), inclusief alle configuratiebestanden en commando’s om het stap voor stap te draaien.