En dehors du titre, le générique masculin est utilisé sans aucune discrimination et uniquement dans le but d'alléger le texte.

Terraform, c’est mon petit plaisir du moment. Obligé je t’en parle. Avec l’explosion de la mouvance DevOps de ces dernières années, l’infrastructure-as-code est devenue un must. Et Terraform, c’est le chouchou dans ce domaine.

Il était une fois

Nous sommes début 2006. Amazon annonce une suite de service légèrement connu aujourd’hui : Amazon Web Service (AWS). Le 25 août de la même année, EC2 est lancé en bêta aux États-Unis. Pour la première fois, les développeurs peuvent lancer un serveur dans le cloud à la demande en quelques minutes. Un an plus tard, le service est ouvert publiquement dans le monde entier. Ça allait tout changer.

L’infrastructure en tant que service était née et allait donner du plaisir à beaucoup de monde. Il faut comprendre qu’avant cette révolution, avoir un serveur pour ton app, c’était l’enfer. Ça prenait une demande au service d’infra où t’attendais pendant les étapes de création manuelle d’un serveur physique qui ne bougera plus.

Désormais, ton serveur en trois clics, tu le crées, tu le modifies et tu le détruis. Et ça, quasiment instantanément, dans le cloud mondial des Internets. Mais c’est toujours pas suffisant pour un contrôle total.

Imagine si via ton code, tu pouvais dynamiquement créer et manager l’infrastructure de ton service dans les nuages ? Amazon aime tellement l’idée que le 15 mai 2010 ils annoncent CloudFormation. L’infrastructure en tant que code était née et allait donner du plaisir à autant de monde. L’outil IaC qui fait le plus parler de lui en ce moment c’est Terraform.

C’est quoi Terraform ?

Terraform est un outil d’infrastructure en tant que code. C’est open source et écrit en Go par Hashicorp. Concrètement, ça te permet de déclarer, via ton code, ce que tu veux pour ton infrastructure. Dans des fichiers de configuration structurés tu vas pouvoir manager automatiquement ton infrastructure sans action manuelle. Que ça soit l’approvisionnement initial, la mise à jour ou la destruction de ton infrastructure, c’est le code qui pilote.

Dans le monde de l’infra-as-code tu as plusieurs manières de faire les choses. Terraform opte pour la façon de faire dite déclarative. Tu déclares dans tes fichiers de configs l’état désiré de ton infrastructure. Terraform ne va ne faire qu’exécuter le minimum pour arriver à l’état que tu désires.

Exemple : ton infrastructure en live a déjà un serveur web et une base de données qui tournent. Ta config dans ton code correspond parfaitement à ça. Tu ajoutes dans ta configuration Terraform que finalement tu veux deux bases de données. Quand tu commit, Terraform va créer une base de données en plus et rien faire d’autre. Il a atteint l’état désiré déclaré par ton code. C’est de toute beauté.

C’est beau parce que ça veut dire que tu peux voir et comprendre ton infrastructure en regardant ton code. C’est versionné via git du coup en plus. Comme c’est du code, tu peux l’utiliser pour tester dans plusieurs environnements avant la prod. Pousse le même code en environnement dev et tu peux tester des changements de façon safe. Tu peux checker l’état de ton infra en code review avec tes collègues. Tu peux itérer rapidement et faire des changements importants en changeant quelques lignes. Bref c’est le paradis pour la façon de faire DevOps.

Mais concrètement, comment ça marche ? Comment via un bout de config, Terraform arrive à créer, modifier et détruire ton infrastructure ?

Comment ça marche ?

Terraform opte pour l’approche dite “push”. Il va prendre l’état que tu as déclaré dans tes fichiers de configuration et pousser les modifications vers le provider de destination. Imaginons, tu veux créer un serveur sur AWS. Tu le déclares, tu pousses et pouf ton serveur apparaît dans ton compte AWS.

