Stratégie de retour en arrière
- Leçon 13
- 45 min
- Module 5 : Rétablir et maintenir le processus de mise en production
Leçon 13 sur 15 · CI/CD, tests et mises en production sûresStratégie de retour en arrière
1. Comprendre le chemin de livraison
2. Créer un pipeline CI reproductible
3. Gérer les environnements et les secrets
4. Mettre en production les changements d'IA et de base de données
5. Rétablir et maintenir le processus de mise en production
Ce que vous saurez faire
« Peut-on revenir en arrière? » a trois réponses différentes. Le code, le modèle et les données reviennent en arrière de façons différentes et à des vitesses différentes. L’un des trois ne revient jamais en arrière tout seul. Choisissez la bonne méthode avant d’en avoir besoin.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Révisions : un nouveau déploiement crée une nouvelle révision. L’ancienne garde le trafic jusqu’à ce que la nouvelle soit prête. Voir Configuration opérationnelle : trois sondes, deux points de terminaison dans L’essentiel de Docker et du nuage pour les développeurs en IA.
- Un condensat d’image désigne une construction précise. Voir Registre et déploiement : Registre, dépôt et étiquette dans le cours sur Docker.
- Le flux de travail Deploy et son démarrage manuel avec un condensat, dans Promouvoir en préproduction : le flux de travail Deploy.
- L’indicateur
CLASSIFIER_VERSIONet le déploiement canari, dans Mise en production progressive : exemple détaillé : le canari en ligne et Promouvoir, puis revenir au mode à révision unique. - Les migrations
001et002, l’expansion et la contraction, et le test de coexistence, dans Migrations sûres : ajouter maintenant, retirer plus tard et Exercice guidé : deux versions sur une base de données.
Ce qu’il vous faut sur votre ordinateur
- Votre dépôt
ticket-api-cià la fin du module 4 (version 1.2.0), dans Migrations sûres : exercice guidé : deux versions sur une base de données. Il contient le flux de travail Deploy, présenté dans Promouvoir en préproduction : exercice guidé : promouvoir votre propre empreinte. Les environnementsstagingetproductionviennent de Identifiants et accès : les environnements GitHub. - Parcours simulé : Docker,
.env,secrets/production_api_key.txtetdeploy/local/production.env, dans Séparer la configuration : exercice guidé : les fichiers d’environnement local. Cette leçon utilise les images publiques du dépôt de référence, par condensat. Les étapes fonctionnent donc avant de créer la version 1.2.1 dans la prochaine leçon. - Parcours réel : les applications Azure
ticket-api-stagingetticket-api-production, dans Séparer la configuration : exemple détaillé (réel) : deux applications, une image. Leurs identités viennent de Identifiants et accès : identités à portée limitée (réel). - Si votre projet ne fonctionne plus, copiez l’instantané
end-of-m04, comme dans Utiliser un instantané. Un instantané n’a aucun paramètre GitHub : recréez l’ensemble de règles, les environnements, leurs variables et le secret.
Exécuté avec GitHub Actions (exécuteurs ubuntu-24.04) dans le dépôt public Nextia-Intelligence/ticket-api-ci, Docker Desktop 4.81.0 sur macOS (Apple silicon), et Azure CLI 2.88.0 dans canadacentral. Les étapes Windows et Linux ne sont pas testées.
Trois éléments peuvent revenir en arrière
Section intitulée « Trois éléments peuvent revenir en arrière »Le 7 octobre 2026 à 18 h 41, Amira a trouvé un bogue dans la version 1.2.0. GET /v1/history répondait 500 (la prochaine leçon le montre). Le responsable du soutien a posé la question habituelle : « Peut-on revenir en arrière? »
Regardez ce qui fonctionnait en production à ce moment-là. Une réponse de POST /v1/classify montre trois parties distinctes :
{"category":"shipping","priority":1,"confidence":0.9,"score":0.9,"model_version":"keywords-1.1"}| Partie | Où elle se trouve | Ce qu’elle a fourni à cette réponse |
|---|---|---|
| L’application (le code) | L’image, désignée par son condensat | Le champ score existe seulement dans 1.2.x |
| Le modèle | Le paramètre CLASSIFIER_VERSION=1.1 de l’application |
keywords-1.1, shipping (expédition) et la priorité 1 |
| Les données | Les lignes dans la base de données | Chaque classification est enregistrée, avec ou sans score |
Un retour en arrière ramène une partie à un état antérieur dont le bon fonctionnement était connu. Chaque partie a sa propre méthode :
- Retour en arrière de l’application : déployez l’image précédente, par son condensat. Rien n’est construit.
- Retour en arrière du modèle : changez seulement l’indicateur. L’image reste la même.
- Données : un déploiement ne change jamais les lignes déjà écrites. Pour les corriger, vous faites une réparation en avant : une nouvelle migration remet les données dans le bon état. Une restauration (charger une sauvegarde de la base de données) est le dernier recours. Elle fait perdre toutes les lignes écrites depuis la sauvegarde.
Description textuelle du diagramme
Commencez par la question « Qu’est-ce qui a mal tourné? ». Si le nouveau code est incorrect, ramenez l’application en arrière : déployez le condensat précédent avec le flux de travail Deploy. Dans l’exercice, cela a pris 120 secondes jusqu’à la production, en passant par la préproduction et l’approbation. Cela retire aussi le nouveau champ score et le nouveau modèle, sans changer la base de données. Si seul le nouveau modèle est incorrect, ramenez seulement l’indicateur en arrière : définissez CLASSIFIER_VERSION=1.0 sur la même image. Cela a pris 43 secondes et conserve tout le reste. Si des lignes ont été mal écrites, rien ne les ramène en arrière. Écrivez une nouvelle migration qui les corrige, comme 003_backfill_score. Restaurez une sauvegarde seulement si des données sont perdues. Tout cela fonctionne seulement parce que la migration 002 a ajouté une colonne que la version 1.1.0 peut ignorer.
Amira avait donc trois choix à 18 h 41. Un retour à 1.1.0 arrêterait les réponses 500, car 1.1.0 ne lit pas score. Mais les clients perdraient score, promis dans les notes de mise en production de 1.2.0. Le soutien perdrait keywords-1.1. Un retour en arrière de l’indicateur n’aiderait pas : le bogue était dans le code, pas dans le modèle. Elle a choisi un correctif urgent (leçon 2) : une petite nouvelle version qui corrige seulement le bogue. Le bogue touchait un seul point de terminaison. Tout le reste demeurait disponible en production.
Puis, avec 1.2.1 en production, l’équipe a fait un exercice de retour en arrière. Ce retour planifié mesure la durée et vérifie ce qui change. Ainsi, le vrai retour en arrière n’est pas le premier. Le reste de cette leçon présente cet exercice.
Avant un retour en arrière : que retire-t-il?
Section intitulée « Avant un retour en arrière : que retire-t-il? »Revenir à une ancienne version retire tout ce qui est venu après. Vérifiez-le avant de commencer, pas après. Pour l’API de billets de soutien :
| 1.1.0 | 1.2.0 | 1.2.1 | |
|---|---|---|---|
score dans la réponse |
non | oui | oui |
Reconnaît CLASSIFIER_VERSION |
non : toujours keywords-1.0 | oui | oui |
| Exécute les migrations | non : crée la table au démarrage | oui, 001 et 002 |
oui, 001 et 002 |
GET /v1/history avec une ligne de 1.1.0 |
200 |
500 (le bogue) |
200 |
Deux questions déterminent si un retour en arrière est sûr :
- L’ancienne version fonctionne-t-elle avec la base de données dans son état actuel? Ici, oui : la migration
002a seulement ajouté une colonne, et 1.1.0 l’ignore. Voilà pourquoi Migrations sûres : ajouter maintenant, retirer plus tard faisait commencer chaque migration par une expansion. Si002avait renomméconfidence, 1.1.0 échouerait à chaque requête. La restauration serait alors le seul moyen de revenir en arrière. - Les clients utilisent-ils déjà quelque chose de nouveau? Un client qui lit
scorene fonctionne plus quand le champ disparaît. Prévenez-les avant, ou choisissez un retour en arrière plus limité.
Exemple détaillé : le retour en arrière de l’application (réel)
Section intitulée « Exemple détaillé : le retour en arrière de l’application (réel) »Trouvez le condensat précédent. Un retour en arrière déploie une image que la CI a déjà construite et testée. Ne reconstruisez jamais l’ancien code : une nouvelle construction produit un nouvel artefact, avec de nouveaux paquets, que personne n’a testé. Le condensat se trouve sur la page de mise en production (la dernière ligne de chaque version), ou dans le registre :
docker buildx imagetools inspect ghcr.io/usmanahmed99/ticket-api-ci:1.1.0La sortie (abrégée) :
Name: ghcr.io/usmanahmed99/ticket-api-ci:1.1.0MediaType: application/vnd.oci.image.index.v1+jsonDigest: sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857aUtilisez votre propre nom de propriétaire au lieu de usmanahmed99. L’étiquette 1.1.0 peut changer si quelqu’un l’attribue de nouveau. Le condensat ne peut pas changer : il désigne la version que vous avez testée.
Démarrez manuellement le flux de travail Deploy. Dans votre dépôt, ouvrez Actions → Deploy → Run workflow (actions → déployer → exécuter le flux de travail). Gardez la branche main. Collez le condensat dans digest (condensat), et écrivez la raison dans reason (raison). Sélectionnez ensuite Run workflow. Avec la CLI GitHub, faites la même chose :
gh workflow run deploy.yml \ -f digest=sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857a \ -f reason="Rollback drill: production back to 1.1.0"gh workflow run deploy.yml ` -f digest=sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857a ` -f reason="Rollback drill: production back to 1.1.0"Un retour en arrière suit le même chemin que chaque mise en production. D’abord la préproduction, avec le test de bon fonctionnement, puis l’approbation, puis la production. C’est plus lent qu’un changement direct dans Azure. Mais ce chemin a été testé, il laisse une trace, et une ancienne image défectueuse s’arrête en préproduction. Quand la tâche de préproduction se termine, la tâche de production attend. La personne responsable sélectionne Review deployments (examiner les déploiements), puis production, écrit un commentaire et approuve. Dans l’exercice, le commentaire était « Rollback drill : production back to 1.1.0 » (exercice de retour en arrière : production ramenée à 1.1.0).
Le résultat : exécution 37671631615. Les tâches (réel) :
Find the image 19:03:55 → 19:04:00 successstaging / Deploy to staging 19:04:04 → 19:04:56 successproduction / Deploy to production 19:05:10 → 19:06:01 successL’exécution a commencé à 19 h 03 min 50 s. La production exécutait 1.1.0 environ 120 secondes plus tard. Les 14 secondes entre les deux tâches correspondaient à l’approbation. Les tests de bon fonctionnement ont réussi dans les deux environnements, y compris ok history (503) (historique réussi, code 503). Le nuage n’a pas de base de données.
Vérifiez ce qui a changé. Amira a envoyé un billet à la production. La sortie (réel) :
{"category":"other","priority":3,"confidence":0.2,"model_version":"keywords-1.0"}Et les paramètres et l’image de l’application :
az containerapp show -g rg-ticket-api-ci -n ticket-api-production \ --query "{image: properties.template.containers[0].image, env: properties.template.containers[0].env[].name}"La sortie (réel) :
{ "env": [ "API_KEY", "REQUIRE_API_KEY", "SHOW_DOCS", "CLASSIFIER_VERSION" ], "image": "ghcr.io/usmanahmed99/ticket-api-ci@sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857a"}Lisez les deux sorties ensemble. CLASSIFIER_VERSION est toujours défini (à 1.1), mais la réponse indique keywords-1.0, et score a disparu. La version 1.1.0 ne reconnaît pas l’indicateur, alors elle l’ignore. Le retour en arrière de l’application a retiré trois choses à la fois : le correctif, le champ d’API et le modèle.
Revenez à la version plus récente. Un exercice se termine dans l’état où il a commencé. Amira a déployé le condensat de 1.2.1 de la même façon : exécution 37671961512, 116 secondes. La sortie (réel) :
{"category":"shipping","priority":1,"confidence":0.9,"score":0.9,"model_version":"keywords-1.1"}L’indicateur a immédiatement fonctionné de nouveau, car il était toujours défini sur l’application.
Exemple détaillé : le retour en arrière du modèle seulement (réel)
Section intitulée « Exemple détaillé : le retour en arrière du modèle seulement (réel) »Supposez maintenant que keywords-1.1 donne de mauvais résultats pour un type de billet, et que le code fonctionne bien. Changez seulement l’indicateur :
az containerapp update -g rg-ticket-api-ci -n ticket-api-production \ --set-env-vars CLASSIFIER_VERSION=1.0 --query properties.latestRevisionName -o tsvLa sortie (réel) :
ticket-api-production--0000010Un paramètre modifié crée une nouvelle révision avec la même image. Après 43 secondes, la réponse était (réel) :
{"category":"other","priority":3,"confidence":0.2,"score":0.2,"model_version":"keywords-1.0"}score reste, le correctif reste, seul le modèle revient en arrière. Amira l’a réactivé avec --set-env-vars CLASSIFIER_VERSION=1.1, et la réponse indiquait de nouveau keywords-1.1.
Ce changement ne passe pas par le pipeline : il modifie directement la production. Le déploiement canari dans Mise en production progressive : exemple détaillé : le canari en ligne faisait la même chose. L’approbation de production ne s’applique donc pas. En cas d’urgence, c’est acceptable pour un indicateur. L’indicateur existe pour cela, et la barrière d’évaluation a testé les deux valeurs. Notez le changement (qui, quand, pourquoi), car le pipeline n’en garde aucune trace. En équipe, placez la commande dans une tâche de flux de travail avec environment: production, comme pour le déploiement canari. Une personne l’approuve alors, et l’exécution en garde une trace. La valeur de --set-env-vars reste aussi sur l’application pour les déploiements suivants.
Le parcours simulé
Section intitulée « Le parcours simulé »Le même exercice sur votre ordinateur. L’environnement de production local fonctionne sur http://127.0.0.1:8082, avec sa propre base de données. Exécutez les commandes dans le dossier du projet.
Commencez avec 1.2.1 et l’indicateur activé. Dans deploy/local/production.env, définissez CLASSIFIER_VERSION=1.1. Déployez ensuite l’image 1.2.1 :
scripts/promote-local.sh production ghcr.io/usmanahmed99/ticket-api-ci@sha256:84937455beb63744755622d539748c9adb1772f379c9eb7e4f961c5b7f1bc924curl -s -X POST http://127.0.0.1:8082/v1/classify -H 'Content-Type: application/json' \ -H "X-API-Key: $(cat secrets/production_api_key.txt)" -d '{"subject":"Package never arrived"}'La sortie (simulation, abrégée) :
ok health (200)ok ready (200)ok classify with the key (200)ok classify without a key (401)ok history (200)production now runs ghcr.io/usmanahmed99/ticket-api-ci@sha256:84937455beb63744755622d539748c9adb1772f379c9eb7e4f961c5b7f1bc924{"category":"shipping","priority":1,"confidence":0.9,"score":0.9,"model_version":"keywords-1.1"}Retour en arrière du modèle seulement. Dans deploy/local/production.env, définissez CLASSIFIER_VERSION=1.0. Exécutez la même commande promote-local.sh avec le même condensat. Compose détecte un paramètre modifié et recrée le conteneur api avec la même image. La sortie (simulation, abrégée) :
Container ticket-api-production-api-1 Recreate Container ticket-api-production-api-1 Recreatedok health (200)...{"category":"other","priority":3,"confidence":0.2,"score":0.2,"model_version":"keywords-1.0"}Cela a pris 8 secondes. Les réponses sont les mêmes que celles du parcours réel. Remettez l’indicateur à 1.1, et exécutez de nouveau la commande.
Retour en arrière de l’application. L’environnement local ne peut pas utiliser promote-local.sh pour ce retour en arrière. L’erreur courante à la fin de cette leçon montre pourquoi. Démarrez seulement le service api avec l’ancienne image, sans toucher à la base de données ni aux migrations :
export IMAGE=ghcr.io/usmanahmed99/ticket-api-ci@sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857adocker compose -f deploy/local/compose.yaml --env-file deploy/local/production.env --env-file .env \ up -d --wait --no-deps apiscripts/smoke.sh http://127.0.0.1:8082 "$(cat secrets/production_api_key.txt)"$env:IMAGE = "ghcr.io/usmanahmed99/ticket-api-ci@sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857a"docker compose -f deploy/local/compose.yaml --env-file deploy/local/production.env --env-file .env ` up -d --wait --no-deps apibash scripts/smoke.sh http://127.0.0.1:8082 (Get-Content secrets/production_api_key.txt -Raw)--no-deps démarre seulement le service que vous nommez. La sortie (simulation) :
Container ticket-api-production-db-1 Running Container ticket-api-production-api-1 Starting Container ticket-api-production-api-1 Started Container ticket-api-production-api-1 Waiting Container ticket-api-production-api-1 Healthyok health (200)ok ready (200)ok classify with the key (200)ok classify without a key (401)ok history (200)Cela a pris 6 secondes. Une classification n’a maintenant aucun score et indique keywords-1.0, comme dans l’exercice réel. Revenez à la version plus récente avec la commande promote-local.sh et le condensat de 1.2.1.
Les données ne reviennent pas en arrière
Section intitulée « Les données ne reviennent pas en arrière »Regardez la base de données après le retour en arrière local. La version 1.1.0 a enregistré deux lignes pendant son exécution :
docker exec ticket-api-production-db-1 psql -U tickets -d tickets \ -c "SELECT id, request_id, model_version, confidence, score FROM classifications ORDER BY id"La sortie (simulation) :
id | request_id | model_version | confidence | score----+--------------+---------------+------------+------- 1 | 4d10436731e8 | keywords-1.0 | 0.7 | 0.7 2 | a49f44a4effc | keywords-1.0 | 0.2 | 0.2 3 | ef19e98fb391 | keywords-1.0 | 0.2 | 4 | 119fbf2734a9 | keywords-1.0 | 0.7 | 0.7 5 | d7dc11adb8bf | keywords-1.0 | 0.2 | 0.2 6 | cd2ce474a397 | keywords-1.0 | 0.7 | 7 | a8276a5e60ee | keywords-1.0 | 0.2 |(7 rows)Les lignes 6 et 7 viennent de 1.1.0 pendant le retour en arrière. La ligne 3 venait déjà de 1.1.0, pendant le déploiement de 1.2.0. Quand la production est revenue à 1.2.1, ces lignes sont restées vides. Le retour en arrière n’a pas retiré la colonne score, et le retour à la version récente ne l’a pas remplie. C’est aussi vrai dans l’autre sens. Les lignes écrites par 1.2.x gardent leur score après un retour à 1.1.0. Un retour en arrière change le code exécuté. Il ne change jamais les données déjà écrites.
La version 1.2.1 lit ces lignes correctement. Elle utilise confidence quand score est vide : c’est le correctif urgent de la leçon 2. Mais d’autres lecteurs de la table, comme un rapport, verraient des valeurs vides. Corrigez les données en avant, avec une nouvelle migration :
-- Forward repair: fill score for the rows that version 1.1.0 wrote during the-- rollout of 1.2.0. Run it after no 1.1.0 instance is left, so no new empty-- rows appear. It changes no structure, so every version keeps working.UPDATE classifications SET score = confidence WHERE score IS NULL;Une migration qui remplit une nouvelle colonne à partir d’anciennes données est un remplissage rétroactif (backfill). Trois règles rendent celle-ci sûre :
- Exécutez-la quand il ne reste aucune version 1.1.0. Sinon, 1.1.0 écrit de nouvelles lignes vides après le remplissage rétroactif.
- Elle ne change aucune structure. Les versions 1.1.0, 1.2.0 et 1.2.1 continuent toutes de fonctionner. Elle ne bloque donc pas un retour en arrière ultérieur.
- Faites d’abord une sauvegarde, car elle change des lignes. Un
ADD COLUMN(ajouter une colonne, 002) peut rester sans danger après un retour en arrière. UnUPDATE(mettre à jour) change des données que vous ne pouvez pas récupérer sans copie.
Dans le dépôt de référence, 003 est arrivé dans la demande de tirage nº 8 (feature/backfill-score vers main). Sa construction est l’image du commit (enregistrement des changements) 30e0c7e. L’exécution locale, dans l’ordre :
docker rm -f ticket-api-olddocker exec ticket-api-production-db-1 psql -U tickets -d tickets -At \ -c "SELECT count(*) FROM classifications WHERE score IS NULL"docker exec ticket-api-production-db-1 pg_dump -U tickets -d tickets -t classifications --data-only -f /tmp/before-003.sqlscripts/promote-local.sh production ghcr.io/usmanahmed99/ticket-api-ci@sha256:1ec838b17cdeefb95620a308b99f31799f4fa10c5ddd9d46a17a2506a7e10947docker logs ticket-api-production-migrate-1docker exec ticket-api-production-db-1 psql -U tickets -d tickets -At \ -c "SELECT count(*) FROM classifications WHERE score IS NULL"docker rm -f ticket-api-olddocker exec ticket-api-production-db-1 psql -U tickets -d tickets -At ` -c "SELECT count(*) FROM classifications WHERE score IS NULL"docker exec ticket-api-production-db-1 pg_dump -U tickets -d tickets -t classifications --data-only -f /tmp/before-003.sqlbash scripts/promote-local.sh production ghcr.io/usmanahmed99/ticket-api-ci@sha256:1ec838b17cdeefb95620a308b99f31799f4fa10c5ddd9d46a17a2506a7e10947docker logs ticket-api-production-migrate-1docker exec ticket-api-production-db-1 psql -U tickets -d tickets -At ` -c "SELECT count(*) FROM classifications WHERE score IS NULL"ticket-api-old est le conteneur 1.1.0 du test du bogue dans la prochaine leçon. Si vous ne l’avez pas démarré, docker rm -f indique « No such container » (aucun conteneur de ce nom). C’est normal. La sauvegarde va dans le conteneur de base de données. Elle est donc supprimée avec l’environnement. Pour une vraie base de données, gardez-la ailleurs. La sortie (simulation, abrégée) :
3ok health (200)...ok history (200)production now runs ghcr.io/usmanahmed99/ticket-api-ci@sha256:1ec838b17cdeefb95620a308b99f31799f4fa10c5ddd9d46a17a2506a7e10947applied 003_backfill_scoreDatabase is up to date (3 migrations).0Trois lignes vides avant, aucune après. Le service migrate a exécuté 003 avant le démarrage du nouveau api, comme pour chaque mise en production. Dans l’exécution de référence (avec une ligne vide), le résultat était le même : 1, puis 0. Exécutez de nouveau les migrations, et seule la dernière ligne apparaît : Database is up to date (3 migrations). (la base de données est à jour, 3 migrations).
Quand faut-il restaurer? Seulement quand des données sont perdues ou détruites. Par exemple, après une migration qui a supprimé une colonne par erreur. Une restauration charge la sauvegarde et fait perdre toutes les lignes écrites depuis la sauvegarde. Indiquez combien avant de commencer. Ici, rien n’a été perdu : chaque score vide peut être calculé à partir de confidence. Une réparation en avant est donc le bon choix, et aucune ligne n’est perdue.
Exercice guidé : un exercice de retour en arrière sur votre ordinateur
Section intitulée « Exercice guidé : un exercice de retour en arrière sur votre ordinateur »- Notez l’état initial : exécutez
scripts/promote-local.sh productionavec l’empreinte de la version 1.2.1 etCLASSIFIER_VERSION=1.1. Classez « Package never arrived » (le colis n’est jamais arrivé). Notez la réponse. - Faites une prédiction : après un retour en arrière du drapeau seulement, quels champs changent dans la réponse?
- Définissez
CLASSIFIER_VERSION=1.0dansdeploy/local/production.env. Exécutez de nouveaupromote-local.shavec la même empreinte. - Classez le même billet de soutien. Comparez le résultat à votre prédiction. Puis remettez le drapeau à
1.1et déployez de nouveau. - Faites une prédiction : après un retour de l’application à la version 1.1.0, quels champs changent? Le drapeau fonctionne-t-il encore?
- Revenez à l’ancienne application avec
up -d --wait --no-deps apiet l’empreinte de la version 1.1.0. Exécutezscripts/smoke.sh. - Classez le billet deux fois. Puis comptez les lignes dont le champ
scoreest vide. - Revenez à la version plus récente : exécutez
promote-local.shavec l’empreinte de la version 1.2.1. Comptez de nouveau les lignes vides. - Faites une sauvegarde avec
pg_dump. Déployez l’image contenant003. Comptez les lignes vides une troisième fois.
Afficher les résultats attendus
"score":0.9,"model_version":"keywords-1.1",shipping(expédition), priorité 1.- à 4. Seul le modèle change :
"score":0.2,"model_version":"keywords-1.0",other(autre).scorereste présent, car l’image est la même. - à 7.
scoredisparaît et la réponse indique keywords-1.0, même si le drapeau vaut1.1: la version 1.1.0 ne connaît pas ce drapeau. Les cinq vérifications de fonctionnement réussissent (l’historique de la version 1.1.0 fonctionne). Le nombre de lignes vides augmente de 3 : vos deux billets et celui de la vérification de fonctionnement. - Le nombre ne change pas. Revenir à la version plus récente ne remplit pas les lignes.
applied 003_backfill_score(migration 003_backfill_score appliquée),Database is up to date (3 migrations).(la base de données est à jour, avec 3 migrations), puis0.
À vous de jouer : ajoutez 003 à votre dépôt
Section intitulée « À vous de jouer : ajoutez 003 à votre dépôt »Ajoutez la réparation vers l’avant à votre dépôt avec une demande de tirage (pull request) de feature/backfill-score vers main : le fichier migrations/003_backfill_score.sql. Avant de pousser vos changements, prédisez quelle vérification CI échouera et pourquoi. (Dans le dépôt de référence, c’était la demande de tirage no 8, après le correctif urgent de la leçon 2. Vous pouvez le faire maintenant ou après la leçon 2.)
Afficher la réponse
Le test de base de données test_migrations_apply_once échoue, car il attend la ligne Database is up to date (2 migrations). (la base de données est à jour, avec 2 migrations). Il y en a maintenant 3. Dans tests/test_database.py, remplacez la ligne attendue par (3 migrations) : une nouvelle migration modifie volontairement ce test. Les autres tests réussissent : 003 ne change aucune structure. Dans la description de la demande de tirage, précisez que 003 s’exécute seulement quand aucune instance 1.1.0 ne reste. Après la fusion, le flux de travail Deploy (déployer) déploie la nouvelle construction, et son étape migrate (migrer) applique 003. Le parcours réel n’a pas de base de données, donc 003 n’y fait rien. Comparez avec la demande de tirage no 8, qui ajoute aussi la section Rollback (retour en arrière) de docs/release-process.md.
Erreur courante
Le retour en arrière local a arrêté la production
Symptôme (simulé) : Amira revient à l’ancienne version en production locale avec le script habituel et l’empreinte de la version 1.1.0 :
scripts/promote-local.sh production ghcr.io/usmanahmed99/ticket-api-ci@sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857aLa sortie (simulée, abrégée) :
Container ticket-api-production-migrate-1 Recreate Container ticket-api-production-api-1 Recreate Container ticket-api-production-api-1 Recreated Container ticket-api-production-migrate-1 Started Container ticket-api-production-migrate-1 Error service "migrate" didn't complete successfully: exit 1service "migrate" didn't complete successfully: exit 1Après 2 secondes, le script s’arrête avec le code de sortie 1. curl http://127.0.0.1:8082/health ne reçoit aucune réponse. Le conteneur 1.2.1 a disparu, et le nouveau conteneur api est seulement à l’état « Created » (créé). Le retour en arrière n’a pas rétabli l’ancienne version : il a arrêté la production.
Cause : l’étape de déploiement de la version 1.2.x exécute d’abord les migrations, avec l’image qu’elle déploie. docker logs ticket-api-production-migrate-1 affiche :
/usr/local/bin/python: No module named ticket_api.migrateLa version 1.1.0 n’a pas d’exécuteur de migrations : elle est antérieure aux migrations. Compose a supprimé les anciens conteneurs avant que les nouveaux soient prêts. Il ne restait donc aucune solution de repli.
Solution :
- Démarrez seulement l’application avec l’ancienne image :
docker compose … up -d --wait --no-deps api, comme dans le parcours simulé. La base de données possède déjà le schéma nécessaire à la version 1.1.0. - Vérifiez avec
scripts/smoke.sh, comme après chaque déploiement. - Avant le prochain vrai retour en arrière, faites un exercice. Un exercice révèle ce problème à un moment calme.
Pourquoi le parcours réel n’a pas échoué : les applications infonuagiques n’ont pas de base de données. Le flux de travail Deploy n’a donc aucune étape de migration. De plus, Container Apps garde l’ancienne révision jusqu’à ce que la nouvelle soit prête. Un déploiement Compose local n’offre pas cette protection.
Pour un vrai service d’IA : un retour en arrière exécute l’ancienne version avec les nouvelles étapes de déploiement. Certaines étapes sont apparues après l’ancienne version, comme une migration, un téléchargement de modèle ou un nouveau secret. Si l’ancienne version ne peut pas exécuter une telle étape, le retour en arrière échoue exactement quand vous en avez besoin. Gardez les étapes de déploiement rétrocompatibles et exercez-vous au retour en arrière avant l’incident.
Vérifiez votre compréhension
Après le retour de l'application à la version 1.1.0, CLASSIFIER_VERSION=1.1 était encore défini, mais la réponse indiquait keywords-1.0. Pourquoi? Qu'est-ce que cela signifie pour un plan de retour en arrière?
Afficher la réponse
La version 1.1.0 ne connaît pas le paramètre CLASSIFIER_VERSION : elle est antérieure au drapeau. Elle l’ignore donc et utilise toujours keywords-1.0. Un retour en arrière de l’application retire tout ce qui est apparu après l’ancienne version. Ici, il retire le correctif, le champ score et le nouveau modèle. Un plan de retour en arrière doit énumérer ces pertes avant le début de l’opération. Informez les clients et le service d’assistance. Si seul le modèle pose problème, un retour en arrière du drapeau est plus limité et plus rapide (43 secondes). Il conserve le reste.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Après un retour de la version 1.2.1 à la version 1.1.0, puis à la version 1.2.1, certaines lignes ont un score vide. Amira dit que le retour à la version 1.2.1 les remplira. A-t-elle raison? Que faites-vous?
Afficher la réponse
Non. Un déploiement change le code exécuté, jamais les lignes déjà écrites. Les lignes écrites par la version 1.1.0 restent sans score, même après le retour à la version 1.2.1. (La version 1.2.1 les affiche correctement, car elle utilise confidence quand score est vide, mais les lignes restent vides.) Réparez-les vers l’avant avec une migration qui remplit la colonne : 003_backfill_score. Exécutez-la quand aucune instance 1.1.0 ne reste. Faites d’abord une sauvegarde, car elle modifie des lignes. Une restauration de sauvegarde sert seulement à récupérer des données perdues : elle supprimerait aussi toutes les lignes écrites depuis la sauvegarde.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Pourquoi faut-il déployer l'ancienne empreinte plutôt que reconstruire l'ancien commit pour revenir à une ancienne version de l'application?
Afficher la réponse
L’empreinte désigne l’image exacte que la CI a testée et qui a déjà fonctionné en production. Une nouvelle construction du même commit (enregistrement des changements) produit un artéfact différent. L’image de base et les paquets peuvent être plus récents, et aucune vérification n’a été exécutée sur cet artéfact. Le retour en arrière déploierait alors un élément non testé, au pire moment. L’empreinte accélère aussi le retour en arrière, car rien n’est construit. Elle assure également la traçabilité : la page de mise en production et build-info.json indiquent le commit et l’exécution qui l’ont produite.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- Trois parties peuvent revenir en arrière, chacune à sa façon : l’application (déployer l’empreinte précédente, environ 2 minutes avec préproduction et approbation), le modèle (changer seulement le drapeau, 43 secondes) et les données (jamais par un retour en arrière).
- Avant un retour en arrière, énumérez ce qu’il retire. De la version 1.2.x à la version 1.1.0 : le correctif,
scoreet keywords-1.1, car la version 1.1.0 ne connaît pas le drapeau. - Un retour en arrière fonctionne seulement si l’ancienne version fonctionne avec la base de données actuelle. Les migrations qui élargissent d’abord le schéma (
002a ajouté une colonne) gardent cette possibilité. - Corrigez les données erronées vers l’avant, avec une migration comme
003_backfill_score: quand aucune ancienne instance ne reste, et après une sauvegarde. Restaurez une sauvegarde seulement si des données sont perdues. Précisez aussi ce qui est perdu depuis la sauvegarde. - Faites un exercice avant d’avoir besoin du vrai retour en arrière. Localement, l’exercice a révélé que l’ancienne version ne peut pas exécuter la nouvelle étape de migration.
Leçon suivante : Procédure de correctif urgent