Aller au contenu
English

Procédure de correctif urgent

  • Leçon 14
  • 50 min
  • Module 5 : Rétablir et maintenir le processus de mise en production
Leçon 14 sur 15 · CI/CD, tests et mises en production sûresProcédure de correctif urgent

Ce que vous saurez faire

Un correctif urgent incite tout le monde à sauter des étapes. Suivez plutôt une courte procédure. Partez de ce qui tourne en production, prouvez le bogue avec un test et exécutez toutes les vérifications. Ramenez ensuite le correctif dans main, pour que la prochaine mise en production ne ramène pas le bogue.

Avant de commencerCe qu’il faut savoir et avoir sous la mainEnviron 50 minutes

Ce que vous devez savoir

Ce qu’il vous faut sur votre ordinateur

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 ont été utilisés. Les étapes Windows et Linux ne sont pas testées.

La version 1.2.0 a ajouté score à côté de confidence, avec la migration 002. Dans Migrations sûres : exercice guidé : deux versions sur une base de données, 1.1.0 et 1.2.0 utilisaient une seule base de données. Les deux pouvaient classifier. Amira n’a pas vérifié un autre point de terminaison : l’historique. Voici de nouveau le test, sur l’environnement de production local. La version 1.2.0 y tourne. La version 1.1.0 tourne à côté sous le nom ticket-api-old, sur le même réseau et la même base de données. C’est ce qui arrive pendant un déploiement réel :

Terminal (simulated)
KEY=$(cat secrets/production_api_key.txt)
scripts/promote-local.sh production ghcr.io/usmanahmed99/ticket-api-ci@sha256:bd2316b56067c738199d6203a23845c97fb7b85b550379411eddd7cc6a2ddab8
docker run -d --name ticket-api-old --platform linux/amd64 --network ticket-api-production_default \
-p 127.0.0.1:8083:8000 -e API_KEY=old-key \
-e DATABASE_URL="postgresql://tickets:$(grep '^POSTGRES_PASSWORD=' .env | cut -d= -f2)@db:5432/tickets" \
ghcr.io/usmanahmed99/ticket-api-ci@sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857a
curl -s -X POST http://127.0.0.1:8082/v1/classify -H 'Content-Type: application/json' -H "X-API-Key: $KEY" -d '{"subject":"Package never arrived"}'
curl -s -X POST http://127.0.0.1:8083/v1/classify -H 'Content-Type: application/json' -H "X-API-Key: old-key" -d '{"subject":"Package never arrived"}'
curl -s -w '\nstatus %{http_code}\n' http://127.0.0.1:8082/v1/history -H "X-API-Key: $KEY"

La sortie (simulée; les lignes de déploiement sont abrégées) :

ok health (200)
ok ready (200)
ok classify with the key (200)
ok classify without a key (401)
production now runs ghcr.io/usmanahmed99/ticket-api-ci@sha256:bd2316b56067c738199d6203a23845c97fb7b85b550379411eddd7cc6a2ddab8
{"category":"other","priority":3,"confidence":0.2,"score":0.2,"model_version":"keywords-1.0"}
{"category":"other","priority":3,"confidence":0.2,"model_version":"keywords-1.0"}
{"error":{"code":"internal_error","message":"Something went wrong on the server.","request_id":"9dd82ef5ab9b","fields":[]}}
status 500

Toutes les vérifications de bon fonctionnement ont réussi, et les deux versions classifient. Mais l’historique de 1.2.0 répond 500. Le journal du conteneur api (docker logs ticket-api-production-api-1) explique pourquoi :

pydantic_core._pydantic_core.ValidationError: 1 validation error for HistoryList
items.0.score
Input should be a valid number [type=float_type, input_value=None, input_type=NoneType]
INFO: 172.21.0.1:57096 - "GET /v1/history HTTP/1.1" 500 Internal Server Error

Et la table :

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 |
(3 rows)

La ligne 3 vient de 1.1.0, qui ne connaît pas score. Le modèle de réponse de 1.2.0 exige un nombre pour score. Une seule ancienne ligne fait donc échouer toute la liste. Dans l’exécution de référence, le même test a donné le même 500 à 18 h 41 UTC.