En fait, ce qui s’est passé sous le moteur c’est que Terraform a utilisé le SDK d’AWS écrit en Go. Via ce SDK il a créé directement ton serveur sur le cloud. Mais t’as pas à te faire chier avec le code du SDK ou vraiment comprendre ce qui se passe derrière. Terraform gère sa merde et toi tu gères ton code. Ça rend le flow de mise à jour d’infrastructure hyper simple et très rapide pour toi.

Et le truc de fifou c’est que Terraform n’est pas limité à un provider (AWS) ou au Cloud de façon générale. Presque tous les types d’infrastructure peuvent être représentés comme une ressource dans Terraform. De tes containers docker sur ta machine en local à ton compte cloud sur DigitalOcean, c’est un truc de dingue. Je ne vais pas te faire la liste entière des providers possibles, y’a toute la terre. Et ça rend Terraform encore plus magnifique. T’as le pouvoir de création sur n’importe quelle destination.

OK, c’est bien beau tout ça, mais ça sera tout pour la théorie. Mettons les mains dedans.

Fais voir le code

Commençons par installer cette affaire. Je pars du principe que tu es sur une sainte distrib Linux. Si c’est pas le cas, tu peux regarder de ce côté-là.

# on installe les dépendances sudo apt-get install wget unzip # on telecharge la derniére version wget https://releases.hashicorp.com/terraform/0.12.20/terraform_0.12.20_linux_amd64.zip # on unzip le contenu dans le dossier bin sudo unzip ./terraform_0.12.20_linux_amd64.zip -d /usr/local/bin/ # on vérifie si l'installation s'est bien passé terraform -v

Maintenant, on va écrire notre premier fichier de configuration. Aujourd’hui on va faire simple : on va créer un serveur EC2 et une base de données Postgres sur AWS. Je pars donc du principe que tu as un compte AWS avec les crédentials qui vont bien. Première étape, on crée un fichier main.tf ou on va simplement définir notre provider.

main.tf

# definition provider provider "aws" { version = "~> 2.0" region = "${var.provider_region}" access_key = "${var.secret_access_key}" secret_key = "${var.secret_key}" }

On commence simplement par dire qu’on veut travailler sur AWS et on choisit notre version et notre région. Pour accéder à AWS on précise les credentials qui sont nos deux clefs habituelles. Elles sont référencées via un système de variable. Ces variables sont toutes stockées dans un fichier qu’on va voir plus tard.

Maintenant qu’on a déclaré la destination, on va déclarer ce qu’on veut créer comme infrastructure. Comme d’hab, quand sait pas par quoi commencer, on regarde la doc. On va créer un fichier resource.tf où on déclare une machine Ubuntu et une base de données Postgres.

resource.tf

# le serveur via une instance aws resource "aws_instance" "web" { ami = "${data.aws_ami.ubuntu.id}" instance_type = "${var.server_instance_type}" tags = { Name = "${var.server_tag_name}" } } # on utilise une image machine amazon (ami) pour mettre un ubuntu data "aws_ami" "ubuntu" { most_recent = true filter { name = "name" values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"] } owners = ["099720109477"] } # la base de données postgres sql via une intance db aws resource "aws_db_instance" "default" { allocated_storage = "${var.db_allocated_storage}" storage_type = "${var.db_storage_type}" engine = "${var.db_engine}" engine_version = "${var.db_engine_version}" instance_class = "${var.db_instance_class}" name = "${var.db_database_name}" username = "${var.db_database_username}" password = "${var.secret_db_database_password}" final_snapshot_identifier = "${var.db_snapshot_identifier}" }

Maintenant qu’on a défini tout ce qu’on voulait, on va remplir tout ça avec les variables correspondantes. On crée un fichier variables.tf dédié à ça. À noter que les secrets ne devraient jamais être en clair dans un fichier, mais pour simplifier, on va faire comme ça.

