Skip to main content

Docker Swarm exercises and projects


Chapter objectives

  • Consolidate the knowledge acquired
  • Practice with real-world scenarios
  • Complete full projects
  • Prepare for production

1 - Basic exercises

Exercise 1: Cluster initialization

Objective: Create a basic Swarm cluster

# 1. Initialiser le Swarm
docker swarm init --advertise-addr <votre-ip>

# 2. Vérifier l'état
docker node ls
docker info | grep -i swarm

# 3. Créer votre premier service
docker service create --name hello --replicas 3 -p 80:80 nginx:alpine

# 4. Vérifier le service
docker service ls
docker service ps hello

# 5. Tester l'accès
curl http://localhost

# 6. Nettoyer
docker service rm hello

Questions:

  • What does the join token contain?
  • Why use --advertise-addr?

Exercise 2: Scaling and rolling updates

Objective: Master scaling and updates

# 1. Créer un service
docker service create --name web \
--replicas 3 \
--update-parallelism 1 \
--update-delay 10s \
nginx:1.24-alpine

# 2. Observer les tâches
watch docker service ps web

# 3. Scaler à 10 réplicas
docker service scale web=10

# 4. Mettre à jour l'image
docker service update --image nginx:1.25-alpine web

# 5. Observer le rolling update
docker service ps web

# 6. Faire un rollback
docker service rollback web

Exercise 3: Overlay networks

Objective: Configure inter-service communication

# 1. Créer un réseau overlay
docker network create --driver overlay app-network

# 2. Créer un service backend
docker service create --name api \
--network app-network \
--replicas 2 \
nginx:alpine

# 3. Créer un service frontend
docker service create --name web \
--network app-network \
--replicas 2 \
-p 80:80 \
nginx:alpine

# 4. Tester la connectivité
docker exec -it $(docker ps -q -f name=web) ping api

# 5. Vérifier le DNS interne
docker exec -it $(docker ps -q -f name=web) nslookup api

2 - Intermediate exercises

Exercise 4: Secrets and configurations

Objective: Manage sensitive data

# 1. Créer un secret
echo "supersecret123" | docker secret create db_password -

# 2. Créer une configuration
cat << EOF > nginx.conf
server {
listen 80;
location / {
return 200 'Hello from Swarm!';
}
}
EOF
docker config create nginx_conf nginx.conf

# 3. Créer un service utilisant le secret et la config
docker service create --name secure-app \
--secret db_password \
--config source=nginx_conf,target=/etc/nginx/conf.d/default.conf \
nginx:alpine

# 4. Vérifier l'accès au secret (dans le conteneur)
docker exec -it $(docker ps -q -f name=secure-app) cat /run/secrets/db_password

Exercise 5: Placement constraints

Objective: Control the placement of services

# 1. Ajouter des labels aux nœuds
docker node update --label-add env=production $(docker node ls -q | head -1)
docker node update --label-add type=web $(docker node ls -q | head -1)

# 2. Créer un service avec contraintes
docker service create --name prod-only \
--constraint 'node.labels.env==production' \
--replicas 3 \
nginx:alpine

# 3. Vérifier le placement
docker service ps prod-only

# 4. Tester une contrainte impossible
docker service create --name impossible \
--constraint 'node.labels.env==staging' \
--replicas 2 \
nginx:alpine
# Observer que le service ne peut pas démarrer

# 5. Utiliser des préférences de spread
docker service create --name spread-test \
--replicas 6 \
--placement-pref 'spread=node.labels.type' \
nginx:alpine

Exercise 6: Complete stack

Objective: Deploy a multi-service application

Create the stack.yml file:

version: "3.9"

services:
web:
image: nginx:alpine
ports:
- "80:80"
networks:
- frontend
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 10s

api:
image: httpd:alpine
networks:
- frontend
- backend
deploy:
replicas: 2

db:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_pass
secrets:
- db_pass
volumes:
- db-data:/var/lib/postgresql/data
networks:
- backend
deploy:
replicas: 1
placement:
constraints:
- node.role == manager

networks:
frontend:
driver: overlay
backend:
driver: overlay
internal: true

volumes:
db-data:

secrets:
db_pass:
file: ./db_password.txt
# Créer le fichier de secret
echo "mydbpassword" > db_password.txt

# Déployer la stack
docker stack deploy -c stack.yml myapp

# Vérifier
docker stack services myapp
docker stack ps myapp

# Supprimer
docker stack rm myapp

3 - Complete projects

Project 1: High availability web application

Architecture:

                   Internet


┌───────────────┐
│ Traefik │
│ (Reverse │
│ Proxy) │
└───────┬───────┘

┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Web 1 │ │ Web 2 │ │ Web 3 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└────────────┼────────────┘


┌───────────────┐
│ PostgreSQL │
│ (Primary) │
└───────────────┘

docker-compose.yml:

version: "3.9"

services:
traefik:
image: traefik:v2.10
command:
- "--api.dashboard=true"
- "--providers.docker=true"
- "--providers.docker.swarmMode=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- traefik-public
deploy:
mode: global
placement:
constraints:
- node.role == manager
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`traefik.localhost`)"
- "traefik.http.routers.dashboard.service=api@internal"

