Terraform Layering : Pourquoi et comment ?

Terraform, issu des fonderies Hashicorp, est en passe de devenir le standard de-facto pour ce qui est de la gestion d’infrastructures cloud. Cependant, Terraform génère également un certain nombre de questions quant aux bonnes pratiques à appliquer et aux techniques de craftsmanship à garder à l’esprit. En effet, à partir d’un certain volume de sources, le temps de communication entre Terraform et les différentes APIs des providers peut rendre l’expérience pour le moins frustrante.

Nous allons vous exposer une technique que nous utilisons couramment pour gérer les sources Terraform : le Layering, en espérant que cela puisse vous rendre service, ou vous inspirer pour développer vos propres techniques.

Dissection

La technique consiste à découper vos descriptions de ressources Terraform en plusieurs couches (layers) dont chacune dépend d’un ou plusieurs layers précédents. Le lien entre les différents layers est basé sur la consommation des tfstates des layers précédents.

Terraform est doté de 2 fonctionnalités pour permettre cela :

  • Le Remote Storage, qui permet de stocker le tfstate non seulement en local mais aussi dans un backend choisi par vos soins.
  • La datasource terraform_remote_state, qui permet de consommer les outputs d’un remote state en se branchant sur les mêmes backends que le Remote Storage.

Si on joue sur ces 2 fonctionnalités, il est donc possible de passer les outputs d’une stack Terraform à une autre sans problème.

Bénéfices

C’est bien beau de savoir ça, mais comment en tirer quelque chose de bénéfique dans votre code ? Ce que beaucoup expérimentent en avançant sur le chemin de Terraform c’est le temps d’exécution qui explose à mesure que le code d’une stack Terraform grossit. En effet, le calcul du plan d’exécution passe par une interrogation massive des APIs de votre provider, pour s’assurer de l’état réel et pouvoir le comparer à la description de vos fichiers Terraform. Plus le code est conséquent, plus le temps de refresh va augmenter.

Le problème est qu’une bonne partie de ce temps va être consommée pour vérifier l’état des couches stables de votre stack. Si vous faites des modifications, elles ne porteront probablement que sur un aspect à la fois (instances EC2 ou instances RDS ou VPC etc).

L’approche en layers permet de segmenter le travail afin de restreindre l’action de Terraform. Cela affaiblit le couplage entre les différents composants de votre infrastructure et vous fait ainsi gagner en temps d’exécution.

Si votre gestion des droits utilisateurs dans IAM est réalisée de façon à restreindre l'accès par services (EC2, subnets, VPC) alors vous pourrez étendre cette technique à celle des layers. Ainsi, les utilisateurs ne pourront accéder qu'aux couches pour lesquelles ils disposent des autorisations adéquates.

Contraintes

Les avantages listés ici viennent avec un coût inévitable : le besoin de cohérence.
Si chaque layer stocke son tfstate dans un backend, il vous faut réfléchir à une convention commune pour assurer que chaque équipe/layer soit à même d’aller piocher les informations techniques nécessaires à son fonctionnement. Il faut donc bien penser à :

  • Une norme de nommage des outputs.
  • Une norme de stockage des tfstates.
  • Une documentation claire des différents layers et de ce qu’ils offrent comme outputs.

Une petite couche d’orchestration à base de scripts, pour formaliser les options à passer à Terraform pour déployer un layer peut également être une excellente idée (voire un passage obligé).

Cas d’usages

Dès que vous sortez du cadre “expérimentations préliminaires”, je vous conseille d’envisager la segmentation en layers. Même dans un cadre mono-projet, la séparation des fichiers de sources par répertoires réduit les risques de destruction potentielle de ressources censées être stables dans le temps (VPC, IAM Instance Profiles, …).

Expérimenter

Vous pouvez dès aujourd’hui tester la validité de cette technique.

  • Découpez vos stacks Terraform en sous-répertoires pour obtenir une structure comme celle-ci :
...
└── terraform
    ├── 00-access-rights
    │   ├── input.tf
    │   ├── main.tf
    │   └── output.tf
    ├── 01-landscape
    │   ├── input.tf
    │   ├── main.tf
    │   └── output.tf
    └── 02-servers
        ├── input.tf
        ├── main.tf
        └── output.tf

Pour éviter de vous compliquer la vie pour ce premier essai, ne changez rien à la configuration de Remote Storage de Terraform et laissez le déposer ses fichiers tfstate dans chacun des répertoires.

Supposons maintenant que notre layer 02-servers ait besoin de faire référence à des objets du layer 01-landscape et du layer 00-access-rights. Voici ce qu’il faut rajouter (idéalement dans le fichier input.tf, parce qu’il s’agit bien de données entrantes nécessaires au bon déroulement de votre stack).

data "terraform_remote_state" "landscape" {  
 backend = "local"
 config {
   path = "${path.module}/../01-landscape/terraform.tfstate"
 }
}

data "terraform_remote_state" "access-rights" {  
 backend = "local"
 config {
   path = "${path.module}/../00-access-rights/terraform.tfstate"
 }
}

À partir de là, vous avez accès à tous les outputs définis par chacun de ces layers, en usant d’une référence comme celle-ci :

${data.terraform_remote_state.<nom de datasource>.<nom d’output>}

Il s’agit bien sûr de l’usage des remote state au niveau zéro, puisque sur disque local et en chemin relatif… Mais je suis certain que vous voyez l’idée. Si vous creusez du côté des différents Backends disponibles, vous trouverez sûrement votre bonheur.

Conclusion

J’espère que ce petit exposé vous aura inspiré. L’essentiel à retenir est cet axe de design supplémentaire pour votre code Terraform. N’oubliez pas que dans Infrastructure as code (IaC), il y a Code, avec tout ce que ça peut impliquer en terme de recherche de qualité, de stabilité et de maintenabilité. À bientôt pour d’autres tips.

Have fun. Hack in Peace.