Home >> Blog >> Terragrunt : Simplifiez la gestion de vos infrastructures Terraform

Terragrunt : Simplifiez la gestion de vos infrastructures Terraform

11 mars 2024

By Lionel Gurret.

Introduction

La mise en place et la gestion de l’infrastructure pour vos projets de Cloud computing peuvent être un processus fastidieux et chronophage.

Une solution d’Infrastructure as Code comme Terraform permet de créer et de gérer des ressources cloud à l’aide de fichiers de configuration.

Comme nous le verrons dans cet article, Terraform peut être particulièrement complexe à gérer lorsqu’il est nécessaire de maintenir plusieurs environnements en minimisant la réutilisation de code (DRY : don’t repeat yourself!), ou encore pour gérer plusieurs backends distincts pour chacun des environnements. Malgré l’utilisation des workspaces, Terraform ne répondrait qu’à la première des problématiques !

Heureusement pour nous, Terragrunt répond à tous ces aspects !

Présentation de Terragrunt

Voyons ensemble les avantages principaux de Terragrunt.

Arborescence des fichiers sous Terragrunt

Terragrunt permet de simplifier la gestion des configurations Terraform pour plusieurs environnements. Prenons un exemple concret sur Azure.

Nous souhaitons provisionner une application qui utilisera plusieurs ressources (AppService, AppService Plan, Resource Group, KeyVault etc.) ainsi qu’une base de données SQL pour les données persistantes :

Nous souhaitons déployer cette application sur un environnement de développement (dev), de pré-production (staging) et de production (prod).

Nous allons coder au préalable 2 modules Terraform :

  • app : pour la partie applicative et le resource group
  • db : pour la partie base de données

A titre d’exemple, nous pourrions avoir ce type d’arborescence sur Terraform, où chaque fichier Terraform est dupliqué par environnement, contre un code Terragrunt plus simple à comprendre et considéré comme DRY :

Sur la gauche de l’image, nous pouvons observer que le code Terraform “classique” doit être maintenu pour chacun des environnements. Nous devons pour les deux modules app et db, copier le code 3 fois, ce n’est pas idéal à maintenir. De plus, il sera nécessaire de gérer un bloc “backend” pour chacun des environnements.

Au contraire avec Terragrunt, nous séparons physiquement notre code Terraform dans un dossier, du code Terragrunt, qui lui ne contiendra que les variables propres aux environnements.

Nous observons par ailleurs :

  • un fichier terragrunt.hcl racine qui nous permettra de gérer les configurations globales de tous nos environnements ainsi que de générer de façon dynamique un backend.
  • un fichier environment_specific.hcl dans chaque dossier qui contiendra les configurations propres à nos environnements et indépendants des modules. Nous aurions pu l’appeler terragrunt.hclmais je l’ai renommé dans un souci de compréhension.
  • un fichier propre à chaque module terragrunt.hcl qui contiendra la définition du module (où se trouve notre module ?) ainsi que les variables à lui passer.

Lorsque nous voudrons lancer la génération et l’application de notre plan Terragrunt (terragrunt plan, terragrunt apply), il sera nécessaire de se rendre dans le dossier spécifique à notre environnement.

Terragrunt sera capable de lire les fichiers hcl dans les répertoires parents pour avoir le code au complet avant d’être lancé.

Par exemple, si nous souhaitons appliquer les changements pour donner suite à des modifications sur le fichier terragrunt/dev/app/terragrunt.hcl, il faudra se rendre dans le dossier terragrunt/dev/app pour lancer les commandes terragrunt plan et terragrunt apply.

A titre d’information il est également possible de se rendre dans le dossier terragrunt/dev et lancer la commande terragrunt run-all plan. Tous les plans seront affichés séquentiellement.

Nous pourrions, bien sûr, gérer tout le code Terraform dans un autre repository pour que chacun des dossiers ait son propre cycle de vie.

Gestion du backend

Terragrunt permet de simplifier la gestion du backend de Terraform en utilisant une configuration centralisée. Dans notre cas, nous stockerons le backend sur Terraform Cloud. Nous allons avoir la possibilité de créer un backend pour chaque environnement (dev, staging, prod) en spécifiant un seul bloc de code Terragrunt dans le fichier racine terragrunt.hcl :

# /home/lionel/demo-terragrunt/terragrunt/terragrunt.hcl
locals {
    # Get Project Name
    env_config = read_terragrunt_config(find_in_parent_folders("environment_specific.hcl"))
    environment_name= local.env_config.locals.environment_name
    terraform_token   = get_env("TF_CLOUD_API_TOKEN")
}

# Generate a backend (one per project)
generate "backend" {
  path = "backend.tf"
  if_exists = "overwrite_terragrunt"
  contents = <<EOF
terraform {
  cloud {
  organization = "sokube-test"
    workspaces {
      name = "demo-${local.environment_name}"
    }
  }
}
EOF
}

Dans un premier temps, nous pouvons remarquer l’utilisation d’un bloc “locals” qui va contenir deux variables :

  • environment_name qui va être générée directement en lisant le fichier environment_specific.hcl qui contiendra le nom de notre environnement. Il est intéressant de noter que nous allons utiliser la commande find_in_parent_folders() puisque comme indiqué précédemment, nous allons lancer la commande terragrunt plan dans le dossier terragrunt/<environnement>/<app|db>.
  • terraform_token qui va être initialisée en récupérant la variable d’environnement TF_CLOUD_API_TOKEN permettant de se connecter à Terraform Cloud pour créer le backend. Nous ne souhaitons pas avoir cette valeur dans notre code pour des raisons évidentes de sécurité. Nous aurions pu bien sûr stocker ce backend en local ou en remote sur AWS,Azure ou GCP.

