Skip to content

Mises à jour ​

Mettre à jour Mankomail, c’est remplacer l’image de l’application et la redémarrer. Le schéma de la base suit tout seul : les migrations s’appliquent au démarrage de l’application. Cette page décrit ce mécanisme, la procédure, et le retour à la version précédente.

Le fonctionnement des migrations ​

  • Elles s’appliquent au démarrage. Quand un processus applicatif démarre, il applique les migrations que contient son image et que la base n’a pas encore, puis commence à servir. Il n’y a pas de commande de migration séparée.
  • Sous un verrou. Les migrations s’exécutent sous un verrou consultatif PostgreSQL. Plusieurs processus peuvent démarrer en même temps : un seul migre, les autres attendent, puis constatent qu’il ne reste rien à faire.
  • Une transaction par migration. Une migration qui échoue est annulée entièrement, celles d’avant restent appliquées, et le processus s’arrête avec migration <fichier> failed dans sa sortie d’erreur.
  • Uniquement vers l’avant. Il n’y a pas de migration « descendante ». Une base migrée par une version plus récente ne peut pas revenir à un schéma plus ancien, sauf en restaurant une sauvegarde.
  • Immuables. La base enregistre une empreinte de chaque migration appliquée. Si une image livre une migration dont le contenu diffère de celle déjà appliquée, le processus refuse de démarrer (was modified after being applied).
  • Sans limite de durée. Les migrations ne sont pas concernées par DATABASE_STATEMENT_TIMEOUT_MS : une longue migration sur une grosse base n’est pas annulée.

Vous pouvez les suivre dans les journaux : une ligne applying migration par migration, puis database schema is up to date.

DATABASE_RUN_MIGRATIONS=false désactive les migrations pour un processus : il écrit migrations are disabled et démarre sans migrer. Son /readyz répond alors 503 avec la liste des migrations en attente, jusqu’à ce qu’un autre processus les ait appliquées.

Avant la mise à jour ​

  1. Faites une sauvegarde, et gardez-la jusqu’à ce que la nouvelle version ait fait ses preuves :

    bash
    ./scripts/backup.sh

    Voir Sauvegardes. Le manifeste enregistre la version du schéma : c’est cette sauvegarde que vous restaurerez en cas de retour arrière.

  2. Notez la version qui tourne aujourd’hui, d’après /healthz :

    bash
    curl -s localhost:3000/healthz
  3. Gardez un moyen de revenir à l’image actuelle : le commit ou le tag à partir duquel vous l’avez construite, ou la référence de l’image publiée.

Mettre à jour une instance construite depuis les sources ​

  1. Récupérez la nouvelle version du code source et placez-vous dessus, par exemple sur un tag :

    bash
    git fetch --tags
    git checkout <version>
  2. Renseignez APP_VERSION=<version> dans .env. Elle devient la version affichée par /healthz et sur chaque ligne de journal, et celle qu’enregistrent les manifestes de sauvegarde.

  3. Construisez l’image et recréez les conteneurs :

    bash
    docker compose -f compose.reference.yaml up -d --build
  4. Vérifiez la mise à jour.

Lisez les modifications de compose.reference.yaml et de .env.example entre les deux versions : une nouvelle version peut ajouter une variable ou changer un service. Si vous gardez votre propre copie du fichier Compose, reportez-y ces modifications.

Mettre à jour avec une image publiée ​

Si vous faites tourner une image publiée au lieu de construire la vôtre :

  1. Renseignez dans .env APP_IMAGE avec la référence de la nouvelle image, et APP_VERSION avec sa version.

  2. Récupérez l’image et recréez le conteneur applicatif :

    bash
    docker compose -f compose.reference.yaml pull app
    docker compose -f compose.reference.yaml up -d app
  3. Vérifiez la mise à jour.

Vérifier la mise à jour ​

bash
docker compose -f compose.reference.yaml logs app | grep -E 'applying migration|schema is up to date|configuration:'
curl -s localhost:3000/healthz
curl -s localhost:3000/readyz
  • Les journaux montrent les migrations appliquées, puis database schema is up to date.
  • /healthz rend la nouvelle version.
  • /readyz répond 200 avec "status":"ready".
  • Aucun avertissement configuration: n’est apparu : une variable renommée ou retirée par la nouvelle version se signalerait ici.

