Aller au contenu
English
Compte : non connecté, synchronisation possible Mon apprentissage
Selectionner le thème
English

Préparer l'exploitation, et reconnaître les limites d'un mono-hôte

Par mis à jour le

Améliorer cette page

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.

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.

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.

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.

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.

Fenêtre de terminal
mkdir ~/atelier-vigie
cd ~/atelier-vigie
mkdir source donnees
printf 'station=ambre\n' > donnees/bulletin.txt

Enregistrez 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 json
import os
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from threading import Lock
visites = 0
verrou = 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: 5s

Dans 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.

Fenêtre de terminal
IMAGE=python:3.13-slim@sha256:ae14d4ce8771606477a81446e7ef429edd6762305fc50c84507c1041fa8285d1
docker pull "$IMAGE"
docker compose -p vigie config -q
docker compose -p vigie up -d --wait --wait-timeout 60 --pull never
curl --fail --max-time 2 http://127.0.0.1:18764/healthz
curl --fail --max-time 2 http://127.0.0.1:18764/bulletin

Les 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 bash
set -eu
cd "$HOME/atelier-vigie"
ok=0
ko=0
date -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 1
done
date -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.

Fenêtre de terminal
bash mesurer-service.sh

La mesure du 4 novembre 2026, de 14:25:16 UTC à 14:25:36 UTC, a donné ce bilan :

succes=14 echecs=6 total=20
taux=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 bash
set -eu
conteneur=$1
attendu=$2
for 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 1
done
printf 'Etat attendu absent : %s ; dernier etat : %s\n' "$attendu" "$etat" >&2
exit 1

Relevez le compteur et le PID, provoquez la panne, puis relevez-les à nouveau. ps -aq retrouve aussi un conteneur arrêté, contrairement à ps -q.

Fenêtre de terminal
cd ~/atelier-vigie
C=$(docker compose -p vigie ps -aq vigie)
bash attendre-sante.sh "$C" healthy
docker inspect -f 'sante={{.State.Health.Status}} RestartCount={{.RestartCount}} Pid={{.State.Pid}} StartedAt={{.State.StartedAt}}' "$C"
touch donnees/indisponible
bash attendre-sante.sh "$C" unhealthy
sleep 20
docker 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.

Fenêtre de terminal
docker inspect -f '{{range .State.Health.Log}}{{.ExitCode}} {{.Output}}{{end}}' "$C" | tail -n 15
docker logs --tail 10 "$C"
rm donnees/indisponible
bash attendre-sante.sh "$C" healthy
curl --fail --max-time 2 http://127.0.0.1:18764/healthz

Le 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.

Fenêtre de terminal
docker inspect -f 'sante={{.State.Health.Status}} RestartCount={{.RestartCount}} OOMKilled={{.State.OOMKilled}} ExitCode={{.State.ExitCode}}' "$C"
docker stats --no-stream "$C"
docker system df
RACINE_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éfinirSignal et commandeEntré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ètentVariation 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éedocker 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.

Chronologie verticale en trois étapes : la dernière sauvegarde restaurable, puis l'incident, puis la santé et les données vérifiées sur la reprise. La flèche entre la sauvegarde et l'incident porte l'âge de la sauvegarde, à comparer au RPO. La flèche entre l'incident et la reprise vérifiée porte la durée de reprise, à comparer au RTO.âge de la sauvegardeà comparer au RPOdurée de repriseà comparer au RTODernière sauvegarderestaurableIncidentSanté et donnéesvérifiées sur la reprise
Source · SVG
Échap pour fermer, + et − pour zoomer, défilement pour se déplacer. 100 %

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.

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.