Nous souhaitons isoler au maximum les environnements entre eux, grâce à cette solution, chaque environnement aura son propre TFState !

Gestion des variables avec les fichiers terragrunt.hcl

Comme nous l’avons vu précédemment, nous allons stocker la variable environment_name dans le fichier environment_specific.hcl présent dans chaque sous dossier dev, staging et prod. Cette variable nous a servi pour générer le backend mais elle pourrait aussi nous servir dans notre convention de nommage de nos ressources.

Voici à quoi notre fichier va ressembler :

# /home/lionel/demo-terragrunt/terragrunt/dev/environment_specific.hcl
locals {
    environment_name = "dev"
}

Gestion des modules

A présent nous allons nous consacrer aux fichiers terragrunt/dev/app/terragrunt.hcl et terragrunt/dev/db/terragrunt.hcl.

Nous allons déclarer dans ces fichiers le chemin où se trouve le module terraform :

# /home/lionel/demo-terragrunt/terragrunt/dev/app/terragrunt.hcl
# Include root terragrunt.hcl
include "root" {
  path = find_in_parent_folders()
}

locals {
  # Get Environment name from env_specific.hcl file
  env_config = read_terragrunt_config(find_in_parent_folders("env_specific.hcl"))
  environment_name = local.env_config.locals.environment_name
}

# Terraform bloc to call app module
terraform {
    source = "../../../terraform//app/"
}
# /home/lionel/demo-terragrunt/terragrunt/dev/db/terragrunt.hcl
# Include root terragrunt.hcl
include "root" {
  path = find_in_parent_folders()
}

locals {
  # Get Environment name from env_specific.hcl file
  env_config = read_terragrunt_config(find_in_parent_folders("env_specific.hcl"))
  environment_name = local.env_config.locals.environment_name
}

# Terraform bloc to call db module
terraform {
    source = "../../../terraform//db/"
}

Il est également possible de spécifier un repository GIT comme source.

Variables propres aux environnements

Il ne nous reste plus qu’à passer les variables nécessaires à nos modules pour qu’ils soient appelés. Pour cela, il suffit d’utiliser un bloc inputs. Voici à quoi pourraient ressembler nos fichiers finaux :

# /home/lionel/demo-terragrunt/terragrunt/dev/app/terragrunt.hcl
# Include root terragrunt.hcl
include "root" {
  path = find_in_parent_folders()
}

locals {
  # Get Environment name from env_specific.hcl file
  env_config = read_terragrunt_config(find_in_parent_folders("env_specific.hcl"))
  environment_name = local.env_config.locals.environment_name
}

# Terraform bloc to call app module
terraform {
    source = "../../../terraform//app/"
}

# Values
inputs = {
  # I.e used for resources names
  env_name = local.environment_name
  # Other inputs
  variable1 = "My first value"
  variable2 = "My second value"
  variable3 = "My third value"
  variable4 = "My forth value"
  ...
}
# /home/lionel/demo-terragrunt/terragrunt/dev/db/terragrunt.hcl
# Include root terragrunt.hcl
include "root" {
  path = find_in_parent_folders()
}

locals {
  # Get Environment name from env_specific.hcl file
  env_config = read_terragrunt_config(find_in_parent_folders("env_specific.hcl"))
  environment_name = local.env_config.locals.environment_name
}

# Terraform bloc to call db module
terraform {
    source = "../../../terraform//db/"
}

# Values
inputs = {
  # I.e used for resources names
  env_name = local.environment_name
  # Other inputs
  variable1 = "My first value"
  variable2 = "My second value"
  variable3 = "My third value"
  variable4 = "My forth value"
  ...
}

Application des changements

Une fois notre code terminé, il ne nous reste plus qu’à générer le plan terragrunt et provisionner les ressources.

Voici comment nous pourrions provisionner les ressources ou appliquer les changements après la modification d’une variable d’input dans le fichier terragrunt/dev/db/terragrunt.hcl

# We move to the following directory :
cd /home/lionel/demo-terragrunt/terragrunt/dev/db
# Initialize terragrunt
terragrunt init
# Generate Terragrunt plan
terragrunt plan -out myplan.tfplan
# Apply plan
terragrunt apply myplan.tfplan

Cette façon de procéder nous permet de vraiment isoler les environnements ainsi que les modifications sur un module en particulier.

Le plan affiché sera en tout point le même qu’un plan affiché via Terraform !

Conclusion

Suite à cette utilisation basique de Terragrunt, nous avons pu voir ensemble à quel point cet outil offre de nombreux avantages pour gérer efficacement les déploiements Terraform en plus de minimiser la duplication de code.

Il permet de naviguer et comprendre facilement le système de fichiers en définissant des configurations différentes dans les fichiers terragrunt.hcl.

Nous avons pu également générer des backends séparés pour chaque environnement afin de les isoler l’un de l’autre. Ceci n’aurait pas pu être faisable en utilisant les workspaces de Terraform.

Cependant, bien que Terragrunt puisse offrir des avantages pratiques, il peut également présenter certains inconvénients, notamment sur le fait qu’il nécessite l’installation d’un outil séparé et l’apprentissage d’une couche d’abstraction supplémentaire aux équipes.

Il est enfin à noter que Terragrunt n’est pas nativement pris en charge par Terraform Cloud et Terraform Enterprise, bien que des solutions de contournement soient disponibles.

Terragrunt reste sans aucun doute une solution très intéressante, en particulier sur des contextes comprenant beaucoup d’environnements. Dans le cas d’une utilisation simple sur 1 ou 2 environnements, Terraform seul peut suffire.

Keep it dry but keep it simple!

Sources

Terragrunt Documentation

Blog Terragrunt

Laisser un commentaire

  Edit this page