Si le processus s’arrête sur une migration en échec, le conteneur redémarre et échoue de nouveau, en boucle. Lisez l’erreur dans les journaux, puis corrigez sa cause ou revenez en arrière.

Plusieurs processus applicatifs ​

Quand vous faites tourner des processus api et worker séparés, ou plusieurs répliques :

  • Démarrez d’abord un processus de la nouvelle version et attendez que son /readyz réponde 200 : les migrations sont alors appliquées. Remplacez ensuite les autres processus.
  • Des processus qui démarrent ensemble ne risquent rien de toute façon : le verrou ne laisse migrer qu’un seul d’entre eux.
  • Pour choisir précisément le processus qui migre, réglez DATABASE_RUN_MIGRATIONS=false sur tous les autres.
  • Ne laissez pas tourner côte à côte des processus de deux versions plus longtemps que le remplacement ne l’exige.

Le travail en cours pendant une mise à jour ​

Arrêter un processus ne perd aucun travail, car tout l’état vit dans la base.

  • À la réception de SIGTERM, le processus cesse d’accepter des requêtes, arrête son planificateur, puis laisse les tâches de fond qu’il tient se terminer pendant 30 secondes au plus. Les tâches encore en cours au-delà sont abandonnées.
  • Une tâche abandonnée garde son bail jusqu’à son expiration (60 secondes), puis un processus qui fait tourner le travail de fond (all ou worker) la rend à la file, et elle s’exécute de nouveau.
  • Les exécutions en attente d’un délai, d’une approbation ou d’un signal sont enregistrées en base. Le redémarrage ne les touche pas.

Docker laisse par défaut 10 secondes à un conteneur pour s’arrêter avant de le tuer. Pour laisser aller au bout l’attente de 30 secondes, augmentez le délai de grâce du service applicatif dans votre fichier Compose :

yaml
services:
  app:
    stop_grace_period: 40s

Revenir en arrière ​

Comme les migrations ne vont que vers l’avant, revenir en arrière consiste à restaurer la sauvegarde faite avant la mise à jour, avec l’image précédente :

  1. Revenez à la version précédente : git checkout <version précédente> puis reconstruisez, ou remettez dans APP_IMAGE l’image précédente. Remettez aussi l’ancienne APP_VERSION.

  2. Restaurez la sauvegarde faite avant la mise à jour :

    bash
    ./scripts/restore.sh backups/<sauvegarde faite avant la mise à jour>

    Le script redémarre l’application avec l’image désormais configurée.

  3. Vérifiez /healthz et /readyz.

Tout ce qui a été créé entre la sauvegarde et le retour arrière est perdu : exécutions, modifications de workflows, tables, réglages. Les boîtes rattrapent seules leurs fournisseurs.

WARNING

Ne démarrez pas une image plus ancienne sur une base déjà migrée par une version plus récente. L’ancienne image n’applique que les migrations qu’elle connaît et démarre, mais son code ne correspond pas au schéma plus récent.

Mettre à jour PostgreSQL ​

Le fichier Compose de référence fixe la version majeure de PostgreSQL (POSTGRES_IMAGE, postgres:16 par défaut). Récupérer une nouvelle image de la même version majeure est une mise à jour mineure ordinaire. Un changement de version majeure ne se fait pas en changeant seulement l’image : les fichiers de données d’une version majeure ne sont pas lisibles par une autre. Passez par un export et une restauration.

Avant de commencer, lisez les notes de l’image PostgreSQL de la nouvelle version majeure : l’emplacement du répertoire de données dans le conteneur peut changer d’une version majeure à l’autre, et le volume du fichier Compose doit y correspondre.

  1. Arrêtez l’application, pour que rien ne change après la sauvegarde : docker compose -f compose.reference.yaml stop app. Faites ensuite une sauvegarde avec ./scripts/backup.sh.
  2. Arrêtez la pile : docker compose -f compose.reference.yaml down (sans -v).
  3. Supprimez uniquement le volume PostgreSQL. Son nom est le nom du projet Compose suivi de _postgres-data ; listez les volumes avec docker volume ls pour le trouver, puis docker volume rm <nom>.
  4. Renseignez dans .env POSTGRES_IMAGE avec la nouvelle version majeure.
  5. Restaurez la sauvegarde avec ./scripts/restore.sh. Il démarre le nouveau PostgreSQL sur un volume vide, puis y restaure l’export.

Répétez d’abord cette procédure sur une copie, comme décrit dans Tester une restauration sans toucher à la production.