Promouvoir en préproduction
- Leçon 9
- 50 min
- Module 3 : Gérer les environnements et les secrets
Leçon 9 sur 15 · CI/CD, tests et mises en production sûresPromouvoir en préproduction
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
Transférez l’image testée en préproduction sans que personne tape une commande de déploiement. Vérifiez-la là, puis autorisez sa production seulement après une approbation.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Les révisions, et pourquoi l’ancienne garde le trafic jusqu’à ce que la nouvelle soit prête, dans Configuration opérationnelle : trois sondes, deux points de terminaison.
- Les vérifications sommaires, dans Vérifier et supprimer : vérifications sommaires et Une vérification rapide du cours Créer et déployer votre première API.
- Les étiquettes Git, dans Branches de version et étiquettes : étiqueter la version.
- Les environnements, les identités et les secrets, dans Séparer la configuration : une image, plusieurs environnements et Identifiants et accès : deux questions pour le pipeline.
Ce qu’il vous faut sur votre ordinateur
- Votre projet sur la branche
feature/deploy, avecdeploy/local/et les clés, dans Séparer la configuration : exercice guidé. - Les environnements GitHub
staging(préproduction) etproduction, dans Identifiants et accès : les environnements GitHub. Parcours réel : aussi les identités, les identifiants fédérés et les variables de la même leçon. scripts/smoke.sh, dans Construire et tester : exercice guidé : ajouter les vérifications et la tâche d’image, et la preuve de constructionbuild-info.json, dans Publier les preuves : exercice guidé : conserver les preuves.- Docker Desktop, dans Installer et vérifier : installer Docker du cours L’essentiel de Docker et du nuage pour les développeurs en IA. Sous Windows, exécutez les scripts
.shdans Git Bash ou WSL. - Si votre projet manque ou ne fonctionne plus, copiez l’instantané
end-of-m02, comme dans Utiliser un instantané. Refaites ensuite l’exercice guidé des deux dernières leçons. Les environnements GitHub sont des paramètres : recréez-les s’ils ont disparu.
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 (Engine 29.6.1, Compose 5.2.0) sur macOS (Apple silicon), et Azure CLI 2.88.0 dans canadacentral. Les étapes pour Windows et Linux n’ont pas été testées.
Le parcours d’une empreinte
Section intitulée « Le parcours d’une empreinte »La règle d’Amira pour cette leçon : après CI, personne ne construit d’image ni ne tape de commande de déploiement. Le pipeline prend l’empreinte dans les preuves de CI et la transfère, un environnement à la fois. Une promotion est ce transfert du même artefact vers l’environnement suivant.
Description textuelle du diagramme
Le flux de travail démarre seulement après la réussite de CI sur main. Sa première tâche lit l’empreinte dans le fichier build-info.json de CI. La préproduction la déploie, attend que la nouvelle révision soit prête, puis lance la vérification sommaire. Un échec à n’importe quelle étape termine l’exécution : la production ne démarre jamais. Si la préproduction réussit, la tâche de production attend une personne responsable de la révision. Une approbation envoie la même empreinte dans les mêmes étapes. Un refus laisse la production telle quelle.
Trois types de vérifications ont lieu en préproduction :
| Vérification | Ce qu’elle prouve ici | Qui la fait |
|---|---|---|
| Vérification de santé | La nouvelle révision démarre et répond : /health et /ready donnent 200 |
La plateforme (les sondes) et la vérification sommaire |
| Vérification d’intégration | Cette image fonctionne avec les paramètres et le secret de cet environnement : la classification donne 200 avec la clé, 401 sans la clé |
La vérification sommaire |
| Vérification d’acceptation par les utilisateurs | Le changement répond aux besoins des utilisateurs, selon le jugement d’une personne | Une personne chargée des tests ou responsable du soutien, en préproduction, avant l’approbation |
La vérification sommaire, dans Définir les vérifications : huit vérifications, huit objectifs, envoie quelques requêtes rapides sur les chemins essentiels d’un déploiement réel. Elle ne remplace pas les tests dans CI; elle prouve que ce déploiement fonctionne. Pour la vérification d’acceptation par les utilisateurs, la préproduction a SHOW_DOCS=true. Une personne chargée des tests ouvre /docs et essaie de vrais billets de soutien dans le navigateur. Pendant l’exécution de test, la personne responsable de la révision a approuvé uniquement les résultats des vérifications sommaires. Une seule personne faisait tout.
Le flux de travail Deploy
Section intitulée « Le flux de travail Deploy »Le flux de travail comprend deux fichiers. deploy.yml décide quoi déployer et dans quel ordre. deploy-to.yml contient les étapes pour un environnement, afin que la préproduction et la production exécutent exactement les mêmes étapes.
# Continuous delivery: promote one image, by digest, to staging and then to# production. It starts when CI succeeds on main, or by hand with a digest# (for a hotfix or a rollback). Production waits for a reviewer's approval.name: Deploy
on: workflow_run: workflows: [CI] types: [completed] branches: [main] workflow_dispatch: inputs: digest: description: "The image digest to deploy, for example sha256:4f1c..." required: true reason: description: "Why you deploy it by hand (hotfix, rollback, ...)" required: true
permissions: contents: read
concurrency: group: deploy cancel-in-progress: false
jobs: resolve: name: Find the image if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-24.04 permissions: actions: read outputs: digest: ${{ steps.digest.outputs.digest }} steps: - name: Get build-info.json from the CI run if: github.event_name == 'workflow_run' uses: actions/download-artifact@v7 with: name: build-info run-id: ${{ github.event.workflow_run.id }} github-token: ${{ github.token }} - name: Choose the digest id: digest env: INPUT_DIGEST: ${{ inputs.digest }} run: | if [ -n "$INPUT_DIGEST" ]; then digest="$INPUT_DIGEST"; else digest=$(jq -r .digest build-info.json); fi case "$digest" in sha256:*) ;; *) echo "Not a digest: $digest"; exit 1;; esac echo "digest=$digest" >> "$GITHUB_OUTPUT" echo "Deploying ghcr.io/${{ github.repository }}@$digest" >> "$GITHUB_STEP_SUMMARY"
staging: needs: resolve uses: ./.github/workflows/deploy-to.yml with: environment: staging image: ghcr.io/${{ github.repository }}@${{ needs.resolve.outputs.digest }} secrets: inherit permissions: contents: read id-token: write
production: needs: [resolve, staging] uses: ./.github/workflows/deploy-to.yml with: environment: production image: ghcr.io/${{ github.repository }}@${{ needs.resolve.outputs.digest }} secrets: inherit permissions: contents: read id-token: writeLe fichier complet se trouve dans le dépôt de référence : deploy.yml à 92fff66. Les parties, dans l’ordre :
| Partie | Ce qu’elle fait, et pourquoi |
|---|---|
workflow_run de CI, completed, branches: [main] |
Démarre quand une exécution de CI sur main se termine. Les demandes de tirage et les autres branches ne déploient jamais. |
if: … conclusion == 'success' |
Une exécution de CI en rouge termine immédiatement l’exécution de Deploy : GitHub saute la tâche resolve et les suivantes. |
workflow_dispatch avec digest et reason |
Un déploiement manuel d’une empreinte que CI a construite auparavant. Le module 5 l’utilise pour le correctif urgent et le retour en arrière. |
concurrency: deploy, cancel-in-progress: false |
Un seul déploiement à la fois, et aucun déploiement en cours ne s’arrête à mi-chemin. Le deuxième attend. |
download-artifact avec run-id |
Prend build-info.json dans l’exécution de CI qui a démarré cette exécution. actions: read permet au jeton de lire les artefacts de cette exécution. |
case "$digest" in sha256:* |
Refuse tout ce qui n’est pas une empreinte, comme une étiquette. Une étiquette peut changer de cible; une empreinte, non. |
uses: ./.github/workflows/deploy-to.yml |
Appelle le flux de travail réutilisable pour un environnement. needs: [resolve, staging] fait attendre la production après la préproduction. |
secrets: inherit |
Transmet les secrets. La tâche appelée reçoit les secrets d’un environnement seulement après avoir satisfait à ses règles. |
Autre fait sur workflow_run : GitHub exécute le fichier du flux de travail Deploy depuis la branche par défaut, main. C’est vrai même quand l’exécution de CI concernait une autre branche. Les étapes de déploiement sont donc toujours celles révisées sur main. Le module 5 en a besoin pour le correctif urgent.
# The same deployment steps for every environment. The environment's settings# (app name, resource group, URL, identity) are variables of the GitHub# environment; its API key for the smoke check is an environment secret.name: Deploy to one environment
on: workflow_call: inputs: environment: required: true type: string image: required: true type: string
jobs: deploy: name: Deploy to ${{ inputs.environment }} runs-on: ubuntu-24.04 timeout-minutes: 20 environment: name: ${{ inputs.environment }} url: ${{ vars.APP_URL }} permissions: contents: read id-token: write steps: - uses: actions/checkout@v7 - name: Sign in to Azure with this environment's identity (no password) uses: azure/login@v3 with: client-id: ${{ vars.AZURE_CLIENT_ID }} tenant-id: ${{ vars.AZURE_TENANT_ID }} subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} - name: Deploy the image env: IMAGE: ${{ inputs.image }} run: | az config set extension.use_dynamic_install=yes_without_prompt --only-show-errors az containerapp update --name "${{ vars.APP_NAME }}" --resource-group "${{ vars.RESOURCE_GROUP }}" \ --image "$IMAGE" --query "properties.latestRevisionName" -o tsv - name: Wait for the new revision run: | for _ in $(seq 1 30); do state=$(az containerapp show -n "${{ vars.APP_NAME }}" -g "${{ vars.RESOURCE_GROUP }}" \ --query "properties.latestReadyRevisionName == properties.latestRevisionName" -o tsv) [ "$state" = "true" ] && echo "The new revision is ready." && exit 0 sleep 10 done echo "The new revision did not become ready in 5 minutes."; exit 1 - name: Smoke check env: SMOKE_API_KEY: ${{ secrets.SMOKE_API_KEY }} run: scripts/smoke.sh "${{ vars.APP_URL }}" "$SMOKE_API_KEY" - name: Record the deployment run: | { echo "### ${{ inputs.environment }}" echo "Image: \`${{ inputs.image }}\`" echo "Revision: $(az containerapp show -n "${{ vars.APP_NAME }}" -g "${{ vars.RESOURCE_GROUP }}" --query properties.latestRevisionName -o tsv)" echo "Started by: ${{ github.actor }}, run ${{ github.run_id }}" } >> "$GITHUB_STEP_SUMMARY"deploy-to.yml à 92fff66. environment: name est la ligne qui relie cette tâche à tous les éléments de la dernière leçon. Elle apporte les variables, le secret, la politique de branche, la personne responsable de la révision et le sujet OIDC. Le même fichier sert aux deux environnements; seuls vars et secrets changent. Les quatre étapes :
- Connectez-vous avec l’identité de l’environnement, comme dans Identifiants et accès.
- Déployez avec
az containerapp update --image. La commande crée une nouvelle révision sans rien changer d’autre dans l’application :SHOW_DOCSet le secret restent tels quels. - Attendez que la révision la plus récente soit aussi la révision prête la plus récente : 30 essais, espacés de 10 secondes, au plus 5 minutes. Jusque-là, l’ancienne révision garde le trafic, comme dans le cours sur Docker.
- Faites une vérification sommaire à l’adresse de l’environnement avec sa propre clé, puis écrivez un résumé.
Exemple détaillé (réel) : le premier déploiement
Section intitulée « Exemple détaillé (réel) : le premier déploiement »Amira a enregistré les deux fichiers de flux de travail avec les fichiers d’environnement local dans un commit (enregistrement Git) sur feature/deploy. Elle a ouvert la demande de tirage de feature/deploy vers main : demande de tirage nº 1. Les deux vérifications obligatoires ont réussi, puis elle a fait la fusion (commit de fusion 43de57b). CI s’est exécuté sur main, et sa réussite a démarré l’exécution de Deploy 37664760027.
La première tentative a échoué à la connexion, comme le montre Identifiants et accès. Après la correction, la tentative 2 s’est exécutée. La chronologie (réelle, tirée des tâches et du journal de l’exécution) :
| Heure (UTC) | Ce qui s’est passé |
|---|---|
| 18 h 10 min 23 s | « Find the image » (trouver l’image) a pris l’empreinte sha256:a8af875a… dans le fichier build-info.json de l’exécution de CI (conservé depuis la tentative 1). |
| 18 h 11 min 32 s | La tâche de préproduction a démarré. 18 h 11 min 57 s : Azure CLI login succeeds by using OIDC. (la connexion à Azure CLI réussit avec OIDC). |
| 18 h 12 min 18 s | az containerapp update a affiché la nouvelle révision ticket-api-staging--0000001. |
| 18 h 12 min 42 s | The new revision is ready. (la nouvelle révision est prête), puis les quatre vérifications sommaires ont réussi. La tâche a pris 1 minute 15 secondes. |
| 18 h 12 min 47 s | La tâche de production attendait la personne responsable de la révision. |
| 18 h 13 min 4 s | Approbation donnée. La tâche de production a démarré avec la même empreinte. |
| 18 h 13 min 29 s | Nouvelle révision ticket-api-production--0000001. 18 h 13 min 54 s : prête, et les quatre vérifications ont réussi. La tâche a pris 55 secondes. |
Le journal de préproduction (réel, abrégé) :
Attempting Azure CLI login by using OIDC...Azure CLI login succeeds by using OIDC.ticket-api-staging--0000001The new revision is ready.ok health (200)ok ready (200)ok classify with the key (200)ok classify without a key (401)
La page montre l’ordre (préproduction avant production), les adresses tirées de environment: url, et l’approbation : qui, pour quel environnement, et pourquoi. Tout le monde peut l’ouvrir. C’est une approbation traçable.
Approuver le déploiement en production
Section intitulée « Approuver le déploiement en production »Quand la tâche de production attend, la page de l’exécution montre un bouton Review deployments (réviser les déploiements) aux responsables de la révision. Les étapes :
- Ouvrez l’exécution (Actions → Deploy → l’exécution).
- Vérifiez la tâche de préproduction : les lignes de vérification sommaire et l’empreinte de l’image dans le résumé. Si une personne a testé la préproduction, lisez son résultat.
- Choisissez Review deployments.
- Sélectionnez
production, puis écrivez un commentaire qui indique ce que vous avez vérifié. - Choisissez Approve and deploy (approuver et déployer). Pour arrêter la mise en production, choisissez Reject (refuser).
L’exécution de test a obtenu l’approbation par l’API REST de GitHub, qui fait la même chose et enregistre le même commentaire. Pour approuver ainsi, vous avez besoin de l’identifiant de l’environnement et de celui de l’exécution. Faites-le seulement dans votre propre dépôt, car cela démarre un déploiement en production :
gh api repos/<you>/ticket-api-ci/environments --jq '.environments[] | {name, id}'gh api --method POST repos/<you>/ticket-api-ci/actions/runs/<run-id>/pending_deployments \ -F "environment_ids[]=<production id>" -f state=approved \ -f comment="Staging smoke checks passed for this digest."L’enregistrement de l’approbation, relu depuis l’exécution (réel) :
gh api repos/Nextia-Intelligence/ticket-api-ci/actions/runs/37664760027/approvals --jq '.[] | {state, comment, user: .user.login, environments: [.environments[].name]}'{"comment":"Staging smoke checks passed for this digest. Approved for production (C19 test run).","environments":["production"],"state":"approved","user":"usmanahmed99"}Les déploiements sur GitHub
Section intitulée « Les déploiements sur GitHub »Chaque tâche avec environment: crée aussi un enregistrement de déploiement. Le dépôt en comptait 21 à la fin de la journée, par exemple (réel) :
gh api repos/Nextia-Intelligence/ticket-api-ci/deployments --jq '.[] | [.environment, .sha[0:7], .created_at] | @tsv'production 92fff66 2026-10-07T18:18:57Zstaging 92fff66 2026-10-07T18:18:02Zproduction 43de57b 2026-10-07T18:12:47Zstaging 43de57b 2026-10-07T18:11:28Zstaging 43de57b 2026-10-07T18:10:28Z(Les plus récents sont omis.) La première ligne pour 43de57b en préproduction est la tentative 1, qui a échoué. La page des déploiements d’un dépôt exige une connexion. Ce cours montre donc la réponse de l’API plutôt qu’une capture d’écran.
Une version sans nouvelle construction
Section intitulée « Une version sans nouvelle construction »L’image en production a une empreinte, mais les gens parlent de versions. La personne responsable du soutien demande « sommes-nous sur la version 1.1.0? », pas « sommes-nous sur sha256:4eb3db8d? ». Une étiquette Git v1.1.0 nomme le commit. L’image a aussi besoin de la version. Ce doit être la même image, pas une nouvelle construction. Séparer la configuration : une deuxième construction pour la mise en production explique pourquoi.
La deuxième demande de tirage d’Amira, de feature/release-tags vers main (demande de tirage nº 2), a supprimé le déclencheur d’étiquette de ci.yml. Elle a ajouté les notes de version docs/releases/1.1.0.md et ce flux de travail :
# A release: when a version tag (v1.2.0) is pushed, give the image that CI# already built and tested for that commit its version tag, and create the# GitHub release. Nothing is built again, so the release is the same digest# that staging and production run.name: Release
on: push: tags: ["v*"]
permissions: contents: write packages: write
jobs: release: runs-on: ubuntu-24.04 timeout-minutes: 10 env: IMAGE: ghcr.io/${{ github.repository }} steps: - uses: actions/checkout@v7 - uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Tag the tested image with the version id: tag run: | version="${GITHUB_REF_NAME#v}" source="$IMAGE:sha-${GITHUB_SHA::7}" docker buildx imagetools inspect "$source" > /dev/null || { echo "No image $source: CI must build and test this commit first."; exit 1; } docker buildx imagetools create --tag "$IMAGE:$version" "$source" digest=$(docker buildx imagetools inspect "$IMAGE:$version" --format '{{json .Manifest}}' | jq -r .digest) echo "version=$version" >> "$GITHUB_OUTPUT" echo "digest=$digest" >> "$GITHUB_OUTPUT" - name: Create the GitHub release env: GH_TOKEN: ${{ github.token }} run: | notes="docs/releases/${{ steps.tag.outputs.version }}.md" [ -f "$notes" ] || { echo "Write the release notes in $notes first."; exit 1; } { cat "$notes" echo echo "Image: \`$IMAGE@${{ steps.tag.outputs.digest }}\` (tag \`${{ steps.tag.outputs.version }}\`)" } > notes.md gh release create "$GITHUB_REF_NAME" --title "$GITHUB_REF_NAME" --notes-file notes.mdrelease.yml à 92fff66. docker buildx imagetools create écrit une nouvelle étiquette dans le registre qui pointe vers l’image existante. La commande ne copie aucune couche et ne construit rien. Si CI n’a jamais construit le commit, la publication s’arrête avec No image …: CI must build and test this commit first. (aucune image : CI doit d’abord construire et tester ce commit). Si les notes de version manquent, elle s’arrête aussi.
La fusion de la demande de tirage nº 2 a créé le commit 92fff66. CI l’a construit (sha-92fff66), et l’exécution de Deploy 37665743749 en a fait la promotion. La préproduction a pris 51 secondes, suivie de l’approbation (« Version 1.1.0 : les vérifications sommaires de préproduction ont réussi. Approuvé pour la production. »), puis la production a pris 1 minute 8 secondes. Amira a ensuite étiqueté le commit. Attendez d’abord que l’exécution de CI sur main soit verte, pour que l’image existe :
git switch maingit pullgit tag -a v1.1.0 -m "ticket-api 1.1.0"git push origin v1.1.0L’étiquette a démarré l’exécution de Release 37665781129. Elle a pris 8 secondes. Son journal (réel, abrégé) :
Login Succeeded!#1 [internal] pushing ghcr.io/usmanahmed99/ticket-api-ci#1 0.000 copying sha256:73311f52b091f4e64e4990f351159f0af4e0735056173260377c21829869dd1b from ghcr.io/usmanahmed99/ticket-api-ci:sha-92fff66 to ghcr.io/usmanahmed99/ticket-api-ci#1 0.000 copying sha256:34c3ddd916f50832e73a8a30877e3ae872a2c7e2510b352f585f2bb049c86f49 from ghcr.io/usmanahmed99/ticket-api-ci:sha-92fff66 to ghcr.io/usmanahmed99/ticket-api-ci#1 0.359 pushing sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857a to ghcr.io/usmanahmed99/ticket-api-ci:1.1.0https://github.com/Nextia-Intelligence/ticket-api-ci/releases/tag/v1.1.0« copying » (copie) signifie ici des pointeurs : les deux manifestes (l’image linux/amd64 et son attestation) reçoivent un deuxième nom. Vérifiez-le vous-même; tout le monde peut lire le paquet public :
docker buildx imagetools inspect ghcr.io/usmanahmed99/ticket-api-ci:sha-92fff66docker buildx imagetools inspect ghcr.io/usmanahmed99/ticket-api-ci:1.1.0La sortie (les deux résultats abrégés aux premières lignes) :
Name: ghcr.io/usmanahmed99/ticket-api-ci:sha-92fff66MediaType: application/vnd.oci.image.index.v1+jsonDigest: sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857a
Name: ghcr.io/usmanahmed99/ticket-api-ci:1.1.0MediaType: application/vnd.oci.image.index.v1+jsonDigest: sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857aUne empreinte, trois noms : sha-92fff66, 1.1.0 et l’empreinte elle-même, que la préproduction et la production exécutaient déjà.

