Alors comme ça, tu as abusé pendant les fêtes ? Tu as le foie plus gras que celui que tu as mis sur tes toasts ? Tu as fini tout le saumon gravlax ? Tu as passé ton 1er à parler à ta cuvette de toilette ?

Pas de soucis mon ami, j’ai ce qu’il te faut pour reprendre une année dans les bonnes dispositions :) 30% de réduction sur un abonnement basic-fit avec le code créateur “JAIVOMIMABUCHE”.

J’ai réalisé que pendant un moment, j’ai choppé des infos çà et là sur de bonnes pratiques et sur des façons de faire du Terraform, mais je n’ai jamais partagé ici. Parce qu’en fait quand on y pense, Hashicorp ne s’est jamais vraiment mouillé dans la doc officielle sur la gestion multienv du code, sur l’organisation des répertoires et sur la modularité du code.

Pour certains ça sera du réchauffé picard de Noël; mais je mettrai quelques petits trucs inédits que je n’ai jamais partagés qui peuvent aider, à vous de voir si ça à du sens ou pas.

On va surtout faire du GCP dans nos exemples, mais c’est tout à fait transposable sur d’autres technos bien sûr :)

Cette introduction est rédigée par ChatGPT, c’est incroyable non ? (c’est faux, mais c’est pour mon référencement ! Pour me faire pardonner, je vous mets un dall-E d’un chat qui pète)

chat qui pète, dans le style de Salvator Dale

Utiliser un remote backend

Bon celle-ci est à la fois noob et en même temps pas si triviale que ça, on déballe les banalités et ensuite on regarde en peu plus en détail ce que je te recommande.

Terraform fonctionnant sur un système d’état différentiel, il est primordial de conserver les fichiers tfstate représentant l’état de vos ressources au moment du dernier apply dans un endroit sécurisé, et c’est aussi la seule bonne façon de partager le state entre plusieurs personnes sans tout péter.

Ma préconisation quand on fonctionne avec plusieurs environnements, c’est de mettre un bucket régional sur chacun des environnements sur lequel tu travailles.

Voici les raisons de cette affirmation:

  • si on décide de supprimer un env, on supprime l’env et le bucket associé sans polluer les autres envs.
  • pas de contact entre les environnements, si un bucket est compromis, ça ne concerne que l’env concerné.
  • moins de risque de se tromper de bucket si on fait des ops nécessitant des actions tf manuelles.
  • le régional est important pour assurer un HA en cas de zone temporairement indisponible (moins de risque de perte de state)

Ça implique d’avoir le remote bucket hardcodé dans la conf des dossiers tf ou via une variable à l’init, on y revient plus tard, mais bien outillé ce n’est pas forcément un problème. Utiliser le même remote backend pour tout les envs en utilisant les workspaces en plus d’avoir plus de risques de se tromper apporte une adhérence entre les environnements.

Et même si tout le monde le fait c’est écrit dans la documentation de Terraform que c’est une mauvaise pratique.

https://developer.hashicorp.com/terraform/language/state/workspaces

Important: Workspaces are not appropriate for system decomposition or deployments requiring separate credentials and access controls. 
Refer to Use Cases in the Terraform CLI documentation for details and recommended alternatives.

Léodagan Kaameloot, moi je réponds merde, en principe ça colle avec tout …

Découper votre infrastructure en petits dossiers fonctionnels

Plus tu travailles sur un nombre important de ressources et plus les apply seront long et plus tu risques de péter des trucs dans ton infra. Deuxième effet kiss kool, tu lock toutes tes ressources et réduit la capacité de travailler en équipe en verrouillant toutes les ressources à chaque apply.

Un dernier point pas du tout négligeable, c’est qu’on veut au maximum éviter de manipuler des ressources critiques si on n’a pas besoin de les modifier. Genre les VPC, les adresses IP réservées les interconnexions, c’est typiquement les ressources qu’on deploy une fois et qu’on touche plus ensuite. Les activations d’API sont particulièrement longues et pénibles, on voudrait pouvoir activer une nouvelle API et continuer de travailler sur autre chose en même temps.

Une façon d’ordonnancer les dossiers, c’est de les préfixer avec un numéro. Attention à ne pas utiliser cette numérotation dans le remote prefix, on veut pouvoir changer l’ordre des numéros de dossier sans avoir à impacter le remote state. ça nous donne un truc comme ça :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
.
├── dev
│   ├── source_env.sh
│   ├── 000_enable_api
│   │   ├── api.tf
│   │   ├── config.tf
│   │   ├── terraform.tfvars
│   │   └── variable.tf
│   ├── 001_private_vpc
│   │   ├── config.tf
│   │   ├── terraform.tfvars
│   │   ├── variable.tf
│   │   └── vpc.tf
│   └── 002_dedicated_gitlab_runner
│       ├── cloud-init.sh
│       ├── compute.tf
│       ├── config.tf
│       ├── datasource.tf
│       ├── terraform.tfvars
│       └── variable.tf

