Guide d'ingénierie
Méthodologie Twelve-Factor App pour les services cloud-native modernes
Utilisez les principes Twelve-Factor comme contraintes de conception opérationnelle pour la conception d'application cloud-native, puis ajoutez des pratiques explicites de fiabilité, de sécurité et d'observabilité pour les systèmes distribués.
Sur cette page
La méthodologie Twelve-Factor App reste utile pour rendre les services portables, reproductibles et plus simples à exploiter. Ce n'est ni une liste de contrôle Kubernetes ni une certification d'architecture. Kubernetes peut fournir des primitives utiles, mais un déploiement peut toujours comporter une configuration cachée, un état local persistant, un comportement de mise en production dangereux ou des dépendances opaques.
Pour les ingénieurs DevOps et logiciels, l'objectif pratique est plus simple : construire un service configurable par environnement, démarrable plusieurs fois, observable sous charge et récupérable sans dépendre d'une machine ou de la mémoire d'un opérateur. Cette conception améliore la sécurité des mises en production et offre aux équipes des options plus claires lorsque la latence augmente, qu'une région se dégrade ou que le trafic croît en périphérie.
Ce dernier point compte particulièrement à l'échelle où opère un orchestrateur d'edge managé : lorsqu'un point d'entrée unique dessert de nombreux services à travers plusieurs fournisseurs best-of-breed, une origine qui viole ces contraintes transforme un changement de cache de routine, un failover ou un basculement de trafic en incident visible par le client. L'orchestration et l'observabilité MYO absorbent une grande partie de la variabilité en périphérie, mais elles ne remplacent pas une origine qui redémarre proprement, externalise son état et expose des signaux qu'un opérateur peut exploiter.
À quoi sert la méthodologie Twelve-Factor App
Les facteurs d'origine couvrent le code source, les dépendances, la configuration, les services externes, la séparation build/release/run, les processus, la liaison de port, la concurrence, la jetabilité, la parité développement-production, les logs et les processus administratifs. Ils répondent à des défaillances récurrentes de livraison :
- Une application se comporte différemment parce que sa configuration est intégrée à son build.
- Un redémarrage perd un état de session ou de travail qu'aucune autre instance ne peut récupérer.
- Un déploiement inclut une dépendance non examinée ou une modification de schéma irréversible.
- Les opérateurs ne peuvent pas relier une requête lente à la dépendance ou à la mise en production qui l'a provoquée.
Considérez les facteurs comme des questions auxquelles répondre lors de la conception et de la revue, et non comme des raisons d'ajouter des outils. Une API sans état peut par exemple rester peu performante si chaque requête traverse de manière synchrone une région distante vers une base de données. La portabilité n'élimine pas les lois de la physique réseau. Ces principes twelve-factor restent des contraintes de conception à réévaluer à chaque changement d'architecture, pas une case à cocher une seule fois.
Kubernetes est une plateforme d'implémentation, pas un label de conformité
Kubernetes peut planifier des réplicas, injecter de la configuration et redémarrer des conteneurs défaillants. Il ne rend pas automatiquement une application Twelve-Factor. Le code applicatif et les politiques opérationnelles déterminent toujours la gestion de l'état, les délais d'attente des dépendances, les migrations, les logs et l'arrêt progressif.
Appliquer les facteurs à un service moderne
Un code source et des dépendances explicites
Conservez un seul code source versionné pour un service déployable. Produisez des artefacts immuables à partir d'un graphe de dépendances verrouillé, enregistrez la révision source et analysez les dépendances de l'application comme celles de l'image de base. Ne dépendez pas d'un paquet déjà installé sur un nœud, d'un tag d'image mutable ou d'un répertoire source monté localement.
Dans un flux de travail conteneurisé, construisez une fois et promouvez le même digest d'image entre les environnements. Un tag latest mutable empêche de reproduire une mise en production. Générez un SBOM lorsque votre processus de chaîne d'approvisionnement l'exige, et faites des mises à jour de dépendances une activité habituelle plutôt qu'une activité réservée aux urgences.
La configuration appartient hors de l'artefact
Placez les valeurs propres au déploiement, telles que les endpoints, les feature flags, les identifiants et les délais d'attente, dans une configuration spécifique à l'environnement. Conservez les secrets dans un système de gestion des secrets dédié plutôt que dans le contrôle de source ou une couche d'image. Validez la configuration requise au démarrage, sans journaliser les valeurs secrètes en cas d'échec de validation.
La configuration doit également être bornée. Une variable d'environnement arbitraire qui modifie l'exactitude du cache ou l'autorisation sans revue constitue un chemin de code de production. Utilisez une configuration typée, des valeurs par défaut sûres pour l'environnement, une responsabilité définie et un historique des changements. Consultez Configuration et secrets cloud-native pour les détails d'implémentation.
Les services externes sont des ressources attachées
Les bases de données, files d'attente, stockages d'objets, fournisseurs d'identité et API tierces doivent être adressés par la configuration plutôt que par des noms d'hôte codés en dur ou des hypothèses locales. Cela rend la substitution possible, mais ne rend pas chaque dépendance interchangeable. Documentez le modèle de cohérence, les quotas, l'authentification, le comportement en cas de défaillance et le budget de latence de chaque service.
Utilisez des pools de connexions et des clients bornés. Un pool correct pour un pod peut épuiser une base de données lorsqu'un autoscaler horizontal crée cinquante pods. Budgétez les connexions à l'échelle de la flotte, réservez de la capacité pour les migrations et la récupération, et fermez les clients inactifs lors de l'arrêt.
Build, release et run sont des préoccupations distinctes
Le build produit un artefact. Une release associe cet artefact à une configuration examinée et à des décisions de migration. Run démarre la release. Séparez ces étapes afin qu'un incident de production puisse être rattaché à un digest d'image, une version de configuration et un enregistrement de changement.
Évitez de télécharger des dépendances applicatives au démarrage d'un conteneur et d'éditer des conteneurs en cours d'exécution. Utilisez un contrôleur de déploiement, un flux GitOps ou un autre mécanisme auditable pour déclarer la release attendue. Les plans de retour arrière doivent considérer la compatibilité des données : revenir à une version antérieure du code après une migration de schéma destructive peut être dangereux.
Là où la méthodologie Twelve-Factor App reste silencieuse
Trois des facteurs d'origine sont faciles à considérer comme déjà résolus dès qu'un service tourne sur une plateforme de conteneurs, parce que la plateforme semble s'en occuper. Ils exigent pourtant toujours une décision explicite, et c'est en sautant cette décision que naissent la plupart des services « twelve-factor de façade ».
Le port binding et la concurrence ne se résument pas au nombre de réplicas
La liaison de port (facteur VII) signifie que le processus est autonome : il ouvre lui-même son port et parle lui-même son protocole, plutôt que d'être injecté dans un serveur web configuré de l'extérieur. Dans un service mesh ou une configuration à sidecar-proxy, l'application doit toujours posséder son propre listener et son propre endpoint de santé, indépendants du proxy, pour qu'un redémarrage de sidecar ne rende pas silencieusement un processus sain inaccessible, sans signal expliquant pourquoi. La concurrence (facteur VIII) signifie une montée en charge par le modèle de processus : de nombreux petits réplicas sans état plutôt qu'un gros processus multi-thread portant des files ou des ordonnanceurs en mémoire. Ce choix, pris tôt, détermine si un PodDisruptionBudget et un autoscaler ont réellement quelque chose à piloter, ou si la flotte n'est en réalité qu'un processus surdimensionné déguisé en Deployment.
La parité dev/prod est une décision sur les services externes, pas un fichier docker-compose
Le facteur X demande aux équipes de garder le développement, la préproduction et la production proches dans le temps (déployer fréquemment, pas par lots massifs), en personnel (celles et ceux qui écrivent le code restent proches de celles et ceux qui l'exploitent) et en outils (le même type de services externes partout). L'échec de parité le plus courant est silencieux : le développement local tourne sur SQLite ou un cache en mémoire, la production tourne sur PostgreSQL et Redis, et la différence de sémantique transactionnelle ou de comportement d'éviction ne se révèle que sous la concurrence de production. Des instances éphémères et conteneurisées des véritables services externes en développement et en CI referment cet écart à faible coût ; un test de contrat contre une dépendance réelle détecte ce qu'un mock ne peut pas détecter.
Processus d'administration et conception d'application cloud-native pour les déploiements progressifs
Exécutez les tâches ponctuelles dans la release, pas à côté d'elle
Le facteur XII, les processus d'administration, est le plus souvent négligé parce qu'il ressemble à de l'outillage plutôt qu'à de la conception d'application. Une migration ou une session console lancée depuis un poste local ou une ancienne image peut utiliser une version de dépendance ou une hypothèse de schéma différente de celle de la release qu'elle est censée accompagner. Exécutez ces tâches avec le même digest d'image et la même révision de configuration que la release — typiquement sous la forme d'un Job Kubernetes qui reprend le digest épinglé du Deployment, avec backoffLimit: 0 pour qu'une migration en échec échoue immédiatement plutôt que de retenter à l'aveugle contre un schéma à moitié appliqué.
Les déploiements progressifs exigent une fenêtre de compatibilité, pas seulement une probe au vert
La conception d'application cloud-native ajoute une contrainte que la méthodologie d'origine ne fait qu'impliquer : comme une mise à jour progressive fait tourner l'ancienne et la nouvelle release côte à côte, les deux versions doivent tolérer le schéma, le contrat d'API et les charges utiles d'événements de l'autre pendant toute la fenêtre du rollout, pas seulement au moment où le trafic bascule. Utilisez une séquence expand/contract pour les changements de schéma — colonne nullable, code qui écrit dans les deux, backfill, code qui ne lit plus que la nouvelle colonne, puis suppression de l'ancienne dans une release ultérieure. Versionnez les API et les événements de façon additive, et traitez tout ce qui repose déjà dans un cache comme si l'une ou l'autre release avait pu l'y écrire : un rollout qui passe toutes les probes de readiness peut malgré tout corrompre des données en plein déploiement si cette fenêtre est ignorée.
Concevoir des processus remplaçables
Exécutez les processus de service comme des workers jetables. Conservez les données de session des requêtes dans un stockage partagé, correctement sécurisé, ou n'encodez côté client qu'un état protégé en intégrité lorsque cela est adapté. N'utilisez pas le système de fichiers d'un pod, une map en mémoire ou le cookie d'affinité d'un load balancer comme seul enregistrement d'une action métier importante.
La jetabilité exige à la fois un démarrage rapide et un arrêt progressif :
- Gérez
SIGTERMet cessez d'accepter de nouveaux travaux. - Maintenez l'endpoint de readiness à false pendant le drainage.
- Terminez ou annulez de manière sûre le travail en cours dans le délai de grâce de terminaison.
- Prolongez la visibilité de la file ou restituez le travail avant la sortie.
- Fermez proprement les connexions de base de données et HTTP.
Les probes Kubernetes de readiness et de liveness ont des objectifs distincts. Une probe de readiness contrôle si un pod reçoit du trafic. Une probe de liveness redémarre un processus considéré comme bloqué. Ne faites pas échouer une probe de liveness uniquement parce qu'une base de données partagée est temporairement indisponible : redémarrer chaque pod peut amplifier une panne externe. Une startup probe est souvent préférable pour une initialisation lente.
Rendre explicites la latence et la montée en charge
Les réplicas horizontaux améliorent le débit uniquement lorsqu'une ressource contrainte ne se trouve pas ailleurs. Avant de monter en charge, identifiez le signal limitant : CPU, mémoire, nombre de connexions, âge de file, saturation de base de données ou latence aval. Un autoscaling basé uniquement sur le CPU peut ajouter des réplicas pendant un incident de dépendance lente, augmentant les tentatives et la pression sur les connexions sans rétablir les requêtes utilisateur.
Définissez une échéance de requête depuis le point d'entrée exposé aux utilisateurs et allouez des budgets plus petits à chaque appel aval. Propagez l'annulation ainsi qu'un contexte de corrélation ou de trace. Pour un parcours interactif, mettez les réponses sûres en cache près des utilisateurs, gardez les appels dynamiques près de leurs données lorsque possible et évitez les chaînes sérielles d'appels distants. Un cache de périphérie peut réduire le travail à l'origine et la latence mondiale pour le contenu pouvant être mis en cache, mais il ne peut pas corriger une dépendance d'origine non bornée.
Les indicateurs de niveau de service utiles incluent la disponibilité, la latence des requêtes réussies, la saturation, le délai de file et le taux d'erreur des dépendances. Instrumentez-les avec des traces, des métriques et des logs structurés avant un incident. Le Google SRE workbook explique comment transformer ces signaux en objectifs de niveau de service et en budgets d'erreur.
Corréler une requête unique à travers chaque fournisseur qu'elle traverse
Les traces d'un service twelve-factor s'arrêtent à sa propre lisière. Les requêtes d'Acme Shop traversent encore un résolveur DNS, un cache de périphérie et parfois une seconde région avant d'atteindre catalog-api, et rattacher une requête lente au bon maillon est exactement la corrélation multi-fournisseurs qu'un orchestrateur managé comme MYO est conçu pour maintenir sur une seule frise temporelle. Cela ne remplace pas les indicateurs de niveau de service et les traces décrits plus haut ; cela évite qu'ils ne se transforment en cinq tableaux de bord disjoints pendant un incident.
Modes de défaillance courants
- Sessions avec état dans un réplica : un redémarrage ou un événement de mise à l'échelle déconnecte les utilisateurs ou perd des transactions. Déplacez l'état durable vers un service partagé et rendez les opérations idempotentes.
- Variables d'environnement traitées comme un coffre-fort : des manifests de déploiement divulgués, une sortie de débogage ou des autorisations de lecture étendues exposent des identifiants. Utilisez une distribution de secrets suivant le moindre privilège et une rotation.
- Un endpoint de readiness pour tout : un incident de dépendance transitoire retire toute la capacité. Vérifiez si cette instance peut servir la route, puis utilisez un circuit breaker et un comportement dégradé pour les dépendances optionnelles.
- Tentatives illimitées : les tentatives augmentent le trafic précisément quand une dépendance est surchargée. Définissez des échéances, ne réessayez que les opérations sûres, utilisez un backoff exponentiel avec jitter et imposez un budget de tentatives.
- Logs uniquement dans le système de fichiers d'un conteneur : les éléments de preuve disparaissent lors du replanification. Émettez des événements structurés vers un pipeline de collecte durable avec des identifiants de trace et de requête.
- Tâches d'administration exécutées hors de la release : une migration lancée depuis un poste local ou une image obsolète utilise une version de dépendance différente de celle du code qui dépend de son résultat. Empaquetez les tâches ponctuelles avec la release et exécutez-les depuis le même digest épinglé.
- Parité dev/prod ignorée pour les services externes : une base de données ou un cache allégé en développement masque un mode de défaillance propre à la production. Alignez les types et versions de services externes entre environnements, et testez contre une instance réelle, pas seulement un mock.
Une séquence d'adoption pratique
Choisissez un service critique et cartographiez sa configuration de démarrage, son état durable, ses dépendances, son chemin de release, son budget de latence et son comportement d'arrêt. Corrigez d'abord l'hypothèse la plus dangereuse, généralement une configuration cachée, un état local ou un client de dépendance non borné. Testez ensuite le remplacement d'un pod, un délai d'attente de dépendance, un retour arrière et une augmentation du trafic dans un environnement réaliste.
Intégrez le résultat à la définition de terminé du service : un build épinglé, un contrat de configuration documenté, des probes, un arrêt progressif, des budgets de dépendances, de la télémétrie et une stratégie de release réversible. Réévaluez-le lorsque le service ajoute une région, une file d'attente, un fournisseur externe ou une route servie en périphérie.
Mettre en pratique : l'API catalogue d'Acme Shop
Pour Acme Shop, l'objectif est qu'un opérateur puisse reproduire, remplacer, observer et annuler catalog-api sans dépendre d'un état métier local à un pod, dans un namespace hors production doté d'un registre de conteneurs et d'un accès aux rollouts et aux logs.
catalog-api sert les fiches produit publiques. PostgreSQL, Redis et le service d'images sont des ressources attachées ; un cache local n'est toléré que parce qu'un cache froid ne peut pas faire perdre une mise à jour de catalogue. Un seul dépôt et un seul lockfile produisent un digest d'image unique, prouvé par la révision source et le SBOM ; les endpoints, identifiants et délais d'attente sont des entrées de release formalisées par un contrat typé ; le digest validé est promu avec une révision de configuration tracée par le déploiement ; les pods ne détiennent aucune session durable et se drainent avant de sortir ; les logs structurés sortent du stdout et les tâches ponctuelles sont versionnées avec un identifiant de trace. La référence originale des facteurs reste l'autorité ; cette cartographie n'en est qu'une lecture pratique au niveau du service.
Le Deployment épingle catalog-api par digest plutôt que par tag, fixe un terminationGracePeriodSeconds de 30 secondes, expose une probe de readiness sur /readyz et déclenche un hook preStop qui appelle la commande de drainage avant que Kubernetes ne coupe le trafic ; une variable REQUEST_DEADLINE_MS de 900 millisecondes borne chaque requête entrante. N'utilisez jamais de tag mutable pour une release approuvée. La migration de schéma suit le même principe : le Job de backfill de la colonne search_vector épingle le digest que le Deployment s'apprête à exécuter, avec backoffLimit: 0, et se termine avec la condition Complete avant la mise à jour du Deployment — la preuve que migration et release partagent la même identité.
Validez en déclenchant kubectl rollout restart deployment/catalog-api -n acme-shop-sandbox pendant une lecture de catalogue synthétique : la readiness doit retomber avant la terminaison, aucun état durable ne doit être perdu, et le p95 doit rester dans le budget convenu. En cas d'échec, mettez le rollout en pause, restaurez le digest et la configuration précédents, et diagnostiquez via les identifiants de trace plutôt que de redémarrer tous les réplicas.
Les symptômes récurrents pointent chacun vers une cause précise : un panier perdu après redémarrage trahit une session locale au pod ; de nouveaux réplicas qui épuisent PostgreSQL trahissent un pool non plafonné multiplié par le nombre de réplicas ; des pods qui redémarrent tous ensemble pendant un pic de latence trahissent une probe de liveness qui appelle la base de données ; un rollback qui ne corrige rien trahit une configuration qui a changé avec l'image ; un Job de migration qui échoue en cours de route trahit un digest ou une révision de configuration désynchronisés de la release ; et des données partagées corrompues en plein rollout trahissent un changement de schéma, de cache ou d'événement non compatible avec les deux releases à la fois — la correction passe alors par une séquence expand, backfill, contract plutôt que par une réécriture directe.
Références faisant autorité
- The Twelve-Factor App
- Evolving the Twelve-Factor App
- Kubernetes: Jobs
- Kubernetes: Container lifecycle hooks and termination
- Kubernetes: Probes
- Google SRE Workbook
- OpenTelemetry documentation
Reliez la conception d'application cloud-native à l'architecture de livraison
Échangez avec Optimi sur l'art de faire cohabiter un service qui redémarre proprement, externalise son état et émet des signaux clairs, avec la Performance, la Sécurité et la Visibilité du chemin de livraison placé devant lui — une seule vision dans MYO, pas un tableau de bord par fournisseur.
Discuter de l'architecture de performance