The DevOps Handbook
Chapitre 7 / 14 · 15 min de lecture

Les fondations du pipeline de déploiement

Bâtir des environnements de type production à la demande, tout mettre en gestion de version et rendre l'infrastructure plus facile à reconstruire qu'à réparer.

Si la deuxième partie du livre traitait de la circulation rapide du travail vue de haut, la troisième s'attaque aux pratiques techniques qui la rendent possible sans semer le chaos en production. L'objectif est de réduire le risque associé à la mise en production des changements en implémentant l'ensemble de pratiques connu sous le nom de livraison continue (continuous delivery). Et tout commence par une fondation que l'on néglige trop souvent : la capacité à obtenir, à chaque étape de la chaîne de valeur (value stream), un environnement vraiment semblable à la production — à la demande, automatisé, en libre-service.

Pourquoi tout part de l'environnement

Trop souvent, la première fois où nous découvrons comment notre application se comporte dans quelque chose qui ressemble à la production, c'est le jour du déploiement — bien trop tard pour corriger les problèmes sans impacter le client. Les équipes ont parfois demandé des environnements de test tôt dans le projet ; mais quand l'exploitation (operations) met des semaines à les livrer, le test arrive trop tard, ou pire, l'environnement est mal configuré, si différent de la production que les problèmes surgissent quand même malgré tous les tests préalables.

À retenir

Dans ce chapitre, le mot environnement désigne tout ce qui se trouve dans la pile applicative à l'exception de l'application elle-même : bases de données, systèmes d'exploitation, réseau, virtualisation, et toutes les configurations associées. C'est précisément la partie qui dérive le plus, et que l'on documente le moins.

L'illustration la plus parlante du livre est le programme Enterprise Data Warehouse, mené par Em Campbell-Pretty dans une grande entreprise australienne de télécommunications en 2009. Devenue directrice générale et sponsor métier de ce programme à 200 millions de dollars, elle hérite de dix flux de travail menés en cascade (waterfall), tous très en retard : un seul a atteint la recette utilisateur (User Acceptance Testing, UAT) dans les temps — et il lui a fallu six mois de plus pour la terminer, avec un résultat bien en deçà des attentes. Cette contre-performance déclenche une transformation Agile qui, après près d'un an, ne produit que de maigres améliorations.

Lors d'une rétrospective à l'échelle du programme, Campbell-Pretty pose la question : « Après réflexion sur toutes nos expériences de la dernière livraison, que pourrions-nous faire pour doubler notre productivité ? » On grommelait depuis longtemps sur le « manque d'engagement métier » ; mais c'est « améliorer la disponibilité des environnements » qui arrive en tête de liste. Avec le recul, l'évidence saute aux yeux : les équipes de développement avaient besoin d'environnements provisionnés pour commencer à travailler, et attendaient jusqu'à huit semaines.

L'équipe constituée pour « intégrer la qualité dans nos processus, plutôt que d'inspecter la qualité après coup » fait une découverte stupéfiante : seulement 50 % du code source des environnements de développement et de test correspondait à ce qui tournait en production. « Soudain, nous avons compris pourquoi nous rencontrions tant de défauts chaque fois que nous déployions notre code dans de nouveaux environnements. Dans chaque environnement, nous corrigions en avançant (fixing forward), mais ces changements n'étaient jamais remis en gestion de version. » Après avoir rétro-ingénieré tous les changements pour les remettre en gestion de version et automatisé la création des environnements, le résultat tombe : le temps pour obtenir un environnement correct passe de huit semaines à un jour.

Note

L'histoire de Campbell-Pretty illustre tout l'éventail des problèmes qui se ramènent à deux causes : des environnements construits de façon incohérente, et des changements qui ne sont pas systématiquement remis en gestion de version. Le reste du chapitre construit les mécanismes qui éliminent ces deux causes à la racine.

Créer à la demande les environnements dev, test et production

La première brique consiste à permettre aux développeurs de faire tourner des environnements de type production (production-like) sur leur propre poste, créés à la demande et en libre-service. Ainsi, ils peuvent exécuter et tester leur code dans des conditions réalistes au quotidien, ce qui leur donne un retour précoce et constant sur la qualité de leur travail.

