Introduction

Terraform est un outil créé en 2014 par HashiCorp, la société qui a créé d'autres outils que vous connaissez sûrement : Consul, Vagrant, Vault, Atlas, Packer et Nomad.

Terraform est un outil open source d’infrastructure as code, écrit en go, dont l’approche est d’autoriser la définition d’une architecture aussi hétérogène que possible et ainsi faire cohabiter des instances Amazon EC2 et Google Cloud Engine, gérer son DNS avec DNSimple ou encore envoyer les mailings avec Mailgun. Sur la page d’introduction du projet, Terraform se compare avec d’autres solutions du marché, assumant pleinement ne pas remplir les mêmes fonctions que Puppet ou Chef. Le duel avec Ansible est malheureusement absent. Saluons cependant la démarche.

Terraform (TF) est un outil qui permet de construire, modifier et versionner une infrastructure.

Contrairement à ce que l'on peut lire sur Internet, la technologie n'est pas « plateforme agnostique », mais elle permet d'utiliser plusieurs providers dans un même template de configuration. Il existe en effet des plugins pour des providers de Cloud, des services d'hébergement, des SCM… Nous le verrons un peu plus tard dans cet article.

Que fait l'outil ?

  • Il assure la création et la cohérence d'infrastructure.
  • Il permet d'appliquer des modifications incrémentales.
  • On peut détruire des ressources si besoin.
  • On peut prévisualiser les modifications avant de les appliquer.

Concrètement, Terraform permet de créer automatiquement son infrastructure, comme des machines virtuelles, des lambdas Amazon Web Services (AWS)… le tout, simplement, en trois étapes :

  • Write : écriture de la définition de ses ressources dans des fichiers au format *.tf ;
  • Plan : un plan des ressources à créer/modifier & supprimer est affiché, avant tout changement ;
  • Create : l'infrastructure voulue est mise en place, et reproductible dans tous les environnements souhaités.

Intégration avec le monde extérieur

L’ensemble d’un projet Terraform s’organise autour de fichiers de configuration, lesquels sont de simples fichiers texte qui peuvent être écrits au format spécifique de Terraform (.tf) ou en JSON (.tf.json). Si le premier est préférable, autorisant les commentaires et utilisant une syntaxe plus concise, le second offre la flexibilité de JSON, interprétable par la grande majorité des langages.

Il est possible d’exporter toutes les valeurs de son infrastructure dans des fichiers texte en utilsant la commande output. Nous pouvons donc, par exemple générer un fichier d’inventaire pour une utilisation ultérieure.

Lors de la création de l’infrastructure, Terraform génère un fichier d’état (terraform.tfstate). Depuis la version 0.3, ce fichier est au format JSON, lisible humainement à des fins de debug mais plus certainement pour s’interfacer avec d’autres outils. Notons dans cette veine le projet terraform-inventory qui transforme un état terraform, en fichier utilisable par Ansible. En pratique, parcourir le fichier d’état est suffisamment simple pour se permettre de créer son propre parser. Voici un exemple de parser écrit en javascript.

Concepts

Les fichiers de configuration s'écrivent en HCL (HashiCorp Configuration Language). Le principe est d'écrire des ressources.

Les ressources peuvent être écrites en JSON également, mais il est recommandé de les écrire en HCL.

Lire un fichier de configuration HCL est plutôt simple et intuitif.

C'est un langage dit « human readable ». Ce qu'il faut savoir, c'est que Terraform scanne tous les fichiers se terminant par “.tf” dans le répertoire courant : Par contre, il ne va pas scanner les répertoires enfants.

Un fournisseur (provider) est responsable du cycle de vie/du CRUD (create, read, update, delete) d'une ressource : sa création, sa lecture, sa mise à jour et sa suppression.

Plus de 125 fournisseurs (providers) sont actuellement supportés :

  • AWS, GCP, Azure, OpenStack… ;
  • Heroku, OVH, 1&1… ;
  • Consul, Chef, Vault… ;
  • Docker, Kubernetes … ;
  • GitLab, BitBucket, GitHub… ;
  • MySQL, PostgreSQL… ;
  • mais encore RabbitMQ, DNSimple, CloudFlare…

