Composer l'application, et générer un manifeste Kubernetes
Étape 1 : la composition déclarative (1 h 30)
1.0 Préparer le répertoire d'initialisation de la base
L'image PostgreSQL exécute, au premier démarrage, tous les scripts *.sql trouvés dans /docker-entrypoint-initdb.d/, par ordre alphabétique. Votre code étant en v1.1 (colonne done, migration du TP 4 du S1), la base doit charger le schéma puis la migration. On rassemble ces deux scripts, préfixés pour l'ordre, dans un répertoire dédié db/initdb/ (qu'on montera en bloc, voir l'encadré plus bas sur le pourquoi) :
cd ~/Github/edu/listify # adaptez à votre chemin
mkdir -p db/initdb
cp db/schema.sql db/initdb/00-schema.sql
cp db/migrations/001-add-done.sql db/initdb/01-add-done.sql
ls db/initdb/ # 00-schema.sql 01-add-done.sql
On garde les fichiers d'origine (db/schema.sql, db/migrations/…) inchangés : ils servent au déploiement Ansible du S1. db/initdb/ est la version « ordonnée pour l'init conteneur ».
1.1 Le fichier Compose
Lancer trois podman run avec les bons réseaux, volumes et variables, dans le bon ordre, c'est la douleur impérative du S1 qui revient. La réponse est la même : décrire l'état voulu. Créez compose.yaml à la racine du dépôt :
services:
db:
image: docker.io/library/postgres:16-alpine
environment:
POSTGRES_DB: listify
POSTGRES_USER: listify
POSTGRES_PASSWORD: ${DB_PASSWORD:-secret}
volumes:
- listify-data:/var/lib/postgresql/data
# On monte un RÉPERTOIRE d'init (et non des fichiers un par un) : plus robuste,
# et les scripts s'y exécutent par ordre alphabétique (00- avant 01-).
- ./db/initdb:/docker-entrypoint-initdb.d:ro,z
healthcheck:
test: ["CMD", "pg_isready", "-U", "listify"]
interval: 5s
timeout: 3s
retries: 5
backend:
build: ./backend
image: listify-backend:1.0
environment:
DB_HOST: db
DB_PASSWORD: ${DB_PASSWORD:-secret}
depends_on:
db:
condition: service_healthy
frontend:
build: ./frontend
image: listify-frontend:1.0
ports:
- "8080:80"
depends_on:
- backend
volumes:
listify-data:
Chaque ligne fait écho au parcours (ch. 18 §3.2) : les services se joignent par nom (DB_HOST: db), le volume persiste la base, ${DB_PASSWORD} porte la configuration (facteur III), le schéma est chargé automatiquement au premier démarrage via le répertoire d'init de l'image postgres, et surtout depends_on: condition: service_healthy résout déclarativement l'ordre de démarrage qui nous poursuit depuis le S1.
1.2 Lancer toute l'application en une commande
cd ~/Github/edu/listify
export DB_PASSWORD=un-mot-de-passe-fort
podman-compose up -d # ou : podman compose up -d
podman-compose ps # les trois services
Testez la chaîne complète depuis votre poste :
curl -s http://localhost:8080/api/health # {"api":"ok","database":"ok"}
curl -s -X POST http://localhost:8080/api/tasks \
-H 'Content-Type: application/json' -d '{"title":"composé"}'
curl -s http://localhost:8080/api/tasks
Ouvrez http://localhost:8080 au navigateur : Listify fonctionne, entièrement conteneurisé, sans une seule VM, décrit par un fichier de trente lignes. Comparez au S1 : quatre machines, des dizaines de fichiers Ansible, pour la même application. Mesurez le chemin parcouru.
1.3 Arrêter, et observer la persistance
podman-compose down # arrête et supprime les conteneurs ET le réseau
podman volume ls | grep listify # MAIS le volume persiste
podman-compose up -d # tout revient, avec les données
down détruit les conteneurs (jetables) mais pas le volume (précieux) : la distinction stateless/stateful, opérationnalisée. Pour tout supprimer, volume compris : podman-compose down -v.
Point de contrôle n° 1 : l'ordre de démarrage géré déclarativement
Regardez les logs au démarrage : le backend attend que la base soit healthy avant de démarrer (grâce à depends_on: condition: service_healthy). Au S1, cet ordre reposait sur la discipline (le play db avant le play backend) et sur la tolérance de l'application (503 propre). Ici, il est déclaré. Notez la progression, elle mène droit aux probes de Kubernetes (bloc 2).
Étape 2 : le pod, unité de Kubernetes (1 h)
2.1 Regrouper dans un pod
Le pod (ch. 18 §4) groupe des conteneurs qui partagent le namespace réseau : ils ont la même adresse IP et se joignent par localhost. Reconstruisons Listify en pod pour découvrir ce concept, central au bloc 2 :
# Un pod qui publie le port 80 (du frontend) sur le 8081 de l'hôte
podman pod create --name listify-pod -p 8081:80
# La base, DANS le pod
podman run -d --pod listify-pod --name db \
-e POSTGRES_DB=listify -e POSTGRES_USER=listify -e POSTGRES_PASSWORD=secret \
docker.io/library/postgres:16-alpine
# attendre la disponibilité, puis charger le schéma ET la migration (code v1.1)
for i in $(seq 1 20); do podman exec db pg_isready -U listify >/dev/null 2>&1 && break; sleep 1; done
podman exec -i db psql -U listify -d listify < db/schema.sql
podman exec -i db psql -U listify -d listify < db/migrations/001-add-done.sql
# Le backend : il joint la base par 127.0.0.1 (réseau PARTAGÉ dans le pod !)
podman run -d --pod listify-pod --name backend \
-e DB_HOST=127.0.0.1 -e DB_PASSWORD=secret listify-backend:1.0
Point remarquable, à consigner : dans un pod, DB_HOST=127.0.0.1 fonctionne, parce que les conteneurs partagent la même pile réseau (ch. 18 §4.1). C'est différent du réseau nommé de l'étape 1 (où l'on utilisait DB_HOST=db). Vérifiez :
podman exec backend python3 -c "import urllib.request as u; print(u.urlopen('http://127.0.0.1:8000/api/health').read().decode())"
# {"api":"ok","database":"ok"}
2.2 Générer le manifeste Kubernetes
Le geste-passerelle du bloc. Podman traduit votre pod en YAML au format Kubernetes :
podman kube generate listify-pod -f listify-pod.yaml
cat listify-pod.yaml
Lisez ce fichier attentivement (c'est un livrable et un objet d'étude) : apiVersion: v1, kind: Pod, une liste de containers avec leurs image, env, ports. C'est votre premier objet Kubernetes, produit à partir de conteneurs que vous maîtrisez. Vous n'avez pas encore installé Kubernetes, et vous avez déjà écrit (fait écrire) un de ses manifestes.
2.3 Rejouer un manifeste
L'opération inverse fonctionne aussi : Podman sait exécuter un manifeste Kubernetes localement.
podman pod rm -f listify-pod # on repart de zéro
podman kube play listify-pod.yaml # recrée tout depuis le YAML
podman pod ps
podman kube play lit le même format que kubectl apply consommera au bloc 2. Vous tenez la continuité : le YAML que vous venez de générer se rejouera, presque tel quel, sur un vrai cluster Kubernetes.
Point de contrôle n° 2 : lire l'objet Pod
Dans listify-pod.yaml, identifiez et notez : le kind, le nombre de containers, comment le port est publié (hostPort/containerPort), où sont les variables d'environnement. Comparez mentalement avec le compose.yaml de l'étape 1 : qu'est-ce qui se ressemble, qu'est-ce qui diffère ? (Le pod ne gère pas le build des images ni les depends_on : Kubernetes a d'autres mécanismes pour cela, au bloc 2.)
Étape 3 : nettoyage et synthèse (30 min)
podman pod rm -f listify-pod
podman-compose down -v
podman network rm listify-net 2>/dev/null
Rédigez au runbook la comparaison compose vs pod : quand utiliser l'un, quand l'autre ? (Indice : Compose orchestre des services indépendants reliés par un réseau ; le pod regroupe des conteneurs inséparables partageant le réseau. Kubernetes utilise les deux idées : le Pod comme unité, et des objets de plus haut niveau pour relier les Pods entre eux, au bloc 2.)
Point de contrôle final
-
compose.yaml: les trois services démarrent en une commande, chaîne complète fonctionnelle au navigateur -
depends_on: service_healthy: ordre de démarrage géré déclarativement (observé dans les logs) - Persistance :
downpuisupconserve les données (volume) - Pod : backend joignant la base par
127.0.0.1(réseau partagé), health ok/ok -
listify-pod.yamlgénéré, lu et commenté ; rejoué avecpodman kube play - Comparaison compose/pod rédigée ;
compose.yamletdb/initdb/committés
Pour aller plus loin (bonus)
- Profils Compose : ajoutez un service
adminer(interface web pour PostgreSQL) sous un profildebug(profiles: [debug]), lancé seulement avecpodman-compose --profile debug up. À quoi servent les profils ? - Healthcheck du backend : ajoutez un
healthcheckau servicebackenddanscompose.yaml(une requête sur/api/health). Le frontend peut-il alorsdepends_on: backend: condition: service_healthy? - Volume nommé vs bind mount : montez le code du backend en bind mount (
./backend:/app) pour l'éditer à chaud sans reconstruire l'image. Quand ce mode est-il utile, et pourquoi ne l'utilise-t-on jamais en production ?
Questions de compréhension (à préparer pour le TD et l'examen)
- Dans le
compose.yaml, le backend joint la base parDB_HOST: db; dans le pod, parDB_HOST: 127.0.0.1. Expliquez la différence en termes de namespaces réseau (ch. 15 et 18). depends_on: condition: service_healthyrésout un problème qui nous suit depuis le S1. Lequel, et comment le résolviez-vous avant (systemd, health check applicatif) ?- Vous avez généré un
kind: Pod. En quoi le Pod est-il l'unité de déploiement de Kubernetes, et pourquoi Podman propose-t-il exactement ce concept ? - Comparez le rôle du fichier
compose.yaml(S2) à celui duVagrantfile+ rôles Ansible (S1) : que décrivent-ils chacun, et à quelle échelle ?