Le parcours local
Section intitulée « Le parcours local »Le parcours simulé fait la même promotion avec deux scripts. promote-local.sh démarre un environnement avec une image, puis exécute le même smoke.sh que le pipeline :
#!/usr/bin/env bash# Deploy one image to a local environment, then smoke-check it.# scripts/promote-local.sh staging ghcr.io/<you>/ticket-api-ci@sha256:...# scripts/promote-local.sh production ghcr.io/<you>/ticket-api-ci@sha256:...# Needs .env (POSTGRES_PASSWORD) and secrets/<environment>_api_key.txt.set -euo pipefail
environment=${1:?Give the environment: staging or production}export IMAGE=${2:?Give the image with its digest}settings="deploy/local/$environment.env"[ -f "$settings" ] || { echo "No settings file $settings"; exit 1; }
docker compose -f deploy/local/compose.yaml --env-file "$settings" --env-file .env up -d --waitport=$(grep '^PORT=' "$settings" | cut -d= -f2)scripts/smoke.sh "http://127.0.0.1:$port" "$(cat "secrets/${environment}_api_key.txt")"echo "$environment now runs $IMAGE"#!/usr/bin/env bash# Stop a local environment. Add --volumes to delete its database too.# scripts/stop-local.sh staging [--volumes]set -euo pipefailenvironment=${1:?Give the environment: staging or production}shift# compose.yaml needs IMAGE to read the file, but "down" does not use it.IMAGE=unused docker compose -f deploy/local/compose.yaml \ --env-file "deploy/local/$environment.env" --env-file .env down "$@"up -d --wait retourne seulement quand chaque conteneur est en santé. La vérification sommaire ne s’exécute donc jamais sur une API à moitié démarrée. set -euo pipefail arrête le script au premier échec. Rendez les deux scripts exécutables : chmod +x scripts/promote-local.sh scripts/stop-local.sh. Git enregistre cela, donc le changement fait partie de votre commit.
L’ordre est celui du pipeline. Vous exécutez la préproduction d’abord. Vous exécutez la production seulement après la réussite de la vérification sommaire de préproduction : cette deuxième commande est votre approbation.
L’image qui ne démarrait pas sur un Mac
Section intitulée « L’image qui ne démarrait pas sur un Mac »Amira a lancé la préproduction locale avec l’image de la version 1.1.0, sur son Mac avec puce Apple :
scripts/promote-local.sh staging ghcr.io/usmanahmed99/ticket-api-ci@sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857aErreur courante
no matching manifest for linux/arm64/v8 (aucun manifeste correspondant pour linux/arm64/v8)
Symptôme : le script s’est arrêté avant le démarrage du moindre conteneur (simulation) :
Image ghcr.io/usmanahmed99/ticket-api-ci@sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857a Pulling Image ghcr.io/usmanahmed99/ticket-api-ci@sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857a Error no matching manifest for linux/arm64/v8 in the manifest list entries: no match for platform in manifest: not foundError response from daemon: no matching manifest for linux/arm64/v8 in the manifest list entries: no match for platform in manifest: not foundCause : la CI construit seulement linux/amd64, le type de processeur du nuage. Docker demande l’image adaptée à son ordinateur, soit linux/arm64/v8 sur une puce Apple. L’image n’en contient aucune. La commande imagetools inspect ci-dessus le montre : un manifeste linux/amd64 et l’attestation (unknown/unknown). Cette erreur ne se produit pas sur un ordinateur Intel ou AMD.
Solution : ne construisez pas une deuxième image arm64 pour votre ordinateur : ce serait encore un autre artefact. Demandez l’image du nuage et laissez Docker Desktop l’émuler. Ajoutez une ligne, avec son commentaire, au service api dans deploy/local/compose.yaml :
api: image: ${IMAGE:?Set IMAGE to the image name with its digest} # The pipeline builds for the cloud's processors. On Apple silicon (arm64), # Docker Desktop emulates them, so you run the same build as the cloud. platform: linux/amd64Le dépôt de référence a ajouté cette ligne plus tard, dans la demande de tirage nº 6, après la version 1.2.0. Vous l’ajoutez maintenant. L’émulation est plus lente, mais l’API de test a démarré et réussi sa vérification de bon fonctionnement en environ 10 secondes.
Pour un vrai service d’IA : un serveur de modèles utilise souvent du code propre au processeur, comme des bibliothèques mathématiques plus rapides. Une construction arm64 pour votre portable peut produire des nombres ou des erreurs différents de la construction amd64 en production. Testez la construction utilisée en production, même si elle est plus lente sur votre ordinateur.
Exemple détaillé (simulation) : préproduction, puis production
Section intitulée « Exemple détaillé (simulation) : préproduction, puis production »Avec la ligne de plateforme, la même commande donne ceci (simulation) :
Image ghcr.io/usmanahmed99/ticket-api-ci@sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857a Pulled Network ticket-api-staging_default Created Volume ticket-api-staging_pgdata Created Container ticket-api-staging-db-1 Created Container ticket-api-staging-api-1 Created Container ticket-api-staging-db-1 Healthy Container ticket-api-staging-api-1 Healthyok health (200)ok ready (200)ok classify with the key (200)ok classify without a key (401)staging now runs ghcr.io/usmanahmed99/ticket-api-ci@sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857a(Les lignes Creating (création), Starting (démarrage) et Waiting (attente) sont omises.) Le téléchargement et le démarrage ont pris 10 secondes au total. Dans le conteneur, Python indique le processeur émulé : docker exec ticket-api-staging-api-1 python -c "import platform; print(platform.machine())" a affiché x86_64.
La préproduction a réussi. Amira approuve donc sa propre production locale : scripts/promote-local.sh production, avec la même image. La sortie contient les mêmes lignes avec ticket-api-production et se termine par production now runs ghcr.io/usmanahmed99/ticket-api-ci@sha256:4eb3db8d… (la production exécute maintenant cette image). L’image était déjà sur l’ordinateur. Aucun téléchargement n’était donc nécessaire.
Comparons maintenant les deux environnements, qui utilisent la même image :
docker inspect --format '{{.Name}} {{.Image}}' ticket-api-staging-api-1 ticket-api-production-api-1curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8081/docscurl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8082/docsLa sortie (simulation) :
/ticket-api-staging-api-1 sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857a/ticket-api-production-api-1 sha256:4eb3db8d4ea249fb9b44b27d96deb68aba6da3e3fbc9cf32311a9cbadf4d857a200404L’empreinte est la même que dans le nuage, et la différence reste la même : /docs est activé en préproduction, désactivé en production. Chaque environnement possède aussi ses propres données. La requête de classification de la vérification de bon fonctionnement a écrit une ligne dans chaque base de données. En préproduction, /v1/history affiche seulement les lignes de préproduction, avec des identifiants de requête absents de la production.
Exercice guidé : promouvoir votre propre empreinte
Section intitulée « Exercice guidé : promouvoir votre propre empreinte »Choisissez votre méthode. Vous avez besoin de l’empreinte de votre dernière exécution CI réussie sur main, tirée de build-info.json.
- Ajoutez
platform: linux/amd64au serviceapidansdeploy/local/compose.yaml. - Ajoutez
scripts/promote-local.shetscripts/stop-local.sh, puis rendez-les exécutables. - Simulation : exécutez
scripts/promote-local.sh staging ghcr.io/<you>/ticket-api-ci@<digest>. Résultat attendu : quatre lignesok(réussite) etstaging now runs …(la préproduction exécute maintenant…). - Simulation : exécutez la même commande avec
production. Résultat attendu : les mêmes lignes pour la production. - Simulation : comparez les deux conteneurs
apiavecdocker inspectet/docs, comme dans l’exemple détaillé. - En conditions réelles : ajoutez
.github/workflows/deploy.ymletdeploy-to.yml. Créez un commit (enregistrement des changements) avec tous les fichiers surfeature/deploy. - En conditions réelles : ouvrez la demande de tirage de
feature/deployversmain. Attendez les deux vérifications obligatoires, puis fusionnez. - En conditions réelles : ouvrez Actions → Deploy (actions → déployer). Attendez la réussite de la préproduction, puis approuvez la production avec un commentaire.
- Dans les deux cas : sur une nouvelle branche
feature/release-tags, retirez le déclencheur par étiquette deci.yml. Suivez Séparer la configuration : une deuxième construction pour la mise en production. Créez un commit avec ce changement,docs/releases/1.1.0.mdetrelease.yml. Fusionnez la branche dansmainpar demande de tirage. (En simulation, la demande de tirage de l’étape 7 ne contient aucun fichier de flux de travail. Fusionnez-la avec les fichiers locaux.) - Dans les deux cas : quand la CI sur
mainréussit, attribuez l’étiquettev1.1.0au commit de fusion et poussez l’étiquette. Vérifiez que l’exécution Release (mise en production) réussit. Vérifiez aussi queimagetools inspectdonne la même empreinte pour:1.1.0et:sha-<7>.
Afficher le résultat attendu
Simulation : les deux scripts se terminent par … now runs ghcr.io/<you>/ticket-api-ci@sha256:… (exécute maintenant cette image), avec votre empreinte. docker inspect montre le même identifiant d’image pour les deux conteneurs api. /docs renvoie 200 sur 8081 et 404 sur 8082.
En conditions réelles : l’exécution Deploy montre trois tâches réussies, comme dans la capture d’écran ci-dessus. Le journal de la tâche de préproduction contient The new revision is ready. (la nouvelle révision est prête) et les quatre lignes ok. La page de l’exécution affiche votre approbation et son commentaire. Le chemin /docs de vos applications renvoie 200 en préproduction et 404 en production.
Dans les deux cas : l’exécution Release réussit. Votre dépôt contient la mise en production v1.1.0 avec la ligne d’image. L’étiquette :1.1.0 a la même empreinte que :sha-<7> du commit étiqueté.
À vous de jouer : une préproduction en échec
Section intitulée « À vous de jouer : une préproduction en échec »Prédisez le résultat, puis essayez en simulation. Arrêtez la base de données de préproduction locale, mais laissez son API fonctionner :
docker stop ticket-api-staging-db-1scripts/smoke.sh http://127.0.0.1:8081 "$(cat secrets/staging_api_key.txt)"Quelles vérifications échouent? Que ferait le flux de travail Deploy avec la production si cela arrivait dans la tâche de préproduction? Redémarrez la base de données avec docker start ticket-api-staging-db-1.
Afficher la réponse
La sortie (simulation) :
ok health (200)FAIL ready: expected 200, got 503ok classify with the key (200)ok classify without a key (401)/health répond toujours 200 : le processus fonctionne. /ready vérifie la base de données. Il répond donc 503 pendant son arrêt, et smoke.sh se termine avec 1. La classification réussit encore, car le classificateur n’a pas besoin de la base de données. Une seule vérification échouée suffit. Dans le pipeline, une vérification de bon fonctionnement échouée fait échouer la tâche de préproduction. production contient needs: [resolve, staging]. Cette tâche ne démarre donc jamais, et personne ne reçoit même de demande d’approbation. La production conserve son ancienne révision. Voilà le rôle de la préproduction : arrêter un problème avant qu’il atteigne les utilisateurs. Après docker start, la même vérification de bon fonctionnement a réussi de nouveau pendant le test.
Vérifiez votre compréhension
Où le flux de travail Deploy prend-il l'empreinte à déployer, et pourquoi refuse-t-il une valeur qui ne commence pas par sha256:?
Afficher la réponse
Dans build-info.json, l’artefact de l’exécution CI sur main qui a déclenché l’exécution Deploy. Pour un déploiement manuel, il utilise plutôt l’entrée digest. La CI a construit et testé exactement cette image. Le flux de travail refuse toute valeur qui n’est pas une empreinte. Une étiquette comme 1.1.0 ou latest peut être déplacée vers un autre contenu. Une empreinte désigne toujours les mêmes octets. La préproduction et la production déploient donc ce que la CI a testé, et rien d’autre.
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
Prédisez : la vérification de bon fonctionnement en préproduction échoue dans une exécution Deploy. Qu'arrive-t-il à la tâche de production, et que fait fonctionner la production ensuite?
Afficher la réponse
La tâche de préproduction échoue. La tâche de production dépend de celle de préproduction. GitHub l’ignore donc. Elle n’attend jamais d’approbation et ne se connecte jamais. La production continue d’exécuter sa révision actuelle, avec l’ancienne empreinte. La nouvelle empreinte reste en préproduction jusqu’à ce qu’un correctif passe de nouveau par la CI.
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 release.yml utilise-t-il docker buildx imagetools create plutôt que docker build pour l'étiquette de version? Comment vérifier le résultat?
Afficher la réponse
imagetools create donne une deuxième étiquette, 1.1.0, à l’image que la CI a déjà construite et testée (sha-92fff66). Cette commande ne construit rien. La mise en production utilise donc le même artefact que la préproduction a vérifié et que la production exécute. Une construction créerait un nouvel artefact que personne n’a testé. Vérifiez les deux étiquettes avec docker buildx imagetools inspect. Les lignes Digest: (empreinte) doivent être identiques (sha256:4eb3db8d… pour 1.1.0).
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.
Exercice du module
Section intitulée « Exercice du module »Tâche : déployez le même artefact dans deux environnements d’exercice, locaux ou hébergés. Prouvez que seuls les paramètres diffèrent.
- Prenez l’empreinte d’une exécution CI réussie sur
main. - Déployez-la en préproduction et vérifiez-la avec le flux de travail Deploy réel ou
scripts/promote-local.sh staging. - Approuvez-la pour la production (en conditions réelles : Review deployments (examiner les déploiements), avec un commentaire). Sinon, exécutez
scripts/promote-local.sh productionen simulation. - Rassemblez les preuves dans un court tableau. Indiquez l’empreinte, le résultat de la vérification de bon fonctionnement et le statut de
/docspour chaque environnement. Ajoutez qui a approuvé et pourquoi, ainsi que l’emplacement de chaque paramètre et secret. - Faites ensuite l’exercice sans le nuage ou sans le pipeline, selon la méthode que vous n’avez pas utilisée. Lisez les preuves de l’autre méthode ci-dessus. Écrivez une phrase sur ce qu’elle prouve que la vôtre ne prouve pas.
Afficher une réponse modèle
Les preuves de référence pour la mise en production 1.1.0 :
| Preuve | Préproduction | Production |
|---|---|---|
| Image (conditions réelles, exécution 37665743749) | ghcr.io/usmanahmed99/ticket-api-ci@sha256:4eb3db8d… |
la même |
Image (simulation, docker inspect) |
sha256:4eb3db8d… |
la même |
| Vérification de bon fonctionnement | 4 × ok |
4 × ok |
/docs |
200 |
404 |
| Paramètre différent | SHOW_DOCS=true (paramètre de l’application; deploy/local/staging.env) |
SHOW_DOCS=false |
| Clé | Propre à la préproduction : secret d’application api-key, secret d’environnement GitHub SMOKE_API_KEY; secrets/staging_api_key.txt |
Propre à la production, aux mêmes endroits |
| Identité (conditions réelles) | id-deploy-staging, Container Apps Contributor (contributeur Container Apps) sur ticket-api-staging |
id-deploy-production, sur ticket-api-production |
| Approbation | aucune requise | usmanahmed99 : « Mise en production 1.1.0 : les vérifications de bon fonctionnement en préproduction ont réussi. Approuvée pour la production. » |
| Mise en production | Étiquette v1.1.0 = sha-92fff66 = sha256:4eb3db8d… |
Seule la méthode en conditions réelles prouve ces éléments : connexion sans mot de passe stocké, rôles limités, approbation enregistrée et HTTPS sur une vraie adresse. La simulation prouve sans frais ces éléments : un projet Compose par environnement, sa propre base de données et l’image exacte du nuage, par émulation.
Vérification du module
Section intitulée « Vérification du module »Vérifiez votre compréhension
Rappel : nommez les cinq éléments qui peuvent différer entre préproduction et production dans ce module, et celui qui doit rester identique.
Afficher la réponse
Peuvent différer : les paramètres, comme SHOW_DOCS, le secret, soit la clé d’API de chaque environnement, et l’identité de déploiement avec son rôle. Le stockage des données et les règles peuvent aussi différer : la production exige une personne responsable de la révision. Doit rester identique : l’artefact, soit l’empreinte de l’image, et donc le code et les paquets qu’elle contient.
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
Expliquez ou prédisez : un collègue désactive la révision obligatoire en production pour gagner du temps. Une fusion dans main contient un bogue que la vérification de bon fonctionnement ne détecte pas. Qu'est-ce qui change dans le chemin vers la production, et quelles protections fonctionnent encore?
Afficher la réponse
Sans révision obligatoire, la tâche de production démarre dès que la préproduction réussit. L’empreinte atteint la production environ une minute après la préproduction, sans que personne examine d’abord celle-ci. Les autres protections fonctionnent encore : l’ensemble de règles et les vérifications obligatoires avant la fusion, ainsi que l’empreinte construite une seule fois. La vérification de bon fonctionnement en préproduction fonctionne aussi : un échec bloque toujours la production. La politique de branche et l’identité aux droits limités restent également actives. Mais le bogue non détecté par la vérification de bon fonctionnement atteint directement les utilisateurs. L’approbation permet une vérification d’acceptation par les utilisateurs en préproduction. Conservez-la.
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
Vérification finale : montrez que la préproduction et la production se comportent différemment uniquement à cause de leurs paramètres, sans modifier le code de l'application.
Afficher la réponse
Montrez la même empreinte dans les deux environnements. Utilisez les lignes IMAGE: (image) du journal Deploy en conditions réelles. En simulation, utilisez docker inspect --format '{{.Name}} {{.Image}}' sur les deux conteneurs api. Montrez ensuite une différence causée par un paramètre : /docs renvoie 200 en préproduction et 404 en production. Puis modifiez seulement le paramètre. Écrivez SHOW_DOCS=false dans deploy/local/staging.env et exécutez scripts/promote-local.sh staging avec la même image. L’exécution de test a affiché Container ticket-api-staging-api-1 Recreate (recréation du conteneur), puis les quatre lignes ok. Ensuite, /docs a renvoyé 404 en préproduction. Avec SHOW_DOCS=true de nouveau, il a renvoyé 200. L’historique de préproduction contenait toujours ses 4 lignes : la base de données n’avait pas été modifiée. Aucun code, aucune image et aucune construction n’ont changé. (En conditions réelles, az containerapp update -n ticket-api-staging -g rg-ticket-api-ci --set-env-vars SHOW_DOCS=false modifie le paramètre de la même façon. Cette commande n’a pas été exécutée pendant le test.)
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
- Le flux de travail Deploy démarre après la réussite de la CI sur
mainet prend l’empreinte dansbuild-info.json. Il la promeut : préproduction, vérification de bon fonctionnement, approbation, puis production avec les mêmes étapes. deploy-to.ymlcontient les étapes d’un environnement : connexion (OIDC),az containerapp update --image, attente de la révision prête, vérification de bon fonctionnement, résumé.environment:fournit les variables, le secret et les règles.- Un échec à n’importe quelle étape arrête l’empreinte là où elle est. La production conserve son ancienne révision.
- L’approbation est publique et traçable : qui, quel environnement et le commentaire.
release.ymlattribue l’étiquette de version à l’image testée, sans construction :1.1.0=sha-92fff66=sha256:4eb3db8d….- Localement :
promote-local.sh staging, puisproduction. Sur une puce Apple,platform: linux/amd64exécute l’image du nuage par émulation.
Réinitialisation. Supprimez les environnements locaux, leurs données et l’image :
scripts/stop-local.sh staging --volumesscripts/stop-local.sh production --volumesdocker image rm ghcr.io/<you>/ticket-api-ci@sha256:<digest>L’exécution de test a affiché Removed (supprimé) pour les deux conteneurs api, les deux conteneurs db, les deux réseaux et les deux volumes. En conditions réelles : le module 4 réutilise les deux applications. Elles ne coûtent presque rien au repos. Si vous arrêtez ici, supprimez tout avec az group delete --name rg-ticket-api-ci --yes --no-wait. Les environnements, variables et mises en production GitHub ne coûtent rien et peuvent rester. Pour commencer le module 4 dans un état connu, utilisez l’instantané end-of-m03. Recréez ensuite les paramètres GitHub de ce module.
Ensuite, nous modifierons le modèle d’IA lui-même. Le pipeline doit décider si un nouveau classificateur est assez bon avant de le transmettre à un environnement.
Leçon suivante : Barrière d'évaluation de l'IA