Plutôt que de simplement documenter les spécifications de la production dans un document ou sur une page de wiki, on crée un mécanisme de build commun (common build mechanism) qui fabrique tous les environnements — développement, test, production. N'importe qui peut alors obtenir un environnement de type production en quelques minutes, sans ouvrir de ticket, et a fortiori sans attendre des semaines. Toutes nos exigences sont codifiées non pas dans des documents ou dans la tête de quelqu'un, mais dans un processus de build automatisé qui incarne le savoir collectif de l'organisation : des environnements stables, sécurisés, dans un état de risque réduit.

Au lieu que l'exploitation construise et configure manuellement l'environnement, on automatise tout ou partie des opérations suivantes :

ApprocheOutils typiques (de la source)
Copier un environnement virtualiséimage VMware, script Vagrant, AMI démarrée dans EC2
Construire depuis le « bare metal »installation PXE depuis une image de référence
Infrastructure-as-code (gestion de configuration)Puppet, Chef, Ansible, Salt, CFEngine
Configuration automatisée du système d'exploitationSolaris Jumpstart, Red Hat Kickstart, Debian preseed
Assembler à partir d'images virtuelles ou de conteneursVagrant, Docker
Provisionner dans un cloud public, privé ou un PaaSAWS, Google App Engine, Microsoft Azure, OpenStack, Cloud Foundry

Parce que nous avons soigneusement défini tous les aspects de l'environnement à l'avance, nous savons non seulement créer de nouveaux environnements rapidement, mais aussi garantir qu'ils seront stables, fiables, cohérents et sécurisés. L'exploitation y gagne, car l'automatisation impose la cohérence et supprime un travail manuel fastidieux et source d'erreurs. Le développement y gagne, car il peut reproduire toutes les parties nécessaires de la production sur son poste, isolé en toute sécurité des services partagés, pour reproduire, diagnostiquer et corriger les défauts — voire expérimenter sur l'infrastructure-as-code elle-même, créant ainsi un savoir partagé entre dev et ops.

Astuce

L'idéal est de trouver les erreurs avant les tests d'intégration, trop tardifs pour donner un retour rapide. Si l'on n'y parvient pas, c'est probablement le signe d'un problème d'architecture : concevoir des systèmes testables — où l'on détecte la plupart des défauts dans un environnement virtuel non intégré, sur le poste du développeur — fait partie intégrante d'une architecture qui supporte la circulation et le retour rapides.

Le dépôt unique de vérité pour tout le système

Une fois la création d'environnements à la demande en place, il faut s'assurer que toutes les parties du système logiciel sont en gestion de version (version control). Un système de gestion de version enregistre les modifications apportées aux fichiers — code source, ressources, documents — par groupes appelés commits ou révisions ; chaque révision, accompagnée de métadonnées (qui, quand), permet de comparer, fusionner et restaurer des états antérieurs. Il minimise le risque en établissant un moyen de revenir à une version précédente connue et fonctionnelle.

Lorsque les développeurs y placent tout leur code et toutes leurs configurations, la gestion de version devient le dépôt unique de vérité (single repository of truth) contenant l'état exact et voulu du système. Mais comme délivrer de la valeur au client exige à la fois le code et les environnements où il tourne, ces environnements doivent eux aussi être en gestion de version. Autrement dit, la gestion de version est l'affaire de tout le monde dans la chaîne de valeur : QA, exploitation, sécurité de l'information (infosec), autant que les développeurs.

À retenir

