Aller au contenu
English

Mise en production progressive

  • Leçon 12
  • 50 min
  • Module 4 : Mettre en production les changements d'IA et de base de données
Leçon 12 sur 15 · CI/CD, tests et mises en production sûresMise en production progressive

Ce que vous saurez faire

La barrière a évalué keywords-1.1 sur 33 billets de soutien. La production en compte des milliers, dont certains ne ressemblent à aucun billet du jeu de données. Cette leçon expose d’abord une petite part des requêtes réelles au nouveau classificateur, puis surveille cette part séparément. Ensuite, elle active le classificateur pour tout le monde, ou le désactive.

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

  • La version 1.2.0 fusionnée dans main et déployée. Parcours simulé : dans votre production locale. Parcours en ligne : dans vos deux applications conteneurisées. Consultez Migrations sûres : exercice guidé. Les deux utilisent CLASSIFIER_VERSION=1.0 en production.
  • Parcours en ligne seulement : Azure CLI 2.88.0 avec l’extension containerapp, connecté à l’abonnement de vos applications. Un déploiement canari ajoute une deuxième révision pendant une courte période. Avec un minimum de 0 réplique et quelques centaines de requêtes, il reste dans l’allocation mensuelle gratuite, comme dans le cours Docker. Vous supprimez le groupe de ressources au module 5.
  • Parcours simulé seulement : Python 3 (l’environnement virtuel du projet suffit) et Docker Desktop.
  • Si votre projet manque ou ne fonctionne plus, copiez l’instantané end-of-m04 et promouvez-le comme dans Migrations sûres.

Les étapes en ligne ont été exécutées avec Azure CLI 2.88.0 (containerapp 1.3.0b5) dans canadacentral. Elles ont modifié l’application ticket-api-production de Nextia-Intelligence/ticket-api-ci. Les étapes simulées ont été exécutées le même jour avec Docker Desktop 4.81.0 et Python 3.14.6 sur macOS (Apple silicon). Les étapes pour Windows et Linux n’ont pas été testées.

La version 1.2.0 est passée en préproduction, puis en production par le pipeline, après l’approbation : exécution Deploy 37667574234. Amira lui a donné l’étiquette v1.2.0, et le flux de travail Release (mise en production) a étiqueté la même image (exécution 37667587273). En production, CLASSIFIER_VERSION vaut encore 1.0, donc tous les utilisateurs reçoivent encore keywords-1.0. Le nouveau code est là, mais désactivé.

Voilà le premier outil d’une mise en production progressive : le drapeau de fonctionnalité de la première leçon. Activer un drapeau change les paramètres, sans nouvelle construction. L’image conserve la même empreinte que celle testée par la CI. La responsable du soutien demande : « Peut-on l’activer pour tout le monde aujourd’hui? » Amira répond : « D’abord pour un billet sur dix. »

Un canari est une nouvelle version qui reçoit d’abord une petite part du trafic réel. La version actuelle, la version stable, reçoit le reste. Le nom vient des oiseaux que les mineurs emportaient dans les mines : un problème touche le canari avant tout le monde. Cette part est le poids de trafic : un poids de 10 donne à chaque requête 10 % de chances d’atteindre le canari.

Azure Container Apps permet de le faire avec des révisions. Modifier une variable d’environnement crée une nouvelle révision (Configuration opérationnelle dans le cours Docker). En mode à révisions multiples, plusieurs révisions sont actives, et le point d’entrée répartit les requêtes selon leur poids.

La boucle d'Amira envoie 200 requêtes avec le billet Package never arrived (colis jamais arrivé) au point d'entrée de l'application conteneurisée ticket-api-production. Celle-ci utilise le mode à révisions multiples, avec des poids de trafic de 90 et 10. 90 % vont à la révision ticket-api-production--0000004 : image 1.2.0, empreinte sha256:bd2316b5, CLASSIFIER_VERSION=1.0, poids 90. Elle a donné 185 réponses de keywords-1.0, catégorie other (autre). 10 % vont à la révision ticket-api-production--kw11 : même image, CLASSIFIER_VERSION=1.1, poids 10. Elle a donné 15 réponses de keywords-1.1, catégorie shipping (expédition). Log Analytics, requêtes par révision et statut : 0000004, 186 avec le statut 200 et 1 avec 401 (une vérification sommaire sans clé); kw11, 15 avec 200. Chaque requête est choisie séparément : un poids de 10 % a donné 15 requêtes sur 200, soit 7,5 %, pas exactement 20.
Le canari en ligne du 7 octobre 2026 : une image, deux révisions qui diffèrent seulement par le drapeau, et une répartition 90/10. Les réponses et les journaux indiquent tous deux quelle révision a répondu.
Description textuelle du diagramme

Les deux révisions exécutent la même image, 1.2.0. Elles diffèrent par un seul paramètre : CLASSIFIER_VERSION. Le point d’entrée a choisi une révision pour chaque requête : 185 des 200 requêtes ont atteint keywords-1.0 et 15, keywords-1.1. La réponse elle-même indique quel classificateur a répondu (model_version, version du modèle). Log Analytics compte les requêtes par révision et statut. Vous pouvez donc surveiller chaque version séparément.

À quel point une requête sur dix est-elle aléatoire?

Section intitulée « À quel point une requête sur dix est-elle aléatoire? »

Le point d’entrée ne compte pas « neuf à la version stable, une au canari ». Il choisit séparément pour chaque requête. Donc, 200 requêtes à 10 % donnent environ 20 requêtes au canari. L’exécution en ligne en a donné 15.

Explorer

Poids du canari : résultat attendu et un échantillon aléatoire de 200 requêtes

Canari : attendu
20 sur 200
Canari : cet échantillon
17 sur 200 (8,5 %)
La plupart des échantillons de 200 (95 %)
de 12 à 29

Poids 10 % : attendez-vous à 20 requêtes sur 200 vers le canari. L’échantillon 1 en a envoyé 17, 3 de moins que prévu. Chaque requête est choisie seule, donc la plupart des échantillons de 200 en donnent de 12 à 29. Le canari réel du 7 octobre 2026 avait le même poids et a reçu 15 requêtes sur 200 (7,5 %), aussi dans cette plage.

Échantillon 1 : 200 requêtes, poids du canari 10 %
RévisionAttenduCet échantillonPart
Stable keywords-1.018018391,5 %
Canari keywords-1.120178,5 %
  • Révision stable, keywords-1.0
  • Révision canari, keywords-1.1 (avec un point)
