Dans notre métier, il est une bonne pratique d’utiliser de l’Infrastructure as Code et il n’est pas rare de faire le choix d’utiliser Terraform. Outre le fait de fournir une interface déclarative efficace qui s’est grandement améliorée avec l’arrivée du langage HCL2, l’outil permet de manipuler dans les mêmes exécutions ordonnées de nombreux services différents grâce à ses intégrations de multiples providers.
Toutefois, il est courant d’avoir très peu, sinon aucune vérification hormis l’exécution de la commande plan (qui peut s’avérer complexe à valider s’il est très long) avant la création de ressources avec un code Terraform. Certains nomment cette technique (ou absence de technique?) sous le nom poétique de “terraform yolo”, c’est dire.
La qualité du code fourni est donc variable selon la personne qui l’écrira, ce qui est une source de problème si on a pris un virage vers l’industrialisation et l’automatisation des cycles d’application du code Terraform. C'est ici que des pratiques d'Infrastructure Craftsmanship peuvent nous aider, pareil que pour du code applicatif, il nous faut soumettre notre code à un cycle d'intégration continu.
Nous avons identifié plusieurs outils permettant, avant même d’appliquer la moindre commande Terraform dédiée aux cycles de vie des ressources, de valider statiquement le code.
L’intérêt d’avoir ces vérifications statiques est qu’elles consomment peu de ressources, ne faisant que lire le code sans l’appliquer, ce qui les rend rapides et scalables.
L’article de Stéphane vous en disait plus sur pre-commit en abordant des implémentations exemples de scripts pour exécuter des outils Terraform au moment même du commit dans Git. Cette approche contribue à la bonne pratique de réduire au maximum la boucle de retour de qualité pour les développements logiciels.
La CLI Terraform fournit nativement plusieurs commandes permettant de tester la validité brute de notre code :
Validate toutefois ne vérifie aucunement que le code est capable de s’appliquer dans les conditions réelles actuelles de nos infrastructures.
Prenons l’exemple d’une plateforme AWS, est-ce que le vpc_id que je passe à mon code est valide? Est-ce que le type d’instance EC2 que je compte créer existe bien dans la région où je déploie? Un plan Terraform ne pourra pas répondre à ces questions (la validation d’un ID est possible en le passant à une datasource toutefois), laissant la détection d’erreur à l’étape même de l’apply.
Dans des cas non hautement disponibles, dans le cadre d’une migration d’infrastructure sans blue-green deployment par exemple, la création des nouvelles ressources peut tomber en échec laissant le service effectivement interrompu (car l’ancienne infrastructure a été détruite comme prévue) et l’obligation de débugger en ayant la pression du temps d’interruption qui s’allonge sur les épaules.
Tflint est un outil développé en golang appliquant différentes analyses statiques sur le code Terraform de ressources dans les grands Cloud Publiques, principalement AWS (les supports de GCP et Azure sont encore expérimentaux à l’heure d’écriture de cet article). Outre l’application possible de règles de bonnes pratiques (nommage par exemple), l’activation du deep-check mode permet à l’outil de se synchroniser avec les APIs du Cloud Public (moyennant une session ayant les droits de lecture dans le contexte cible) pour valider l’existence des différents IDs passés aux ressources du code.
Maintenant que l’on est sûr que notre code peut s’appliquer dans l’état actuel de notre plateforme, il serait utile de le valider par rapport à nos besoins. Faisons ici un abus de langage et prenons comme modèle les tests unitaires de la pyramide des tests applicatifs qui sont les premiers tests, destinés à être une des premières couches de validation du code.
Ces tests peuvent être nombreux, ils doivent donc consommer le moins de ressources possible, servent à valider unitairement des fonctions du code. Dans une approche TDD, ils servent à la fois à documenter et à identifier les conditions de validité des bouts de code qu’ils testent.
Ici, nous ne voulons tester le code ni du binaire Terraform, ni des providers référencés dans notre code, ni que les APIs qu’ils appellent fonctionnent (selon la confiance qu’on leur accorde bien sûr). Visons plutôt à valider que les déclarations de ressources/variables/outputs de notre code permettront, une fois appliquées, de créer les ressources dont notre infrastructure aura besoin pour fournir ses fonctions.
Une des solutions les plus utilisées pour la validation de document aujourd'hui est OPA, la validation est rapide et peu consommatrice. Concrètement, on déclare des règles dans un langage dédié à l’outil (rego) et on applique ces règles sur les documents que l’on veut valider. A la place d'OPA, nous pouvons utiliser conftest qui applique le langage rego sur différents types de fichiers, notamment Terraform.
L'écriture des règles de validation peut répondre à divers besoins:
- accompagner le développement du code Terraform en documentant en amont la structure des blocs (data, variable, output, resource...) et certains champs de ces blocs pour indiquer leurs relations, des formules de calcul etc, on parle ici de l'approche type TDD
- accompagner l'évolution du code Terraform en marquant les éléments importants à préserver pour détecter les breaking changes avec les précédentes versions
Il est à noter que conftest limite l’utilisation des mots clés d’OPA (on ne dispose que du deny, et non allow, warn, deny), il faut donc écrire les règles en prenant en compte cette contrainte.
Voici quelques exemples de règles:
deny[msg] {
count(output_names) != 1
msg = "Define the names output"
}
deny[msg] {
count([value | value := data_external_names[_].query.path; value == "${var.path}"]) != 1
msg = "The names data external must use the path variable as query path input"
}
L’approche à base de count sur des tableaux avec de l’array comprehension permet d’éviter les cas undefined (une règle ne s’applique pas jusqu'au bout si un élément qu’elle cible n’existe pas) qui pourraient rester silencieux et donc passer sans fail.
Quand la validation conftest passe, le code correspond à la structure exprimée dans les règles.
Voici un résumé des différentes étapes par lesquelles notre code est passé dans notre processus de validation:
Comme je l’ai mentionné en préambule, la cible est de pouvoir exécuter ces analyses statiques de code en local dans le cadre des pratiques d'Infrastructure Craftsmanship, avant même le commit du code dans Git.
La plupart des outils mentionnés précédemment bénéficient d’intégrations pre-commit publiques maintenues par la communauté mais l’intégration de conftest en tant que hook n’a pas été trouvée dans les projets publiques aussi elle a été développée pour nos besoins et est mise à disposition dans notre espace Gitlab Open Source.
Comme l’application des hooks reste facultative (même installés, il est possible de les désactiver pour un commit avec l’option -n), il faudra les appliquer en CI (dans les premières étapes) pour valider le code dans le cycle avant d’aller plus loin dans les étapes d’intégration.
Vous trouverez un exemple d'application complète de tous les outils précédemment cités dans des hooks pre-commit (init est inclus dans le hook validate) dans les quelques modules Terraform que nous avons publiés dans notre espace Open Source.
Ceci conclut le sujet validation statique du code Terraform, il est tout à fait possible d'appliquer la suite du cycle d'Infrastructure Craftsmanship via d'autres types de tests, unitaires, fonctionnels, etc. D'autres articles suivront, décrivant ces pratiques plus en détail.
Vous souhaitez monter en compétence sur Terraform ?
Nos experts vous proposent une formation sur le sujet.
Inscrivez-vous vite sur training@wescale.fr les places sont limitées !