🐧 Edge Linux Debian · diagnostic & exploitation

Chapitre 07
Déploiement Docker

Objectifs du chapitre

1. Docker Compose : la stack en un fichier

Plutôt qu'une série de docker run à rallonge, on décrit toute la stack edge dans un fichier compose.yml — versionné, relisable, reproductible. Le nôtre, dans /opt/edge/ :

# /opt/edge/compose.yml
services:
  gateway:
    image: registry.local/edge:1.8.2      # ← version PRÉCISE, jamais :latest
    container_name: edge-gateway
    restart: unless-stopped
    depends_on:
      - mqtt
    ports:
      - "8080:8080"
    volumes:
      - gateway-data:/data                # données persistantes
      - ./gateway.yml:/cfg/gateway.yml:ro # config en lecture seule
    environment:
      - OPCUA_ENDPOINT=opc.tcp://192.168.0.1:4840
    healthcheck:
      test: ["CMD", "/healthcheck.sh"]
      interval: 30s
      timeout: 5s
      retries: 3
    logging:
      driver: json-file
      options: { max-size: "20m", max-file: "5" }

  mqtt:
    image: eclipse-mosquitto:2.0.18
    container_name: edge-mqtt
    restart: unless-stopped
    volumes:
      - mqtt-data:/mosquitto/data

volumes:
  gateway-data:
  mqtt-data:

Les commandes de base de la stack, exécutées depuis /opt/edge/ :

maint@edge-cell12:/opt/edge$ docker compose ps        # état de la stack
maint@edge-cell12:/opt/edge$ docker compose up -d      # (re)crée ce qui doit l'être, en arrière-plan
maint@edge-cell12:/opt/edge$ docker compose logs -f    # logs agrégés de la stack
maint@edge-cell12:/opt/edge$ docker compose down       # arrête et retire les conteneurs (PAS les volumes)

docker compose down retire les conteneurs et le réseau, mais conserve les volumes nommés — tes données survivent. En revanche docker compose down -v supprime aussi les volumes : sur un edge, c'est effacer les données persistantes du service (historique local, état, base). Ne tape jamais -v par réflexe ; c'est irréversible sans sauvegarde (chapitre 8).

2. Les tags : pourquoi :latest est un piège

Une image est identifiée par un tag (edge:1.8.2). Le tag spécial :latest ne veut pas dire « la plus récente en permanence » : c'est juste un nom par défaut, qui pointe vers une image figée au moment du pull. Sur un edge, il pose deux problèmes graves :

Règle simple : on déploie toujours un tag de version explicite et immuable (1.8.2), on garde une trace de la version précédente, et on ne se sert de :latest que pour du test jetable. Un edge en production doit pouvoir répondre à « quelle version tourne ? » d'un seul docker ps.

3. Le registry : d'où viennent les images

Les images sont stockées dans un registry — public (Docker Hub) ou, en OT, souvent un registry privé de l'usine (registry.local), accessible depuis l'edge. On récupère une image avec pull :

maint@edge-cell12:~$ docker pull registry.local/edge:1.9.0
1.9.0: Pulling from edge
a1b2c3: Pull complete
d4e5f6: Pull complete
Digest: sha256:7f3a...c1
Status: Downloaded newer image for registry.local/edge:1.9.0

Sur un réseau OT cloisonné, l'edge n'a souvent pas d'accès Internet : le registry public est injoignable. Les images passent par le registry interne, ou sont chargées à la main (docker load depuis un .tar transféré par le laptop : docker save registry.local/edge:1.9.0 -o edge-1.9.0.tar côté source, docker load -i edge-1.9.0.tar côté edge). Prévois comment l'image arrive avant de planifier une mise à jour — c'est souvent le vrai point dur en production.

4. Mettre à jour proprement

La mise à jour est un simple changement de tag suivi d'un up -d. Compose ne recrée que ce qui a changé (le service gateway), et laisse le broker tranquille.

# 1. Récupérer la nouvelle image AVANT de toucher au service en marche
maint@edge-cell12:/opt/edge$ docker pull registry.local/edge:1.9.0

# 2. Mettre à jour le tag dans compose.yml : image: registry.local/edge:1.9.0
maint@edge-cell12:/opt/edge$ sudoedit compose.yml

# 3. Appliquer : seul « gateway » est recréé (nouvelle image), « mqtt » reste en place
maint@edge-cell12:/opt/edge$ docker compose up -d
[+] Running 2/2
 ✔ Container edge-mqtt     Running
 ✔ Container edge-gateway  Started

# 4. VÉRIFIER immédiatement que ça remonte bien
maint@edge-cell12:/opt/edge$ docker compose ps
maint@edge-cell12:/opt/edge$ docker logs --tail 20 -f edge-gateway

L'étape 1 (pull avant) est stratégique : le téléchargement peut être long ou échouer ; on veut l'avoir sécurisé pendant que l'ancienne version tourne encore. Le service n'est interrompu qu'à l'étape 3, brièvement, image déjà en local. Ne jamais éditer le tag puis up -d en espérant que le pull se fasse tout seul au pire moment.

5. Rollback : revenir en arrière en une minute

La nouvelle version se comporte mal (le healthcheck vire unhealthy, les logs crachent des erreurs, la supervision ne reçoit plus). On revient à la version précédente — d'où l'importance d'avoir gardé son image et noté son tag.