Requête 1 : stable, keywords-1.0Requête 2 : stable, keywords-1.0Requête 3 : canari, keywords-1.1Requête 4 : stable, keywords-1.0Requête 5 : stable, keywords-1.0Requête 6 : stable, keywords-1.0Requête 7 : stable, keywords-1.0Requête 8 : stable, keywords-1.0Requête 9 : stable, keywords-1.0Requête 10 : stable, keywords-1.0Requête 11 : stable, keywords-1.0Requête 12 : stable, keywords-1.0Requête 13 : stable, keywords-1.0Requête 14 : stable, keywords-1.0Requête 15 : stable, keywords-1.0Requête 16 : stable, keywords-1.0Requête 17 : stable, keywords-1.0Requête 18 : stable, keywords-1.0Requête 19 : stable, keywords-1.0Requête 20 : stable, keywords-1.0Requête 21 : stable, keywords-1.0Requête 22 : stable, keywords-1.0Requête 23 : stable, keywords-1.0Requête 24 : stable, keywords-1.0Requête 25 : stable, keywords-1.0Requête 26 : stable, keywords-1.0Requête 27 : stable, keywords-1.0Requête 28 : stable, keywords-1.0Requête 29 : stable, keywords-1.0Requête 30 : stable, keywords-1.0Requête 31 : stable, keywords-1.0Requête 32 : stable, keywords-1.0Requête 33 : stable, keywords-1.0Requête 34 : stable, keywords-1.0Requête 35 : stable, keywords-1.0Requête 36 : stable, keywords-1.0Requête 37 : stable, keywords-1.0Requête 38 : stable, keywords-1.0Requête 39 : stable, keywords-1.0Requête 40 : stable, keywords-1.0Requête 41 : stable, keywords-1.0Requête 42 : stable, keywords-1.0Requête 43 : stable, keywords-1.0Requête 44 : stable, keywords-1.0Requête 45 : stable, keywords-1.0Requête 46 : stable, keywords-1.0Requête 47 : canari, keywords-1.1Requête 48 : stable, keywords-1.0Requête 49 : stable, keywords-1.0Requête 50 : stable, keywords-1.0Requête 51 : canari, keywords-1.1Requête 52 : stable, keywords-1.0Requête 53 : stable, keywords-1.0Requête 54 : stable, keywords-1.0Requête 55 : stable, keywords-1.0Requête 56 : stable, keywords-1.0Requête 57 : stable, keywords-1.0Requête 58 : stable, keywords-1.0Requête 59 : stable, keywords-1.0Requête 60 : stable, keywords-1.0Requête 61 : stable, keywords-1.0Requête 62 : stable, keywords-1.0Requête 63 : stable, keywords-1.0Requête 64 : stable, keywords-1.0Requête 65 : stable, keywords-1.0Requête 66 : canari, keywords-1.1Requête 67 : stable, keywords-1.0Requête 68 : stable, keywords-1.0Requête 69 : stable, keywords-1.0Requête 70 : stable, keywords-1.0Requête 71 : stable, keywords-1.0Requête 72 : stable, keywords-1.0Requête 73 : stable, keywords-1.0Requête 74 : stable, keywords-1.0Requête 75 : stable, keywords-1.0Requête 76 : stable, keywords-1.0Requête 77 : stable, keywords-1.0Requête 78 : stable, keywords-1.0Requête 79 : stable, keywords-1.0Requête 80 : stable, keywords-1.0Requête 81 : stable, keywords-1.0Requête 82 : canari, keywords-1.1Requête 83 : stable, keywords-1.0Requête 84 : stable, keywords-1.0Requête 85 : canari, keywords-1.1Requête 86 : canari, keywords-1.1Requête 87 : stable, keywords-1.0Requête 88 : stable, keywords-1.0Requête 89 : stable, keywords-1.0Requête 90 : stable, keywords-1.0Requête 91 : stable, keywords-1.0Requête 92 : stable, keywords-1.0Requête 93 : stable, keywords-1.0Requête 94 : stable, keywords-1.0Requête 95 : stable, keywords-1.0Requête 96 : stable, keywords-1.0Requête 97 : stable, keywords-1.0Requête 98 : stable, keywords-1.0Requête 99 : stable, keywords-1.0Requête 100 : stable, keywords-1.0Requête 101 : stable, keywords-1.0Requête 102 : stable, keywords-1.0Requête 103 : canari, keywords-1.1Requête 104 : stable, keywords-1.0Requête 105 : stable, keywords-1.0Requête 106 : stable, keywords-1.0Requête 107 : stable, keywords-1.0Requête 108 : stable, keywords-1.0Requête 109 : stable, keywords-1.0Requête 110 : stable, keywords-1.0Requête 111 : stable, keywords-1.0Requête 112 : stable, keywords-1.0Requête 113 : stable, keywords-1.0Requête 114 : stable, keywords-1.0Requête 115 : stable, keywords-1.0Requête 116 : stable, keywords-1.0Requête 117 : canari, keywords-1.1Requête 118 : stable, keywords-1.0Requête 119 : canari, keywords-1.1Requête 120 : stable, keywords-1.0Requête 121 : stable, keywords-1.0Requête 122 : stable, keywords-1.0Requête 123 : stable, keywords-1.0Requête 124 : canari, keywords-1.1Requête 125 : stable, keywords-1.0Requête 126 : stable, keywords-1.0Requête 127 : stable, keywords-1.0Requête 128 : stable, keywords-1.0Requête 129 : canari, keywords-1.1Requête 130 : stable, keywords-1.0Requête 131 : stable, keywords-1.0Requête 132 : stable, keywords-1.0Requête 133 : stable, keywords-1.0Requête 134 : stable, keywords-1.0Requête 135 : stable, keywords-1.0Requête 136 : stable, keywords-1.0Requête 137 : canari, keywords-1.1Requête 138 : stable, keywords-1.0Requête 139 : stable, keywords-1.0Requête 140 : stable, keywords-1.0Requête 141 : stable, keywords-1.0Requête 142 : stable, keywords-1.0Requête 143 : stable, keywords-1.0Requête 144 : stable, keywords-1.0Requête 145 : stable, keywords-1.0Requête 146 : stable, keywords-1.0Requête 147 : stable, keywords-1.0Requête 148 : stable, keywords-1.0Requête 149 : stable, keywords-1.0Requête 150 : stable, keywords-1.0Requête 151 : stable, keywords-1.0Requête 152 : stable, keywords-1.0Requête 153 : stable, keywords-1.0Requête 154 : stable, keywords-1.0Requête 155 : stable, keywords-1.0Requête 156 : stable, keywords-1.0Requête 157 : stable, keywords-1.0Requête 158 : stable, keywords-1.0Requête 159 : stable, keywords-1.0Requête 160 : stable, keywords-1.0Requête 161 : stable, keywords-1.0Requête 162 : stable, keywords-1.0Requête 163 : stable, keywords-1.0Requête 164 : stable, keywords-1.0Requête 165 : canari, keywords-1.1Requête 166 : stable, keywords-1.0Requête 167 : stable, keywords-1.0Requête 168 : stable, keywords-1.0Requête 169 : stable, keywords-1.0Requête 170 : stable, keywords-1.0Requête 171 : stable, keywords-1.0Requête 172 : stable, keywords-1.0Requête 173 : stable, keywords-1.0Requête 174 : stable, keywords-1.0Requête 175 : stable, keywords-1.0Requête 176 : stable, keywords-1.0Requête 177 : stable, keywords-1.0Requête 178 : stable, keywords-1.0Requête 179 : stable, keywords-1.0Requête 180 : stable, keywords-1.0Requête 181 : stable, keywords-1.0Requête 182 : stable, keywords-1.0Requête 183 : canari, keywords-1.1Requête 184 : stable, keywords-1.0Requête 185 : stable, keywords-1.0Requête 186 : stable, keywords-1.0Requête 187 : stable, keywords-1.0Requête 188 : stable, keywords-1.0Requête 189 : stable, keywords-1.0Requête 190 : stable, keywords-1.0Requête 191 : stable, keywords-1.0Requête 192 : stable, keywords-1.0Requête 193 : stable, keywords-1.0Requête 194 : stable, keywords-1.0Requête 195 : stable, keywords-1.0Requête 196 : stable, keywords-1.0Requête 197 : stable, keywords-1.0Requête 198 : stable, keywords-1.0Requête 199 : canari, keywords-1.1Requête 200 : canari, keywords-1.1
Requête 1 : stable, keywords-1.0Requête 2 : stable, keywords-1.0Requête 3 : canari, keywords-1.1Requête 4 : stable, keywords-1.0Requête 5 : stable, keywords-1.0Requête 6 : stable, keywords-1.0Requête 7 : stable, keywords-1.0Requête 8 : stable, keywords-1.0Requête 9 : stable, keywords-1.0Requête 10 : stable, keywords-1.0Requête 11 : stable, keywords-1.0Requête 12 : stable, keywords-1.0Requête 13 : stable, keywords-1.0Requête 14 : stable, keywords-1.0Requête 15 : stable, keywords-1.0Requête 16 : stable, keywords-1.0Requête 17 : stable, keywords-1.0Requête 18 : stable, keywords-1.0Requête 19 : stable, keywords-1.0Requête 20 : stable, keywords-1.0Requête 21 : stable, keywords-1.0Requête 22 : stable, keywords-1.0Requête 23 : stable, keywords-1.0Requête 24 : stable, keywords-1.0Requête 25 : stable, keywords-1.0Requête 26 : stable, keywords-1.0Requête 27 : stable, keywords-1.0Requête 28 : stable, keywords-1.0Requête 29 : stable, keywords-1.0Requête 30 : stable, keywords-1.0Requête 31 : stable, keywords-1.0Requête 32 : stable, keywords-1.0Requête 33 : stable, keywords-1.0Requête 34 : stable, keywords-1.0Requête 35 : stable, keywords-1.0Requête 36 : stable, keywords-1.0Requête 37 : stable, keywords-1.0Requête 38 : stable, keywords-1.0Requête 39 : stable, keywords-1.0Requête 40 : stable, keywords-1.0Requête 41 : stable, keywords-1.0Requête 42 : stable, keywords-1.0Requête 43 : stable, keywords-1.0Requête 44 : stable, keywords-1.0Requête 45 : stable, keywords-1.0Requête 46 : stable, keywords-1.0Requête 47 : canari, keywords-1.1Requête 48 : stable, keywords-1.0Requête 49 : stable, keywords-1.0Requête 50 : stable, keywords-1.0Requête 51 : canari, keywords-1.1Requête 52 : stable, keywords-1.0Requête 53 : stable, keywords-1.0Requête 54 : stable, keywords-1.0Requête 55 : stable, keywords-1.0Requête 56 : stable, keywords-1.0Requête 57 : stable, keywords-1.0Requête 58 : stable, keywords-1.0Requête 59 : stable, keywords-1.0Requête 60 : stable, keywords-1.0Requête 61 : stable, keywords-1.0Requête 62 : stable, keywords-1.0Requête 63 : stable, keywords-1.0Requête 64 : stable, keywords-1.0Requête 65 : stable, keywords-1.0Requête 66 : canari, keywords-1.1Requête 67 : stable, keywords-1.0Requête 68 : stable, keywords-1.0Requête 69 : stable, keywords-1.0Requête 70 : stable, keywords-1.0Requête 71 : stable, keywords-1.0Requête 72 : stable, keywords-1.0Requête 73 : stable, keywords-1.0Requête 74 : stable, keywords-1.0Requête 75 : stable, keywords-1.0Requête 76 : stable, keywords-1.0Requête 77 : stable, keywords-1.0Requête 78 : stable, keywords-1.0Requête 79 : stable, keywords-1.0Requête 80 : stable, keywords-1.0Requête 81 : stable, keywords-1.0Requête 82 : canari, keywords-1.1Requête 83 : stable, keywords-1.0Requête 84 : stable, keywords-1.0Requête 85 : canari, keywords-1.1Requête 86 : canari, keywords-1.1Requête 87 : stable, keywords-1.0Requête 88 : stable, keywords-1.0Requête 89 : stable, keywords-1.0Requête 90 : stable, keywords-1.0Requête 91 : stable, keywords-1.0Requête 92 : stable, keywords-1.0Requête 93 : stable, keywords-1.0Requête 94 : stable, keywords-1.0Requête 95 : stable, keywords-1.0Requête 96 : stable, keywords-1.0Requête 97 : stable, keywords-1.0Requête 98 : stable, keywords-1.0Requête 99 : stable, keywords-1.0Requête 100 : stable, keywords-1.0Requête 101 : stable, keywords-1.0Requête 102 : stable, keywords-1.0Requête 103 : canari, keywords-1.1Requête 104 : stable, keywords-1.0Requête 105 : stable, keywords-1.0Requête 106 : stable, keywords-1.0Requête 107 : stable, keywords-1.0Requête 108 : stable, keywords-1.0Requête 109 : stable, keywords-1.0Requête 110 : stable, keywords-1.0Requête 111 : stable, keywords-1.0Requête 112 : stable, keywords-1.0Requête 113 : stable, keywords-1.0Requête 114 : stable, keywords-1.0Requête 115 : stable, keywords-1.0Requête 116 : stable, keywords-1.0Requête 117 : canari, keywords-1.1Requête 118 : stable, keywords-1.0Requête 119 : canari, keywords-1.1Requête 120 : stable, keywords-1.0Requête 121 : stable, keywords-1.0Requête 122 : stable, keywords-1.0Requête 123 : stable, keywords-1.0Requête 124 : canari, keywords-1.1Requête 125 : stable, keywords-1.0Requête 126 : stable, keywords-1.0Requête 127 : stable, keywords-1.0Requête 128 : stable, keywords-1.0Requête 129 : canari, keywords-1.1Requête 130 : stable, keywords-1.0Requête 131 : stable, keywords-1.0Requête 132 : stable, keywords-1.0Requête 133 : stable, keywords-1.0Requête 134 : stable, keywords-1.0Requête 135 : stable, keywords-1.0Requête 136 : stable, keywords-1.0Requête 137 : canari, keywords-1.1Requête 138 : stable, keywords-1.0Requête 139 : stable, keywords-1.0Requête 140 : stable, keywords-1.0Requête 141 : stable, keywords-1.0Requête 142 : stable, keywords-1.0Requête 143 : stable, keywords-1.0Requête 144 : stable, keywords-1.0Requête 145 : stable, keywords-1.0Requête 146 : stable, keywords-1.0Requête 147 : stable, keywords-1.0Requête 148 : stable, keywords-1.0Requête 149 : stable, keywords-1.0Requête 150 : stable, keywords-1.0Requête 151 : stable, keywords-1.0Requête 152 : stable, keywords-1.0Requête 153 : stable, keywords-1.0Requête 154 : stable, keywords-1.0Requête 155 : stable, keywords-1.0Requête 156 : stable, keywords-1.0Requête 157 : stable, keywords-1.0Requête 158 : stable, keywords-1.0Requête 159 : stable, keywords-1.0Requête 160 : stable, keywords-1.0Requête 161 : stable, keywords-1.0Requête 162 : stable, keywords-1.0Requête 163 : stable, keywords-1.0Requête 164 : stable, keywords-1.0Requête 165 : canari, keywords-1.1Requête 166 : stable, keywords-1.0Requête 167 : stable, keywords-1.0Requête 168 : stable, keywords-1.0Requête 169 : stable, keywords-1.0Requête 170 : stable, keywords-1.0Requête 171 : stable, keywords-1.0Requête 172 : stable, keywords-1.0Requête 173 : stable, keywords-1.0Requête 174 : stable, keywords-1.0Requête 175 : stable, keywords-1.0Requête 176 : stable, keywords-1.0Requête 177 : stable, keywords-1.0Requête 178 : stable, keywords-1.0Requête 179 : stable, keywords-1.0Requête 180 : stable, keywords-1.0Requête 181 : stable, keywords-1.0Requête 182 : stable, keywords-1.0Requête 183 : canari, keywords-1.1Requête 184 : stable, keywords-1.0Requête 185 : stable, keywords-1.0Requête 186 : stable, keywords-1.0Requête 187 : stable, keywords-1.0Requête 188 : stable, keywords-1.0Requête 189 : stable, keywords-1.0Requête 190 : stable, keywords-1.0Requête 191 : stable, keywords-1.0Requête 192 : stable, keywords-1.0Requête 193 : stable, keywords-1.0Requête 194 : stable, keywords-1.0Requête 195 : stable, keywords-1.0Requête 196 : stable, keywords-1.0Requête 197 : stable, keywords-1.0Requête 198 : stable, keywords-1.0Requête 199 : canari, keywords-1.1Requête 200 : canari, keywords-1.1
Déplacez le poids et comparez le nombre attendu à un échantillon aléatoire de 200 requêtes généré avec une graine. Chaque carré représente une requête, dans l'ordre d'envoi. Un autre échantillon tire les 200 suivantes.
Description textuelle de l’explorateur