Pourquoi toutes les vérifications l’ont manqué : les tests de base de données créaient toutes leurs lignes avec le code de 1.2.0. Aucune ligne n’avait donc de score vide. La vérification de bon fonctionnement n’appelait pas /v1/history. Et les applications infonuagiques n’ont pas de base de données. Leur historique répond donc 503 par conception. Le bogue exige deux versions sur une seule base de données. Un déploiement de 1.2.0 crée exactement cette situation.

Un correctif urgent est une petite mise en production qui corrige un seul bogue urgent dans la version en production. Rien d’autre. Un correctif normal passe par une branche feature/ vers main et attend la prochaine mise en production. Utilisez un correctif urgent lorsque ces deux conditions sont vraies :

  • Le bogue ne peut pas attendre la prochaine mise en production normale.
  • main contient des changements après la version publiée que vous ne pouvez pas mettre en production maintenant.

Le correctif urgent part alors de l’étiquette de version, pas de main. v1.2.0 correspond exactement au code en production, donc 1.2.1 = 1.2.0 + un correctif. Dans le dépôt de référence, main avait un changement après v1.2.0 : PR no 6, les lignes de plateforme du fichier Compose local. Ce changement était sans danger, et un correctif passant par main aurait aussi fonctionné. Amira a quand même suivi la procédure, comme l’indique docs/release-process.md. Une procédure utilisée seulement lors d’une vraie urgence est une procédure qui n’a jamais été testée.

Trois branches de gauche à droite. main : c3c7135 avec l'étiquette v1.2.0, puis 6ae49dc (PR no 6, lignes de plateforme, fusion depuis le commit 5893093 de feature/local-platform), puis 3464f2c (PR no 7, fusion en retour). hotfix/1.2.1 part de l'étiquette v1.2.0 : commit fc9005a avec l'étiquette v1.2.1, test qui échoue puis correctif, toutes les vérifications au vert; depuis ce commit, déploiement manuel : l'empreinte de fc9005a passe par la préproduction et l'approbation pour la production, avec le flux de travail Deploy de main. Puis 543b003 : la PR no 7 était BEHIND (en retard), Update branch (mettre à jour la branche) y fusionne main, comme l'indique une flèche pointillée depuis 6ae49dc. Ensuite, hotfix/1.2.1 est fusionnée dans main à 3464f2c. Une note rouge : la branche ne contient pas 6ae49dc avant la fusion.
L'historique réel du correctif urgent 1.2.1 : créé depuis l'étiquette, publié et déployé depuis son propre commit, puis fusionné dans main après une mise à jour.
Description textuelle du diagramme

La branche de correctif urgent hotfix/1.2.1 part de l’étiquette v1.2.0 (commit c3c7135). Pendant ce temps, main reçoit la PR no 6 (commit 6ae49dc). La branche de correctif urgent reçoit un commit, fc9005a : un test qui échoue, puis le correctif. Elle reçoit l’étiquette v1.2.1. Son image passe manuellement en production, après la préproduction et l’approbation. La branche ne contient pas 6ae49dc. Pour la fusion en retour, la demande de tirage no 7 (hotfix/1.2.1 vers main) était en retard sur main. « Update branch » a donc fusionné main dans la branche de correctif urgent (543b003). Ensuite, la PR no 7 a été fusionnée dans main (3464f2c).

Terminal
git fetch --tags
git switch -c hotfix/1.2.1 v1.2.0

La sortie :

Switched to a new branch 'hotfix/1.2.1'

Vérifiez que vous êtes sur le bon code. git log --oneline -1 montre le commit de l’étiquette, et grep version pyproject.toml montre version = "1.2.0".

Écrivez le test avant le correctif. Un test qui échoue d’abord prouve deux choses : il trouve vraiment le bogue et, plus tard, il prouve que le correctif fonctionne. Ajoutez-le à la fin de tests/test_database.py :

tests/test_database.py (added at the end)
def test_history_reads_rows_from_version_1_1(database):
"""Version 1.1.0 writes no score. 1.2.0 failed with 500 on such rows."""
migrate.main([])
with psycopg.connect(database, autocommit=True) as conn:
conn.execute(
"INSERT INTO classifications"
" (request_id, category, priority, confidence, model_version)"
" VALUES ('old111', 'shipping', 1, 0.7, 'keywords-1.0')"
)
[row] = History(database).recent(10)
assert row["score"] == pytest.approx(0.7)

