La gestion du state
L’état des ressources
L’architecture de Terraform repose sur la notion de state.
Le state peut être vu comme une base de données, qui contient l’état des ressources qui ont été créées sur le service cloud par Terraform et leur lien avec le code.
Lors de l’exécution d’un terraform plan, Terraform compare le code au state, calcule la différence et planifie les opérations :
-
Une ressource présente dans le code, mais absente du state, est marquée pour création.
-
Une ressource présente dans le state, mais absente du code, est marquée pour suppression.
-
Si une ressource présente dans le state et dans le code, et que des attributs sont modifiés, elle est marquée pour mise à jour, ou pour recréation si les attributs ne peuvent pas être mis à jour.
Le state est donc un élément indispensable pour pouvoir gérer l’infrastructure. Sans state, il serait impossible de détecter la suppression de ressources du code, ou les mises à jour par exemple.
Le state contient l’intégralité des attributs des ressources. Il peut donc contenir des données sensibles, comme des mots de passe, des clés d’API, etc.
Le fichier de state
Par défaut, Terraform stocke son state dans un fichier nommé terraform.tfstate. Lors d’un terraform apply, Terraform modifie le fichier de state pour y stocker l’état appliqué de l’infrastructure. Dans ce fonctionnement par défaut, Terraform sauvegarde également le dernier state dans un fichier terraform.tfstate.backup.
Attention à ne pas publier ces fichiers par mégarde. Comme ils peuvent contenir des données sensibles, évitez de les stocker ces fichiers sur vos repositories Git. Utilisez un .gitignore pour cela. Un exemple de fichier .gitignore est donné au chapitre Outils externes - .gitignore.
Ce fichier terraform.tfstate est un fichier au format JSON. Il contient la structure suivante (cette structure est simplifiée) :
{
"version": 4,
"terraform_version": "1.7.5",
"serial": 12,
"lineage": "d1a0918e-7f5b-b55b-62c1-b43925a978f3",
"outputs": {},
"resources": []
}
Les champs version et terraform_version permettent d’identifier quelle version de Terraform a produit le fichier de state, et de pouvoir proposer une migration si besoin.
1. serial et lineage
Le state est identifié par son lineage qui est une valeur unique, générée lors de la première création du state, pendant l’exécution de terraform apply. Le champ serial permet d’identifier quel est le numéro de version du state. À chaque exécution d’un terraform apply, le champ serial est incrémenté.
Ce champ est utilisé par Terraform lorsqu’un terraform apply est exécuté en lui passant en paramètre un plan précalculé. Le plan contient alors le serial du state qui a servi à le calculer. Terraform vérifie si le state n’a pas été modifié depuis la production du plan, pour éviter les effets de bord liés à des modifications concurrentes du state.
Si le serial ou le lineage du state ne correspond pas aux valeurs du plan, l’exécution du apply est annulée, et un message d’erreur est affiché à l’utilisateur. ...
Le backend
La section précédente a présenté le concept de state, et son stockage sous la forme d’un fichier JSON nommé terraform.tfstate.
Lorsque plusieurs personnes souhaitent travailler sur la même infrastructure et le même code Terraform, il n’est pas raisonnable de se passer le state de machine en machine, ni de l’archiver sur un repository Git auprès du code, à cause de la sensibilité des infos qu’il contient.
La notion de backend permet de répondre à cette problématique de travail partagé (et parfois concurrent) sur la même infrastructure et donc le même code Terraform.
Un backend permet de stocker le state en dehors de la machine de l’utilisateur. Il permet également de poser un verrou lock pour signifier aux autres utilisateurs que l’on est en train d’appliquer des modifications. Le lock et unlock du state est fait automatiquement lors d’un terraform plan ou d’un terraform apply.
Il existe plusieurs types de backend, qui s’appuient principalement sur les services d’object-storage des différents providers de cloud, ou des systèmes de base de données. Parmi les backend s’appuyant sur les services d’object storage, nous retrouvons (sans ordre particulier) :
-
Azurerm, qui s’appuie sur le service Blob Storage de Microsoft Azure ;
-
Cos, qui s’appuie sur le service Cloud Object Storage de Tencent Cloud ;
-
Gcs, qui s’appuie sur le service Google Cloud Storage de Google Cloud ;
-
Oss, qui s’appuie sur le service Object Storage Service de Alibaba Cloud ;
-
s3, qui s’appuie sur le service de même nom de Amazon Web Services.
Ces cinq backend permettent de démarrer rapidement chez les principaux cloud.
Si votre provider de cloud n’est pas dans cette liste, pas de panique ! La plupart des providers fournissent un service compatible avec les API de Amazon S3. Il est donc souvent possible d’adapter une configuration pour le backend s3.
Les backend de type object storage sont les plus utilisés, car ils sont les plus simples à mettre en place, et fournissent tous les prérequis de sécurité, contrôle d’accès, chiffrement au stockage et versioning des différentes versions.
Parmi les autres...
Adresses des ressources et modules
Le fichier de state, qu’il soit stocké en local ou dans un backend, contient les informations des ressources, data, et modules utilisés dans le code.
Pour faire le lien entre le code, et les objets JSON du state, Terraform utilise la notion d’adresse.
L’adresse d’une ressource ou d’un module data est son identifiant unique dans le code, en tenant compte des éventuels count et for_each, et inclusion dans des modules nested.
Pour une ressource simple, l’adresse est construite avec la norme suivante :
<TYPE>.<NAME>
En prenant l’exemple de code suivant :
resource "google_storage_bucket" "student_bucket" {
name = "gcs-bucket-luke"
location = "eu"
}
Cette ressource aura pour adresse google_storage_bucket.student_ bucket.
Pour une ressource avec un count, l’adresse de chaque instance contiendra l’index associé à l’instance entre les caractères [].
<TYPE>.<NAME>[<COUNT_INDEX>]
Par exemple, avec le code suivant :
resource "google_storage_bucket" "luke_buckets" {
count = 2
name = "gcs-bucket-luke-${count.index}"
location = "eu"
}
Cette ressource aura deux instances (count = 2). La première...
Opérations sur le state
La commande terraform state propose des sous-commandes qui permettent de manipuler directement le state. Ces commandes sont exécutées dans des cas généralement avancés, ou pour restaurer des données suite à une mauvaise manipulation.
1. Sauvegarde et restauration
Lorsqu’un backend est configuré, il est possible de récupérer le state courant avec la commande terraform state pull.
Le contenu du state est alors affiché sur la sortie standard. Cette commande est souvent utilisée pour effectuer une sauvegarde du state sur un poste local, avant d’exécution d’autres manipulations, comme une migration de state ou des renommages ou suppressions.
Cette commande ne présente pas un grand intérêt quand aucun backend n’est configuré, puisque le fichier de state est disponible directement dans terraform.tfstate.
Voici un exemple d’exécution de la commande terraform state pull, qui sauvegarde le contenu du state dans un fichier pour en faire une copie locale :
$ terraform state pull > backup.tfstate
La restauration du fichier sauvegardé se fait avec la commande terraform state push, en lui passant en paramètre le fichier à restaurer :
$ terraform state push backup.tfstate
La commande terraform state push s’assure de ne pas écraser un fichier de state ayant un lineage différent ou un numéro de serial plus récent. Si c’est le cas, un message d’erreur est affiché :
$ terraform state push backup.tfstate
Failed to write state: cannot import state with serial 6 over
newer state with serial 7
Il existe un flag -force qui permet de forcer l’écriture du nouveau state et d’écraser le state existant.
Attention aux fausses manipulations ici ! Vérifiez bien quel state vous écrivez quand vous faites un terraform state push -force ! Vous risquez de perdre des données !
$ terraform state push -force backup.tfstate
Lorsque vous utilisez un backend de type object storage, pensez à activer le versioning des objets sur votre bucket chez votre provider de cloud. Le versioning vous permettra de récupérer les anciennes versions des objets, et de pouvoir revenir en arrière en cas de fausse manipulation. C’est...
taint et recréations forcées
Lorsque des ressources sont dans un état instable, il est parfois nécessaire de les recréer en repartant de zéro.
Plutôt que d’exécuter un terraform destroy puis un terraform apply, qui aura pour effet de recréer toute l’infrastructure, Terraform propose un moyen de cibler les ressources à recréer.
1. taint / untaint
La commande terraform taint <ADDRESS> permet de marquer une ressource ou un module pour qu’il soit recréé lors de la prochaine exécution d’un plan/apply.
À l’inverse, la commande terraform untaint <ADDRESS> permet d’enlever le marquage d’une ressource ou d’un module.
Le marquage de la ressource est stocké dans le fichier de state.
Voici un exemple d’exécution de terraform taint suivi d’un terraform plan :
$ terraform taint scaleway_instance_server.web
Resource instance scaleway_instance_server.web has been marked
as tainted.
$ terraform plan
scaleway_instance_server.db: Refreshing state...
[id=fr-par-1/3d9bbe45-ec91-467a-900d-b1d3751def0c]
scaleway_instance_server.web: Refreshing state...
[id=fr-par-1/4bbe7a2f-6a54-4d4c-85cf-ab951c9213ff]
Terraform used the selected providers to generate the following
execution plan. Resource actions are indicated with the following
symbols: ...
Workspaces
1. Manipuler des workspaces
Les workspaces Terraform permettent de gérer plusieurs instances du même code, dans des state séparés.
Ils sont souvent utilisés pour instancier plusieurs environnements, avec des ensembles de variables différents.
Utiliser des workspaces permet de réutiliser du code, sans devoir le dupliquer.
La commande terraform workspace et ses sous-commandes permettent de manipuler les workspaces, les lister, les créer et les sélectionner, ainsi que les supprimer.
Lister des workspaces se fait avec la commande terraform workspace list, dont voici un exemple :
$ terraform workspace list
* default
Le workspace actuellement sélectionné est signalé avec une astérisque *.
Par défaut, Terraform manipule un workspace nommé default.
La création d’un workspace se fait avec la commande terraform workspace new <NAME>.
$ terraform state list
scaleway_instance_server.web
$ terraform workspace new development
Created and switched to workspace "development"!
You're now on a new, empty workspace. Workspaces isolate their state,
so if you run "terraform plan" Terraform will not see any existing
state for this configuration.
$ terraform state list
# No state
Comme indiqué par l’exécution de la commande terraform...
Conclusion
Ce chapitre vous a donné tous les éléments qui permettent de comprendre et manipuler le state Terraform. La configuration d’un backend est un élément incontournable de tout code Terraform. Les commandes de manipulation de state pourront vous sauver de cas qui peuvent sembler désespérés (renommages, imports, suppression et recréations).
Le chapitre suivant aborde les tests dans Terraform. Ils ont longtemps été très mal outillés par Terraform. La commande terraform test corrige ce point et permet aux développeurs de tester leurs modules sans avoir besoin d’outil supplémentaire ni d’écrire des cas de tests en Go.