Comment Slack a développé son usage de Terraform

Slack Terraform

Slack revient sur la manière dont il a généralisé, à renfort d’outils maison, l’usage de Terraform sur son infrastructure cloud.

Un peu de GCP, de DigitalOcean, de NS1… et beaucoup d’AWS. Ainsi se présente l’infrastructure de Slack. L’entreprise en assure la gestion avec Terraform. Ses équipes techniques sont revenues sur la mise en place de l’outil.

Slack n’utilisait initialement qu’un compte AWS. Avec un fichier d’état par région et un fichier d’état séparé pour les services globaux comme IAM et CloudFront. Les environnements dev et prod avaient les mêmes paramètres.

L’infrastructure se développant, il fallut créer des comptes dédiés à des équipes et à des services. Pour déployer les changements fusionnés dans les fichiers d’état, Slack mit en place des pipelines Jenkins à deux étapes (plan, apply). À l’époque, l’équipe Ops était responsable de l’infra, de la codebase Terraform et des fichiers d’état.

Aujourd’hui, il n’y a plus d’équipe Ops centrale. Une équipe Cloud Foundations gère la plate-forme Terraform. Il y a désormais un fichier d’état par région dans chaque compte enfant, et toujours un fichier d’état séparé pour les services globaux. Les plus gros services peuvent aussi avoir le leur, afin de limiter la quantité de ressources qu’aurait à gérer un même fichier d’état. Le tout est stocké dans un bucket S3 avec gestion des versions. Les nœuds Jenkins dédiés aux pipelines ont les rôles IAM nécessaires pour créer des ressources dans les comptes enfants.

Pour permettre aux ingénieurs de tester les changements et les nouveaux modules, Slack a mis à leur disposition une UI web pour lancer des « box Ops ». Plus précisément, des instances dont ils choisissent la taille, la région et la capacité de stockage. Elles reproduisent l’environnement des workers Jenkins, mais n’ont qu’un accès en lecture seule aux comptes AWS.

Une montée de version progressive depuis Terraform 0.11

Au départ, Slack ne prenait en charge qu’une version de Terraform (0.11). En 2019, il mit à jour ses fichiers d’état pour les rendre compatibles avec la version 0.12. De l’un à l’autre, il y eut des changements de syntaxe. La procédure nécessita un trimestre. Dans ce cadre, on développa un wrapper qui vérifiait, dans chaque fichier d’état, le version.tf, et sélectionnait le binaire Terraform en conséquence.

Slack resta près de deux ans sur Terraform 0.12. Puis décida de passer à la version 0.13. Et, en parallèle, de mettre à jour le provider AWS (de la version 3.74.1 à la version 4.x).

Pour pouvoir déployer de multiples versions des binaires et des plug-in et les sélectionner selon les fichiers d’état, chaque box fut dotée d’un fichier de configuration spécifiant la version de Terraform. Et le wrapper fut mis à jour.

Terraform 0.13+ permet d’intégrer plusieurs versions d’un même provider dans le dossier des plug-in. Cela facilita la mise à jour parallèle du binaire Terraform et du provider AWS. Si une nouvelle version du premier entraînait des breaking changes sur un fichier d’état donné, on pouvait épingler la version précédente en attendant un correctif.

Une fois tous les fichiers d’état mis à jour vers les dernières versions des providers, Slack retira les versions épinglées. Tout en encourageant les équipes responsables de ses services à ne pas épingler, autant que possible, de versions spécifiques.

De Bash à Go, l’évolution des outils internes

Pour gérer les mises à jour de Terraform, un outil fut développé en interne. Pour un état donné, il :

– Contrôle la version actuelle
– Vérifie s’il existe des changemenets non appliqués
– Contrôle si le fichier d’état est utilisé comme état distant (Terraform 0.12 ne peut pas recherche d’état distant sur des fichiers d’état Terraform 0.13+)
– Vérifie si après la mise à jour, le fichier d’état peut exécuter un Terraform plan

L’outil était initialement un script Bash. Slack a fini par y substituer un binaire Golang. Les bibliothèques hclsyntx, gohcl et terraform-exec ont simplifié la lecture de la configuration, le chargement d’éléments dans des structures de données Go et l’exécution d’opérations plan. On a par ailleurs doté ce binaire de capacités telles que le contrôle des arbres de dépendance de modules.

Fichiers d’état et module se trouvent dans un repo unique. Slack utilise le fichier CODEOWNERS pour assigner les révisions aux équipes.

À l’origine, pour les modules, on utilisait un chemin relatif. Avantage : des tests facilités. Inconvénient : le rique de casser les autres fichiers d’état utilisant le même module.
Dans ce contexte, Slack a parfois privilégié les chemins GitHub. Cela lui a permis de lier un fichier d’état à une version spécifique de module. Contrepartie : chaque opération plan ou apply doit cloner tout le repo. Les hashs Git ne sont, en outre, pas les plus évidents à lire et à comparer.

Un schéma spécifique au nom de la conformité

Dorénavant, un pipeline se déclenche à chaque changement fusionné sur le repo Terraform. Les modules mis à jour sont empaquetés (tarball) et transférés vers le compartiment S3, en tant que nouvelle version. Ils s’accompagnent d’un fichier versions.json qui contient l’historique du module. Et d’un outil tf-module-viewer qui permet de lister les versions.

Cette approche n’est implémentée que pour les fichiers d’état pour lesquels il existe des exigences de conformité. Les autres emploient encore la technique du chemin relatif.

Lors de l’ajout d’un module au catalogue, un outil Terraform Smart Planner demande à l’utilisateur de le désépingler afin de tester tous les fichiers d’état qui en font usage. Aucun mécanisme ne force néanmoins cette démarche, quand bien même un changement peut casser un fichier d’état. Les utilisateurs ne le découvriraient alors qu’après avoir mis à jour la version de module épinglée. Bien qu’aisé, le rollback reste un désagrément. Et il suppose d’uploader une version corrigée du module dans le catalogue avant d’essayer de l’utiliser.

Pour créer les pipelines, Slack utilise une bibliothèque maison (Groovy) et le plug-in Jenkins Job DSL. Lorsqu’une équipe crée un fichier d’état, elle crée un nouveau pipeline ou elle l’ajoute dans un pipeline existant. Une démarche rendue possible par l’ajout d’un script DSL dans un dossier que Jenkins surveille. Pour faciliter les choses, on y a couplé un programme qui crée les scripts à partir de fichiers YAML.

Dans les pipelines, l’opération plan doit intervenir avant l’apply. Cela implique de fusionner au prélable les changements sur la branche master du repo Terraform. On n’est donc pas à l’abri qu’un changement indésirable bloque le pipeline. Là intervient Terraform Smart Planner. Pour chaque changement, il recherche les fichiers d’état affectés, exécute une opération plan sur chacun et intègre le résultat dans le PR. Même fonctionnement lors de la mise à jour d’un module.
Intégrer l’output dans le PR permet aux réviseurs de constater quelles ressources un changement affecte. Cela les aide aussi à découvrir tout fichier d’état indirectement affecté.

Illustration © zeeika – Adobe Stock