Exercice 07 : Capstone - Déployer une application multi-tiers
🎯 Objectifs
À la fin de cet exercice, vous serez capable de :
- ✅ Déployer une architecture 3 tiers complète : frontend (Nginx) + API (Node.js) + base de données (PostgreSQL)
- ✅ Utiliser des ConfigMaps pour configurer les applications
- ✅ Utiliser des PVC pour persister les données PostgreSQL
- ✅ Connecter les tiers via des Services Kubernetes
- ✅ Vérifier que l'application est fonctionnelle et résiliente
Durée estimée : 60 minutes
Difficulté : ⭐⭐⭐☆☆ (Avancé)
Prérequis : Exercices 01 à 05 complétés
📖 Contexte
Jusqu'ici, vous avez travaillé sur chaque concept séparément. Dans cet exercice, vous allez les combiner pour déployer une vraie stack applicative comme on le fait en production. Chaque composant sera isolé dans son propre Deployment et communiquera avec les autres via des Services.
Architecture cible :
[Browser] → [NodePort 30090] → [Nginx Frontend] → [ClusterIP] → [API Node.js] → [ClusterIP] → [PostgreSQL + PVC]📋 Énoncé
Déployez les 3 composants de l'application, configurez leur communication et vérifiez l'ensemble.
🧭 Déroulement de l'exercice
Tâche 1 : Préparer le namespace et la base de données
# Créer un namespace dédié au capstone
kubectl create namespace capstone
# PVC pour PostgreSQL
cat > 01-postgres-pvc.yaml << 'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: capstone
spec:
accessModes:
- ReadWriteOnce
storageClassName: standard
resources:
requests:
storage: 2Gi
EOF
# ConfigMap pour PostgreSQL
cat > 02-postgres-config.yaml << 'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config
namespace: capstone
data:
POSTGRES_DB: "appdb"
POSTGRES_USER: "appuser"
POSTGRES_PASSWORD: "apppassword123"
PGDATA: "/var/lib/postgresql/data/pgdata"
EOF
# Deployment PostgreSQL
cat > 03-postgres-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: capstone
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
containers:
- name: postgres
image: postgres:16-alpine
envFrom:
- configMapRef:
name: postgres-config
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
livenessProbe:
exec:
command: ["pg_isready", "-U", "appuser", "-d", "appdb"]
initialDelaySeconds: 30
periodSeconds: 10
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
EOF
# Service PostgreSQL (ClusterIP - interne seulement)
cat > 04-postgres-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: capstone
spec:
type: ClusterIP
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
EOF
kubectl apply -f 01-postgres-pvc.yaml
kubectl apply -f 02-postgres-config.yaml
kubectl apply -f 03-postgres-deployment.yaml
kubectl apply -f 04-postgres-service.yaml
# Attendre que PostgreSQL soit prêt
kubectl rollout status deployment/postgres -n capstoneTâche 2 : Déployer l'API
Nous simulons une API simple avec un conteneur qui expose un endpoint de health check.
# ConfigMap pour l'API
cat > 05-api-config.yaml << 'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: api-config
namespace: capstone
data:
NODE_ENV: "production"
PORT: "3000"
LOG_LEVEL: "info"
# L'URL de la base utilise le DNS Kubernetes : <service>.<namespace>.svc.cluster.local
DATABASE_URL: "postgresql://appuser:apppassword123@postgres.capstone.svc.cluster.local:5432/appdb"
EOF
# Deployment API (on simule avec nginx qui répond en JSON)
cat > 06-api-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
namespace: capstone
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
tier: backend
spec:
containers:
- name: api
image: nginx:1.27-alpine
ports:
- containerPort: 80
envFrom:
- configMapRef:
name: api-config
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
EOF
# Service API (ClusterIP - accessible depuis le frontend seulement)
cat > 07-api-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
name: api
namespace: capstone
spec:
type: ClusterIP
selector:
app: api
ports:
- port: 3000
targetPort: 80
EOF
kubectl apply -f 05-api-config.yaml
kubectl apply -f 06-api-deployment.yaml
kubectl apply -f 07-api-service.yaml
kubectl rollout status deployment/api -n capstoneTâche 3 : Déployer le frontend
# ConfigMap avec la config Nginx du frontend (proxy vers l'API)
cat > 08-frontend-config.yaml << 'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: frontend-nginx-conf
namespace: capstone
data:
default.conf: |
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /api/ {
proxy_pass http://api.capstone.svc.cluster.local:3000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
location /health {
return 200 '{"status":"ok","service":"frontend"}';
add_header Content-Type application/json;
}
}
EOF
# Deployment Frontend
cat > 09-frontend-deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: capstone
spec:
replicas: 2
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
tier: frontend
spec:
volumes:
- name: nginx-config
configMap:
name: frontend-nginx-conf
items:
- key: default.conf
path: default.conf
containers:
- name: frontend
image: nginx:1.27-alpine
ports:
- containerPort: 80
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/conf.d/
readOnly: true
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
EOF
# Service Frontend (NodePort - accessible depuis l'hôte)
cat > 10-frontend-service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
name: frontend
namespace: capstone
spec:
type: NodePort
selector:
app: frontend
ports:
- port: 80
targetPort: 80
nodePort: 30090
EOF
kubectl apply -f 08-frontend-config.yaml
kubectl apply -f 09-frontend-deployment.yaml
kubectl apply -f 10-frontend-service.yaml
kubectl rollout status deployment/frontend -n capstoneTâche 4 : Valider la stack complète
# Vue d'ensemble
kubectl get all -n capstone
# Tous les pods doivent être Running
kubectl get pods -n capstone
# Tester le frontend depuis l'hôte
MINIKUBE_IP=$(minikube ip)
curl http://$MINIKUBE_IP:30090/health
# Attendu : {"status":"ok","service":"frontend"}
# Tester la communication frontend → API via proxy
curl http://$MINIKUBE_IP:30090/api/
# Vérifier que PostgreSQL est accessible depuis l'API
API_POD=$(kubectl get pods -n capstone -l app=api -o jsonpath='{.items[0].metadata.name}')
kubectl exec "$API_POD" -n capstone -- \
wget -qO- http://postgres.capstone.svc.cluster.local:5432 2>&1 || \
echo "PostgreSQL répond (connexion TCP OK)"
# Tester la résilience : supprimer un pod frontend
FRONTEND_POD=$(kubectl get pods -n capstone -l app=frontend -o jsonpath='{.items[0].metadata.name}')
kubectl delete pod "$FRONTEND_POD" -n capstone
# Le Deployment recrée immédiatement un nouveau pod
kubectl get pods -n capstone -wTâche 5 : Observer les health checks
# Les livenessProbe et readinessProbe sont visibles dans describe
kubectl describe deployment/frontend -n capstone | grep -A 10 "Liveness\|Readiness"
# Voir les events des health checks
kubectl get events -n capstone --sort-by='.lastTimestamp' | tail -20
# Scaler l'API à 3 réplicas sans downtime
kubectl scale deployment/api --replicas=3 -n capstone
kubectl rollout status deployment/api -n capstone
# Mettre à jour le frontend (simule une nouvelle version)
kubectl set image deployment/frontend frontend=nginx:1.26-alpine -n capstone
kubectl rollout status deployment/frontend -n capstone✅ Vérification du résultat
kubectl get pods -n capstoneaffiche 5 pods Running (1 postgres + 2 api + 2 frontend)curl http://$(minikube ip):30090/healthretourne{"status":"ok"}kubectl get pvc -n capstoneaffiche le PVC en étatBound- La suppression d'un pod frontend est suivie de sa recréation automatique
- Le scaling de l'API à 3 réplicas se fait sans downtime
💡 À retenir
Une application multi-tiers dans Kubernetes suit ce schéma standard :
[Trafic externe]
↓
[Service NodePort / LoadBalancer] → [Frontend Pods (Deployment x2)]
↓
[Service ClusterIP] → [API Pods (Deployment x2)]
↓
[Service ClusterIP] → [DB Pod (Deployment x1 + PVC)]Les DNS internes éliminent le besoin de coder les IPs en dur :
http://api.capstone.svc.cluster.local:3000
http://postgres.capstone.svc.cluster.local:5432Les liveness/readiness probes garantissent :
- Que le trafic n'est envoyé qu'aux pods prêts (readiness)
- Que les pods bloqués sont redémarrés automatiquement (liveness)
✨ Solution Complète
# Déployer la stack entière en une commande
kubectl apply -f . -n capstone # Si tous les YAML sont dans le même dossier
# Vérifier
kubectl get all -n capstone
curl http://$(minikube ip):30090/health
# Nettoyer
kubectl delete namespace capstone