L’explorateur a un seul curseur : le poids de trafic du canari, de 0 % à 100 %. Il envoie 200 requêtes et décide séparément pour chacune. Une requête va au canari lorsque son nombre aléatoire est inférieur au poids. L’affichage montre le nombre attendu de requêtes au canari (200 × poids) et le nombre dans cet échantillon. Il montre aussi l’intervalle contenant 95 % des échantillons de 200. Un tableau montre les nombres attendus et obtenus pour les deux révisions. Une grille représente les 200 requêtes par des carrés : bleus pour la révision stable, orange avec un point pour le canari.

Au départ (poids de 10 %), on attend 20 requêtes. L’échantillon 1 en envoie 17 au canari et 183 à la révision stable. La plupart des échantillons en donnent de 12 à 29. Le canari en ligne en a reçu 15, dans cet intervalle. Les échantillons suivants donnent 20, 18, 22 et 21. À 0 %, aucune requête n’atteint le canari, dans aucun échantillon; à 100 %, toutes l’atteignent. Une graine fixe rend l’échantillon identique à chaque visite. Une requête envoyée au canari à un poids faible y reste à un poids plus élevé.

C’est important quand vous analysez un canari. Avec 200 requêtes à 10 %, le canari en reçoit généralement de 12 à 29. Si une seule échoue, le taux d’erreur se situe entre 3 % et 8 %, pour une seule requête. Un petit canari trouve vite les gros problèmes. Pour voir de petites différences, il lui faut plus de requêtes ou plus de temps.