La liste complète des fournisseurs (providers) est disponible sur le site de Terraform : https://www.terraform.io/docs/providers/index.html.

Vous ne trouvez pas le fournisseur de vos rêves ? Pas de soucis, vous pouvez écrire votre propre plugin et participer à l'élaboration de nouvelles fonctionnalités de Terraform.

Pour configurer un fournisseur, si nous voulons par exemple déployer une infrastructure Amazon Web Services (AWS), il suffit de créer une ressource fournisseur “aws” dans un fichier se terminant par “.tf” :

   provider "aws" {
     region = "eu-central-1"
   }
   

Note:

Bonne pratique : ne pas mettre les informations de connexion directement dans la ressource fournisseur “aws”

Opter plutôt modifier vos variables d'environnement :

   export AWS_ACCESS_KEY=YOUR_ACCESS_KEY
   export AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY
   export AWS_DEFAULT_REGION=eu-central-1

Ou bien vous pouvez les mettre dans un fichier .aws/credentials :

   [default]
   aws_access_key_id = YOUR_ACCESS_KEY
   aws_secret_access_key = YOUR_SECRET_KEY

Attention:

Il va de soi que vous devrez prendre les tokens d'un utilisateur qui aura les droits/la bonne policy pour créer les services que vous voulez.

Afin de faire du code propre et qui se réutilise, il est recommandé d'initialiser des variables et de les utiliser dans les autres fichiers “.tf”.

Définissez vos variables dans un fichier, par exemple :

variables.tf

   variable "default-aws-region" {
     default = "eu-central-1"
   }
   
   variable "aws_account_id" {
     default = "123456789123"
   }
   
   variable "tags-tool" {
     default = "Terraform"
   }
   
   variable "tags-contact" {
     default = "John DOE"
   }
   
   variable "aws_s3_folder" {
     default = "s3"
   }
   
   variable "aws_s3_bucket_terraform" {
     default = "mon.bucket.terraform"
   }
   

Appelez-les dans vos ressources :

aws_s3.tf

   resource "aws_s3_bucket" "mon-bucket-terraform" {
     bucket = "${var.aws_s3_bucket_terraform}"
     acl    = "private"
     tags {
       Tool    = "${var.tags-tool}"
       Contact = "${var.tags-contact}"
     }
   }

Les modules sont utilisés pour créer des composants réutilisables, améliorer l'organisation et traiter les éléments de l'infrastructure comme une boite noire.

C'est un groupe de ressources qui prennent en entrée des paramètres et retournent en sortie des outputs.

Dans le même fichier, root.tf, dans lequel vous avez défini votre fournisseur, vous pouvez ensuite définir vos modules :

   module "lambda" {
     source            = "./lambda/"
     toto              = "${var.aws_s3_bucket_toto_lambda_key}"
     titi              = "${var.aws_s3_bucket_titi_key}"
     tutu              = "${var.aws_s3_bucket_tutu_key}"
   }

Les modules peuvent produire des outputs que l'on pourra utiliser dans d'autres ressources.

lambda/outputs.tf

   output "authorizer_uri" {
     value = "${aws_lambda_function.lambda_toto.invoke_arn}"
   }
   

ws_api_gw.tf

   resource "aws_api_gateway_authorizer" "custom_authorizer" {
     name                             = "CustomAuthorizer"
     rest_api_id                      = "${aws_api_gateway_rest_api.toto_api.id}"
     authorizer_uri                   = "${module.lambda.authorizer_uri}"
     identity_validation_expression   = "Bearer .*"
     authorizer_result_ttl_in_seconds = "30"
   }

Les sources de données (Data Sources) servent à récupérer une donnée/ressource existante dans votre infrastructure.

Utiliser une source de données est une bonne pratique. En effet, il est bon de privilégier son utilisation plutôt que de coder en dur des subnet_id, account_id ou autre donnée qui peut être récupérée !

data.tf

   # VPC
   data "aws_subnet" "subnet_id_1" {
     filter {
       name = "tag:Name"
       values = ["mysubnet-for-developpez"]
    }
   }

Et ensuite vous pouvez appeler cette source de données simplement dans vos fichiers .tf comme ceci :

