45 minutes (+ exercice optionnel)
Ce manifeste reconstruit l'état du fil rouge à la fin du TP 4 : la chaîne complète de traitement (vote, redis, worker, db) avec ses Services, et l'exposition NodePort 31000. La base est encore éphémère : c'est tout le sujet de ce TP.
Créez le fichier fil-rouge-tp05.yaml :
# fil-rouge-tp05.yaml
apiVersion: v1
kind: Namespace
metadata:
name: voting-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: vote
namespace: voting-app
labels:
app: vote
tier: frontend
spec:
replicas: 3
selector:
matchLabels:
app: vote
template:
metadata:
labels:
app: vote
tier: frontend
spec:
containers:
- name: vote
image: dockersamples/examplevotingapp_vote:latest
ports:
- containerPort: 80
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: voting-app
labels:
app: redis
tier: backend
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
tier: backend
spec:
containers:
- name: redis
image: redis:8-alpine
ports:
- containerPort: 6379
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: db
namespace: voting-app
labels:
app: db
tier: database
spec:
replicas: 1
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
tier: database
spec:
containers:
- name: postgres
image: postgres:16-alpine
env:
- name: POSTGRES_USER
value: "postgres"
- name: POSTGRES_PASSWORD
value: "postgres"
ports:
- containerPort: 5432
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
namespace: voting-app
labels:
app: worker
tier: backend
spec:
replicas: 1
selector:
matchLabels:
app: worker
template:
metadata:
labels:
app: worker
tier: backend
spec:
containers:
- name: worker
image: dockersamples/examplevotingapp_worker:latest
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "512Mi"
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: voting-app
labels:
app: redis
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: db
namespace: voting-app
labels:
app: db
spec:
selector:
app: db
ports:
- port: 5432
targetPort: 5432
---
apiVersion: v1
kind: Service
metadata:
name: vote
namespace: voting-app
labels:
app: vote
spec:
type: NodePort
selector:
app: vote
ports:
- port: 80
targetPort: 80
nodePort: 31000
Puis appliquez-le et placez-vous dans le namespace :
kubectl apply -f fil-rouge-tp05.yaml
kubectl config set-context --current --namespace=voting-app
kubectl wait --for=condition=Available deployment --all --timeout=180s
kubectl get pods
NAME READY STATUS RESTARTS AGE
db-58fd6c9bd4-mv7xw 1/1 Running 0 90s
redis-6f4b8d7c9d-x2kqp 1/1 Running 0 90s
vote-7d94f6c8b5-4x8mn 1/1 Running 0 90s
vote-7d94f6c8b5-jw8tn 1/1 Running 0 90s
vote-7d94f6c8b5-qp2rk 1/1 Running 0 90s
worker-5b8f9d6c47-t8slk 1/1 Running 1 (60s ago) 90s
(Un ou deux RESTARTS du worker au démarrage sont normaux, le temps que redis et db soient joignables.)
Découvrir le volume le plus simple : un espace partagé entre les conteneurs d'un même Pod… qui meurt avec le Pod.
Créez d'abord le fichier shared-volume-pod.yaml :
# shared-volume-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: shared-volume-pod
spec:
containers:
- name: writer
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
while true; do
echo "Données écrites par writer à $(date)" >> /shared/log.txt
sleep 10
done
volumeMounts:
- name: shared-data
mountPath: /shared
- name: reader
image: busybox:1.36
command: ["sh", "-c"]
args:
- |
sleep 5
while true; do
echo "=== Lecture des données ==="
cat /shared/log.txt 2>/dev/null || echo "Aucun fichier trouvé"
sleep 15
done
volumeMounts:
- name: shared-data
mountPath: /shared
volumes:
- name: shared-data
emptyDir: {}
Puis créez le Pod et observez le partage :
kubectl apply -f shared-volume-pod.yaml
# Le conteneur reader voit ce que le conteneur writer écrit
kubectl logs shared-volume-pod -c reader
kubectl delete pod shared-volume-pod
kubectl apply -f shared-volume-pod.yaml
# Le fichier repart de zéro : les anciennes lignes ont disparu
kubectl exec shared-volume-pod -c reader -- cat /shared/log.txt
❌ emptyDir partage des données entre conteneurs d'un même Pod, mais sa durée de vie est celle du Pod. C'est exactement le problème que nous avons constaté sur la base de données à la fin du TP 4.
Q: Que se passe-t-il aux données emptyDir quand un Pod est supprimé ?
R: Elles sont définitivement perdues. Un volume emptyDir a exactement la même durée de vie que le Pod qui le contient (il survit en revanche au redémarrage d'un conteneur au sein du Pod).
Q: Quels sont les cas d'usage appropriés pour emptyDir ?
R: Partage temporaire entre conteneurs d'un même Pod (sidecars), cache, espace de travail pour des calculs ; jamais des données à conserver.
Comprendre le couple PersistentVolume (l'offre de stockage) / PersistentVolumeClaim (la demande) sur un exemple minimal.
Créez d'abord le fichier demo-pv.yaml :
# demo-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: demo-pv
labels:
type: local-demo
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: manual
hostPath:
path: /tmp/k8s-demo-data
type: DirectoryOrCreate
kubectl apply -f demo-pv.yaml
kubectl get pv demo-pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
demo-pv 1Gi RWO Retain Available manual 10s
Créez d'abord le fichier demo-pvc.yaml :
# demo-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: demo-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: manual
resources:
requests:
storage: 500Mi
selector:
matchLabels:
type: local-demo
kubectl apply -f demo-pvc.yaml
# Le binding s'opère : le PVC trouve le PV compatible
kubectl get pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
persistentvolume/demo-pv 1Gi RWO Retain Bound voting-app/demo-pvc manual 60s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/demo-pvc Bound demo-pv 1Gi RWO manual 10s
✅ Available → Bound : Kubernetes a apparié la demande (500Mi, RWO, classe manual) avec l'offre compatible. Notez que le PVC obtient la capacité réelle du PV (1Gi), pas seulement ce qu'il demandait.
Q: Quelle est la différence entre PV et PVC ?
R: Le PV est la ressource de stockage (créée par l'administrateur ou provisionnée dynamiquement) ; le PVC est la demande de stockage exprimée par l'application. La séparation découple « qui fournit » de « qui consomme ».
Q: Quels sont les critères de binding entre PV et PVC ?
R: Même storageClassName, accessModes du PV couvrant ceux demandés, capacité du PV suffisante, et labels correspondants si le PVC définit un selector.
Q: À quoi sert une StorageClass ?
R: Elle décrit une « gamme » de stockage et surtout son provisioner : avec une classe dynamique (comme local-path sur Killercoda), les PV sont créés automatiquement à la demande, plus besoin de les préparer à la main comme ici.
Réparer le talon d'Achille du fil rouge : donner à la base de données un volume qui survit à ses Pods, provisionné dynamiquement.
Le constat de fin du TP 4 est inacceptable en production : chaque redémarrage de la base efface les votes. Vous connaissez désormais la mécanique PV/PVC : appliquez-la à la vraie base, cette fois avec le provisionnement dynamique du cluster.
postgres-data (1Gi, ReadWriteOnce, StorageClass local-path) et observez son statut : la surprise fait partie de l'exercice.db pour monter ce PVC sur /var/lib/postgresql/data.db, et montrez que le compte en base est intact après recréation.💡 Indices : PGDATA=/var/lib/postgresql/data/pgdata (PostgreSQL refuse la racine d'un volume monté) ; strategy: type: Recreate (jamais deux instances sur les mêmes données) ; un PVC local-path reste Pending tant qu'aucun Pod ne le consomme (WaitForFirstConsumer).
1. Le PVC :
Fichier postgres-pvc.yaml :
# postgres-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: voting-app
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
kubectl apply -f postgres-pvc.yaml
kubectl get pvc postgres-data
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
postgres-data Pending local-path 10s
💡 Pending est normal : la classe local-path est en mode WaitForFirstConsumer ; le volume ne sera créé que quand un Pod l'utilisera, pour le placer sur le bon nœud.
2. et 3. La base branchée sur le volume, pièges déjoués :
Fichier db-deployment-pvc.yaml :
# db-deployment-pvc.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: db
namespace: voting-app
labels:
app: db
tier: database
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
tier: database
spec:
containers:
- name: postgres
image: postgres:16-alpine
env:
- name: POSTGRES_USER
value: "postgres"
- name: POSTGRES_PASSWORD
value: "postgres"
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-data
Deux détails importants :
PGDATA=/var/lib/postgresql/data/pgdata : PostgreSQL refuse de s'initialiser directement à la racine d'un volume monté (il y trouve un dossier lost+found ou des métadonnées et considère le répertoire comme non vide). On l'installe donc dans un sous-répertoire du volume.strategy: Recreate : pour une base mono-instance, on tue l'ancien Pod avant de créer le nouveau. Un RollingUpdate ferait cohabiter deux PostgreSQL sur le même répertoire de données : corruption assurée.kubectl apply -f db-deployment-pvc.yaml
kubectl get pods -l app=db
kubectl get pvc postgres-data
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
postgres-data Bound pvc-8f3a2b1c-9d4e-4f5a-b6c7-d8e9f0a1b2c3 1Gi RWO local-path 2m
4. La preuve de persistance :
Réinjectez des votes (recréez seed-votes-job.yaml du TP 3 si nécessaire, ou votez sur le port 31000) :
kubectl delete job seed-votes --ignore-not-found
kubectl apply -f seed-votes-job.yaml
kubectl wait --for=condition=Complete job/seed-votes --timeout=60s
# Le worker déverse la file dans la base
kubectl exec deployment/db -- psql -U postgres -c 'SELECT COUNT(*) FROM votes;'
count
-------
50
(1 row)
Maintenant, rejouez le crash du TP 4 :
kubectl delete pod -l app=db
kubectl wait --for=condition=Ready pod -l app=db --timeout=120s
kubectl exec deployment/db -- psql -U postgres -c 'SELECT COUNT(*) FROM votes;'
count
-------
50
(1 row)
✅ Les données ont survécu. Le Pod est mort, un autre a pris sa place, et il a retrouvé le même volume : le PVC découple la vie des données de la vie des Pods.
Pending avant le déploiement du Pod ?PGDATA dans un sous-répertoire du volume ?strategy: Recreate pour cette base de données ?Q: Pourquoi le PVC restait-il Pending avant le déploiement du Pod ?
R: La StorageClass local-path utilise volumeBindingMode: WaitForFirstConsumer : le provisionnement attend le premier Pod consommateur afin de créer le volume sur le nœud où ce Pod est schedulé (indispensable pour du stockage local).
Q: Pourquoi définir PGDATA dans un sous-répertoire du volume ?
R: PostgreSQL exige un répertoire de données vide à l'initialisation. La racine d'un volume fraîchement monté contient souvent un dossier lost+found ou des artefacts du système de fichiers : un sous-répertoire (.../pgdata) contourne proprement le problème.
Q: Pourquoi strategy: Recreate pour cette base de données ?
R: Avec le RollingUpdate par défaut, le nouveau Pod démarre avant l'arrêt de l'ancien : deux instances PostgreSQL se disputeraient le même répertoire de données (verrou postmaster.pid, risque de corruption). Recreate garantit qu'une seule instance touche les données à la fois.
Passer la base au contrôleur conçu pour les applications à état : identité stable, volume personnel géré automatiquement.
Un Deployment traite ses Pods comme du bétail interchangeable. Pour une base de données, on préfère un StatefulSet : nom prévisible (db-0), volume personnel créé automatiquement (volumeClaimTemplates), démarrage ordonné.
Supprimez l'ancienne implémentation (le Service db change aussi : un StatefulSet s'appuie sur un Service headless) :
kubectl delete deployment db
kubectl delete service db
kubectl delete pvc postgres-data
Créez le fichier db-statefulset.yaml :
# db-statefulset.yaml
apiVersion: v1
kind: Service
metadata:
name: db
namespace: voting-app
labels:
app: db
spec:
clusterIP: None
selector:
app: db
ports:
- port: 5432
targetPort: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: db
namespace: voting-app
labels:
app: db
tier: database
spec:
serviceName: db
replicas: 1
selector:
matchLabels:
app: db
template:
metadata:
labels:
app: db
tier: database
spec:
containers:
- name: postgres
image: postgres:16-alpine
env:
- name: POSTGRES_USER
value: "postgres"
- name: POSTGRES_PASSWORD
value: "postgres"
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: local-path
resources:
requests:
storage: 1Gi
kubectl apply -f db-statefulset.yaml
kubectl get pods -l app=db
kubectl get pvc
NAME READY STATUS RESTARTS AGE
db-0 1/1 Running 0 40s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-db-0 Bound pvc-1a2b3c4d-5e6f-7a8b-9c0d-e1f2a3b4c5d6 1Gi RWO local-path 40s
💡 Le Pod s'appelle db-0 (pas de suffixe aléatoire) et son PVC data-db-0 a été créé automatiquement par le volumeClaimTemplates. Le nom DNS db continue de fonctionner pour le worker et pour la future application result : c'est le Service headless qui le porte désormais (il résout directement vers l'IP du Pod).
Le nouveau volume data-db-0 démarre vide : la base est repartie de zéro. Re-remplissez-la, puis tuez le Pod :
kubectl delete job seed-votes --ignore-not-found
kubectl apply -f seed-votes-job.yaml
kubectl wait --for=condition=Complete job/seed-votes --timeout=60s
kubectl exec db-0 -- psql -U postgres -c 'SELECT COUNT(*) FROM votes;'
# Suppression du Pod
kubectl delete pod db-0
kubectl wait --for=condition=Ready pod/db-0 --timeout=120s
# Même nom, même volume, mêmes données
kubectl exec db-0 -- psql -U postgres -c 'SELECT COUNT(*) FROM votes;'
count
-------
50
(1 row)
✅ Le Pod est revenu avec le même nom (db-0) et s'est rattaché à son PVC personnel (data-db-0) : identité et données stables, sans aucune intervention.
Q: Comment les StatefulSets garantissent-ils des identités stables ?
R: Noms prévisibles et conservés (db-0, db-1…), DNS individuel par Pod (db-0.db.voting-app.svc.cluster.local), création/suppression ordonnée, et rattachement de chaque Pod à son PVC personnel.
Q: Quelle est la différence entre volumeClaimTemplates et un volume classique ?
R: volumeClaimTemplates génère un PVC distinct par réplique (data-db-0, data-db-1…), que chaque Pod retrouve à l'identique après recréation. Un volume classique référencé dans le template serait partagé par toutes les répliques.
Q: Pourquoi un Service headless pour un StatefulSet ?
R: clusterIP: None fait résoudre le DNS directement vers les IPs des Pods et donne à chaque Pod son propre nom DNS stable, nécessaire quand les répliques ne sont pas interchangeables (primaire/réplicas d'une base, par exemple).
Poser la dernière brique du fil rouge : l'interface des résultats, branchée sur la base désormais durable.
Tout est prêt côté données. Il ne manque que la vitrine, et cette dernière brique est une pure répétition de ce que vous savez déjà faire : à vous l'honneur de terminer l'application.
result (image dockersamples/examplevotingapp_result:latest, port 80, labels app: result / tier: frontend, requests 128Mi/100m, limite 256Mi).result en NodePort 31001.31000 et 31001 dans deux onglets, votez sur l'un, et regardez les pourcentages bouger en temps réel sur l'autre.💡 Indice : result trouve la base tout seul : demandez-vous pourquoi avant d'ouvrir la solution.
1. et 2. Deployment et Service :
Fichier result-deployment.yaml :
# result-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: result
namespace: voting-app
labels:
app: result
tier: frontend
spec:
replicas: 1
selector:
matchLabels:
app: result
template:
metadata:
labels:
app: result
tier: frontend
spec:
containers:
- name: result
image: dockersamples/examplevotingapp_result:latest
ports:
- containerPort: 80
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
---
apiVersion: v1
kind: Service
metadata:
name: result
namespace: voting-app
labels:
app: result
spec:
type: NodePort
selector:
app: result
ports:
- port: 80
targetPort: 80
nodePort: 31001
kubectl apply -f result-deployment.yaml
kubectl get pods -l app=result
kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
db ClusterIP None <none> 5432/TCP 15m
redis ClusterIP 10.108.22.190 <none> 6379/TCP 40m
result NodePort 10.104.55.31 <none> 80:31001/TCP 15s
vote NodePort 10.99.141.72 <none> 80:31000/TCP 40m
3. La grande première - voter et voir le résultat en direct :
Ouvrez deux onglets via le Traffic Port Accessor (menu ☰ → Traffic / Ports) :
31000 : l'interface de vote31001 : l'interface des résultatsVotez sur le premier onglet : les pourcentages bougent en temps réel sur le second. (Si l'affichage semble figé derrière le proxy Killercoda, rechargez simplement la page des résultats.)
🚀 Fin du Jour 2 : l'application est complète et en ligne !
navigateur ──▶ vote (x3, NodePort 31000) │ ▼ redis ──▶ worker ──▶ db-0 (StatefulSet + PVC) │ navigateur ◀── result (NodePort 31001)Chaque brique posée depuis le TP 1 est en place : résilience (Deployments), découverte (Services DNS), persistance (StatefulSet + PVC), exposition (NodePorts). Au Jour 3 : configuration propre (TP 6), durcissement production (TP 7), et reconstruction complète en autonomie (TP 8).
Q: Par quel chemin complet passe un vote, du clic jusqu'à l'affichage des résultats ?
R: Navigateur → Service NodePort vote (31000) → un Pod vote → Service redis → Pod redis (file votes) → worker (consommation) → Service headless db → Pod db-0 (table votes) → lecture par result → navigateur (port 31001).
Q: Pourquoi result n'a-t-il besoin d'aucune configuration pour trouver la base ?
R: Comme les autres composants, il cherche l'hôte db codé en dur, et notre Service headless porte exactement ce nom. La Service Discovery DNS fait le reste.
Apprendre à diagnostiquer et résoudre les problèmes courants de stockage dans Kubernetes.
Cet exercice se déroule hors du fil rouge : revenez d'abord dans le namespace default :
kubectl config set-context --current --namespace=default
Votre équipe vous signale que l'application avec stockage persistant ne fonctionne pas. Identifiez et corrigez les problèmes dans cette configuration :
# Configuration problématique
apiVersion: v1
kind: PersistentVolume
metadata:
name: broken-pv
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
storageClassName: fast-storage
hostPath:
path: /tmp/broken-data
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: broken-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: slow-storage
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: broken-app
spec:
containers:
- name: app
image: nginx:1.29.5
volumeMounts:
- name: storage
mountPath: /data
volumes:
- name: storage
persistentVolumeClaim:
claimName: wrong-pvc-name
Mission :
kubectl describe pour diagnostiquer les erreursDiagnostic des problèmes :
Problème 1 - StorageClass incompatibles :
# PV utilise "fast-storage" mais PVC cherche "slow-storage"
kubectl describe pvc broken-pvc
# Montre : Status: Pending (pas de PV compatible)
Problème 2 - AccessModes incompatibles :
# PV propose ReadWriteMany, PVC demande ReadWriteOnce
# Même si les storageClasses étaient correctes, les accessModes ne matchent pas parfaitement
Problème 3 - Nom PVC incorrect :
# Pod référence "wrong-pvc-name" mais le PVC s'appelle "broken-pvc"
kubectl describe pod broken-app
# Montre : Warning FailedMount: persistentvolumeclaim "wrong-pvc-name" not found
Configuration corrigée :
apiVersion: v1
kind: PersistentVolume
metadata:
name: fixed-pv
spec:
capacity:
storage: 2Gi
accessModes:
- ReadWriteOnce
storageClassName: demo-storage
hostPath:
path: /tmp/fixed-data
type: DirectoryOrCreate
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: fixed-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: demo-storage
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: fixed-app
spec:
containers:
- name: app
image: nginx:1.29.5
volumeMounts:
- name: storage
mountPath: /data
volumes:
- name: storage
persistentVolumeClaim:
claimName: fixed-pvc
Commandes de diagnostic :
# Vérifier le binding PV/PVC
kubectl get pv,pvc
# Diagnostiquer les Pods en erreur
kubectl describe pod broken-app
# Voir les événements du cluster
kubectl get events --sort-by=.metadata.creationTimestamp
# Tester le Pod corrigé
kubectl exec fixed-app -- ls -la /data
Q: Comment diagnostiquer un PVC qui ne trouve pas de PV compatible ?
R:
kubectl describe pvc <nom> → Voir les événements et le statuskubectl get pv → Vérifier les PV disponiblesQ: Que signifie l'état "Pending" d'un PVC ?
R: Soit aucun PV compatible n'est disponible (binding statique), soit, avec une classe en WaitForFirstConsumer, le provisionnement attend simplement le premier Pod consommateur.
Q: Quels sont les critères de binding entre PV et PVC ?
R:
manual)local-path sur Killercoda)Pending sans Pod consommateur est normalPGDATA en sous-répertoire du volume (sinon refus d'initialisation sur un volume non vide)strategy: Recreate pour une base mono-instance (jamais deux instances sur les mêmes données)db-0 garde son nom et retrouve son PVC data-db-0clusterIP: None) : DNS direct vers les Pods, nom db préservé pour les clients