Cette organisation implique d’utiliser des datasources pour accéder aux attributs des ressources qu’on veut partager. Dans cet exemple on voudrait utiliser le VPC* dans le gitlab runner, au lieu de faire un lien direct avec la ressource, on va plutôt utiliser un datasource.

1
2
3
data "google_compute_network" "my-network" {
  name = "default-us-east1"
}

Ce qui implique de connaître le nom du réseau qu’on veut récupérer, qu’on peut choisir de mettre en dur comme ici, ou via une variable. Ce qui m’amène à transiter sur le partage de variable globale pour l’environnement. Notre fichier source_env.sh contiendra toutes les variables qu’on veut partager entre nos dossiers.

Premier cadeau, voici un script qui va générer ce dossier de squelette prêt à fonctionner.

https://github.com/helldrum/bootstrap_terraform_folder

L’idée c’est de construire un dossier qui respecte la convention de nommage et qui contiendra le backend configuré et le fichier de variable tf avec la variable projet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
» ./bootstrap_terraform_folder.py --tf_folders_path=000_enable_api --gcs_backend_name=terraform_state
Folder 000_enable_api will be created, with thoses files :

config.tf

terraform {
  required_providers {
    google = {
      source = "hashicorp/google"
    }
  }
  backend "gcs" {
    bucket = "terraform_state"
    prefix = "enable_api"
  }
}

provider "google" {
  project = var.project
}



variable.tf

variable "project" {}


    

Is this configuration correct [y/n] ?
y
INFO:root:create folder 000_enable_api
INFO:root:files and folder generated

Fonctionnellement il te manquera la variable d’env TF_VAR_project qui contiendra ton nom de projet GCP et un moyen d’authentification avec GCP sur ce projet. Tu peux utiliser la variable GOOGLE_APPLICATION_CREDENTIALS=/path/service/account/d79c867debaa.json de cette façon, ou configurer le default account gcloud.

1
gcloud auth application-default login --project my_project

On découpe tout ça en petits bouts et on fait des paquets cadeau :)

Dexter retrouve une poupée découpée en morceau, emballée dans son frigo

Et le numéro complémentaire

photo du présentateur du loto sur le plateau télé

Quand on travaille en équipe, c’est possible qu’à partir d’un moment on se retrouve avec des numérotations de dossier en conflit ou des briques oubliées qui doivent être créées avant.

Du coup, renommer tous les dossiers manuellement c’est une corvée qu’on voudrait éviter, surtout quand on commence à avoir beaucoup de dossiers.

Du coup premier outils de ma liste du père Noël, un script python qui permet de gérer cette numérotation.

Petit ajout en plus, un paramètre d’exclusion de dossier, au cas où tu traînes des exceptions :)

Je te mets le lien github directement

https://github.com/helldrum/rename_tf_folders

Par exemple j’ai fait un trou dans ma numérotation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
.
├── 000_enable_api
│   ├── api.tf
│   ├── config.tf
│   ├── terraform.tfvars
│   └── variable.tf
├── 001_private_vpc
│   ├── config.tf
│   ├── terraform.tfvars
│   ├── variable.tf
│   └── vpc.tf
├── 002_dedicated_gitlab_runner
│   ├── cloud-init.sh
│   ├── compute.tf
│   ├── config.tf
│   ├── datasource.tf
│   ├── terraform.tfvars
│   └── variable.tf
├── 007_james_bond
│   ├── config.tf
│   ├── james.tf
│   ├── terraform.tfvars
│   └── variable.tf
└── 008_gke_cluster
    ├── config.tf
    ├── gke.tf
    ├── terraform.tfvars
    └── variable.tf

le script va me proposer cette configuration de renommage

1
2
3
4
5
6
7
8
9
» ./rename_tf_folders.py --tf_folders_path=./terraform/dev 
INFO:root:folders in parent [...]./terraform/dev will be renamed this way:
{'000_enable_api': '000_enable_api',
 '001_private_vpc': '001_private_vpc',
 '002_dedicated_gitlab_runner': '002_dedicated_gitlab_runner',
 '007_james_bond': '003_james_bond',
 '008_gke_cluster': '004_gke_cluster'}

Is this configuration correct [y/n] ?

Si je souhaite faire une exclusion de private VPC et james bond je fais comme ça

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
» ./rename_tf_folders.py --tf_folders_path=./terraform/dev --exclude_folders=007_james_bond,001_private_vpc
INFO:root:print exclude folder path(s):

INFO:root:exclude folder [...] /dev/007_james_bond
INFO:root:exclude folder [...] /dev/001_private_vpc