root.tf :

   module "developpez" {
       source = "developpez"
       subnet_id_1 = "${data.aws_subnet.subnet_id_1.id}"
   }

Comme nous l'avons vu, il est possible de récupérer une source de données grâce à un système de filtre. C'est super… mais, et oui il y a un mais… malheureusement ce filtrage n'est pas possible avec tous les types de ressources.

Chez AWS il existe deux types de répartition de charge (load balancer) : classique et réseau (network). Imaginons que nous souhaitions récupérer une répartition de charge de type réseau, chez AWS, pour pouvoir créer une AWS Route 53 Record (entrée dans un nom de domaine). Nous ne pouvons appliquer un filtre sur ce type de ressources, nous allons donc devoir trouver une parade pour récupérer notre fameuse répartition de charge de type réseau.

La solution est de réaliser les actions suivantes.

  • Créer un script qui récupère la ressource.
  • Utiliser le résultat de ce script dans une source de données Terraform avec un external provider.
  • Utiliser cette donnée spéciale dans une ressource que vous voulez créer.

my_module/scripts/get_elb.sh

   #!/bin/bash
   set -e
   kubectl get svc mygateway -n istio-system -o json | jq .status.loadBalancer.ingress[0]
   

Ce script retourne un objet JSON contenant une gateway Kubernetes (Istio) (côté infrastructure il s'agit d'un AWS Load Balancer).

   $ ./my_module/scripts/get_elb.sh
   {
   "hostname": "a123456789gsfgfsgfsg12134gsgsg78-123456789dfdsf45545fdsf.elb.eu-central-1.amazonaws.com"
   }

my_module/data.tf

   data "external" "elb" {
        program= ["bash", "${path.module}/scripts/get_elb.sh"]
   }

my_module/aws_r53.tf

   resource "aws_route53_record" "mymodule_CNAME" {
       zone_id="${data.aws_route53_zone.my_zone.zone_id}"
       name="${var.domain_name}"
       type="CNAME"
       records= ["${data.external.elb.result.hostname}"]
       ttl="${var.route53_ttl}"
   }

Un état (state) est un snapshot de votre infrastructure depuis la dernière fois que vous avez exécuté la commande terraform apply.

Terraform utilise un stockage local pour créer les plans et effectuer les changements sur votre infrastructure, mais il est possible de stocker cet état dans une solution cloud.

Pour configurer le stockage de cet état de manière distante, il suffit de définir un backend.

backend.tf

   # Backend configuration is loaded early so we can't use variables
   terraform { 
     backend "s3" {
       region  = "eu-central-1"
       bucket  = "mon.bucket.terraform"
       key     = "state.tfstate"
       encrypt = true
     }
   }

CLI

Terraform a une CLI (Command-Line Interface) facile à utiliser et composée de plusieurs commandes : nous allons en voir quelques-unes.

   terraform init

La commande init va initialiser votre répertoire de travail qui contient vos fichiers de configuration au format .tf.

C'est la première commande à exécuter pour une nouvelle configuration, ou après avoir fait un checkout d'une configuration existante, depuis votre dépôt git par exemple.

La commande init va :

  • télécharger et installer les fournisseurs (providers) ;
  • initialiser le backend (si défini) ;
  • télécharger et installer les modules (si définis).
   terraform plan

La commande plan permet de créer un plan d'exécution. Terraform va déterminer quelles actions il doit entreprendre afin d'avoir les ressources listées dans les fichiers de configuration par rapport à ce qui est actuellement en place sur l'environnement/le fournisseur cible.

Cette commande n'effectue concrètement rien sur votre infrastructure.

Bonne pratique : afin de sauver le plan, vous pouvez spécifier un fichier de sortie.

   terraform plan -out plateforme.out
   terraform apply plateforme.out

La commande apply, comme son nom l'indique, permet d'appliquer les changements à effectuer sur l'infrastructure. C'est cette commande qui va créer nos ressources.

Attention : depuis la version 0.11 de Terraform, sorti le 16 novembre 2018, lorsque vous utilisez Terraform dans un environnement interactif, en local par exemple, mais pas en CI/CD, il est recommandé de ne plus passer par un plan d'exécution mais de directement utiliser la commande apply et de répondre Yes si vous souhaitez appliquer ce plan.