Le INSERT écrit une ligne exactement comme 1.1.0 : sans score. Les tests de base de données exigent une base PostgreSQL dans TEST_DATABASE_URL, comme en CI. Démarrez une base temporaire, puis exécutez les tests de base de données :

Terminal
docker run -d --name ticket-api-testdb -p 127.0.0.1:5433:5432 \
-e POSTGRES_USER=tickets -e POSTGRES_PASSWORD=ci-only-not-secret -e POSTGRES_DB=tickets postgres:18.6-trixie
export TEST_DATABASE_URL=postgresql://tickets:ci-only-not-secret@127.0.0.1:5433/tickets
python -m pytest -q tests/test_database.py

Le mot de passe est le même que dans ci.yml. Il ne protège rien, et la base est supprimée après la leçon. Si la première exécution indique « connection refused » (connexion refusée), attendez 5 secondes pour que PostgreSQL démarre. La sortie (abrégée) :

> assert row["score"] == pytest.approx(0.7)
E assert None == 0.7 ± 7.0e-07
E
E comparison failed
E Obtained: None
E Expected: 0.7 ± 7.0e-07
tests/test_database.py:61: AssertionError
FAILED tests/test_database.py::test_history_reads_rows_from_version_1_1 - ass...
1 failed, 2 passed in 0.10s

Le test voit le même None que le journal du bogue. Vous avez maintenant une vérification que le pipeline exécutera à chaque changement.

Le correctif change une seule requête SQL dans ticket_api/history.py. Pour une ligne sans score, elle renvoie confidence, qui a la même valeur dans les lignes de 1.1.0 :

