Lab 6.2 : Patterns multi-conteneurs¶
| Durée | 60 à 75 min |
| Niveau | Guidé |
| Prérequis | Lab 6.1 réalisé, cours du chapitre 6 lu, notions de ConfigMap (chapitre 4) et de Service (chapitre 3) |
| Acquis visés | AA2 |
!!! abstract "Objectifs" - Utiliser des init containers pour attendre une dépendance et préparer des fichiers - Diagnostiquer un init container en échec - Partager un volume et le réseau entre les conteneurs d'un Pod (sidecar) - Mettre en place un conteneur ambassador qui sert de proxy vers un Service - Cibler un conteneur précis avec -c dans logs et exec
Contexte et schéma¶
Vous construisez trois Pods à plusieurs conteneurs, chacun illustrant un pattern du cours.
flowchart TB
subgraph A["Pod appli : init containers"]
A1["attendre-backend"] --> A2["preparer-contenu"] --> A3["appli (nginx)"]
end
subgraph B["Pod app-logs : sidecar"]
B1["app<br/>écrit un fichier"] -- "volume partagé" --> B2["lecteur-logs"]
end
subgraph C["Pod ambassador"]
C1["app"] -- "localhost:8080" --> C2["proxy nginx"]
end
C2 --> S["Service backend"]
A1 -. "attend" .-> S
Étapes¶
Étape 1 : Préparer le namespace¶
Étape 2 : Init containers¶
Le Pod appli démarre deux init containers, dans l'ordre :
attendre-backendboucle tant que le Servicebackendn'est pas joignable ;preparer-contenuécrit une page dans un volume partagé avec le conteneur nginx.
Le Service backend n'existe pas encore : l'initialisation va donc rester bloquée.
cat <<'EOF' > appli.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: appli
spec:
replicas: 1
selector:
matchLabels:
app: appli
template:
metadata:
labels:
app: appli
spec:
initContainers:
- name: attendre-backend
image: busybox:1.36
command: ["sh", "-c", "until wget -q -T 2 -O /dev/null http://backend; do echo attente du backend; sleep 2; done; echo backend disponible"]
- name: preparer-contenu
image: busybox:1.36
command: ["sh", "-c", "echo 'Page generee par un init container' > /html/index.html"]
volumeMounts:
- name: html
mountPath: /html
containers:
- name: appli
image: nginx:1.26-alpine
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
emptyDir: {}
EOF
k apply -f appli.yaml
sleep 15
k get pods
k logs deploy/appli -c attendre-backend --tail=3
Le statut est Init:0/2 : le premier init container n'a pas terminé. Créez maintenant le backend, dans un second terminal ouvrez d'abord k get pods -w :
cat <<'EOF' > backend.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: backend
image: nginx:1.26-alpine
---
apiVersion: v1
kind: Service
metadata:
name: backend
spec:
selector:
app: backend
ports:
- port: 80
EOF
k apply -f backend.yaml
Observez la progression Init:0/2, Init:1/2, PodInitializing, Running. Quittez la surveillance avec Ctrl+C, puis vérifiez :
k logs deploy/appli -c attendre-backend --tail=2
k exec deploy/appli -c appli -- wget -qO- http://localhost
!!! success "Point de contrôle" - [ ] Tant que le backend n'existait pas, le Pod restait en Init:0/2 - [ ] Les logs de attendre-backend montrent les tentatives, puis backend disponible - [ ] La page servie par nginx est celle écrite par preparer-contenu
Étape 3 : Diagnostiquer un init container en échec¶
cat <<'EOF' > init-erreur.yaml
apiVersion: v1
kind: Pod
metadata:
name: init-erreur
spec:
initContainers:
- name: init-casse
image: busybox:1.36
command: ["sh", "-c", "echo preparation en cours; exit 1"]
containers:
- name: app
image: nginx:1.26-alpine
EOF
k apply -f init-erreur.yaml
sleep 30
k get pod init-erreur
k logs init-erreur -c init-casse
k describe pod init-erreur | grep -A8 "Init Containers"
k delete pod init-erreur
Les conteneurs de l'application ne démarrent jamais tant que l'init container échoue. Le statut passe par Init:Error puis Init:CrashLoopBackOff.
!!! success "Point de contrôle" - [ ] Le statut du Pod commence par Init: - [ ] Vous avez lu preparation en cours dans les logs de l'init container - [ ] Vous avez relevé le code de sortie dans describe
Étape 4 : Sidecar, volume partagé et réseau partagé¶
Un conteneur app écrit ses logs dans un fichier ; un sidecar les relit pour les rendre visibles avec kubectl logs.
cat <<'EOF' > app-logs.yaml
apiVersion: v1
kind: Pod
metadata:
name: app-logs
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "while true; do date >> /logs/app.log; sleep 5; done"]
volumeMounts:
- name: logs
mountPath: /logs
- name: lecteur-logs
image: busybox:1.36
command: ["sh", "-c", "tail -F /logs/app.log"]
volumeMounts:
- name: logs
mountPath: /logs
volumes:
- name: logs
emptyDir: {}
EOF
k apply -f app-logs.yaml
k wait --for=condition=Ready pod/app-logs --timeout=60s
sleep 15
k get pod app-logs
k logs app-logs -c app
k logs app-logs -c lecteur-logs --tail=3
k exec app-logs -c app -- ls -l /logs
Le Pod affiche READY 2/2. Les logs du conteneur app sont vides (il n'écrit pas sur sa sortie standard), alors que ceux du sidecar montrent les lignes du fichier.
Les conteneurs d'un Pod partagent aussi le réseau : un second conteneur peut joindre le premier avec localhost.
cat <<'EOF' > reseau-partage.yaml
apiVersion: v1
kind: Pod
metadata:
name: reseau-partage
spec:
containers:
- name: web
image: nginx:1.26-alpine
- name: testeur
image: busybox:1.36
command: ["sh", "-c", "while true; do wget -qO- http://localhost | grep -o '<title>.*</title>'; sleep 10; done"]
EOF
k apply -f reseau-partage.yaml
k wait --for=condition=Ready pod/reseau-partage --timeout=60s
sleep 15
k logs reseau-partage -c testeur --tail=2
!!! success "Point de contrôle" - [ ] k logs app-logs -c app est vide, k logs app-logs -c lecteur-logs affiche des dates - [ ] Le fichier /logs/app.log est visible depuis le conteneur app - [ ] Le conteneur testeur obtient la page de nginx via localhost
Étape 5 : Ambassador¶
L'application appelle uniquement localhost:8080 ; un conteneur nginx joue le rôle de proxy vers le Service backend créé à l'étape 2. L'application ignore l'adresse réelle du service distant.
cat <<'EOF' > ambassador.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: ambassador-conf
data:
default.conf: |
server {
listen 8080;
location / {
proxy_pass http://backend:80;
}
}
---
apiVersion: v1
kind: Pod
metadata:
name: ambassador
spec:
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "while true; do wget -qO- http://localhost:8080 2>&1 | grep -Eo '<title>.*</title>|wget: .*'; sleep 5; done"]
- name: proxy
image: nginx:1.26-alpine
volumeMounts:
- name: conf
mountPath: /etc/nginx/conf.d/default.conf
subPath: default.conf
volumes:
- name: conf
configMap:
name: ambassador-conf
EOF
k apply -f ambassador.yaml
k wait --for=condition=Ready pod/ambassador --timeout=60s
sleep 15
k logs ambassador -c app --tail=3
Coupez le backend, puis rétablissez-le, en suivant les logs de l'application :
k scale deployment backend --replicas=0
sleep 15
k logs ambassador -c app --tail=3
k scale deployment backend --replicas=1
k rollout status deployment/backend
sleep 15
k logs ambassador -c app --tail=3
!!! success "Point de contrôle" - [ ] L'application obtient la page du backend en appelant seulement localhost:8080 - [ ] Avec le backend à 0 réplica, les logs montrent une erreur (passerelle ou connexion refusée) - [ ] Après le retour du backend, l'application obtient de nouveau la page, sans avoir été modifiée ni redémarrée
Questions de réflexion¶
Pourquoi un init container est-il préférable à une boucle d'attente écrite dans l'application ?
Il sépare la préparation de l'application, qui reste simple, et garantit que les conteneurs applicatifs ne démarrent qu'une fois la dépendance disponible. Il se diagnostique à part, avec logs -c.
Que se passe-t-il si un init container échoue de façon répétée ?
Les conteneurs de l'application ne démarrent pas. Le Pod reste en Init:Error puis Init:CrashLoopBackOff, avec des redémarrages de plus en plus espacés.
Pourquoi les logs du conteneur app de l'étape 4 sont-ils vides alors que l'application fonctionne ?
Elle écrit dans un fichier et non sur la sortie standard, seule source de kubectl logs. Le sidecar relit le fichier et l'affiche sur sa propre sortie standard.
Qu'est-ce qui permet à testeur d'appeler nginx avec localhost ?
Les conteneurs d'un même Pod partagent la même interface réseau et la même adresse IP, donc localhost désigne le même Pod.
Quel intérêt d'un ambassador pour l'application ?
Elle appelle toujours localhost, quelle que soit la destination réelle. Le proxy peut changer d'adresse, ajouter de la sécurité ou de la reprise sur erreur sans toucher au code de l'application.
Dépannage¶
| Symptôme | Pistes |
|---|---|
Pod bloqué en Init:0/2 |
k logs <pod> -c attendre-backend : le Service backend existe-t-il, a-t-il des endpoints (k get endpoints backend) ? |
Init:CrashLoopBackOff |
k logs <pod> -c <init> et k describe pod : commande ou code de sortie |
a container name must be specified |
Pod à plusieurs conteneurs : ajouter -c <conteneur> (la liste est indiquée dans le message) |
READY 1/2 |
L'un des conteneurs a échoué : k describe pod, section Containers |
| Logs du sidecar vides | Attendre quelques secondes ; vérifier que les deux conteneurs montent le même volume au même chemin |
testeur n'affiche rien |
nginx démarre encore : attendre 10 à 15 secondes puis relire les logs |
| L'ambassador ne répond pas | Nom du Service backend ou port incorrect dans proxy_pass ; contrôler k get endpoints backend |
| Configuration de l'ambassador non prise en compte | Un montage subPath n'est pas rafraîchi : recréer le Pod |