Pour pouvoir restaurer le service de façon répétable, prévisible et idéalement rapide même en cas d'événement catastrophique, voici ce qu'il faut placer dans le dépôt partagé :

  • tout le code applicatif et ses dépendances (bibliothèques, contenu statique) ;
  • tout script créant des schémas de base de données, des données de référence ;
  • tous les outils et artefacts de création d'environnement (images VMware ou AMI, recettes Puppet/Chef) ;
  • tout fichier de création de conteneurs (définitions Docker, fichiers de composition) ;
  • tous les tests automatisés et scripts de test manuels ;
  • tout script de packaging, de déploiement, de migration de base de données, de provisionnement ;
  • tous les artefacts de projet (exigences, procédures de déploiement, notes de version) ;
  • tous les fichiers de configuration cloud (modèles AWS CloudFormation, DSC Azure, OpenStack HEAT) ;
  • toute configuration d'infrastructure transverse (bus de services, SGBD, fichiers de zone DNS, règles de pare-feu).

Pour les objets volumineux, on s'appuie sur des dépôts d'artefacts (artifact repositories, comme Nexus ou Artifactory), des stockages d'objets (buckets Amazon S3) ou des registres Docker — étiquetés et tagués aux côtés du code source. Et il ne suffit pas de pouvoir recréer un état antérieur de la production : il faut aussi pouvoir recréer les processus de pré-production et de build, donc placer en gestion de version tout ce dont ils dépendent, jusqu'aux compilateurs et aux outils de test.

Note

Découverte contre-intuitive du State of DevOps Report 2014 de Puppet Labs : l'usage de la gestion de version par l'exploitation était le meilleur prédicteur à la fois de la performance informatique et de la performance organisationnelle — meilleur que l'usage de la gestion de version par le développement. Pourquoi ? Parce que, dans presque tous les cas, il y a des ordres de grandeur plus de paramètres configurables dans l'environnement que dans le code. C'est donc l'environnement qui a le plus besoin d'être versionné. La gestion de version sert aussi de moyen de communication : voir les changements des autres réduit les surprises et renforce la confiance.

Rendre l'infrastructure plus facile à reconstruire qu'à réparer

Quand on sait reconstruire et recréer applications et environnements à la demande, on peut, en cas de problème, les reconstruire plutôt que les réparer. Presque toutes les grandes exploitations web (plus d'un millier de serveurs) le font ; mais la pratique vaut même avec un seul serveur en production.

Bill Baker, ingénieur distingué chez Microsoft, a résumé le basculement d'une formule devenue célèbre :

Astuce

« Avant, on traitait les serveurs comme des animaux de compagnie : on leur donne un nom, et quand ils tombent malades, on les soigne pour les remettre sur pied. [Désormais], on traite les serveurs comme du bétail : on les numérote, et quand ils tombent malades, on les abat. » C'est l'essence du cattle not pets — la fin des serveurs reliques et de la configuration dérivée.

Avec des systèmes de création d'environnement répétables, on augmente la capacité simplement en ajoutant des serveurs en rotation (montée en charge horizontale). On évite surtout le désastre qui survient inévitablement quand il faut restaurer un service après la panne catastrophique d'une infrastructure irreproductible, fruit d'années de modifications manuelles et non documentées. Pour garantir la cohérence, tout changement de production (configuration, patch, mise à jour) doit être répliqué partout automatiquement — dans la production, la pré-production et tout nouvel environnement.

Plutôt que de se connecter manuellement à des serveurs pour les modifier, on procède de manière à ce que tout changement soit répliqué partout et placé en gestion de version. Deux moyens : s'appuyer sur des systèmes de configuration automatisés (Puppet, Chef, Ansible, Salt, Bosh) ; ou créer de nouvelles machines virtuelles, ou conteneurs, à partir du mécanisme de build et les déployer en détruisant les anciens.

  AVANT — réparer (« pets »)        APRÈS — reconstruire (« cattle »)

  serveur unique, nommé             flotte numérotée, jetable
        │                                  │
   dérive de config                  toute modif → gestion de version
   (snowflake, relique)                     │
        │                                  ▼
   on patche à la main               on recrée depuis zéro
        │                                  │
   panne → restauration              instance malsaine → on la tue,
   improvisée, irreproductible       on en relance une propre

Ce second motif est ce qu'on appelle l'infrastructure immuable (immutable infrastructure) : les changements manuels en production ne sont plus autorisés ; le seul moyen de changer la production est de remettre le changement en gestion de version et de recréer le code et les environnements à partir de zéro. Aucune variance ne peut alors s'infiltrer. Pour empêcher les dérives, on peut désactiver les connexions distantes aux serveurs de production (sauf urgence, avec envoi automatique du journal de console à l'équipe), ou détruire et remplacer régulièrement les instances — ce qui efface toute modification appliquée à la main et incite chacun à passer par la voie correcte. On réduit ainsi systématiquement les chemins par lesquels l'infrastructure peut dériver de son état connu et sain (configuration drift, artefacts fragiles, « œuvres d'art », snowflakes).