INFO:root:exclude folder [...] /dev/001_private_vpc from renaming list.
INFO:root:exclude folder [...] /dev/007_james_bond from renaming list.
INFO:root:folders in parent [...] /terraform/dev will be renamed this way:
{'000_enable_api': '000_enable_api',
 '002_dedicated_gitlab_runner': '001_dedicated_gitlab_runner',
 '008_gke_cluster': '002_gke_cluster'}

Is this configuration correct [y/n] ?

Conserver une configuration flat sur tout les envs

Il faut traiter Terraform comme de la configuration et éviter de mutualiser et partager le même dossier pour plusieurs envs. À tout moment on devrait pouvoir changer le code dans un env sans impacter les autres.

Opinion impopulaire, mais il vaut mieux un environnement un peu déviant, mais complètement tracé en Terraform, que des modifications manuelles intraçables qui reviennent ensuite vous péter à la tronche.

C’est pourquoi il vaut mieux dupliquer le code des ressources pour chaque environnement et avoir une configuration flat à chaque instant.

La duplication et le différentiel d’env peuvent être source de problèmes si on le gère manuellement, c’est pourquoi sur les gros projets je te conseille de t’outiller pour faire cette tâche.

Pour ça j’ai dev un outil qui permet de faire des diffs entre environnements, en excluant le remplissage des variables, la configuration du backend et les fichiers système Terraform.

L’idée étant de ne montrer que les différences de création de ressources, pour repérer les divergences.

différentiel de voiture

J’essaye de diversifier mon audience, voici un différentiel mécanique, pas mal non ?

je vous mets le code de mon script directement ici

https://github.com/helldrum/terraform_diff_envs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
» ./terraform_diff_envs.py --env1=./terraform/dev --env2=./terraform/prod
extra folders found in env1
['008_gke_cluster', '007_james_bond']

diff between dev/000_enable_api/api.tf and prod/000_enable_api/api.tf

15,16c15
< # cette ligne n'existe qu'en dev
< 
---
> # cette ligne n'existe qu'en prod

dans notre cas on a 2 dossiers en plus en dev [‘008_gke_cluster’, ‘007_james_bond’] et une divergence de fichiers entre prod et dev sur 000_enable_api/api.tf

C’est intéressant de mettre un coup de terraform fmt sur tout les dossiers pour garder une cohérence de formatage (certains utilisent des IDEs qui le font directement, certains non).

un petit oneliner un peu claqué au sol pour faire ça :)

1
for env in * ;do  cd $env;  for folder in *;do cd $folder; terraform fmt ;cd ..;done  ;cd .. ;done

Vous comprendrez que du coup ce n’est pas beaucoup plus compliqué de lancer tout ou partie de l’infra en cicd ou manuellement.

Faut juste pas se louper en faisant la loop :)

dernier petit truc, si on veut copier les dossiers d’un env a un autre sans risquer de casser le remote backend ou la configuration ce bon vieux rsync, toujours la pour sauver les miches :)

1
2
rsync -av --exclude=config.tf --exclude=.terraform \
--exclude=.terraform.lock.hcl --exclude=terraform.tfvars dev/000_enable_api prod

Alors oui, ça fait faire pas mal de bordel, mais la faute à qui hein ? Merci hashicorp de ne pas fournir de vrai standard et de solide bonne pratique pour gérer convenablement les environnements multiples.

Je vous laisse ici une petite référence discrète https://www.terraform-best-practices.com/references

Utiliser les modules, seulement quand vous comprenez comment ils marchent

Les terraform module sont une très bonne chose à condition de maitriser 100% de leur fonctionnement, parce que une fois en prod, les conséquences peuvent être dramatiques. On abstrait de la complexité dans un but de packaging de ressources, mais si on ne comprend pas le fonctionnement des ressources et la manière dont elles sont connectées, on peut casser des choses :)

D’une manière générale, mais c’est mon point de vue, si vous n’avez pas l’intention de réutiliser les ressources, rester simple, ne packager pas. Terraform est un outil fait pour faire des choses simples, mais les gens aiment l’utiliser de manière compliquée et tordue.

kaamelott Perceval et Karadoc, c’est compliqué

Le mot de la fin

J’espère que certains d’entre vous on appris des choses :)

Bien évidemment cette organisation n’engage que moi, mais je trouve que c’est un très bon compromis entre la gestion du chaos, la collaboration d’équipe et la modularité. Un grand big up à Thomas Poindessous et Yann Coleu si vous passez par ici, et également aux anciens et nouveaux skalers :)

J’aurais dû le faire depuis un moment, mais il y à un moment pour recevoir, et un moment pour transmettre !

Alors je vous invite à prendre votre plume la plus déglinguée et faire des articles, même si encore aujourd’hui je ne sais pas qui me lis vraiment et quelle mauvaise influence j’ai sur vous :)

Je vois que de plus en plus de bots de Singapour me rendent visite, mais également que la France est enfin devenue ma principale audience, merci à vous !

Je vous souhaite à tous une bonne année !