web:
image: nginx:alpine
networks:
- traefik-public
- backend
deploy:
replicas: 3
update_config:
parallelism: 1
delay: 30s
failure_action: rollback
labels:
- "traefik.enable=true"
- "traefik.http.routers.web.rule=Host(`app.localhost`)"
- "traefik.http.services.web.loadbalancer.server.port=80"
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost"]
interval: 30s
timeout: 10s
retries: 3

api:
image: httpd:alpine
networks:
- traefik-public
- backend
deploy:
replicas: 2
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`api.localhost`)"
- "traefik.http.services.api.loadbalancer.server.port=80"

db:
image: postgres:15-alpine
environment:
POSTGRES_DB: app
POSTGRES_USER: app
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- db-data:/var/lib/postgresql/data
networks:
- backend
deploy:
replicas: 1
placement:
constraints:
- node.labels.db == true
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 30s
timeout: 10s
retries: 5

redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes:
- redis-data:/data
networks:
- backend
deploy:
replicas: 1

networks:
traefik-public:
driver: overlay
backend:
driver: overlay
internal: true

volumes:
db-data:
redis-data:

secrets:
db_password:
external: true

Deployment:

# Préparer
docker node update --label-add db=true $(docker node ls -q | head -1)
echo "secretpassword" | docker secret create db_password -

# Déployer
docker stack deploy -c docker-compose.yml webapp

# Tester
curl -H "Host: app.localhost" http://localhost
curl -H "Host: api.localhost" http://localhost

# Monitoring
watch docker stack services webapp

Project 2: Monitoring stack

docker-compose.monitoring.yml:

version: "3.9"

services:
prometheus:
image: prom/prometheus:v2.47.0
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--web.enable-lifecycle'
volumes:
- prometheus-data:/prometheus
configs:
- source: prometheus_config
target: /etc/prometheus/prometheus.yml
networks:
- monitoring
deploy:
replicas: 1
placement:
constraints:
- node.role == manager

grafana:
image: grafana/grafana:10.1.0
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- grafana-data:/var/lib/grafana
networks:
- monitoring
- traefik-public
deploy:
replicas: 1
labels:
- "traefik.enable=true"
- "traefik.http.routers.grafana.rule=Host(`grafana.localhost`)"
- "traefik.http.services.grafana.loadbalancer.server.port=3000"

cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker:/var/lib/docker:ro
networks:
- monitoring
deploy:
mode: global

node-exporter:
image: prom/node-exporter:v1.6.1
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
networks:
- monitoring
deploy:
mode: global

alertmanager:
image: prom/alertmanager:v0.26.0
volumes:
- alertmanager-data:/alertmanager
configs:
- source: alertmanager_config
target: /etc/alertmanager/alertmanager.yml
networks:
- monitoring
deploy:
replicas: 1

networks:
monitoring:
driver: overlay
traefik-public:
external: true

volumes:
prometheus-data:
grafana-data:
alertmanager-data:

configs:
prometheus_config:
file: ./prometheus.yml
alertmanager_config:
file: ./alertmanager.yml

4 - Review quiz

Questions

  1. What is the minimum number of managers for tolerance to 1 failure?

  2. Which command puts a node in maintenance?

  3. How do you create an encrypted overlay network?

  4. What is the difference between VIP mode and DNSRR?

  5. How do you access secrets in a container?

  6. Which option guarantees a rolling update without downtime?

  7. How do you force the redeployment of a service's tasks?

  8. What is the quorum for a cluster of 5 managers?

Answers

See the answers
  1. 3 managers (quorum = 2, tolerates 1 failure)

  2. docker node update --availability drain <node>

  3. docker network create --driver overlay --opt encrypted <name>

  4. VIP: a single virtual IP with transparent LB. DNSRR: DNS returns all IPs, client-side LB

  5. Secrets are available in /run/secrets/<name>

  6. --update-order start-first (starts the new one before stopping the old one)

  7. docker service update --force <service>

  8. Quorum = 3 (5/2 + 1 = 3)


5 - Advanced challenges

Challenge 1: Blue-Green Deployment

Implement a blue-green deployment:

  • "blue" service active on port 80
  • Deploy "green" on port 8080
  • Test green
  • Switch the traffic
  • Remove blue

Challenge 2: Auto-scaling

Create a script that:

  • Monitors the CPU usage of services
  • Automatically scales between 2 and 10 replicas
  • Logs the scaling actions

Challenge 3: Disaster Recovery

Set up:

  • Automatic daily backup
  • Tested restore script
  • Documented recovery procedure

Course summary

You have learned:

  • The architecture and concepts of Docker Swarm
  • Creating and managing clusters
  • Services, tasks and stacks
  • Overlay networking and routing mesh
  • Managing secrets and configurations
  • Scaling and load balancing
  • High availability
  • Production best practices
Next steps
  • Practice with real environments
  • Explore Kubernetes for advanced needs
  • Integrate Swarm into your CI/CD pipelines
  • Certify your Docker skills

← Best practices | Table of contents