Amira effectue le déploiement canari à la main avec Azure CLI, comme propriétaire de l’abonnement.

Elle examine d’abord les révisions actives (en ligne, tirées du journal d’exécution) :

Terminal (live)
az containerapp revision list -g rg-ticket-api-ci -n ticket-api-production \
--query "[?properties.active].{name:name, traffic:properties.trafficWeight, image:properties.template.containers[0].image}" -o table
Name Traffic Image
------------------------------ --------- -------------------------------------------------------
ticket-api-production--0000003 0 ghcr.io/usmanahmed99/ticket-api-ci@sha256:e5157802...
ticket-api-production--0000004 100 ghcr.io/usmanahmed99/ticket-api-ci@sha256:bd2316b5...

0000004 est la version 1.2.0 et reçoit tout le trafic. Elle passe l’application en mode à révisions multiples et crée la révision canari : la même image, avec le drapeau activé. --revision-suffix kw11 donne à la révision un nom qui indique son rôle :

Terminal (live)
az containerapp revision set-mode -g rg-ticket-api-ci -n ticket-api-production --mode multiple
az containerapp update -g rg-ticket-api-ci -n ticket-api-production \
--set-env-vars CLASSIFIER_VERSION=1.1 --revision-suffix kw11 \
--query properties.latestRevisionName -o tsv
ticket-api-production--kw11

Ensuite, elle vérifie le trafic et remarque un problème :

Name Traffic Health
------------------------------ --------- --------
ticket-api-production--0000004 0 Healthy
ticket-api-production--kw11 100 Healthy

La nouvelle révision a reçu 100 % du trafic d’un coup. L’erreur courante ci-dessous explique pourquoi et présente l’ordre qui l’évite. Elle règle immédiatement les poids :

Terminal (live)
az containerapp ingress traffic set -g rg-ticket-api-ci -n ticket-api-production \
--revision-weight ticket-api-production--0000004=90 ticket-api-production--kw11=10 -o table
RevisionName Weight
------------------------------ --------
ticket-api-production--0000004 90
ticket-api-production--kw11 10

Elle envoie maintenant 200 requêtes avec le même billet, « Package never arrived » (colis jamais arrivé). keywords-1.0 répond other (autre) et keywords-1.1 répond shipping (expédition), donc la réponse indique quelle révision a répondu. Elle compte les valeurs de model_version :

185 keywords-1.0 other
15 keywords-1.1 shipping