Après application de la définition de vos ressources, vous pouvez vérifier les fournisseurs (providers) utilisés par Terraform dans vos projet et modules.

   $ terraform providers
   .
   ├── provider.aws ~> 1.54.0
   └── module.my_module
   ├── provider.aws (inherited)
   └── provider.external
   terraform destroy

La commande destroy permet de supprimer TOUTES les ressources.

Un plan de suppression peut être généré au préalable :

   terraform plan -destroy

Grâce à la commande console, vous pouvez connaître la valeur d'une ressource Terraform. Cette commande est pratique pour faire du déboggage, avant de créer un plan ou de l'appliquer.

   $ echo "aws_iam_user.oowy.arn" | terraform console
   arn:aws:iam::123456789123:user/oowy
   terraform get

Cette commande get est utile si, par le passé, vous avez déjà fait un terraform init puis ajouté un module. Il faut préciser maintenant à Terraform qu'il faut récupérer le module ou bien le mettre à jour. Si vous ne le faites pas, lors d'un terraform plan, Terraform vous demandera de le faire.

   terraform graph | dot -Tpng > graph.png`

La CLI graph permet de dessiner un graphique de dépendances visuel des ressources Terraform en fonction des fichiers de configuration.

Au bout d'un certain nombre de ressources dans un répertoire, Terraform n'arrive plus à générer ce graphique. J'espère que ce problème sera corrigé dans les futures versions.

Mise en pratique

Commençons tout d'abord par installer Terraform.

curl -O https://releases.hashicorp.com/terraform/0.14.7/terraform_0.14.7_linux_amd64.zip
sudo unzip terraform_0.14.7_linux_amd64.zip -d /usr/local/bin/
rm terraform_0.14.7_linux_amd64.zip

0.14.7 étant la dernière version de Terraform, stable, à ce jour.

Ces commandes vont extraire un binaire dans “/usr/local/bin/”, qui est déjà dans la variable d'environnement PATH.

Vous pouvez également installer Terraform grâce à l'outil tfenv, un Terraform version manager. La première chose à faire est de télécharger le binaire de tfenv et de le mettre dans la variable d'environnement PATH.

$ git clone https://github.com/tfutils/tfenv.git
 ~/.tfenv
$ echo 'export PATH="$HOME/.tfenv/bin:$PATH"'
 >> $HOME/bashrc

Ensuite, vous pouvez simplement installer Terraform ou le mettre à jour en précisant la version désirée.

tfenv install 0.14.7

Afin de vérifier que Terraform est correctement installé, veuillez vérifier la version courante de l'outil.

$ terraform --version
 Terraform v0.14.7

Pour information la commande terraform --version fonctionne également.

Création d'un répertoire

$ mkdir build/
$ vi aws_s3.tf
############### AWS S3 ###############
resource "aws_s3_bucket" "build-terraform" {
bucket = "${var.aws_s3_bucket_terraform}"
acl = "private"
    tags {
        Tool = "${var.tags-tool}"
        Contact = "${var.tags-contact}"
    }
}

Pour indiquer à Terraform sur quel compte AWS vous souhaitez déployer l'infrastructure souhaitée, vous devez définir des variables d'environnement AWS au préalable, par exemple dans un fichier “.aws/credentials” ou avec des variables d'environnement.

$ export AWS_ACCESS_KEY_ID="an_aws_access_key"
$ export AWS_SECRET_ACCESS_KEY="a_aws_secret_key"
$ export AWS_DEFAULT_REGION="a-region"
$ terraform init
[0m[1mDownloading modules...[0m
[0m[1mInitializing the backend...[0m
[0m[1mInitializing provider plugins...[0m
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.
* provider.aws: version = "~> 1.3"
$ terraform plan -out crf.out
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
aws_s3_bucket.build-terraform: Refreshing state... (ID: build.terraform)
...
Plan: 1 to add, 0 to change, 0 to destroy.
$ terraform apply plan.out
...
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Annexe

Ce site web utilise des cookies. En utilisant le site Web, vous acceptez le stockage de cookies sur votre ordinateur. Vous reconnaissez également que vous avez lu et compris notre politique de confidentialité. Si vous n'êtes pas d'accord, quittez le site.En savoir plus