Bonjour, et bienvenue dans ce cinquième article dédié à OpenStack et Terraform. Aujourd'hui, nous allons aborder des concepts clés dans la simplification de votre vie avec Terraform (et, avouons le, nous adorons tous nous simplifier la vie). Alors, c'est parti.

La Collaboration sur Terraform : Si simple et pourtant si compliqué...

tfstate, je tuerai pour toi...

Comme nous l'avons déjà vu dans l'article présentant Terraform, l'ensemble des actions effectuées par le logiciel est stocké dans un fichier, le tfstate. Ce dernier permet à Terraform de connaître l'état précis du projet sur OpenStack, à savoir ce qui a été déjà déployé. Hors, ce fichier est de base sur votre ordinateur, dans un dossier .terraform. Résultat : Lorsque vous travaillez sur votre infrastructure, vous seul pouvez agir, sur votre ordinateur : Si un autre utilisateur tente de déployer, ça risque de faire des Chocapics! (Et pour avoir vécu la situation, promis, c'est pas beau à voir...). Si par malheur cela vous arrive, sachez que Terraform sera totalement incompétent pour rétablir le projet OpenStack dans un état cohérent : Il faudra alors tout supprimer à la main via Horizon, puis supprimer votre tfstate...

Heureusement, il existe une solution : le Stockage Objet. En effet, OpenStack dispose d'une fonction, via son module Swift, de stockage objet. Cela va nous permettre d'y créer nos tfstates, et de les entreposer en temps réel! En premier, il vous faut créer un container objet sur Horizon : Object Store -> Container -> + Container. Donnez lui un nom simple, ici tfstate_test. Vous pouvez laisser le container en "Non-public". Ensuite, nous devons créer un fichier backends.tfvars, et y noter :

container = "tfstate_test"

Créons également le fichier provider.tf pour indique à Terraform que nous utilisons Swift :

# Terraform conf
terraform {
  backend "swift" {
    domain_id = ""
  }
}

Ensuite, vous devez supprimer votre dossier .terraform (assurez-vous de bien avoir détruit le projet avant !), et l'initialiser à nouveau en utilisant la commande terraform init -backend-config=backends.tfvars. Voilà, vous stockez votre tfstate en ligne. Fini les soucis de synchronisation entre ordinateurs!

Bordel, qui à mis à jour ?!

Le second problème avec Terraform c'est que, bah, c'est encore tout neuf. Aussi, vous allez réguliérement avoir des mises à jour de Terraform à effectuer. A titre d'exemple, lorsque j'ai terminé mon projet le 26 juillet 2019, nous étions à la 0.12.4. Au 30 août, nous somme à la 0.12.7. Et contrairement à pas mal de projets, HashiCorp n'hésite pas à mettre des breaking-changes sur des mises à jour mineures. Mon conseil est donc : Ne mettez à jour Terraform que quand vous en avez besoin, et attendez toujours un peu avant de le faire ! Par ailleurs, sachez que les projets Terraform ne sont pas "rétrocompatibles" : Si un membre de votre équipe migre vers une version plus récente, vous ne pourrez plus déployer avant d'avoir vous aussi mis à jour. Evitez donc de créer de la frustration en mettant à jour sans rien dire (ça aussi, c'est du vécu).

Diviser pour mieux régner

Dans les articles précédents, nous avons créé un seul projet Terraform, qui s'occupe de tout créer. C'est normal, nous n'avions qu'un tout petit projet à envoyer en ligne. Cependant, il est temps de parler de décomposition du projet. En effet, si Terraform est un outil très puissant, il est également limité dans ses options de déploiement, pour être certain de garder en permanence une cohérence entre son fichier d'état, et l'état réel d'OpenStack.

Admettons que vous ayiez une stack entière à déployer. Cette stack comprends :

  • Une application pour un client, ayant besoin de 2 instances derrière un Load Balancer
  • Une instance pour un projet interne à votre équipe
  • Une instance pour du monitoring
  • Une seconde application client, qui requiert 5 instances, réparties en 2 instances frontal, 2 instances backend, et 1 mariadb.

Dans cette situation, utiliser un seul projet pour tout mettre en ligne est suicidaire : Le terraform destroy détruirait en effet toute l'infrastructure, sans distinction de si les instances sont pour le client A, le client B, ou pour vous. C'est pour cela qu'il est indispensable de séparer votre stack en projets. Ici, vous pouvez créer par exemple 3 projets : Un pour le client A, un pour le client B, un pour tout le reste. Créer un projet est simple : C'est un dossier dans lequel on effectuera un terraform init (Note : chaque dossier doit avoir son tfstate indépendant sur le stockage object, bien évidemment).

Un exemple ici :

  • terraform
    • client_a
      • instances.tf
      • recordsets.tf
      • loadbalancer.tf
      • backends.tfvars
      • ...
    • client_b
      • instances.tf
      • backends.tfvars
      • ...
    • stack_globale
      • instances.tf
      • backends.tfvars
      • ...

Ainsi, vous aurez un projet terraform pour chacune de vos stacks, et vous disposerez d'une meilleure modularité. Il est à noter qu'il est également possible de créer une stack servant de support aux autres. Par exemple, j'utilise une stack "security", qui va créer tout les groupes de sécurité, qui sont ensuite simplement appelés par les autres. Cela évite de dupliquer du code. Terraform offre une énorme flexibilité là dessus, n'hésitez pas à constamment chercher à optimiser ce que vous faites!

Variabiliser, c'est simplifier !

La dernière partie de notre article concerne la variabilisation. La variabilisation va vous permettre de simplifier grandement vos déploiements pour automatiser tout ce qui peut l'être; nous l'avons déjà utilisé à quelques reprises auparavant, mais attelons-nous à l'analyser plus en profondeur. je distingue 2 "familles" de variables :

Les variables globales

Les variables globales sont là pour simplifier la vie : Peut importe votre environnement d'exécution, elle sera toujours identique. Ce peut être par exemple la zone DNS par défaut, ou un port. Pour définir les variables globales, il suffit de créer un fichier variables.tf, et d'y ajouter les variables que vous voulez, sous cette forme :

variable "spring_port" {
  default = "8080"
}

Vous pouvez ensuite vous en servir dans vos autres fichiers. Exemple ici dans le fichier security_groups.tf :

resource "openstack_networking_secgroup_rule_v2" "spring" {
  direction         = "ingress"
  ethertype         = "IPv4"
  protocol          = "tcp"
  port_range_min    = "${var.spring_port}"
  port_range_max    = "${var.spring_port}"
  remote_ip_prefix  = "${var.network}"
  security_group_id = "${openstack_networking_secgroup_v2.spring.id}"
}

Mais, la partie la plus intéressante consiste à créer des variables selon l'environnement qu'on déploie...

Les variables d'environnement de déploiement

Pour cette partie, nous allons devoir créer des dossiers, plus spécifiquement un par environnement de déploiement. Ici, imaginons que nous avons les environnements de développement (dev) et de production (prd). Nous allons donc créer 2 dossiers dans notre projet, un dev, et un prd. Dans chacun de ces dossiers, nous allons créer un fichier backends.tfvars, et un fichier terraform.tfvars (Bien entendu, on supprime ces fichiers de la racine s'ils existaient). Voici donc un schéma de votre arborescence :

  • projet_terraform
    • dev
      • backends.tfvars
      • terraform.tfvars
    • prd
      • backend.tfvars
      • terraform.tfvars
    • instances.tf
    • security_groups.tf
    • variables.tf
    • ...

Dans les fichiers backends.tfvars et terraform.tfvars, nous allons donner toute les variables et configurations qui sont sujettes à changement : Dans le backends.tfvars, le nom du container objet qui sert à sauvegarder le tfstate, par exemple pour la dev:

container = "tfstate-projet-dev"

Dans le fichier terraform.tfvars, on va indiquer les variables qui différent : Par exemple :

env                     = "dev"
network                 = "192.168.10.0/24"
dns_server              = "10.251.10.2"
vol_db                  = "da27de45-4ba1-4c1c-bcb0-4a0039718e09"

Ces variables doivent ensuite être malgré tout déclarées dans le fichier global variables.tf, comme suit :

variable "network" {}
variable "env" {}
variable "vol_db" {}
variable "dns_server" {}

Elles seront ensuite utilisables comme toute autre variable.

Enfin, nous modifions la manière de déployer le projet : Pour déployer la prod, naviguez jusque dans le dossier prd, puis tapez terraform apply ../. A noter que vous devrez également initialiser les fichiers comme suit : terraform init -backend-config=backends.tfvars ../. Voilà, vous disposez d'environnements d'exécutions propres!

Pour le dernier article, nous conclurons notre série par une rétrospective, des remerciements, et vous retrouverez par la même occasion l'intégralité des sources, étapes par étapes. Bisous!

Article précédent Article suivant