La réponse est une source. Les journaux de la plateforme en sont une deuxième, indépendante de l’application. Log Analytics compte les requêtes de la période canari par révision et statut. Ce résultat vient de l’exécution en ligne; le journal n’a conservé que la fin de la requête, summarize requests=count() by RevisionName_s, status :

RevisionName_s Requests Status
------------------------------ ---------- --------
ticket-api-production--kw11 15 200
ticket-api-production--0000004 186 200
ticket-api-production--0000004 1 401
ticket-api-production--0000003 1 401
ticket-api-production--0000003 1 200

Le canari a répondu à ses 15 requêtes avec 200. Les lignes 401 sont des vérifications sommaires sans clé, qui doivent répondre 401. La ligne 0000004 compte une requête de plus que la boucle, provenant d’une autre vérification. Les deux lignes de 0000003 appartiennent à l’ancienne révision, dont le poids était 0 : elles ne font pas partie du canari. Aucune révision n’a répondu 500.

Décidez des règles d’arrêt avant le démarrage du canari, et écrivez-les dans le plan de mise en production. Si vous décidez pendant le canari, chaque résultat semble « probablement correct ». Pour l’API de billets, Amira a écrit :

À surveiller Où Arrêter le canari si
Erreurs Journaux par révision : statuts 500 et 503 Le canari a un 500, ou plus d’erreurs que la révision stable
Latence Journaux par révision, ou métriques de la plateforme Le canari est nettement plus lent
Le modèle en production Réponses par model_version : part de chaque catégorie et de la priorité 1 La part de priorité 1 ou de other du canari est loin des résultats de la barrière
Personnes Le soutien : billets transférés manuellement à une autre équipe Le soutien signale des catégories incorrectes provenant du canari

Les métriques de la barrière nécessitent des étiquettes, et les billets de production n’en ont pas au départ. En production, vous surveillez donc les signes visibles immédiatement : les erreurs et la part de chaque réponse. Les corrections du soutien viennent plus tard. Ce cours a mesuré seulement les requêtes par révision et statut. Les autres lignes indiquent ce qu’une équipe ajoute pour un vrai modèle.

Pour arrêter, donnez 0 % au canari (--revision-weight ticket-api-production--0000004=100 ticket-api-production--kw11=0), puis revenez au mode à révision unique. Les utilisateurs retrouvent keywords-1.0 en quelques secondes, sans nouvelle construction.

Le canari n’a montré aucun problème. Amira lui a donc donné tout le trafic et a remis l’application en mode à révision unique (en ligne) :

Terminal (live)
az containerapp ingress traffic set -g rg-ticket-api-ci -n ticket-api-production \
--revision-weight ticket-api-production--kw11=100 ticket-api-production--0000004=0 -o table
az containerapp revision set-mode -g rg-ticket-api-ci -n ticket-api-production --mode single
az containerapp show -g rg-ticket-api-ci -n ticket-api-production \
--query "properties.template.containers[0].env[?name=='CLASSIFIER_VERSION']" -o table
Name Value
------------------ -------
CLASSIFIER_VERSION 1.1

Ces lignes entraînent deux conséquences :

  • Revenez au mode à révision unique après un canari. Le flux de travail Deploy exécute az containerapp update --image. En mode à révisions multiples, cette nouvelle révision ne reçoit aucun trafic ou le prend entièrement, selon la règle de trafic. Aucun de ces comportements ne correspond à ce qu’attend le pipeline.
  • Le drapeau reste activé. CLASSIFIER_VERSION=1.1 fait maintenant partie du modèle de configuration de l’application. Le prochain déploiement d’une nouvelle image le conserve. Pour désactiver de nouveau le modèle, changez le drapeau. C’est un retour en arrière du modèle, sans retour en arrière de l’application (module 5).

Docker n’a pas de poids de trafic. Vous reproduisez la même structure avec trois petites parties. La version stable est votre production locale sur le port 8082 (drapeau 1.0). Le canari est un conteneur de la même image sur le port 8084 (drapeau 1.1). split.py, sur le port 8090, envoie chaque requête à l’une des deux versions, au hasard, selon le poids. Les clients appellent seulement 8090, comme ils appellent seulement l’adresse de l’application dans le nuage.

  1. Vérifiez que la production locale exécute 1.2.0 : scripts/smoke.sh http://127.0.0.1:8082 "$(cat secrets/production_api_key.txt)" affiche quatre lignes ok (réussi).
  2. Démarrez le canari : même empreinte, même réseau et même base de données, avec les mêmes paramètres que la production sauf le drapeau. La commande se trouve sous les étapes.
  3. Faites une vérification sommaire du canari sur son propre port : scripts/smoke.sh http://127.0.0.1:8084 "$(cat secrets/production_api_key.txt)".
  4. Enregistrez split.py à côté du dossier de votre projet, pas à l’intérieur : par exemple, ~/projects/split.py. Le code se trouve ci-dessous.
  5. Dans un deuxième terminal, démarrez le répartiteur avec un poids de canari de 10. Laissez-le fonctionner.
  6. Dans le premier terminal, envoyez 200 requêtes au port 8090 et comptez les réponses par model_version.
  7. Lisez le deuxième terminal : une ligne par requête, avec la version qui a répondu.
  8. Arrêtez le répartiteur avec Ctrl+C. Il affiche les requêtes par version et statut.

Le conteneur canari (étape 2). La production masque /docs, donc le canari aussi : un canari doit différer par un seul paramètre.

Terminal (in the project, simulated)
NEW=ghcr.io/usmanahmed99/ticket-api-ci@sha256:bd2316b56067c738199d6203a23845c97fb7b85b550379411eddd7cc6a2ddab8
docker run -d --name ticket-api-canary --platform linux/amd64 \
--network ticket-api-production_default -p 127.0.0.1:8084:8000 \
-e API_KEY_FILE=/run/secrets/api_key -e REQUIRE_API_KEY=true -e SHOW_DOCS=false \
-e CLASSIFIER_VERSION=1.1 \
-v "$PWD/secrets/production_api_key.txt:/run/secrets/api_key:ro" \
-e DATABASE_URL="postgresql://tickets:$(grep '^POSTGRES_PASSWORD=' .env | cut -d= -f2)@db:5432/tickets" \
"$NEW"

Utilisez votre propre empreinte de 1.2.0 pour NEW. Le canari utilise le fichier de clé de production, donc les clients n’ont pas besoin d’une nouvelle clé. Il utilise aussi la base de données de production, donc son historique se trouve dans la même table.