Piège courant

Il faut aussi maintenir les environnements de pré-production à jour : les développeurs doivent rester sur l'environnement le plus récent. Ils préfèrent souvent rester sur d'anciens environnements par crainte qu'une mise à jour ne casse l'existant — mais c'est précisément l'inverse qu'il faut faire. Mettre à jour fréquemment, c'est trouver les problèmes au plus tôt dans le cycle de vie, là où ils coûtent le moins cher.

L'illustration emblématique est Netflix, dont les instances AWS ont en moyenne vingt-quatre jours d'âge, 60 % ayant moins d'une semaine. L'infrastructure n'y est jamais une relique : elle est constamment recyclée, ce qui rend toute dérive impossible et fait de la reconstruction le mode de réparation par défaut.

Faire entrer « tourner en production » dans la définition de « terminé »

Maintenant que les environnements se créent à la demande et que tout est en gestion de version, l'objectif est de s'assurer qu'on les utilise effectivement dans le travail quotidien. Il faut vérifier que l'application tourne comme prévu dans un environnement de type production bien avant la fin du projet ou le premier déploiement.

Les méthodes modernes prescrivent des intervalles de développement courts et itératifs, à l'opposé de l'approche « big bang » de la cascade : en règle générale, plus l'intervalle entre déploiements est long, plus les résultats sont mauvais. Dans Scrum, un sprint est un intervalle borné dans le temps (un mois ou moins) au bout duquel on doit livrer du « code fonctionnel et potentiellement livrable ». Les auteurs proposent d'étendre la définition de terminé (definition of done) au-delà du seul fonctionnement correct du code :

  Définition de « terminé »

  AVANT :  code intégré, testé, fonctionnel et potentiellement livrable

  APRÈS :  code intégré, testé, fonctionnel et potentiellement livrable,
           DÉMONTRÉ dans un environnement de TYPE PRODUCTION
           (idéalement sous charge réaliste, avec jeu de données réaliste)

Autrement dit, on n'accepte le travail comme terminé que lorsqu'il a pu être construit, déployé et confirmé comme fonctionnant comme attendu dans un environnement de type production — et non quand un développeur croit qu'il est terminé. Cela évite la situation classique où une fonctionnalité est dite « terminée » au seul motif qu'elle tourne sur le portable d'un développeur, et nulle part ailleurs.

En faisant écrire, tester et exécuter le code par les développeurs eux-mêmes dans un environnement de type production, l'essentiel du travail d'intégration du code et des environnements se fait au quotidien, plutôt qu'à la fin de la version. Dès le premier intervalle, l'application est démontrée comme fonctionnant correctement, code et environnement ayant déjà été intégrés ensemble de nombreuses fois — idéalement de façon entièrement automatisée, sans bricolage manuel. Mieux : à la fin du projet, on aura déployé et exécuté le code dans des environnements de type production des centaines, voire des milliers de fois, ce qui donne la quasi-certitude que la plupart des problèmes de déploiement ont déjà été trouvés et corrigés.

L'idéal est d'utiliser les mêmes outils — supervision (monitoring), journalisation (logging), déploiement — en pré-production qu'en production. On gagne ainsi une familiarité et une expérience qui faciliteront non seulement le déploiement, mais aussi le diagnostic et la correction une fois le service en production. En permettant à dev et ops d'acquérir une maîtrise partagée de l'interaction code/environnement, et en pratiquant les déploiements tôt et souvent, on réduit fortement les risques liés aux mises en production et l'on élimine toute une classe de défauts opérationnels, de sécurité et d'architecture habituellement détectés trop tard pour être corrigés.

