Cours - Bonnes pratiques d'organisation des fichiers et dossiers d'un projet
Objectifs
Section titled “Objectifs”- Comprendre comment bien structurer ses projets dans Terraform avec Terragrunt
Architecture modulaire
Section titled “Architecture modulaire”Point sur les parties statiques et dynamiques de l’infrastructure
Section titled “Point sur les parties statiques et dynamiques de l’infrastructure”
Certaines parties de l’infrastructure vont évoluer plus rapidement et nécessiter des mises à jour plus fréquentes que d’autres.
Pour prendre le cas le plus sensible, une base de données de production n’est pas censé bouger, hormis quand on orchestre une migration pour changer de version.
Ces parties de l’infrastructure peuvent considérées comme “statiques”.
À l’inverse, le code d’une application peut évoluer plusieurs fois par jour, et dans une infrastructure élastique les noeuds de calculs sont par nature transients.
On va appeler “dynamiques” ces parties de l’infrastructure.
Il y a a plusieurs avantages à séparer dans l’IAC les parties statiques et dynamiques.
- Assurance : éviter le risque de détruire par erreur une base de données
- Rapidité : éviter de perdre du temps dans de la planification et du déploiement
- Sobriété : éviter les surconsommations d’énergie / réseau / disque
Conséquence 1 : séparer le code des différentes parties
Section titled “Conséquence 1 : séparer le code des différentes parties”On va segmenter le projet en modules différents.
Dans notre exemple, le déploiement de la base de données et la gestion des serveurs web sont dans deux modules séparés.
Conséquence 2 : utiliser des méthodes de déploiement légères pour les parties dynamiques
Section titled “Conséquence 2 : utiliser des méthodes de déploiement légères pour les parties dynamiques”On va utiliser des outils adaptés à de la CI/CD pour les déploiements réguliers.
La bonne solution dépend des compétences techniques de l’équipe.
Aujourd’hui on conseillerait d’utiliser Kubernetes et des images de conteneurs comme méthode de déploiement du code.
Mais K8s n’est pas simple, et dans des cas plus raisonnables on peut utiliser ansible en coordination avec des utilitaires de déploiement continu.
Ansible va utiliser un inventaire dynamique, basé sur les API du cloud provider voire même en utilisant l’état Terraform.
Les utilitaires de déploiement vont récupérer à la demande les nouvelles versions du code, qui peut être déployé via
- des conteneurs : docker-compose
- des
git pull: capistrano et ses alternatives
Point sur l’architecture modulaire, les workspaces et les fichiers d’états
Section titled “Point sur l’architecture modulaire, les workspaces et les fichiers d’états”Notre vision de Terraform a évolué : il y a des limitations d’usage dans ces techniques un peu complexes, et on voit qu’il faut bien structurer son code à terme.
Nous allons voir que sur cette base le logiciel terragrunt va nous permettre de résoudre plusieurs problèmes avec quelques principes de base.
- Don’t Repeat Yourself (DRY) : éviter la répétition du code
- Usage de conventions structurelles : travailler uniquement avec des modules
- Standardisation des environnements : gestions des états d’infrastructure
Terragrunt
Section titled “Terragrunt”La ligne de commande terragrunt
Section titled “La ligne de commande terragrunt”À la base, Terragrunt est une “surcouche” qui va piloter terraform pour vous.
Les versions de Terragrunt sont à aligner avec celles de Terraform.
Pour lancer Terragrunt, on utilisera les mêmes actions que Terraform
$ terragrunt init$ terragrunt plan$ terragrunt apply$ terragrunt destroyMais là où Terragrunt va présenter une différence, c’est qu’il peut opérer sur plusieurs recettes à la fois.
$ terragrunt run-all apply$ terragrunt run-all destroyTerragrunt : Terraform sous stéroïdes
Section titled “Terragrunt : Terraform sous stéroïdes”Avec Terragrunt, on passe d’une structure redondante :
└── live ├── prod │ ├── app │ │ └── main.tf + variables.tf + output.tf + ... │ ├── mysql │ │ └── main.tf + variables.tf + output.tf + ... │ └── vpc │ └── main.tf + variables.tf + output.tf + ... ├── qa │ ├── app │ │ └── main.tf + variables.tf + output.tf + ... │ ├── mysql │ │ └── main.tf + variables.tf + output.tf + ... │ └── vpc │ └── main.tf + variables.tf + output.tf + ... └── stage ├── app │ └── main.tf + variables.tf + output.tf + ... ├── mysql │ └── main.tf + variables.tf + output.tf + ... └── vpc └── main.tf + variables.tf + output.tf + ...à ça
└── live ├─ terragrunt.hcl ├── prod │ ├── app │ │ └── terragrunt.hcl (v 1.7.4) │ ├── mysql │ │ └── terragrunt.hcl (v 2.1.0) │ └── vpc │ └── terragrunt.hcl (v 1.8.2) ├── qa │ ├── app │ │ └── terragrunt.hcl (v 1.7.5) │ ├── mysql │ │ └── terragrunt.hcl (v 2.1.0) │ └── vpc │ └── terragrunt.hcl (v 1.8.3) └── stage ├── app │ └── terragrunt.hcl (v 1.7.7) ├── mysql │ └── terragrunt.hcl (v 2.1.2) └── vpc └── terragrunt.hcl (v 1.8.3)Gestion des dépendances
Section titled “Gestion des dépendances”Chaque fichier terragrunt.hcl contient les informations nécessaires pour gérer l’infrastructure via Terraform.
terraform { # Deploy version v0.0.3 in stage source = "git::git@github.com:foo/modules.git//app?ref=v0.0.3"}
inputs = { instance_count = 3 instance_type = "t2.micro"}Gestion des backends de fichiers d’état
Section titled “Gestion des backends de fichiers d’état”On a vu une limitation des backends : impossible d’utiliser des variables quand on déclare un backend de remote state.
Et il fallait créer le bucket S3 pour stocker notre remote state séparément.
Avec Terragrunt, on a une syntaxe qui va prendre ça en charge.
remote_state { backend = "s3" generate = { path = "backend.tf" if_exists = "overwrite" } config = { bucket = "my-terraform-state" key = "${path_relative_to_include()}/terraform.tfstate" region = "us-east-1" encrypt = true dynamodb_table = "my-lock-table" }}Et d’autres avantages…
Section titled “Et d’autres avantages…”Parmi les aspects pratiques de Terraform, on peut utiliser des “hooks” qui déclenchent des actions à des moments clefs.
terraform { # Pull the terraform configuration at the github repo "acme/infrastructure-modules", under the subdirectory # "networking/vpc", using the git tag "v0.0.1". source = "git::git@github.com:acme/infrastructure-modules.git//networking/vpc?ref=v0.0.1"
# For any terraform commands that use locking, make sure to configure a lock timeout of 20 minutes. extra_arguments "retry_lock" { commands = get_terraform_commands_that_need_locking() arguments = ["-lock-timeout=20m"] }
# You can also specify multiple extra arguments for each use case. Here we configure terragrunt to always pass in the # `common.tfvars` var file located by the parent terragrunt config. extra_arguments "custom_vars" { commands = [ "apply", "plan", "import", "push", "refresh" ]
required_var_files = ["${get_parent_terragrunt_dir()}/common.tfvars"] }
# The following are examples of how to specify hooks
# Before apply or plan, run "echo Foo". before_hook "before_hook_1" { commands = ["apply", "plan"] execute = ["echo", "Foo"] }
# Before apply, run "echo Bar". Note that blocks are ordered, so this hook will run after the previous hook to # "echo Foo". In this case, always "echo Bar" even if the previous hook failed. before_hook "before_hook_2" { commands = ["apply"] execute = ["echo", "Bar"] run_on_error = true }
# After running apply or plan, run "echo Baz". This hook is configured so that it will always run, even if the apply # or plan failed. after_hook "after_hook_1" { commands = ["apply", "plan"] execute = ["echo", "Baz"] }
# After an error occurs during apply or plan, run "echo Error Hook executed". This hook is configured so that it will run # after any error, with the ".*" expression. error_hook "error_hook_1" { commands = ["apply", "plan"] execute = ["echo", "Error Hook executed"] on_errors = [ ".*", ] }
}Gestion de l’architecture DRY
Section titled “Gestion de l’architecture DRY”Pour éviter de se répéter entre configurations d’environnement, Terragrunt utilise une directive include.
└── live ├── terragrunt.hcl ├── _env │ ├── app.hcl │ ├── mysql.hcl │ └── vpc.hcl ├── prod │ ├── app │ │ └── terragrunt.hcl │ ├── mysql │ │ └── terragrunt.hcl │ └── vpc │ └── terragrunt.hclterraform { source = "github.com/<org>/modules.git//app?ref=v0.1.0"}
dependency "vpc" { config_path = "../vpc"}
dependency "mysql" { config_path = "../mysql"}
inputs = { basename = "example-app" vpc_id = dependency.vpc.outputs.vpc_id subnet_ids = dependency.vpc.outputs.subnet_ids mysql_endpoint = dependency.mysql.outputs.endpoint}include "root" { path = find_in_parent_folders()}
include "env" { path = "${get_terragrunt_dir()}/../../_env/app.hcl"}
inputs = { env = "prod"}Rappel des objectifs
Section titled “Rappel des objectifs”- Comprendre comment bien structurer ses projets dans Terraform avec Terragrunt