Afficher split.py
split.py
"""A small traffic splitter for the simulated canary (C19-M04-L03).
python3 split.py --stable http://127.0.0.1:8082 --canary http://127.0.0.1:8084 --weight 10
Clients call http://127.0.0.1:8090 only. Each request is sent to the canary
with the chance --weight (in percent), chosen on its own, as in Azure.
Only the Python standard library. For practice on your computer only: no
TLS and no protection. Stop it with Ctrl+C.
"""
import argparse
import random
import signal
import sys
import urllib.error
import urllib.request
from collections import Counter
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
HOP_BY_HOP = {"connection", "keep-alive", "transfer-encoding", "host", "content-length"}
def make_handler(stable: str, canary: str, weight: int, choose, counts: Counter):
class Split(BaseHTTPRequestHandler):
def forward(self) -> None:
target = "canary" if choose() < weight / 100 else "stable"
base = canary if target == "canary" else stable
length = int(self.headers.get("Content-Length") or 0)
body = self.rfile.read(length) if length else None
headers = {k: v for k, v in self.headers.items() if k.lower() not in HOP_BY_HOP}
request = urllib.request.Request(base + self.path, body, headers, method=self.command)
try:
with urllib.request.urlopen(request, timeout=30) as answer:
status, data, answer_headers = answer.status, answer.read(), answer.headers
except urllib.error.HTTPError as error:
status, data, answer_headers = error.code, error.read(), error.headers
except OSError:
status, data, answer_headers = 502, b'{"error":"upstream not reachable"}', {}
counts[(target, status)] += 1
self.send_response(status)
for k, v in answer_headers.items():
if k.lower() not in HOP_BY_HOP:
self.send_header(k, v)
self.send_header("X-Served-By", target)
self.send_header("Content-Length", str(len(data)))
self.end_headers()
self.wfile.write(data)
print(f"{target:<6} {status} {self.command} {self.path}", flush=True)
do_GET = do_POST = forward
def log_message(self, *args) -> None: # one line per request is enough
pass
return Split
def stop(*_) -> None:
raise KeyboardInterrupt
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("--stable", required=True, help="address of the current version")
parser.add_argument("--canary", required=True, help="address of the new version")
parser.add_argument("--weight", type=int, default=10, help="percent of requests for the canary")
parser.add_argument("--port", type=int, default=8090)
parser.add_argument("--seed", type=int, help="repeat the same random choices (for tests)")
args = parser.parse_args()
if not 0 <= args.weight <= 100:
parser.error("--weight must be from 0 to 100")
choose = random.Random(args.seed).random
counts: Counter = Counter()
handler = make_handler(args.stable.rstrip("/"), args.canary.rstrip("/"), args.weight, choose, counts)
server = ThreadingHTTPServer(("127.0.0.1", args.port), handler)
print(f"127.0.0.1:{args.port}: {100 - args.weight}% to {args.stable}, "
f"{args.weight}% to {args.canary}", flush=True)
# Ctrl+C, or `kill` from a script (a background job ignores Ctrl+C).
signal.signal(signal.SIGTERM, stop)
try:
server.serve_forever()
except KeyboardInterrupt:
pass
print("\nrequests by version and status:", file=sys.stderr)
for (target, status), n in sorted(counts.items()):
print(f" {target:<6} {status} {n}", file=sys.stderr)
return 0
if __name__ == "__main__":
sys.exit(main())

Le programme lit chaque requête et choisit une cible avec un nombre aléatoire. Il y envoie la même méthode, le même chemin, les mêmes en-têtes et le même corps. Il retourne la réponse avec un en-tête supplémentaire, X-Served-By. Il accomplit le travail du point d’entrée dans le nuage, en environ 60 lignes.

Étapes 5 et 6 :

Second terminal (leave it running)
python3 ~/projects/split.py --stable http://127.0.0.1:8082 --canary http://127.0.0.1:8084 --weight 10
First terminal (in the project)
KEY=$(cat secrets/production_api_key.txt)
for i in $(seq 200); do
curl -s -X POST http://127.0.0.1:8090/v1/classify -H "X-API-Key: $KEY" \
-H "Content-Type: application/json" -d '{"subject": "Package never arrived"}'
echo
done | grep -o '"model_version":"[^"]*"' | sort | uniq -c

La sortie (simulée, première exécution de l’auteur) :

177 "model_version":"keywords-1.0"
23 "model_version":"keywords-1.1"

Le deuxième terminal affiche 127.0.0.1:8090: 90% to http://127.0.0.1:8082, 10% to http://127.0.0.1:8084 (90 % vers le port 8082, 10 % vers le port 8084). Ensuite, il affiche une ligne par requête, comme stable 200 POST /v1/classify ou canary 200 POST /v1/classify (version stable ou canari, statut 200). Après Ctrl+C (simulation, exécution ultérieure) :

requests by version and status:
canary 200 17
stable 200 183

Cinq exécutions au poids de 10 ont donné 23, 27, 20, 24 et 17 requêtes au canari : la même dispersion que l’explorateur. Vos nombres diffèrent; ils devraient se situer entre 12 et 29 la plupart du temps. --seed 7 rend les choix répétables : deux exécutions avec cette option ont toutes deux donné 175 et 25.

Pour promouvoir dans le parcours local, définissez CLASSIFIER_VERSION=1.1 dans deploy/local/production.env. Exécutez de nouveau scripts/promote-local.sh production "$NEW" avec la même empreinte, puis supprimez le canari avec docker rm -f ticket-api-canary. La promotion de l’auteur a pris environ 10 secondes. « Package never arrived » a ensuite reçu la réponse shipping avec keywords-1.1. En équipe, la modification de production.env passe par une demande de tirage (pull request), comme tout autre changement. Le dépôt de référence a conservé 1.0 dans ce fichier.

Afficher le résultat attendu du canari simulé

Les étapes 1 et 3 affichent chacune quatre lignes ok. Une requête à chaque port montre la différence : 8082 répond {"category":"other","priority":3,"confidence":0.2,"score":0.2,"model_version":"keywords-1.0"}, et 8084 répond {"category":"shipping","priority":1,"confidence":0.9,"score":0.9,"model_version":"keywords-1.1"}. Le compte des 200 requêtes comporte deux lignes dont la somme est 200, avec environ 20 pour keywords-1.1. Le résumé de split.py donne les deux mêmes nombres, tous avec le statut 200.

Changez une condition : pendant le canari, arrêtez son conteneur, comme si la nouvelle version avait planté. Gardez split.py en marche avec un poids de 10, et envoyez de nouveau 200 requêtes. Comptez les codes de statut plutôt que les versions, avec cette boucle :

First terminal
for i in $(seq 200); do
curl -s -o /dev/null -w '%{http_code}\n' -X POST http://127.0.0.1:8090/v1/classify -H "X-API-Key: $KEY" \
-H "Content-Type: application/json" -d '{"subject": "Package never arrived"}'
done | sort | uniq -c

Prédisez d’abord : combien de requêtes échouent, et comment verriez-vous quelle version ne fonctionne plus?

Afficher la réponse

Après docker stop ticket-api-canary, l’exécution de l’auteur a donné (simulation) :

183 200
17 502

et le résumé de split.py :

requests by version and status:
canary 502 17
stable 200 183