Le pipeline, vu d'ensemble

Les quatre briques de ce chapitre s'enchaînent en un flux : tout part de la gestion de version, chaque commit déclenche une cascade automatisée qui aboutit, à chaque étape, à un environnement de type production identique au précédent.

  Gestion de version  (TOUT : code + config système + config appli
                       + scripts de build/déploiement + état de l'infra)


   commit  ──►  mécanisme de build commun

        ┌──────────────┼───────────────────┐
        ▼              ▼                     ▼
   env. DEV       env. TEST            env. PRODUCTION
   (à la demande, en libre-service, créés par LE MÊME mécanisme)
        │              │                     │
        └──────────────┴─────────────────────┘

        identiques, de type production, recréés depuis zéro
        plutôt que réparés (infrastructure immuable)

Attention

Le piège récurrent reste la dérive : un changement appliqué « juste cette fois » directement sur un serveur, hors gestion de version, et l'on retombe dans le scénario Enterprise Data Warehouse — 50 % d'écart entre le code testé et le code en production. La règle est sans exception : si ce n'est pas en gestion de version, cela n'existe pas. La version control n'est pas réservée au code ; elle concerne la configuration, les scripts et l'état de l'infrastructure tout entier.

La circulation rapide du travail exige que n'importe qui puisse obtenir des environnements de type production à la demande. En laissant les développeurs les utiliser dès les premières étapes du projet, on réduit fortement le risque de problèmes plus tard — une démonstration de plus que l'exploitation peut rendre les développeurs nettement plus productifs. On ancre cette pratique en l'inscrivant dans la définition de « terminé ». En plaçant tous les artefacts de production en gestion de version, on se dote d'une source unique de vérité qui permet de recréer toute la production de façon rapide, répétable et documentée. Et en rendant l'infrastructure plus facile à reconstruire qu'à réparer, on accélère la résolution des problèmes comme l'extension de capacité. Ces fondations posées ouvrent la voie à l'automatisation complète des tests, sujet du chapitre suivant.

À retenir

  • Il faut pouvoir créer à la demande, en libre-service et de façon automatisée des environnements de type production (production-like) identiques à chaque étape de la chaîne de valeur, depuis le poste du développeur jusqu'à la production — sinon on découvre le comportement réel de l'application le jour du déploiement, trop tard.
  • Plutôt que de documenter la production dans un wiki, on crée un mécanisme de build commun et de l'infrastructure-as-code (Puppet, Chef, Ansible, Docker, modèles cloud) qui codifie le savoir de l'organisation dans un processus reproductible.
  • On met en gestion de version TOUT, pas seulement le code : configuration système, configuration applicative, scripts de build et de déploiement, et l'état de l'infrastructure — créant un dépôt unique de vérité. L'usage de la version control par l'exploitation est le meilleur prédicteur de performance, car l'environnement a bien plus de paramètres que le code.
  • On rend l'infrastructure plus facile à reconstruire qu'à réparer : du bétail, pas des animaux de compagnie (cattle not pets), via l'infrastructure immuable où le seul moyen de changer la production passe par la gestion de version — fin des serveurs reliques et des snowflakes (chez Netflix, l'âge moyen d'une instance est de vingt-quatre jours).
  • On fait entrer dans la définition de terminé (definition of done) le fait de tourner dans un environnement de type production, idéalement sous charge et données réalistes — pas seulement « ça marche sur mon portable ».
  • L'étude de cas Enterprise Data Warehouse (Em Campbell-Pretty) montre les ravages d'environnements incohérents : 50 % d'écart avec la production, huit semaines d'attente ramenées à un jour une fois tout versionné et automatisé.
  • En utilisant les mêmes outils (supervision, journalisation, déploiement) en pré-production qu'en production et en déployant tôt et souvent, on bâtit une maîtrise partagée dev/ops de l'interaction code/environnement et l'on élimine en amont une classe entière de défauts opérationnels, de sécurité et d'architecture, habituellement détectés trop tard pour être corrigés.