# 1. Constat : la 1.9.0 est mauvaise
maint@edge-cell12:/opt/edge$ docker logs --tail 10 edge-gateway
2026-07-04T10:12:03Z FATAL schema mismatch: expected v3, got v4

# 2. Remettre le tag précédent dans compose.yml : image: registry.local/edge:1.8.2
maint@edge-cell12:/opt/edge$ sudoedit compose.yml

# 3. Ré-appliquer : l'image 1.8.2 est toujours en local, redéploiement immédiat
maint@edge-cell12:/opt/edge$ docker compose up -d
 ✔ Container edge-gateway  Started
maint@edge-cell12:/opt/edge$ docker ps --format '{{.Names}}\t{{.Image}}\t{{.Status}}'
edge-gateway   registry.local/edge:1.8.2   Up 4 seconds (health: starting)
v1.8.2 stable pull 1.9.0 v1.9.0 up -d unhealthy ✗ v1.8.2 rollback
Figure 7.1. Le cycle sûr : on garde l'image 1.8.2 en local même après être passé en 1.9.0. Au moindre défaut, le rollback n'est qu'un changement de tag + up -d — pas un nouveau téléchargement. C'est pourquoi on ne purge jamais l'image de la version précédente (chapitre 6).

Le rollback n'est rapide que si trois conditions sont réunies : (1) l'image précédente est encore en local (ne pas l'avoir pruneée), (2) tu connais son tag exact, (3) les données du volume restent compatibles. Le point (3) est le piège : si la 1.9.0 a migré le schéma des données dans le volume, revenir à la 1.8.2 peut ne plus lire ces données. D'où l'ordre de bataille : sauvegarde du volume (chapitre 8) avant toute mise à jour, pour pouvoir restaurer aussi les données, pas seulement l'image.

6. Politiques de redémarrage

La clé restart: du compose dit à Docker quoi faire quand un conteneur s'arrête :

PolitiqueComportement
none jamais redémarrer (défaut)
on-failureredémarre seulement si sortie en erreur (code ≠ 0)
alwaysredémarre toujours, même après un stop manuel + reboot
unless-stoppedcomme always, mais respecte un arrêt manuel

Pour un service edge, unless-stopped est le bon défaut : il redémarre le conteneur après un crash et après un reboot de l'edge, tout en te laissant l'arrêter volontairement pour une maintenance sans qu'il ressuscite tout seul.

7. Compose ou systemd ? Les deux

On a vu deux mécanismes de résilience : la politique restart: de Docker (chapitre 7) et l'unité systemd edge-gateway.service (chapitre 3). Ils ne s'excluent pas :

Ne fais pas gérer le même conteneur par les deux à la fois de façon concurrente : si une unité systemd fait compose up pendant que Docker applique aussi restart: always, tu peux avoir des courses au démarrage (double lancement, ports en conflit). Choisis un chef : soit systemd pilote la stack (et le compose garde restart pour le seul cas du crash entre deux passages de systemd), soit Docker gère tout seul et tu n'écris pas d'unité. Le plus courant sur un edge : Docker enabled + unless-stopped, sans unité dédiée.

Récapitulatif

Exercices

Exercice 1 — Mettre à jour sans casser la prod

Tu dois passer edge-gateway de 1.8.2 à 1.9.0. Donne la séquence, dans le bon ordre, en minimisant l'interruption et en gardant la possibilité de rollback.

Voir la solution
$ docker pull registry.local/edge:1.9.0   # 1. pull d'abord (ancienne toujours en marche)
# 2. éditer compose.yml : image: ...edge:1.9.0 (NE PAS supprimer l'image 1.8.2 locale)
$ docker compose up -d                     # 3. bascule brève
$ docker compose ps && docker logs -f edge-gateway   # 4. vérifier healthcheck + remontée

Rollback possible parce qu'on n'a pas purgé l'image 1.8.2 : si la 1.9.0 déraille, on remet le tag 1.8.2 et up -d. Idéalement, on a aussi sauvegardé le volume avant l'étape 3 (chapitre 8) au cas où la 1.9.0 aurait modifié les données.

Exercice 2 — Le rollback qui ne rollback pas

Tu reviens de 1.9.0 à 1.8.2, le conteneur démarre, mais il plante toujours avec des erreurs de lecture de données. Que s'est-il passé, et qu'aurais-tu dû faire ?

Voir la solution

La 1.9.0 a probablement migré le format des données dans le volume (gateway-data). Revenir à l'image 1.8.2 ne suffit pas : le code 1.8.2 ne sait pas lire des données au nouveau format. Le rollback d'image ne restaure que le code, pas les données. Il aurait fallu sauvegarder le volume avant la mise à jour et le restaurer en même temps que l'image (chapitre 8). C'est la raison pour laquelle « sauvegarde avant déploiement » est une règle, pas une option.

Exercice 3 — Quelle politique de restart ?

Tu veux que le service edge : (a) revienne seul après un crash, (b) revienne après un reboot de l'edge, mais (c) reste arrêté si toi l'arrêtes pour une maintenance. Quelle valeur de restart: ?

Voir la solution

restart: unless-stopped. Il couvre (a) et (b) comme always, mais respecte un arrêt manuel (c) : après docker compose stop, le conteneur ne redémarre pas tout seul, y compris après reboot, jusqu'à ce que tu le relances explicitement. always, lui, le ressusciterait même après ton arrêt volontaire — gênant en maintenance.