Environ une requête sur dix a échoué, et chaque échec venait du canari. Les codes de statut seuls disent : « quelque chose ne va pas pour 8,5 % des requêtes ». Le compte par version indique quoi : le canari. Voilà pourquoi il faut surveiller chaque révision séparément. Avec tout le trafic sur une version, la même panne ferait échouer toutes les requêtes. Arrêtez le canari : réglez son poids à 0 (ici, arrêtez split.py et appelez directement 8082). Ensuite, redémarrez le conteneur canari avec docker start ticket-api-canary, ou supprimez-le.

Erreur courante

La révision canari reçoit 100 % du trafic

Symptôme (en direct) : juste après az containerapp update … --revision-suffix kw11, la liste des révisions affichait ticket-api-production--kw11 100 et ticket-api-production--0000004 0. Pendant un court moment, tous les utilisateurs ont reçu keywords-1.1 : précisément ce que le canari devait éviter.

Cause : la règle de trafic de l’application suivait la dernière révision. En mode unique, c’est le comportement voulu : une nouvelle révision reçoit le trafic quand elle est prête. C’est ce que vous avez vu dans le cours L’essentiel de Docker et du nuage pour les développeurs en IA. Passer en mode multiple ne change pas la règle. La nouvelle révision a donc reçu tout le trafic, comme chaque nouvelle révision auparavant.

Correction : fixez le trafic sur la révision actuelle par son nom, avant de créer le canari. La dernière révision commence alors avec 0 % :

Terminal (live, the right order)
az containerapp revision set-mode -g rg-ticket-api-ci -n ticket-api-production --mode multiple
az containerapp ingress traffic set -g rg-ticket-api-ci -n ticket-api-production \
--revision-weight ticket-api-production--0000004=100
az containerapp update -g rg-ticket-api-ci -n ticket-api-production \
--set-env-vars CLASSIFIER_VERSION=1.1 --revision-suffix kw11
az containerapp revision list -g rg-ticket-api-ci -n ticket-api-production \
--query "[?properties.active].{name:name, traffic:properties.trafficWeight}" -o table
az containerapp ingress traffic set -g rg-ticket-api-ci -n ticket-api-production \
--revision-weight ticket-api-production--0000004=90 ticket-api-production--kw11=10

Vérifiez la liste après la mise à jour : le canari doit afficher 0 avant que vous lui donniez 10. L’essai a suivi ces étapes dans l’autre ordre. Cet ordre découle donc de la cause, et non d’un deuxième essai.

Pour un vrai service d’IA : un modèle peut réussir sur les données d’évaluation, mais échouer sur des entrées absentes de ces données. C’est dans les premières minutes d’une mise en production que vous en savez le moins. Un canari qui passe discrètement à 100 % donne à tous les utilisateurs la version la plus risquée. Il ne reste alors rien pour la comparer. Vérifiez les poids après chaque étape et gardez cette vérification dans le plan de mise en production.

Vérifiez votre compréhension

Pourquoi Amira pouvait-elle utiliser la même empreinte d'image pour les deux révisions du canari? Pourquoi est-ce préférable à un canari utilisant une nouvelle image?

Afficher la réponse

La version 1.2.0 contenait déjà keywords-1.1, désactivé par l’indicateur CLASSIFIER_VERSION. Une révision avec l’indicateur activé constitue un changement de paramètres. Les deux révisions utilisent donc exactement l’image testée par la CI et vérifiée par la barrière. Leur seule différence est le modèle. Si le canari révèle un problème, la cause ne peut être que keywords-1.1. Pour l’arrêter, changez les poids ou l’indicateur, sans construction.

Vérifiez votre compréhension

Après la promotion, Amira oublie set-mode single. La prochaine mise en production normale exécute le flux Deploy avec une nouvelle empreinte. Que peut-il arriver?

Afficher la réponse

En mode multiple, az containerapp update --image crée une nouvelle révision à côté de kw11. Selon la règle de trafic, elle ne reçoit aucun trafic ou reçoit tout le trafic d’un coup. Sans trafic, la mise en production semble réussir, mais les utilisateurs gardent l’ancienne version. Avec tout le trafic, il n’y a aucun canari. L’attente et le test de bon fonctionnement du pipeline ne distinguent pas ces cas, car ils vérifient la dernière révision. Revenez au mode unique après chaque canari et vérifiez le trafic des révisions actives.

Exercice guidé → tâche autonome. Vous avez vu Amira mettre en production deux changements à la fois : un nouveau champ d’API (score, avec des migrations) et une nouvelle version du modèle (keywords-1.1, derrière un indicateur). Planifiez maintenant votre propre mise en production avant d’écrire le code. Elle doit changer un champ d’API et une version du modèle.

Choisissez votre propre changement, ou utilisez celui-ci. L’API reçoit un nouveau champ queue : l’équipe qui devrait prendre le billet, par exemple billing-team. Le classificateur reçoit une nouvelle version, keywords-1.2, avec une règle qui distingue « colis perdu » de « mot de passe perdu ». Le plan tient dans un fichier, docs/releases/<version>.md, et répond aux questions suivantes :

  1. Changements pour les clients : qu’est-ce qui est nouveau, qu’est-ce qui est obsolète, et jusqu’à quelle version?
  2. Le modèle : qu’est-ce qui change, comment l’activer et quel est le résultat de la barrière face à la référence de base (exactitude, rappel, cas obligatoires)? Quel billet obligatoire est nouveau?
  3. La base de données : quelles migrations, dans quel ordre, et quelle version fonctionne à chaque étape? Faut-il une sauvegarde?
  4. Le déploiement progressif : l’ordre des étapes, le poids du canari, la durée et les règles d’arrêt, écrites avant le début.
  5. Retour en arrière : pour chaque partie (application, modèle, données), ce que vous rétablissez et comment.
  6. Responsable et surveillance : qui est responsable de la mise en production, que surveille-t-on ensuite, et pendant combien de temps?
Afficher un exemple de réponse : le plan de la version 1.2.0

Voici le plan suivi par Amira, tiré du fichier docs/releases/1.2.0.md du dépôt de référence. Les étapes de déploiement progressif et de retour en arrière y ont été ajoutées :

docs/releases/1.2.0.md (with the rollout and rollback plan)
## ticket-api 1.2.0
- **New for clients:** every classification has `score`, from 0 to 1. `confidence`
has the same value and is **deprecated**: it stays until version 2.0, so clients
have time to change. History items have `score` too.
- **New, off by default:** classifier `keywords-1.1` (`CLASSIFIER_VERSION=1.1`).
On the 33 holdout tickets: accuracy 0.909 (keywords-1.0: 0.636), every must-pass
ticket right. We turn it on in production step by step.
- **Database:** migrations replace the table that the app made at start-up. Run
`python -m ticket_api.migrate` before the new version starts (Compose does it in
the `migrate` service). Migration `002_add_score` adds a column; version 1.1.0
keeps working with it.
- **Owner:** Amira. **Watch after release:** the smoke checks, the error rate in
the logs, and `GET /v1/history`, for one day.
- **Rollback:** deploy the 1.1.0 digest. The database needs no change: 1.1.0
ignores the new column.
### Rollout (in this order)
1. PR #3: keywords-1.1 behind the flag, and the gate in CI (required check renamed).
2. PR #5: migrations 001 and 002, `score` next to `confidence`, database tests.
3. Deploy 1.2.0 with the flag off: staging, then production after approval.
Migrations run before the app. Check old and new on one database.
4. Canary in production: pin 0000004=100, make revision kw11 (flag 1.1),
then 90/10. Stop if kw11 has any 500, or more errors than 0000004.
5. Promote: kw11=100, then back to single revision mode.
### Rollback, by part
- Model only: CLASSIFIER_VERSION=1.0 (no new build; during the canary: kw11=0).
- Application: deploy the 1.1.0 digest. Users lose `score` and keywords-1.1.
- Data: none needed for 002. A backfill (003) only after no 1.1.0 runs.
- Contract (remove `confidence`): not in 1.2.x. In 2.0, after clients moved.