# provider variables variable "provider_region" { description = "Provider region" default = "us-east-1" } variable "secret_access_key" { description = "Provider access key" default = "YOUR-ACCESS-KEY" } variable "secret_key" { description = "Provider secret key" default = "YOUR-SECRET-KEY" } # instance web variables variable "server_instance_type" { description = "Server instance type" default = "t2.micro" } variable "server_tag_name" { description = "Server tag name" default = "JeSuisUnDev" } # instance base de données RDS postgres variables variable "db_allocated_storage" { description = "Allocated storage" default = 20 } variable "db_storage_type" { description = "Storage type" default = "gp2" } variable "db_engine" { description = "Storage engine" default = "postgres" } variable "db_engine_version" { description = "Storage engine version" default = "11.5" } variable "db_instance_class" { description = "Storage instance class" default = "db.t2.micro" } variable "db_database_name" { description = "Storage database name" default = "postgres" } variable "db_database_username" { description = "Storage database name" default = "postgres" } variable "secret_db_database_password" { description = "Storage database secret password" default = "postgres" } variable "db_snapshot_identifier" { description = "Storage database snapshot identifier" default = "postgres" }

Et voilà ! Fin de la configuration ! Il est temps de tester tout ça. Pour ce faire, on va y aller en plusieurs étapes. On va d’abord initialiser Terraform, valider nos fichiers, planifier nos actions et enfin appliquer nos changements. C’est parti pour utiliser la CLI Terraform !

Commençons par taper la commande d’initialisation de Terraform. Il faut le faire dans le dossier courant où tu as créé les fichiers précédents.

> terraform init Initializing the backend... Initializing provider plugins... - Checking for available provider plugins... - Downloading plugin for provider "aws" (hashicorp/aws) 2.48.0... Terraform has been successfully initialized!

On valide que tout va bien dans notre configuration.

> terraform validate Success! The configuration is valid.

Ensuite, on planifie notre prochaine action. On ne devrait avoir que des créations puisqu’on est sur une infra vierge.

> terraform plan An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_db_instance.default will be created # aws_instance.web will be created Plan: 2 to add, 0 to change, 0 to destroy.

Et maintenant, le moment fatidique qui fait toute la magie de ce genre d’outil. On va appliquer notre plan. Ça va avoir pour effet de créer réellement notre infrastructure sur le cloud.

> terraform apply aws_instance.web: Creating... aws_db_instance.default: Creating... aws_instance.web: Still creating... [10s elapsed] aws_instance.web: Still creating... [30s elapsed] aws_instance.web: Creation complete after 43s [id=i-0f285230749e69a67] aws_db_instance.default: Still creating... [50s elapsed] aws_db_instance.default: Still creating... [1m50s elapsed] aws_db_instance.default: Still creating... [2m50s elapsed] aws_db_instance.default: Still creating... [3m50s elapsed] aws_db_instance.default: Still creating... [4m0s elapsed] aws_db_instance.default: Creation complete after 4m8s [id=terraform-20200208064624729700000001] Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Et voilà. Tu viens de créer une infrastructure avec du code. Si c’est pas magnifique ça ! Bon maintenant que ça c’est fait, tu peux regarder son statut avec un petit “terraform show”. Et surtout tu peux tout détruire aussi facilement que tu l’as créé.

> terraform destroy An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: - destroy aws_instance.web: Destroying... [id=i-0f285230749e69a67] aws_db_instance.default: Destroying... [id=terraform-20200208064624729700000001] aws_instance.web: Destruction complete after 30s aws_db_instance.default: Destruction complete after 50s Destroy complete! Resources: 0 added, 0 changed, 2 destroyed.

Tu as maintenant un contrôle complet via ton code sur toute ton infrastructure. Et si tu intègres tout ça dans un pipeline CI/CD, là tu rigoles plus du tout.

Épilogue

Tu veux en savoir plus ? Je t’invite à bouffer entièrement la doc, elle est très bien faite. Il y a également un stepper complet ici fait par Hashicorp qui est vraiment bien foutu. En cinq minutes, on a un peu survolé les possibilités de Terraform, mais la base est là !