ticket_api/history.py (in recent)
return conn.execute(
"SELECT request_id, category, priority, confidence, score,"
" model_version, created_at"
# Rows from version 1.1.0 have no score: use confidence.
"SELECT request_id, category, priority, confidence,"
" COALESCE(score, confidence) AS score, model_version, created_at"
" FROM classifications ORDER BY id DESC LIMIT %s",

COALESCE(a, b) donne a, ou b quand a est vide. Le correctif ne change pas la table. Exécutez de nouveau les tests. La sortie :

... [100%]
3 passed in 0.10s

4. Le reste de la mise en production, et une vérification qui aurait trouvé le bogue

Section intitulée « 4. Le reste de la mise en production, et une vérification qui aurait trouvé le bogue »

Un correctif urgent est une mise en production. Il reçoit donc tout ce qu’une mise en production reçoit :

  • La version 1.2.1 (le dernier nombre : un correctif, aucune nouvelle fonctionnalité), dans pyproject.toml, ticket_api/main.py et contract/openapi.json (python -m ticket_api.export_contract).
  • Les notes de version docs/releases/1.2.1.md. Communiquer une mise en production explique chaque ligne.
  • Une vérification de bon fonctionnement pour l’historique dans scripts/smoke.sh. Le bogue est passé parce qu’aucune vérification ne l’appelait :
scripts/smoke.sh (added)
# 200 with a database, 503 when history is off. A 500 is a bug (as in 1.2.0).
check "history" "200|503" "$url/v1/history" -H "X-API-Key: $key"

La fonction check compare maintenant avec un motif ([[ "$status" =~ ^($expected)$ ]]). Une vérification peut donc accepter 200 ou 503. Voir toutes les différences dans le commit fc9005a : 7 fichiers, 29 lignes ajoutées, 6 supprimées.

Exécutez ensuite toutes les vérifications, comme le fait la CI. Aucune vérification n’est omise pour un correctif urgent. Un changement urgent est justement celui que les gens testent le moins. Avec TEST_DATABASE_URL toujours défini :

Terminal
ruff check . && python -m pytest -q && pip-audit -r requirements-run.txt --progress-spinner off && python -m evaluation.run --classifier 1.1

La sortie (abrégée) :

All checks passed!
31 passed in 0.32s
No known vulnerabilities found
keywords-1.1 on 33 holdout tickets (baseline keywords-1.0)
accuracy 0.909
...
must-pass failures: 0
Gate passed.

scripts/check.sh exécute les mêmes vérifications, puis construit l’image et vérifie son bon fonctionnement.

Terminal
git add -A
git commit -m "Hotfix 1.2.1: history works with rows from version 1.1.0"
git push -u origin hotfix/1.2.1

Avant le commit, vérifiez avec git status que .env et secrets/ ne figurent pas dans la liste. Ils sont dans .gitignore. La CI s’exécute lors des poussées vers hotfix/**, pas seulement vers main. La branche de correctif urgent reçoit donc les mêmes vérifications et la même tâche d’image que main : exécution 37669759163. « Lint, test, audit, evaluate » (analyser, tester, auditer, évaluer) a pris 52 secondes. « Build, smoke-test and push the image » (construire, vérifier le bon fonctionnement et pousser l’image) a pris 47 secondes. L’image a reçu l’étiquette sha-fc9005a.

Terminal
git tag -a v1.2.1 -m "ticket-api 1.2.1: hotfix for GET /v1/history with rows from 1.1.0"
git push origin v1.2.1

L’étiquette lance le flux de travail Release (exécution 37669994015, 10 secondes). Il donne à l’image sha-fc9005a l’étiquette 1.2.1 et crée la version GitHub à partir de docs/releases/1.2.1.md. Rien n’est reconstruit. Si la CI n’avait pas terminé, le flux de travail Release s’arrêterait avec « CI must build and test this commit first. » (la CI doit d’abord construire et tester ce commit).

La page de version GitHub v1.2.1, marquée Latest (la plus récente), créée par github-actions depuis l'étiquette v1.2.1 au commit fc9005a. Les notes : ticket-api 1.2.1 (correctif urgent), avec les lignes Fixed (corrigé), Why the checks missed it (pourquoi les vérifications l'ont manqué), Made from (créé depuis), Owner (responsable), Watch after release (surveillance après la mise en production) et Rollback (retour en arrière). La dernière ligne : Image ghcr.io/usmanahmed99/ticket-api-ci@sha256:84937455beb63744755622d539748c9adb1772f379c9eb7e4f961c5b7f1bc924, étiquette 1.2.1.
La version v1.2.1 dans le dépôt de référence : les notes de docs/releases/1.2.1.md et l'empreinte que reçoit le flux de travail Deploy. L’image est en anglais.

La page de version indique le commit (fc9005a) et l’empreinte. Ces deux valeurs correspondent à ce que vous déployez et à ce que vous pouvez retracer.

Le flux de travail Deploy démarre automatiquement seulement pour les exécutions CI sur main. Une branche de correctif urgent n’est pas main. Démarrez donc le flux de travail manuellement, avec l’empreinte de la version. Sélectionnez Actions → Deploy → Run workflow (actions → déployer → exécuter le flux de travail), branche main, empreinte sha256:84937455beb63744755622d539748c9adb1772f379c9eb7e4f961c5b7f1bc924. Indiquez la raison « Hotfix 1.2.1: GET /v1/history answered 500 with rows from 1.1.0 » (correctif urgent 1.2.1 : GET /v1/history répondait 500 avec des lignes de 1.1.0). Ou :

Terminal (live)
gh workflow run deploy.yml -f digest=sha256:84937455beb63744755622d539748c9adb1772f379c9eb7e4f961c5b7f1bc924 \
-f reason="Hotfix 1.2.1: GET /v1/history answered 500 with rows from 1.1.0"

Le correctif urgent passe par la préproduction et l’approbation, comme chaque mise en production. Le commentaire de révision était « Hotfix 1.2.1 passed staging. Approved for production. » (le correctif urgent 1.2.1 a réussi en préproduction; approuvé pour la production). Les tâches de l’exécution 37670050850 (réelle) :

Find the image 18:51:44 → 18:51:47 success
staging / Deploy to staging 18:51:52 → 18:52:43 success
production / Deploy to production 18:52:59 → 18:53:50 success

Environ 2 minutes du démarrage à la production. Consultez les détails de l’exécution. Elle a utilisé les fichiers de flux de travail de main au commit 6ae49dc, pas ceux de la branche de correctif urgent. Les étapes de déploiement viennent toujours de main, même pour un correctif urgent. C’est voulu : le processus de déploiement a une seule version. Communiquer une mise en production en montre une conséquence.

Parcours simulé. Déployez la même empreinte localement. Exécutez la commande depuis une copie de travail de main (l’erreur fréquente ci-dessous explique pourquoi) :

Terminal (simulated)
scripts/promote-local.sh production ghcr.io/usmanahmed99/ticket-api-ci@sha256:84937455beb63744755622d539748c9adb1772f379c9eb7e4f961c5b7f1bc924
curl -s -w '\nstatus %{http_code}\n' 'http://127.0.0.1:8082/v1/history?limit=3' -H "X-API-Key: $KEY"

La sortie (simulée, 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
{"items":[{"request_id":"119fbf2734a9",…},{"request_id":"ef19e98fb391","category":"other","priority":3,"confidence":0.2,"score":0.2,"model_version":"keywords-1.0",…},…]}
status 200

L’historique fonctionne, et l’ancienne ligne ef19e98fb391 affiche un score de 0,2, tiré de son confidence. La vérification de bon fonctionnement de history réussit. Dans la base de données, la valeur de la ligne est toujours vide. Le correctif change la façon dont l’API lit les données, pas les données elles-mêmes. Stratégie de retour en arrière répare les données avec 003.

La production contient le correctif. main ne le contient pas. Si la prochaine version vient de main, le bogue revient. Ouvrez donc immédiatement une demande de tirage hotfix/1.2.1 vers main, avec la raison dans la description. Dans le dépôt de référence, c’était la PR no 7, ouverte à 18 h 55.

La PR no 7 ne pouvait pas être fusionnée au début. Son état était BEHIND (en retard) : main contenait la PR no 6. L’ensemble de règles exige qu’une branche soit à jour avec main avant la fusion (mode strict). Sur la page de la demande de tirage, GitHub indique que la branche est en retard sur la branche de base. Il propose Update branch aux personnes qui peuvent fusionner. La personne responsable de la révision sélectionne ce bouton. Cela fusionne main dans la branche de correctif urgent (commit 543b003, « Merge branch ‘main’ into hotfix/1.2.1 », fusionner la branche main dans hotfix/1.2.1). Toutes les vérifications s’exécutent de nouveau sur le résultat. Le mode strict existe pour cette raison. Les vérifications doivent réussir sur le code que main contiendra après la fusion, pas seulement sur la branche. Avec l’interface en ligne de commande, c’est pareil : gh pr update-branch 7.

Demande de tirage 7, Merge hotfix 1.2.1 back into main (fusionner le correctif urgent 1.2.1 dans main), Merged (fusionnée) : usmanahmed99 a fusionné 2 commits dans main depuis hotfix/1.2.1. La description : le correctif urgent 1.2.1 a été créé depuis v1.2.0 et est déjà publié et déployé (empreinte sha256:84937455…). Cette fusion apporte le même correctif, son test de base de données et la vérification de bon fonctionnement de l'historique dans main. La prochaine version issue de main les conservera donc. Les commits : Hotfix 1.2.1: history works with rows from version 1.1.0 (correctif urgent 1.2.1 : l'historique fonctionne avec les lignes de la version 1.1.0, fc9005a), et Merge branch 'main' into hotfix/1.2.1 (543b003). Puis : commit 3464f2c fusionné dans main, 4 vérifications réussies.
La PR no 7 après la fusion. Le deuxième commit, 543b003, correspond à Update branch : la demande de tirage était en retard sur main. main a donc été fusionnée dans la branche de correctif urgent, et les vérifications ont été relancées. L’image est en anglais.

Les vérifications ont de nouveau réussi, et la PR no 7 a été fusionnée à 18 h 58 sous 3464f2c. Cette poussée vers main a démarré automatiquement la CI, puis le flux de travail Deploy. Le commentaire d’approbation était « main now has hotfix 1.2.1. Staging passed, including the new history check. » (main contient maintenant le correctif urgent 1.2.1; la préproduction a réussi, y compris la nouvelle vérification de l’historique).

Ne vous fiez pas à « merged » (fusionné). Vérifiez trois choses dans votre propre clone, après git switch main et git pull :

Terminal
git branch --contains v1.2.1
git log main --oneline | grep Hotfix
grep -n "def test_history_reads_rows_from_version_1_1" tests/test_database.py

La sortie (du dépôt d’exercice local, abrégée) :

hotfix/1.2.1
* main
b3ceff1 Hotfix 1.2.1: history works with rows from version 1.1.0
51:def test_history_reads_rows_from_version_1_1(database):

main contient le commit de l’étiquette. Le commit du correctif urgent est dans l’historique de main. Le test est dans les fichiers de main, donc chaque changement ultérieur l’exécute. Le bogue ne peut pas revenir sans une vérification au rouge. Utilisez git branch -r --contains v1.2.1 pour voir aussi les branches sur GitHub.

Erreur courante

Les outils de la branche de correctif urgent sont anciens

Symptôme (simulé, exécution de référence) : après le correctif urgent, Amira a déployé 1.2.1 localement depuis sa copie de travail de hotfix/1.2.1. Elle utilisait son Mac (Apple silicon). Elle a ensuite demandé l’historique :

$ scripts/promote-local.sh production ghcr.io/usmanahmed99/ticket-api-ci@sha256:84937455… # 1.2.1
$ curl http://127.0.0.1:8082/v1/history
{"error":{"code":"internal_error","message":"Something went wrong on the server.","request_id":"bd725fc294ad","fields":[]}}
status 500

Toujours 500, après le correctif. La promotion avait échoué, et 1.2.0 tournait encore.

Cause : la branche de correctif urgent avait été créée depuis v1.2.0. Son deploy/local/compose.yaml était celui de v1.2.0, sans la ligne platform: linux/amd64. Cette ligne est arrivée plus tard dans la PR no 6 sur main. L’image existe seulement pour linux/amd64. Sur Apple silicon, Docker ne pouvait donc pas la récupérer. Il indiquait « no matching manifest for linux/arm64/v8 » (aucun manifeste correspondant pour linux/arm64/v8), comme dans Promouvoir en préproduction : l’image qui ne démarrait pas sur un Mac. Une branche de correctif urgent contient le code de la version, mais aussi ses anciens outils. Tous les correctifs apportés plus tard aux scripts, aux fichiers Compose ou à la documentation sur main sont absents. (Dans votre propre dépôt, v1.2.0 contient déjà les lignes platform. Vous les avez ajoutées dans Promouvoir en préproduction : l’image qui ne démarrait pas sur un Mac. Cette erreur ne vous arrive donc pas. Le piège reste le même pour chaque outil ajouté à main après l’étiquette.)

Correctif :

  1. Exécutez les outils de déploiement depuis main : git switch main, puis la même commande promote-local.sh avec la même empreinte. Seule l’image vient du correctif urgent. Dans l’exécution de référence, l’historique a ensuite répondu 200.
  2. Le flux de travail Deploy le fait déjà : il utilise toujours les fichiers de flux de travail de main.
  3. Fusionnez rapidement le correctif urgent dans main, pour que les deux branches ne s’éloignent pas davantage.

Si l’image est déjà sur votre ordinateur, Docker ne la récupère pas. L’ancien fichier Compose peut alors fonctionner par hasard. Ne comptez pas là-dessus : exécutez les outils depuis main chaque fois.

Pour un vrai service d’IA : c’est aussi vrai pour la configuration de service des modèles, les données d’évaluation et les scripts de déploiement. Un correctif créé depuis une ancienne version ne doit pas ramener d’anciens outils. Gardez le processus de déploiement sur main et changez seulement l’artefact.

Exercice guidé : un correctif urgent dans un dépôt d’exercice

Section intitulée « Exercice guidé : un correctif urgent dans un dépôt d’exercice »

Faites l’exercice dans une copie jetable. Ainsi, une erreur ne coûte rien. Vous avez besoin de Git et de l’instantané end-of-m04.

  1. Copiez l’instantané end-of-m04 dans un nouveau dossier, par exemple hotfix-practice. Dans ce dossier, exécutez git init -b main, git add -A, git commit -m "Version 1.2.0" et git tag v1.2.0.
  2. Faites avancer main : ajoutez une ligne à README.md, puis créez un commit sur main.
  3. Créez la branche du correctif urgent à partir de l’étiquette : git switch -c hotfix/1.2.1 v1.2.0. Vérifiez que README.md ne contient pas votre nouvelle ligne.
  4. Ajoutez le test test_history_reads_rows_from_version_1_1. Démarrez la base de données de test, puis exécutez python -m pytest -q tests/test_database.py. Le test doit échouer avec assert None == 0.7.
  5. Appliquez le correctif dans ticket_api/history.py. Relancez les tests : 3 passed (3 tests réussis).
  6. Changez la version pour 1.2.1 aux trois endroits, ajoutez la vérification sommaire de l’historique et rédigez docs/releases/1.2.1.md.
  7. Exécutez toutes les vérifications. Créez un commit avec le message « Hotfix 1.2.1: history works with rows from version 1.1.0 ». Ajoutez l’étiquette v1.2.1.
  8. Vérifiez le piège : git branch --contains v1.2.1 ne doit pas encore afficher main.
  9. Mettez la branche à jour : exécutez git merge main depuis hotfix/1.2.1. Puis exécutez git switch main et git merge --no-ff hotfix/1.2.1.
  10. Exécutez les trois vérifications de l’étape 9 de l’exemple détaillé.
Afficher le résultat attendu

Après l’étape 7, git log --oneline --graph --all --decorate montre deux branches issues de v1.2.0 :

* b3ceff1 (HEAD -> hotfix/1.2.1, tag: v1.2.1) Hotfix 1.2.1: history works with rows from version 1.1.0
| * 8a1d23c (main) Describe the local environments in the README
|/
* c63905b (tag: v1.2.0) Version 1.2.0

L’étape 8 affiche seulement * hotfix/1.2.1. git rev-list --count hotfix/1.2.1..main affiche 1 : la branche a un commit de retard sur main. C’est l’état BEHIND (en retard) de la demande de tirage nº 7. Après l’étape 9 :

* b0c1a03 (HEAD -> main) Merge hotfix 1.2.1 back into main
|\
| * aaf466e (hotfix/1.2.1) Merge branch 'main' into hotfix/1.2.1
| |\
| |/
|/|
* | 8a1d23c Describe the local environments in the README
| * b3ceff1 (tag: v1.2.1) Hotfix 1.2.1: history works with rows from version 1.1.0
|/
* c63905b (tag: v1.2.0) Version 1.2.0

La forme est la même que dans le diagramme. L’étape 10 affiche main, trouve le commit du correctif urgent et trouve le test. Vos identifiants de commit sont différents. La vérification complète donne 31 passed (31 tests réussis) et « Gate passed. » (barrière franchie).

Supprimez la base de données de test quand vous avez terminé : docker rm -f -v ticket-api-testdb.

À vous de jouer : le correctif a d’abord été fait sur main

Section intitulée « À vous de jouer : le correctif a d’abord été fait sur main »

Parfois, l’ordre est inversé. Un développeur corrige d’abord le bogue dans une demande de tirage (pull request) vers main. Ensuite, l’équipe décide que la production en a besoin immédiatement. main contient aussi d’autres changements qui ne sont pas prêts. Dans votre dépôt d’exercice, créez le correctif dans un commit sur main. Comment créer la version 1.2.1 à partir de ce commit, sans les autres changements de main? Quelles vérifications prouvent que la version 1.2.1 contient le correctif, et rien d’autre?

Afficher la réponse

Créez la branche du correctif urgent à partir de l’étiquette, comme avant. Copiez seulement le commit du correctif avec cherry-pick (Appliquer un correctif avec cherry-pick dans le cours sur Git) :

Terminal
git switch -c hotfix/1.2.1 v1.2.0
git cherry-pick <commit of the fix on main>

Suivez ensuite les mêmes étapes : version, notes, toutes les vérifications, étiquette v1.2.1, puis déploiement de l’empreinte. Les preuves : le nouveau test échoue sur v1.2.0 et réussit sur v1.2.1; git diff v1.2.0 v1.2.1 --stat affiche seulement les fichiers du correctif et de la mise en production; git log v1.2.0..v1.2.1 --oneline montre un seul commit. Dans cet ordre, main contient déjà le correctif. Fusionnez quand même la branche du correctif urgent dans la branche principale. Vous pouvez aussi vérifier avec git log main --oneline | grep que le correctif s’y trouve. Ainsi, le changement de version et les notes de mise en production se trouvent aussi sur main.

Vérifiez votre compréhension

Le correctif urgent presse. Sam propose de sauter la barrière d'évaluation de l'IA et l'audit cette fois. Le changement touche une seule ligne SQL. Que répondez-vous?

Afficher la réponse

On ne saute aucune vérification pour un correctif urgent. Les gens vérifient moins les changements urgents, et la production est déjà en difficulté. Un deuxième bogue serait maintenant pire que le premier. Les vérifications sont rapides. Dans l’exécution de référence, elles ont toutes pris 52 secondes, et la tâche de construction de l’image a pris 47 secondes. Elles ont prouvé quelque chose d’utile : le nouveau test échouait sans le correctif et réussissait avec lui. La barrière a aussi montré que le modèle n’avait pas changé. Un changement SQL d’une seule ligne peut quand même briser d’autres requêtes ou le contrat. La procédure reste la même; seule la branche change.

Vérifiez votre compréhension

Prédisez : Amira déploie la version 1.2.1 depuis la branche du correctif urgent et oublie la demande de tirage vers main. Deux semaines plus tard, la version 1.3.0 est mise en production depuis main. Que se passe-t-il?

Afficher la réponse

La version 1.3.0 est construite depuis main, qui n’a jamais reçu le correctif. La requête d’historique sur main lit encore score sans COALESCE. Le bogue revient donc en production : GET /v1/history répond 500 pour toute ligne sans score. Le nouveau test de base de données et la vérification sommaire de l’historique manquent aussi. Aucune vérification ne détecte donc le bogue. C’est une régression. git branch --contains v1.2.1 le montrerait : main ne figure pas dans la liste. Voilà pourquoi la procédure de correctif urgent se termine par une fusion dans la branche principale. C’est aussi pourquoi la vérification après cette fusion fait partie de la procédure.

Vérifiez votre compréhension

La demande de tirage nº 7 avait des vérifications réussies, mais son état était BEHIND. Pourquoi l'ensemble de règles bloque-t-il la fusion, et que fait Update branch?

Afficher la réponse

L’ensemble de règles est strict : une branche doit être à jour avec main. Les vérifications de la demande de tirage nº 7 ont porté uniquement sur la branche du correctif urgent. Celle-ci ne contenait pas la demande de tirage nº 6. Après une fusion, main contiendrait les deux, et personne n’avait vérifié cette combinaison. Update branch (mettre la branche à jour) fusionne main dans la branche du correctif urgent (commit 543b003). Chaque vérification obligatoire s’exécute alors de nouveau sur le code combiné. La demande de tirage peut être fusionnée seulement après ces vérifications. Sans le mode strict, deux changements qui réussissent séparément peuvent briser main ensemble.

Récapitulatif

  • Un correctif urgent est une petite mise en production qui corrige un seul problème dans la version en production. Partez de l’étiquette de mise en production : git switch -c hotfix/1.2.1 v1.2.0.
  • Écrivez un test qui échoue à cause du bogue, puis faites le plus petit correctif possible. Ajoutez la vérification qui aurait détecté le bogue (ici, la vérification sommaire de l’historique).
  • Exécutez toutes les vérifications. La CI s’exécute sur hotfix/** et construit l’image. L’étiquette v1.2.1 met cette image en production sans nouvelle construction.
  • Déployez l’empreinte manuellement avec le flux de travail Deploy (déployer) : préproduction, approbation, production. Les étapes de déploiement proviennent de main.
  • Fusionnez immédiatement le correctif urgent dans main. Si la demande de tirage est BEHIND, utilisez Update branch et laissez les vérifications s’exécuter de nouveau.
  • Vérifiez : git branch --contains v1.2.1 affiche main, et le test se trouve sur main. Exécutez les outils locaux depuis main : la branche du correctif urgent contient d’anciens outils.

Leçon suivante : Communiquer une mise en production