Fenêtre de terminal
(
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 bash
set -eu
debut=$(date +%s%N)
cd "$HOME/sauvegardes-vigie"
sha256sum -c SHA256SUMS
mkdir "$HOME/vigie-reprise"
tar xzf vigie-projet.tar.gz -C "$HOME/vigie-reprise" --strip-components=1
docker load -i vigie-image.tar
IMAGE=python:3.13-slim@sha256:ae14d4ce8771606477a81446e7ef429edd6762305fc50c84507c1041fa8285d1
docker image inspect "$IMAGE" --format '{{.Id}}'
cd "$HOME/vigie-reprise"
PORT_VIGIE=18766 docker compose -p vigie-reprise up -d \
--wait --wait-timeout 60 --pull never
curl --fail --max-time 2 http://127.0.0.1:18766/healthz
bulletin=$(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 1
fi
printf '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.

Fenêtre de terminal
bash ~/atelier-vigie/restaurer-vigie.sh

Le 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.

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.

Fenêtre de terminal
(
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.txt
date -u +%s > ~/sauvegardes-vigie/instant-incident
docker compose -p vigie -f ~/atelier-vigie/compose.yaml stop
python3 - <<'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")
PY
bash ~/atelier-vigie/restaurer-vigie.sh
test ! -e ~/vigie-reprise/donnees/journal.txt
printf 'ecriture posterieure absente de la reprise\n'
date -u +%s > ~/sauvegardes-vigie/instant-reprise
printf '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.

MesureBornes et contextePremier rejeuSecond rejeu
Restauration préventiveDu début du script à la santé et au bulletin vérifiés6,5 snon relevée
Âge de sauvegardeDe l'instant enregistré avec la sauvegarde à celui de l'incident déclaré26 s18 s
Restauration après incidentDu début du second lancement du script aux mêmes vérifications6,4 snon relevée
Incident à reprise vérifiéeDe instant-incident, écrit juste avant l'arrêt du primaire, à instant-reprise, écrit après la validation des donnéesnon 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.

Fenêtre de terminal
cd ~/atelier-vigie
docker compose -p vigie up -d --wait --wait-timeout 60 --pull never
IMAGE=python:3.13-slim@sha256:ae14d4ce8771606477a81446e7ef429edd6762305fc50c84507c1041fa8285d1
docker 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.py
sleep 12
curl --fail --max-time 2 http://127.0.0.1:18765/healthz
for i in 1 2 3; do
curl --fail --max-time 2 http://127.0.0.1:18764/visites
done
cd ~/vigie-reprise
PORT_VIGIE=18766 docker compose -p vigie-reprise stop
systemctl is-enabled docker
systemctl is-active docker
cat /proc/sys/kernel/random/boot_id

Le 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.

Fenêtre de terminal
sudo systemctl reboot

Après reconnexion, ne relancez aucun conteneur avant l'inventaire. Ces commandes permettent de constater ce qui est revenu seul.

Fenêtre de terminal
systemctl is-enabled docker
systemctl is-active docker
cat /proc/sys/kernel/random/boot_id
cd ~/atelier-vigie
docker ps -a --format '{{.Names}} | {{.Status}}'
C=$(docker compose -p vigie ps -aq vigie)
bash attendre-sante.sh "$C" healthy
docker 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-temoin
curl --fail --max-time 2 http://127.0.0.1:18764/visites
curl --fail --max-time 2 http://127.0.0.1:18764/bulletin

L'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.

Fenêtre de terminal
curl --fail --max-time 2 http://127.0.0.1:18765/healthz
printf 'code=%s\n' "$?"
docker start vigie-temoin
sleep 2
curl --fail --max-time 2 http://127.0.0.1:18765/healthz

Avant 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.

ÉtapeCommande ou procédure à rejouerRésultat attendu et décision
Vérifier le démon après redémarragesystemctl is-enabled docker puis systemctl is-active dockerenabled 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/healthzhealthy, 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 portLe port 18765 répond ok. Le compteur en mémoire recommence.
Vérifier la sauvegarde avant reprisesha256sum -c SHA256SUMS dans le dossier des sauvegardesToutes les empreintes sont OK. Une erreur interdit de poursuivre avec cette copie.
Restaurer sur le projet distinctbash ~/atelier-vigie/restaurer-vigie.sh avec un dossier cible absentLe 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étiercurl --fail --max-time 2 http://127.0.0.1:18766/bulletinstation=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.

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.

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.

Fenêtre de terminal
(
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.

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 unhealthy ne 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-stopped respecte 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.

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

6 questions
6 min.
70% requis

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

Ce projet vous est utile ?

Les guides, formations, quiz et labs restent gratuits. Si vous les utilisez régulièrement, vous pouvez contribuer à leur maintenance et à leur développement.

Devenir membre de soutien (nouvel onglet) Soutenir une fois (nouvel onglet) En savoir plus

Suivez aussi le projet sur LinkedIn