Une personne qui révise le plan le juge démontré quand quatre conditions sont remplies. Chaque étape nomme la version qui doit encore fonctionner à ce moment. La base de données change avant le code qui en dépend, et rien n’est retiré dans la même mise en production. Le modèle a un résultat de barrière comparé à la référence de base et peut être désactivé sans construction. Le canari a un poids et des règles d’arrêt écrites avant le début. Un plan limité à « déployer, puis revenir en arrière au besoin » est pas encore démontré. Il ne précise pas ce que le retour en arrière retire.

Vérifiez votre compréhension

Rappel : nommez les trois parties de la barrière d'évaluation et les quatre étapes d'expansion et de contraction du champ score.

Afficher la réponse

La barrière :

  • Minimums : exactitude d’au moins 0,6, chaque rappel d’au moins 0,5.
  • Aucune régression par rapport à la référence de base en production : baisse d’exactitude d’au plus 0,02, baisse de rappel d’au plus 0,1 par catégorie.
  • Billets obligatoires correctement classés, avec la bonne priorité : E019, E026, E033.

Expansion et contraction :

  1. Expansion : la migration 002 ajoute score, qui peut être vide.
  2. Transition : la version 1.2.x écrit les deux champs, l’API marque confidence comme obsolète, et les clients s’adaptent.
  3. Remplissage rétroactif : la migration 003 remplit score à partir de confidence, une fois qu’aucune version 1.1.0 ne tourne.
  4. Contraction : la version 2.0 retire confidence.

Vérifiez votre compréhension

Prédiction : Amira lance un deuxième canari avec un poids de 5 % et envoie 100 requêtes. Le canari reçoit 3 requêtes, dont une renvoie 500. La révision stable en reçoit 97 sans erreur. Que conclut-elle et que fait-elle?

Afficher la réponse

Une erreur sur 3 requêtes représente un taux d’erreur élevé, mais sur très peu de requêtes. Il peut s’agir d’une seule requête problématique ou d’une véritable défaillance. Sa règle d’arrêt dit « tout code 500 sur le canari », alors elle l’arrête d’abord. Avec un poids de 0, les utilisateurs reviennent à la version stable en quelques secondes, sans construction. Elle lit ensuite le journal du canari pour cet identifiant de requête afin de trouver la cause. À 5 % de 100 requêtes, on en attend 5, et 95 % des échantillons donnent de 1 à 10 requêtes. L’explorateur montre le même type de dispersion pour 200. Un petit canari doit donc avoir des règles d’arrêt qui réagissent à chaque erreur, ou recevoir plus de requêtes avant une décision.

Vérifiez votre compréhension

Vérification de fin : démontrez la compatibilité pendant que les anciennes et nouvelles versions de l'application coexistent. Quelles preuves présentez-vous?

Afficher la réponse

Présentez les preuves de l’exercice guidé de Migrations sûres :

  • la sortie de migrate (migrer), avant le démarrage de la nouvelle version;
  • les versions 1.2.0 et 1.1.0 qui fonctionnent sur la même base de données en même temps (docker ps avec les deux conteneurs);
  • une classification de chaque version, toutes deux avec 200 : la nouvelle avec score, l’ancienne sans ce champ;
  • la table, avec une ligne de chaque version (la ligne de la version 1.1.0 a un champ score vide).

Ajoutez le canari de cette leçon : une image avec deux valeurs d’indicateur sur une base de données, les deux répondant aux requêtes. Utilisez les décomptes de model_version, le résumé de split.py, ou le résultat en direct de 185/15 et les journaux par révision. Indiquez les parties que vous n’avez pas vérifiées : ici, le point de terminaison d’historique de chaque version. Chaque résultat porte la mention « en direct » ou « simulé ». La personne qui révise juge le résultat démontré seulement avec des sorties des deux versions obtenues en même temps, pas l’une après l’autre.

Récapitulatif

  • Un indicateur de fonctionnalité (CLASSIFIER_VERSION) place le nouveau code du modèle en production, mais le laisse désactivé. L’activer change les paramètres de la même image.
  • Un canari donne à la nouvelle version une petite part du trafic réel. Dans Azure Container Apps : mode à révisions multiples, deuxième révision avec l’indicateur activé et poids de trafic. Localement : un conteneur canari et split.py.
  • Chaque requête est choisie indépendamment : 10 % de 200 a donné 15 en direct, et de 17 à 27 localement. Attendez-vous à une plage, pas à un nombre exact.
  • Fixez le trafic sur la révision actuelle par son nom avant de créer le canari. Sinon, la nouvelle révision peut recevoir 100 % d’un coup, comme le 7 octobre 2026.
  • Surveillez chaque version séparément (réponses par model_version, journaux par révision et statut), avec des règles d’arrêt écrites avant le début. Faites la promotion, puis revenez au mode à révision unique.
  • Le module : la barrière a bloqué un modèle moins bon (demande de tirage nº 4). L’expansion et la contraction ont permis aux versions 1.1.0 et 1.2.0 de fonctionner sur une même base de données. Le canari a d’abord présenté keywords-1.1 à une requête sur dix.

Réinitialisation. Parcours simulé : arrêtez split.py (Ctrl+C), supprimez le canari (docker rm -f ticket-api-canary) et tout conteneur ticket-api-old. Gardez ou arrêtez les environnements locaux (scripts/stop-local.sh production; ajoutez --volumes pour supprimer aussi leurs bases de données). Rétablissez CLASSIFIER_VERSION dans deploy/local/production.env à la valeur dans Git (git diff l’affiche). Parcours en direct : az containerapp revision list … -o table doit afficher une seule révision active avec un trafic de 100. La commande az containerapp show … --query properties.configuration.activeRevisionsMode doit afficher Single (unique). Gardez les applications pour le module 5, ou supprimez le groupe de ressources si vous arrêtez ici. Pour réinitialiser les fichiers, copiez l’instantané end-of-m04. Il contient le code de la version 1.2.0 tel que mis en production, avec le bogue découvert au module 5.

Leçon suivante : Stratégie de retour en arrière