Terraform : remote state configuration DRY avec Terragrunt

Loin de remettre en cause Terraform qui est un outil largement utilisé pour déployer des infrastructures chez les principaux fournisseurs de Cloud, l’idée est de mettre le doigt sur une des limites de Terraform.
Celle-ci concerne la reproductibilité du nommage de l’arborescence des fichiers de backend « terraform.tfstate » sur un support de stockage distant, et de proposer une solution à l’aide d’un wrapper  qui se nomme Terragrunt. Mes exemples sont déployés dans AWS. Cf. https://terragrunt.gruntwork.io/docs/features/keep-your-remote-state-configuration-dry/

Une deuxième amélioration fournie par Terragrunt est la possibilité de lancer des commandes multiples sur un ensemble de répertoires de manière concurrente ou gérée par des liens de dépendance. Cf. https://terragrunt.gruntwork.io/docs/features/execute-terraform-commands-on-multiple-modules-at-once/

Mon article est loin d’être exhaustif sur les possibilités de Terragrunt, d’autres possibilités d’utilisation de Terragrunt sont décrites ici    https://terragrunt.gruntwork.io/docs/#features

 

1) Introduction de notre sujet

Terraform stocke l’image de l’infrastructure déployée dans un fichier nommé terraform.tfstate, avec un format json.
Ce fichier est stocké :

  • par défaut dans chaque répertoire contenant un groupe de ressources ou modules à déployer. Nous avons donc un fichier terraform.tfstate à l’intérieur de chaque répertoire. Cette solution présente l’inconvénient d’un travail collaboratif difficile ainsi que la question de la sauvegarde de ces fichiers. Le backend est dit « local ».
├── ec2-site1
│   ├── ec2-site1.auto.tfvars
│   ├── main.tf
│   ├── terraform.tfstate
│   └── var.tf
│
├── ec2-site2
│   ├── ec2-site2.auto.tfvars
│   ├── main.tf
│   ├── terraform.tfstate
│   └── var.tf
  • Dans un lieu de stockage mutualisé et sauvegardé, avec éventuellement l’existence d’un versionning. Nous utiliserons ici la solution de backend S3 sur AWS. Cette solution permet un travail collaboratif sur les fichiers terraform.tfstate avec l’utilisation conjointe d’une table DynamoDB pour gérer les accès concurrents. Les fichiers terraform.tfstate ne sont plus stockés localement.
├── ec2-site1
│   ├── ec2-site1.auto.tfvars
│   ├── main.tf
│   └── var.tf
│
├── ec2-site2
│   ├── ec2-site2.auto.tfvars
│   ├── main.tf
│   └── var.tf

mais sont contenus dans un repo S3  :

$ aws s3 ls jpc-terraform-repo/us-west-2 --recursive
2020-07-14 19:38:23 0 us-west-2/
2020-07-25 11:18:50 157 us-west-2/projetx/ec2-site1/terraform.tfstate
2020-07-25 11:18:50 157 us-west-2/projetx/ec2-site2/terraform.tfstate

Pour utiliser un backend S3, il faut positionner dans un fichier du répertoire des ressources à créer le code suivant :

terraform {
  backend "s3" {
    bucket = "jpc-terraform-repo"
    key    = "path/to/my/key"
    region = "us-west-2"
  }
}

Et c’est ici que la problématique que je veux introduire apparait. Il n’est pas possible, de par la construction de Terraform, de générer automatiquement la valeur du champ « key ». Il faut donc laborieusement, pour chaque répertoire créé, écrire en dur la valeur de « key » pour l’adapter aux ressources contenues dans le répertoire. Ce que l’on gagne par rapport au backend « local » avec le backend S3, on le perd en automatisation. Et cela peut être source d’erreur puisque la reproductibilité est liée à une action humaine.

Ici ne s’applique pas le principe DRY énoncé dans le livre « The Pragmatic Programmer » de David Thomas et Andrew Hunt :

« Don’t Repeat Yourself. »

Partant au contraire de ce principe, un wrapper pour Terraform, en l’occurence Terragrunt, va nous permettre de palier cet inconvénient et appliquer le précepte « DRY » au remote backend notamment. Cf. https://terragrunt.gruntwork.io/docs/features/keep-your-remote-state-configuration-dry

 

2) Mise en place du paramétrage remote backend DRY avec Terragrunt

La mise en place de Terragrunt pour l’automatisation des remote backends repose sur la création de fichiers terragrunt.hcl :

  • Au plus bas de l’arborescence du projet.
    Dans ce fichier global est défini le paramétrage des backends, avec principalement le champ « key » sous forme de variable :
remote_state {
backend = "s3"

config = {
   bucket = "jpc-terraform-repo"
  key = "us-west-2/${path_relative_to_include()}/terraform.tfstate"
  region = "us-west-2"
  encrypt = false
  dynamodb_table = "jpc-terraform-lock"
   }
}
  • Dans chacun des autres sous-répertoires, un fichier terragrunt.hcl contenant la valeur générique :
include {
path = find_in_parent_folders()
}

L’arborescence des répertoires du projet correspondra à celle-ci :

├── projetx
│ │
│   ├── ec2-site1
│   │   ├── ec2-site1.auto.tfvars
│   │   ├── main.tf
│   │   ├── terragrunt.hcl   <======== FICHIER GENERIQUE
│   │   └── var.tf
│ │
│   ├── ec2-site2
│   │   ├── ec2-site2.auto.tfvars
│   │   ├── main.tf
│   │   ├── terragrunt.hcl <======== FICHIER GENERIQUE
│   │   └── var.tf
│
│
├── terragrunt.hcl    <====== FICHIER GLOBAL

 

3) Déploiement

Terragrunt nécessite l’installation préalable de Terraform car c’est seulement un wrapper de Terraform. Il s’installe de la même manière que Terraform, par téléchargement d’un binaire.

Il suffit de lancer la commande  « terragrunt init » à la place de la commande « terraform init » pour que le fichier de backendS3  soit créé avec une clé générée automatiquement, correspondant au pattern défini dans le fichier terragrunt.hcl global.

$ aws s3 ls jpc-terraform-repo/us-west-2 --recursive
2020-07-14 19:38:23 0 us-west-2/
2020-07-25 11:18:50 157 us-west-2/projetx/ec2-site1/terraform.tfstate
2020-07-25 11:18:50 157 us-west-2/projetx/ec2-site2/terraform.tfstate

En fait, Terragrunt surcouche Terraform en paramétrant le backend sur la ligne de commande Terraform :

# terragrunt init    terraform init -backend-config=bucket=jpc-terraform-repo -backend-config=dynamodb_table=jpc-terraform-lock -backend-config=encrypt=false -backend-config=key=us-west-2/projetx/ec2/terraform.tfstate -backend-config=region=us-west-2

Une fois le backend S3 initié grâce à la commande « terragrunt init », les commandes, dans ce cas de figure d’utilisation de Terragrunt, peuvent être indifféremment « Terragrunt nom_commande_terraform » ou « Terraform nom_commande_terraform« . La commande « terragrunt init » ayant configuré le fichier ./.terraform/terraform.tfstate avec les caractéristiques du backend S3.

 

4) Lancement de commandes multiples

Une fois le paramétrage ci-dessus initié, il est possible aussi de lancer des commandes qui exécuteront de manière concurrente ou gérée par des liens de dépendance les répertoires des différents répertoires du projet.

Les commandes seront :

terragrunt apply-all, terragrunt destroy-all, terragrunt output-all, terragrunt plan-all.

Il n’y a pas par contre de commande terragrunt init.