Votre application tourne. Si elle ne répond plus demain, savez-vous quelle alerte vous recevrez, quelle commande lancer et quelles données vous pourrez retrouver ? Cette leçon vous fait préparer ces réponses sur un hôte Docker. Vous mesurez le service depuis un client, vous provoquez une panne de santé, vous chronométrez une restauration et vous redémarrez la machine. Vous consignez ensuite ce qui revient seul et ce qui demande une intervention. Ces essais ne prouvent pas la haute disponibilité d'un hôte unique.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »Vous allez relier les observations techniques à des décisions d'exploitation. Chaque résultat doit pouvoir être retrouvé avec une commande.
- Écrire un objectif de service avec un indicateur, une cible et une fenêtre.
- Relier chaque alerte à un signal Docker et à une procédure d'intervention.
- Distinguer une sonde en échec d'un processus qui s'arrête.
- Mesurer l'âge d'une sauvegarde restaurable et la durée d'une restauration.
- Vérifier ce qui revient après le redémarrage de l'hôte.
- Écrire une documentation d'exploitation rejouable et reconnaître les limites d'un hôte unique.
Prérequis
Section intitulée « Prérequis »Vous devez avoir terminé ces trois leçons. Leurs gestes sont repris ici, sans réenseigner les politiques de redémarrage ni la sauvegarde d'une base.
- Encadrer ressources et redémarrage d'une stack
pour comprendre
unless-stopped, l'arrêt volontaire etOOMKilled. - Lire les journaux et attribuer une saturation pour rapprocher journaux, état des conteneurs et ressources consommées.
- Sauvegarder et restaurer une application pour distinguer une archive présente d'une sauvegarde réellement restaurable.
Travaillez sur une VM dédiée que vous pouvez redémarrer, jamais sur un
serveur partagé. Il vous faut un compte autorisé à utiliser Docker et sudo
pour le redémarrage, Docker Engine 29.9.2 et Compose 5.6.1 du dépôt officiel,
ainsi que Bash, Python 3, curl, tar, sha256sum et les outils GNU usuels.
Les mesures citées ont été faites le 4 novembre 2026 sur Debian 13.7,
sur la VM de référence, avec ces versions de Docker et Compose.
Les ports locaux 18764, 18765 et 18766 et les dossiers
~/atelier-vigie, ~/vigie-reprise et ~/sauvegardes-vigie doivent être
libres. Le seul téléchargement nécessaire est l'image publique Python.
Le serveur Python sert à l'exercice, pas à publier une application sur Internet.
Quelle application sert de démonstration ?
Section intitulée « Quelle application sert de démonstration ? »L'application vigie sert un bulletin enregistré dans un fichier. Son point
/healthz répond correctement tant que le fichier indisponible n'existe
pas. Son compteur /visites reste en mémoire. Ce sont deux états différents :
le bulletin doit survivre à un redémarrage, pas le compteur.
Créez le dossier dédié. L'absence de -p fait échouer mkdir si ce dossier
existe déjà, plutôt que de mélanger l'exercice avec des fichiers antérieurs.
mkdir ~/atelier-vigiecd ~/atelier-vigiemkdir source donneesprintf 'station=ambre\n' > donnees/bulletin.txtEnregistrez ce programme dans source/serveur.py. Il n'écrit pas dans le
dossier des données. Vous seul y créerez le fichier qui provoque la panne.
import jsonimport osfrom http.server import BaseHTTPRequestHandler, ThreadingHTTPServerfrom threading import Lock
visites = 0verrou = Lock()
class Vigie(BaseHTTPRequestHandler): def repondre(self, code, corps, type_mime="text/plain; charset=utf-8"): octets = corps.encode() self.send_response(code) self.send_header("Content-Type", type_mime) self.send_header("Content-Length", str(len(octets))) self.end_headers() self.wfile.write(octets)
def do_GET(self): global visites if self.path == "/healthz": if os.path.exists("/donnees/indisponible"): self.repondre(503, "indisponible\n") else: self.repondre(200, "ok\n") elif self.path == "/visites": with verrou: visites += 1 valeur = visites self.repondre(200, json.dumps({"visites": valeur}) + "\n", "application/json") elif self.path == "/bulletin": with open("/donnees/bulletin.txt", encoding="utf-8") as fichier: self.repondre(200, fichier.read()) else: self.repondre(404, "inconnu\n")
def log_message(self, format, *args): print(self.address_string(), format % args, flush=True)
ThreadingHTTPServer(("0.0.0.0", 8000), Vigie).serve_forever()Écrivez ensuite compose.yaml. Le port est publié sur la boucle locale
de la VM, pas sur toutes ses interfaces. La source et les données sont
montées en lecture seule. La variable PORT_VIGIE permettra de rejouer la
restauration sur un autre port local.
Deux réglages comptent pour l'exercice. Avec restart: unless-stopped, Docker
applique une politique de redémarrage : il relance le conteneur quand
son processus s'arrête, sauf si vous l'avez arrêté vous-même. Le bloc
healthcheck définit une sonde de santé. Toutes les cinq secondes,
Docker lance dans le conteneur une commande qui interroge /healthz, et
deux échecs de suite font passer l'état du conteneur à unhealthy.
services: vigie: image: python:3.13-slim@sha256:ae14d4ce8771606477a81446e7ef429edd6762305fc50c84507c1041fa8285d1 command: ["python", "/app/serveur.py"] restart: unless-stopped read_only: true user: "65534:65534" environment: PYTHONDONTWRITEBYTECODE: "1" ports: - "127.0.0.1:${PORT_VIGIE:-18764}:8000" volumes: - ./source:/app:ro - ./donnees:/donnees:ro healthcheck: test: - CMD - python - -c - "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/healthz', timeout=2)" interval: 5s timeout: 3s retries: 2 start_period: 5sDans le terminal de la VM, définissez la référence qui servira aussi à la sauvegarde, puis démarrez le projet. Conservez ce terminal jusqu'au redémarrage de l'hôte.
IMAGE=python:3.13-slim@sha256:ae14d4ce8771606477a81446e7ef429edd6762305fc50c84507c1041fa8285d1docker pull "$IMAGE"docker compose -p vigie config -qdocker compose -p vigie up -d --wait --wait-timeout 60 --pull nevercurl --fail --max-time 2 http://127.0.0.1:18764/healthzcurl --fail --max-time 2 http://127.0.0.1:18764/bulletinLes deux requêtes doivent répondre ok puis station=ambre. --wait
attend ici la santé du conteneur ; une simple présence dans docker ps
ne suffirait pas.
Si up --wait s'arrête sur container … is unhealthy et rend un code non
nul, lisez docker compose -p vigie logs. Une cause probable est un
umask de 077 au moment de créer source/ et donnees/ : le compte
65534 du conteneur ne peut plus lire les fichiers. Les journaux affichent
alors can't open file '/app/serveur.py': [Errno 13] Permission denied
et le conteneur redémarre en boucle (cas rejoué avec cp -r sous
umask 077).
Comment mesurer le service depuis l'extérieur du conteneur ?
Section intitulée « Comment mesurer le service depuis l'extérieur du conteneur ? »Un SLO, pour Service Level Objective, est un objectif de service.
Il associe un indicateur mesuré, appelé SLI (Service Level Indicator),
une cible et une fenêtre d'observation. Le SLI de cet exercice est la part
de requêtes vers /healthz qui reçoivent HTTP 200 avant le délai maximal
du client. Docker ne le déduit pas de l'état running. Le guide
SLO, SLI et error budgets
détaille la méthode ; un exemple minimal suffit ici.
Voici l'objectif de l'exercice : « Pendant la fenêtre de cet exercice, au
moins 99 % des vingt requêtes vers /healthz doivent recevoir HTTP 200
en moins de deux secondes. » Les requêtes partent de l'hôte, avec une
pause d'une seconde entre la fin d'un essai et le début du suivant. La
cible de 99 % est un choix d'exercice. Avec vingt requêtes, un seul échec
donne déjà 95 % : cette cible n'en tolère aucun.
Enregistrez ce script dans mesurer-service.sh. Il provoque une panne
pendant les requêtes 8 à 13 pour que vous puissiez voir ce que le calcul
compte. Les échecs restent dans la sortie avec leur code de retour.
#!/usr/bin/env bashset -eucd "$HOME/atelier-vigie"ok=0ko=0date -u '+debut=%Y-%m-%dT%H:%M:%SZ'for i in $(seq 1 20); do if [ "$i" -eq 8 ]; then touch donnees/indisponible; fi if [ "$i" -eq 14 ]; then rm donnees/indisponible; fi if code=$(curl --fail --silent --show-error --max-time 2 \ --output /dev/null --write-out '%{http_code}' \ http://127.0.0.1:18764/healthz); then rc=0 else rc=$? fi if [ "$rc" -eq 0 ] && [ "$code" = 200 ]; then ok=$((ok + 1)) else ko=$((ko + 1)) fi printf 'requete=%s http=%s rc=%s\n' "$i" "$code" "$rc" sleep 1donedate -u '+fin=%Y-%m-%dT%H:%M:%SZ'printf 'succes=%s echecs=%s total=%s\n' "$ok" "$ko" "$((ok + ko))"awk -v ok="$ok" 'BEGIN { printf "taux=%.1f%% cible=99%%\n", 100*ok/20 }'Lancez-le depuis la VM, donc en dehors du conteneur.
bash mesurer-service.shLa mesure du 4 novembre 2026, de 14:25:16 UTC à 14:25:36 UTC, a donné ce bilan :
succes=14 echecs=6 total=20taux=70.0% cible=99%Les six réponses HTTP 503 ont fait rendre le code 22 à curl --fail.
Le service rétabli à la fin n'efface pas ces échecs. L'objectif de cette
fenêtre n'est pas atteint.
Ce client observe le port publié, mais il tourne encore sur le même hôte.
Il ne peut plus mesurer quand la VM tombe. En exploitation, la mesure
doit aussi venir d'une machine extérieure à l'hôte, avec le chemin réseau
réel des utilisateurs. Une sonde locale qui répond ne prouve ni le DNS,
ni le TLS, ni une opération métier non exercée par /healthz. La
définition des SLI et SLO du livre SRE de Google
distingue ainsi l'indicateur observé de la cible qu'on lui fixe.
Pourquoi une sonde en échec ne relance-t-elle rien ?
Section intitulée « Pourquoi une sonde en échec ne relance-t-elle rien ? »Un processus peut continuer à tourner tout en refusant le service.
La politique de redémarrage réagit à son arrêt, pas au passage à
unhealthy. Le fichier indisponible va faire échouer la sonde sans tuer
Python. La leçon Démarrage, disponibilité et santé
démontre déjà qu'un service unhealthy n'est pas redémarré ; ici, vous le
prouvez avec RestartCount et vous reliez cet état à l'alerte « Santé »
du tableau plus bas.
Écrivez attendre-sante.sh pour borner les attentes. Le script rend 1
si l'état attendu n'arrive pas ; il ne transforme pas une attente expirée
en succès.
#!/usr/bin/env bashset -euconteneur=$1attendu=$2for tentative in $(seq 1 60); do etat=$(docker inspect -f '{{.State.Health.Status}}' "$conteneur") if [ "$etat" = "$attendu" ]; then printf 'sante=%s\n' "$etat" exit 0 fi sleep 1doneprintf 'Etat attendu absent : %s ; dernier etat : %s\n' "$attendu" "$etat" >&2exit 1Relevez le compteur et le PID, provoquez la panne, puis relevez-les à
nouveau. ps -aq retrouve aussi un conteneur arrêté, contrairement à
ps -q.
cd ~/atelier-vigieC=$(docker compose -p vigie ps -aq vigie)bash attendre-sante.sh "$C" healthydocker inspect -f 'sante={{.State.Health.Status}} RestartCount={{.RestartCount}} Pid={{.State.Pid}} StartedAt={{.State.StartedAt}}' "$C"touch donnees/indisponiblebash attendre-sante.sh "$C" unhealthysleep 20docker inspect -f 'sante={{.State.Health.Status}} RestartCount={{.RestartCount}} Pid={{.State.Pid}} StartedAt={{.State.StartedAt}}' "$C"Lors de la mesure, RestartCount est resté à 0, y compris après les
vingt secondes supplémentaires, et seule la santé avait changé. Le PID et
StartedAt n'ont pas bougé non plus : un second rejeu a relevé le PID
3955 et StartedAt=2026-11-04T14:50:35.669721637Z, identiques avant et
après l'attente. Vos valeurs seront différentes ; c'est leur absence de
changement qui compte. Un PID identique indique que le processus n'a pas
été relancé, et un StartedAt inchangé le confirme.
Consultez le détail de la sonde et les journaux avant de décider d'une action. Ici, vous connaissez la cause et vous pouvez enlever le fichier.
docker inspect -f '{{range .State.Health.Log}}{{.ExitCode}} {{.Output}}{{end}}' "$C" | tail -n 15docker logs --tail 10 "$C"rm donnees/indisponiblebash attendre-sante.sh "$C" healthycurl --fail --max-time 2 http://127.0.0.1:18764/healthzLe journal de santé contient HTTP Error 503. Après le retrait du
fichier, la réponse redevient ok, sans recréer le conteneur. Un
redémarrage aveugle aurait conservé ce fichier monté depuis l'hôte et
n'aurait donc pas corrigé la cause.
Quels signaux doivent déclencher une intervention ?
Section intitulée « Quels signaux doivent déclencher une intervention ? »Une alerte doit désigner ce que vous avez observé et où trouver la procédure correspondante. Le signal ne donne pas toujours la cause. Docker n'envoie pas d'alerte lui-même : il expose des signaux qu'un outil de supervision lit, comme dans le guide Alerting efficace. Cette leçon décide de ce qu'il faut surveiller ; le choix de l'outil vous appartient. Commencez par un relevé de l'état et de la consommation de Docker.
docker inspect -f 'sante={{.State.Health.Status}} RestartCount={{.RestartCount}} OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}' "$C"docker stats --no-stream "$C"docker system dfRACINE_DOCKER=$(docker info -f '{{.DockerRootDir}}')df -h "$RACINE_DOCKER"Dans la mesure, l'inspection donnait healthy, RestartCount=0
et OOMKilled=false. docker system df comptait une image de 178,4 MB
et un conteneur de 12,29 kB. Le système de fichiers de /var/lib/docker
avait 17 G disponibles sur 20 G, avec 10 % d'occupation. Ces chiffres
décrivent cet hôte ce jour-là ; ils ne sont pas des seuils recommandés.
docker system df n'indique pas l'espace libre du disque. Il
comptabilise les objets Docker. df regarde le système de fichiers qui
peut aussi porter vos sauvegardes et d'autres fichiers. La
référence de docker system df
décrit ce périmètre.
Le tableau suivant peut devenir la table d'entrée de votre documentation d'exploitation. Il n'existe pas de seuil universel pour la fréquence des redémarrages ou l'espace restant : choisissez les vôtres pour votre service, puis éprouvez-les.
| Alerte à définir | Signal et commande | Entrée de la procédure et résultat attendu |
|---|---|---|
| Le service refuse les requêtes | /healthz et .State.Health.Status dans docker inspect | « Santé » : lire le journal de sonde et docker logs, corriger la cause, retrouver healthy et HTTP 200. |
| Les redémarrages se répètent | Variation de .RestartCount entre deux relevés du même conteneur | « Arrêts répétés » : conserver les journaux et l'identité du conteneur, identifier l'arrêt avant de relancer. Le compteur ne doit plus progresser pendant la fenêtre de vérification. |
| Un processus du conteneur a été tué par manque de mémoire | .State.OOMKilled dans docker inspect et événement oom de docker events --filter event=oom | « Mémoire » : corréler l'événement aux journaux et aux limites de ressources configurées, puis mesurer avec docker stats. Ne pas augmenter une limite sans vérifier la capacité de l'hôte. |
| Le disque approche de la limite décidée | docker system df et df -h "$RACINE_DOCKER" | « Disque » : identifier ce qui occupe l'espace, préserver les données, retirer seulement les objets reconnus jetables et vérifier à nouveau l'espace libre. |
RestartCount n'est pas un compteur de redémarrages de la VM. Une
recréation produit aussi un nouveau conteneur et un nouveau compteur.
Conservez donc l'identifiant du conteneur avec vos relevés.
De même, OOMKilled=false décrit l'état inspecté, pas l'absence de tout
incident mémoire dans le passé. À l'inverse, OOMKilled peut valoir true
sur un conteneur qui tourne encore : le champ passe à true dès qu'un
processus du conteneur est tué par manque de mémoire, et il le reste.
Un code de sortie 137 sans OOMKilled ni événement oom ne permet pas
de conclure : un arrêt forcé par SIGKILL peut aussi le produire.
La leçon Plafonner la mémoire et le nombre de tâches
mesure ces cas.
Si l'application ou Docker ne répond plus, la collecte locale peut elle aussi disparaître. Faites alors vérifier l'hôte depuis l'extérieur et transmettez les derniers relevés à la personne chargée de la reprise. Une alerte qui ne débouche que sur « redémarrer » est incomplète.
Comment chronométrer une restauration complète ?
Section intitulée « Comment chronométrer une restauration complète ? »Le RPO, pour Recovery Point Objective, fixe la perte de données acceptable, exprimée ici en durée. L'âge de la dernière sauvegarde restaurable au moment de l'incident permet de comparer votre reprise à cet objectif. Le RTO, pour Recovery Time Objective, fixe le délai de reprise acceptable. Vous devez relever une durée et ses bornes avant de pouvoir la comparer à cet objectif.
Le schéma place les deux mesures sur une même chronologie. Regardez d'où part chaque durée : le RPO se lit avant l'incident, entre la dernière sauvegarde restaurable et l'incident ; le RTO se lit après, jusqu'à la reprise vérifiée.
Vous choisissez ces objectifs avec les personnes qui utilisent le service. Aucune commande ne les garantit. Les définitions du NIST, dans Recovery Point Objective et Recovery Time Objective, les rattachent à ce que l'activité doit pouvoir retrouver et au délai qu'elle peut supporter.
Que faut-il conserver pour cet exercice ?
Section intitulée « Que faut-il conserver pour cet exercice ? »Il faut le programme, sa configuration, les données et l'image exacte. Le bulletin ne change pas pendant la copie et le programme ne l'écrit jamais : cette copie de fichiers est cohérente dans ce cas précis. Elle ne remplace pas la sauvegarde logique d'une base enseignée dans Sauvegarder et restaurer une application.
Un export par digest seul perd le nom de l'image. Sur Docker 29.9.2,
docker image save accepte la référence épinglée, mais après docker load
la sortie ne donne que Loaded image ID: sha256:…, docker image ls ne
liste aucune image et docker image inspect sur la référence épinglée
répond No such image. Le bloc ci-dessous crée donc un alias local à
partir de la référence épinglée, vérifie qu'il désigne la même image, puis
exporte les deux références. Cet alias sert au transport ; les conteneurs restent configurés
avec tag@sha256. Comme docker image tag remplace une association
existante portant le même nom, n'exécutez pas ce bloc sur un hôte où
python:3.13-slim sert à un autre projet.
( set -eu cd ~/atelier-vigie IMAGE=python:3.13-slim@sha256:ae14d4ce8771606477a81446e7ef429edd6762305fc50c84507c1041fa8285d1 TAG=${IMAGE%@*} docker image tag "$IMAGE" "$TAG" test "$(docker image inspect -f '{{.Id}}' "$IMAGE")" = \ "$(docker image inspect -f '{{.Id}}' "$TAG")" mkdir -m 700 ~/sauvegardes-vigie date -u +%s > ~/sauvegardes-vigie/instant-sauvegarde tar czf ~/sauvegardes-vigie/vigie-projet.tar.gz -C ~ \ atelier-vigie/compose.yaml atelier-vigie/source atelier-vigie/donnees docker image save -o ~/sauvegardes-vigie/vigie-image.tar "$IMAGE" "$TAG" cd ~/sauvegardes-vigie sha256sum vigie-projet.tar.gz vigie-image.tar instant-sauvegarde > SHA256SUMS sha256sum -c SHA256SUMS)Chaque ligne du contrôle doit terminer par OK. Une empreinte détecte une
altération de ces fichiers, pas une sauvegarde incomplète ni un auteur
malveillant qui pourrait aussi remplacer le fichier d'empreintes.
Seule la restauration va éprouver le contenu.
Comment rejouer la reprise sur un environnement distinct ?
Section intitulée « Comment rejouer la reprise sur un environnement distinct ? »Créez restaurer-vigie.sh dans ~/atelier-vigie. Il utilise un nouveau
dossier, un nouveau projet et le port 18766. Il échoue si le dossier
cible existe déjà ou si la santé et le bulletin ne sont pas retrouvés.
La durée n'est affichée qu'après ces vérifications.
#!/usr/bin/env bashset -eudebut=$(date +%s%N)cd "$HOME/sauvegardes-vigie"sha256sum -c SHA256SUMSmkdir "$HOME/vigie-reprise"tar xzf vigie-projet.tar.gz -C "$HOME/vigie-reprise" --strip-components=1docker load -i vigie-image.tarIMAGE=python:3.13-slim@sha256:ae14d4ce8771606477a81446e7ef429edd6762305fc50c84507c1041fa8285d1docker image inspect "$IMAGE" --format '{{.Id}}'cd "$HOME/vigie-reprise"PORT_VIGIE=18766 docker compose -p vigie-reprise up -d \ --wait --wait-timeout 60 --pull nevercurl --fail --max-time 2 http://127.0.0.1:18766/healthzbulletin=$(curl --fail --silent --show-error --max-time 2 \ http://127.0.0.1:18766/bulletin)if [ "$bulletin" != 'station=ambre' ]; then printf 'Bulletin restaure inattendu : %s\n' "$bulletin" >&2 exit 1fiprintf 'donnees=station=ambre\n'fin=$(date +%s%N)awk -v debut="$debut" -v fin="$fin" \ 'BEGIN { printf "restauration=%.1f s\n", (fin-debut)/1000000000 }'Rejouez d'abord la procédure sans arrêter le service initial. Vous éprouvez la sauvegarde sans supprimer ce qui fonctionne.
bash ~/atelier-vigie/restaurer-vigie.shLe résultat attendu est un contrôle d'empreintes réussi, une image
rechargée, le service sain, ok, puis donnees=station=ambre et une
durée. Une erreur interrompt le script avec un code non nul.
Le projet distinct évite de prendre les données du service initial pour
des données restaurées. Il partage cependant encore le disque et Docker
avec lui : cet essai ne prouve pas une reprise après perte de la VM.
En exploitation, conservez aussi la sauvegarde hors de l'hôte et rejouez
sur une machine distincte.
Le chargement doit aussi être éprouvé avec une image absente de la cible.
Ce contrôle a été fait séparément sur la VM de mesure : après retrait
de l'image réservée à l'essai, docker load a rendu de nouveau
utilisable sa référence épinglée avec --pull never. La procédure
ci-dessus garde le cache d'image partagé du même Docker. Ne retirez
pas une image utilisée ailleurs pour reproduire le contrôle d'absence ;
utilisez une cible vierge.
Que perd-on entre la sauvegarde et l'incident ?
Section intitulée « Que perd-on entre la sauvegarde et l'incident ? »Ajoutez une écriture postérieure à la sauvegarde. Elle ne figurera pas dans les données restaurées : c'est la perte de données que le RPO borne. Retirez d'abord le projet de reprise de l'essai préventif pour que la seconde restauration reparte d'un dossier absent.
( set -eu cd ~/vigie-reprise PORT_VIGIE=18766 docker compose -p vigie-reprise down cd ~ rm -r -- vigie-reprise)printf 'mesure posterieure\n' > ~/atelier-vigie/donnees/journal.txtdate -u +%s > ~/sauvegardes-vigie/instant-incidentdocker compose -p vigie -f ~/atelier-vigie/compose.yaml stoppython3 - <<'PY'from pathlib import Path
dossier = Path.home() / "sauvegardes-vigie"sauvegarde = int((dossier / "instant-sauvegarde").read_text())incident = int((dossier / "instant-incident").read_text())print(f"age_sauvegarde_a_incident={incident - sauvegarde} s")PYbash ~/atelier-vigie/restaurer-vigie.shtest ! -e ~/vigie-reprise/donnees/journal.txtprintf 'ecriture posterieure absente de la reprise\n'date -u +%s > ~/sauvegardes-vigie/instant-repriseprintf 'reprise_moins_incident=%s s\n' "$(( $(cat ~/sauvegardes-vigie/instant-reprise) - $(cat ~/sauvegardes-vigie/instant-incident) ))"Le dernier contrôle prouve ici l'absence de cette écriture, pas une
perte arbitraire de toutes les données. Le bulletin sauvegardé est
toujours disponible sur le port de reprise. Les deux dernières lignes
relèvent l'instant de la reprise vérifiée et le soustraient à l'instant
de l'incident, écrit juste avant l'arrêt du primaire : c'est la durée que
le RTO doit borner, à la seconde près puisque date -u +%s compte des
secondes entières.
Deux rejeux, le 4 novembre 2026, ont donné les valeurs ci-dessous. Le
second a été fait après l'ajout de instant-reprise, qui manquait au
premier. Dans les deux, le dossier de reprise était recréé à chaque essai
et l'image était encore dans le cache de la même VM.
| Mesure | Bornes et contexte | Premier rejeu | Second rejeu |
|---|---|---|---|
| Restauration préventive | Du début du script à la santé et au bulletin vérifiés | 6,5 s | non relevée |
| Âge de sauvegarde | De l'instant enregistré avec la sauvegarde à celui de l'incident déclaré | 26 s | 18 s |
| Restauration après incident | Du début du second lancement du script aux mêmes vérifications | 6,4 s | non relevée |
| Incident à reprise vérifiée | De instant-incident, écrit juste avant l'arrêt du primaire, à instant-reprise, écrit après la validation des données | non relevé | 17 s |
Les dix secondes environ d'arrêt du primaire font partie de la dernière durée : chronométré à part, cet arrêt a duré 10,3 s. Docker attend ici la fin du délai de grâce avant de tuer le programme Python. Ce délai n'est pas un temps de réaction humaine. Le relevé mesure le lancement de l'arrêt jusqu'à la validation ; il ne mesure pas, requête par requête, la durée exacte d'indisponibilité.
Prenez comme objectifs d'exercice un RPO de cinq minutes et un RTO de trente secondes, du lancement de l'arrêt à la validation des données. Les deux âges de sauvegarde (26 s puis 18 s) et les 17 secondes entre l'incident et la reprise vérifiée restent en dessous de ces bornes. L'âge dépend de la vitesse à laquelle vous enchaînez les commandes : relevez le vôtre. Les 6,4 secondes du script seul ne suffiraient pas à vérifier le second objectif, car elles omettent l'arrêt du primaire.
Ces objectifs sont des choix pédagogiques. Une mesure sur une VM sans
charge, avec une petite sauvegarde déjà accessible, ne devient pas un
engagement de production. Elle ne compte ni l'appel de l'astreinte, ni
le temps nécessaire pour obtenir une machine de secours, ni la
redirection des utilisateurs du port 18764 vers le port 18766. Choisissez
vos objectifs avec les personnes qui utilisent le service et ne les
réécrivez pas après coup pour déclarer la réussite.
Que revient-il après le redémarrage de l'hôte ?
Section intitulée « Que revient-il après le redémarrage de l'hôte ? »Cet essai suppose que les deux restaurations sont terminées et que le
projet de reprise existe encore. Le retour du démon ne suffit pas à
rétablir tous les services : il faut examiner les politiques de
redémarrage et les arrêts volontaires. Préparez un témoin sans politique
de redémarrage, puis remettez vigie en service.
cd ~/atelier-vigiedocker compose -p vigie up -d --wait --wait-timeout 60 --pull neverIMAGE=python:3.13-slim@sha256:ae14d4ce8771606477a81446e7ef429edd6762305fc50c84507c1041fa8285d1docker run -d --name vigie-temoin --read-only --user 65534:65534 \ -p 127.0.0.1:18765:8000 \ -v "$PWD/source:/app:ro" -v "$PWD/donnees:/donnees:ro" \ "$IMAGE" python /app/serveur.pysleep 12curl --fail --max-time 2 http://127.0.0.1:18765/healthzfor i in 1 2 3; do curl --fail --max-time 2 http://127.0.0.1:18764/visitesdonecd ~/vigie-reprisePORT_VIGIE=18766 docker compose -p vigie-reprise stopsystemctl is-enabled dockersystemctl is-active dockercat /proc/sys/kernel/random/boot_idLe témoin doit répondre ok et le compteur de vigie doit atteindre 3
si vous ne l'avez pas interrogé auparavant. Docker était enabled et
active sur la VM de mesure. Le projet de reprise est arrêté
volontairement pour comparer son cas avec celui de vigie. Conservez
l'identifiant de démarrage de l'hôte.
Redémarrez uniquement la VM dédiée autorisée. Cette commande coupe la connexion SSH ; reconnectez-vous quand la VM revient.
sudo systemctl rebootAprès reconnexion, ne relancez aucun conteneur avant l'inventaire. Ces commandes permettent de constater ce qui est revenu seul.
systemctl is-enabled dockersystemctl is-active dockercat /proc/sys/kernel/random/boot_idcd ~/atelier-vigiedocker ps -a --format '{{.Names}} | {{.Status}}'C=$(docker compose -p vigie ps -aq vigie)bash attendre-sante.sh "$C" healthydocker inspect -f 'etat={{.State.Status}} sante={{.State.Health.Status}} RestartCount={{.RestartCount}}' "$C"docker inspect -f 'etat={{.State.Status}} politique={{.HostConfig.RestartPolicy.Name}} OOMKilled={{.State.OOMKilled}}' vigie-temoincurl --fail --max-time 2 http://127.0.0.1:18764/visitescurl --fail --max-time 2 http://127.0.0.1:18764/bulletinL'identifiant de démarrage doit avoir changé. Sur la VM de mesure,
Docker est revenu actif, vigie est redevenu sain et le bulletin était
intact. La première lecture du compteur a répondu {"visites": 1}.
L'état en mémoire avait disparu. RestartCount était pourtant
toujours à 0 : il ne compte pas les démarrages de l'hôte.
vigie tournait avant l'arrêt et avait unless-stopped : Docker l'a
relancé. Le témoin, lui, n'avait aucune politique et est resté arrêté.
Le projet de reprise avait unless-stopped, mais vous l'aviez arrêté
volontairement. Les
règles de redémarrage de Docker
décrivent notamment cette différence. « Une politique est présente »
ne suffit donc pas à promettre un retour automatique.
Vérifiez l'échec du témoin, puis relancez-le manuellement.
curl --fail --max-time 2 http://127.0.0.1:18765/healthzprintf 'code=%s\n' "$?"docker start vigie-temoinsleep 2curl --fail --max-time 2 http://127.0.0.1:18765/healthzAvant docker start, curl a rendu le code 7, car la connexion
était refusée. Après la relance, le témoin a répondu ok.
Les conteneurs arrêtés pendant cet essai ont aussi pu afficher le code
137. Le programme Python est le PID 1 du conteneur et n'a pas de
gestionnaire pour SIGTERM : le noyau ne lui applique pas l'action par
défaut, il reste en vie, et Docker le tue par SIGKILL après dix secondes.
Ce code n'est pas la preuve d'un OOM. La leçon
Tester le contrat d'une image
montre comment refuser une image qui ignore SIGTERM.
Que faut-il écrire dans la documentation d'exploitation ?
Section intitulée « Que faut-il écrire dans la documentation d'exploitation ? »Un runbook, ou documentation d'exploitation, dit à la personne qui intervient quoi vérifier, quoi faire et comment reconnaître le résultat. Il contient aussi les conditions où elle doit s'arrêter et demander de l'aide. Le guide Runbooks détaille leur structure. Il ne vaut pas parce qu'il paraît complet à la lecture : il faut le rejouer, par une autre personne ou sur un environnement distinct.
Pour vigie, notez les dépendances avant les procédures : le service
Docker, l'image épinglée, compose.yaml, le programme dans source/,
le bulletin dans donnees/, les droits de lecture du compte numéro 65534,
sous lequel tourne le conteneur, et l'accès à la sauvegarde. Le seul
accès publié est local à la VM. Il n'y a pas de base ni de service
extérieur à attendre.
La table des alertes donne les entrées « Santé », « Arrêts répétés », « Mémoire » et « Disque ». Ajoutez les étapes de reprise avec leurs commandes et leur résultat attendu. Les commandes ci-dessous sont celles de l'exercice ; ce n'est pas une procédure universelle pour un autre service.
| Étape | Commande ou procédure à rejouer | Résultat attendu et décision |
|---|---|---|
| Vérifier le démon après redémarrage | systemctl is-enabled docker puis systemctl is-active docker | enabled et active. Sinon, traiter le démarrage du démon avant de relancer l'application. |
| Vérifier la santé | C=$(docker compose -p vigie ps -aq vigie), puis bash ~/atelier-vigie/attendre-sante.sh "$C" healthy, puis curl --fail --max-time 2 http://127.0.0.1:18764/healthz | healthy, puis ok. Si l'attente échoue, consulter la sonde et les journaux ; ne pas annoncer la reprise. |
| Reprendre le témoin resté arrêté | docker start vigie-temoin puis la requête sur son port | Le port 18765 répond ok. Le compteur en mémoire recommence. |
| Vérifier la sauvegarde avant reprise | sha256sum -c SHA256SUMS dans le dossier des sauvegardes | Toutes les empreintes sont OK. Une erreur interdit de poursuivre avec cette copie. |
| Restaurer sur le projet distinct | bash ~/atelier-vigie/restaurer-vigie.sh avec un dossier cible absent | Le script rend 0 seulement après la santé et le bulletin attendus. Conserver la durée et les bornes du relevé. |
| Vérifier les données métier | curl --fail --max-time 2 http://127.0.0.1:18766/bulletin | station=ambre. La santé seule ne valide pas les données. |
Le script de reprise a été rejoué dans le projet distinct
vigie-reprise, avant puis après l'incident. Le tableau des mesures
conserve le contexte de chaque durée. Faites aussi relever les étapes où
une personne doit deviner un chemin, un droit ou une valeur : chacune
signale un manque dans la documentation.
Cette procédure ne simule pas la perte du disque physique. Elle ne vérifie pas non plus qu'un autre opérateur dispose des accès nécessaires pendant une panne. Ces dépendances et la perte des écritures postérieures à la sauvegarde doivent rester explicites.
Quand un hôte unique ne suffit-il plus ?
Section intitulée « Quand un hôte unique ne suffit-il plus ? »Tous les conteneurs de cet exercice dépendent de la même machine. Quand elle tombe, ils s'arrêtent ensemble. Une politique de redémarrage ne déplace pas un service sur un autre hôte, et une copie de sauvegarde sur le même disque disparaît avec lui.
Le redémarrage et la restauration montrent une reprise avec coupure. Recréer le seul conteneur disponible ne démontre pas une mise à jour sans interruption. Ajouter des conteneurs sur ce même hôte ne supprime pas ce point de défaillance.
Le besoin de placer des répliques sur plusieurs hôtes, de remplacer un hôte perdu ou d'organiser des mises à jour sans coupure justifie d'étudier un orchestrateur, Kubernetes par exemple. Il faut aussi résoudre l'accès au service et la disponibilité des données. Adopter un orchestrateur ne garantit pas la haute disponibilité à lui seul. La leçon sur la disponibilité et la durabilité explique pourquoi ces deux propriétés ne se confondent pas.
Comment tout nettoyer après l'exercice ?
Section intitulée « Comment tout nettoyer après l'exercice ? »Le nettoyage retire seulement les ressources de cet atelier. Il ne
lance aucun prune et conserve le cache d'image, qui pourrait servir à
un autre projet. Si vous voulez garder les mesures ou la sauvegarde,
copiez-les hors des dossiers ci-dessous avant de les retirer.
( set -eu cd ~/atelier-vigie docker compose -p vigie down docker rm -f vigie-temoin cd ~/vigie-reprise PORT_VIGIE=18766 docker compose -p vigie-reprise down cd ~ rm -r -- atelier-vigie vigie-reprise sauvegardes-vigie docker ps -a --filter name=vigie --format '{{.Names}}' test ! -e ~/atelier-vigie test ! -e ~/vigie-reprise test ! -e ~/sauvegardes-vigie)La liste filtrée ne doit plus afficher de conteneur de l'atelier et les
trois contrôles de dossiers doivent réussir. L'alias python:3.13-slim
créé pour la sauvegarde reste dans le cache d'image, à côté de la
référence épinglée. docker image rm python:3.13-slim supprimerait alors
l'image entière, référence épinglée comprise : ne le faites que si aucun
autre projet ne s'en sert. La VM reste démarrée ; sa destruction
éventuelle relève du cycle de vie de votre environnement de lab, pas d'une
commande visant tous les objets Docker de l'hôte.
À retenir
Section intitulée « À retenir »Un service exploitable se décrit par son objectif, ses observations et ses procédures de reprise. Les essais servent à confronter ces trois éléments, pas à transformer une bonne mesure locale en garantie.
- Le SLO associe un indicateur, une cible et une fenêtre. Comptez les échecs depuis l'extérieur du conteneur et prévoyez aussi une observation extérieure à l'hôte.
- Un état
unhealthyne déclenche pas la politique de redémarrage. - Chaque alerte doit renvoyer à un signal précis et à une action vérifiable.
- Comparez le RPO à l'âge de la sauvegarde restaurable et le RTO à une durée de reprise dont les bornes sont explicites.
- Le retour du démon ne restaure ni les conteneurs sans politique ni
l'état en mémoire.
unless-stoppedrespecte aussi un arrêt volontaire. - Une restauration locale et un reboot réussi ne prouvent ni la bascule ni la haute disponibilité d'un hôte unique.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez vos décisions face à une sonde en échec, aux signaux Docker et aux mesures de reprise. Le quiz porte sur les notions enseignées ici, pas sur la mémorisation des valeurs de la VM.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Épreuve : reprendre un service inconnu
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications