Les fichiers Terraform porte l'extension .tf dont le principal se nomme main.tf. Comme la plupart des produits HashiCorp, Terraform utilise le langage HCL.
Le langage de configuration HashiCorp (HCL) permet de décrire rapidement des ressources à l'aide de blocs, d'arguments et d'expressions.
Il existe l'extension Visual Code de Terraform, écrite par Hashicorp.
Terraform s'appuie sur des plugins appelés « providers » pour interagir avec les fournisseurs de cloud, les fournisseurs SaaS et d'autres API. Les configurations Terraform doivent déclarer les fournisseurs dont elles ont besoin pour que Terraform puisse les installer et les utiliser.
De plus, certains fournisseurs nécessitent une configuration (comme des URL de point de terminaison ou des régions cloud) avant de pouvoir être utilisés.
provider "libvirt" {
uri = "qemu:///system"
}
Ici, nous utiliserons le fournisseur libvirt. Nous utiliserons ici une ressource de type volume du provider libvirt qui porte l'étiquette os_image.
Les providers sont distribués séparément de Terraform lui-même, mais ils sont installés automatiquement lors de l'initialisation de votre configuration. Terraform détermine généralement automatiquement le fournisseur à utiliser en fonction des types de ressource. Pour les providers, comme celui de libvirt, vous devez utiliser un bloc required_providers :
terraform {
required_providers {
libvirt = {
source = "dmacvicar/libvirt"
version = "0.6.14"
}
}
}
Une ressource est une entité d'un service (cloud ou pas). De multiples ressources forment ainsi une infrastructure.
Chaque type de ressource est fournie par un provider.
resource "libvirt_volume" "os_image" {
name = "${var.hostname}-os_image"
pool = "default"
source = "bionic-server-cloudimg-amd64.img"
format = "qcow2"
}
Les Input variables ou variables d'entrée pour nous français, sont définies en indiquant un nom, un type et une valeur par défaut (default = "valeur"). Le type est optionnel, car Terraform les déduit automatiquement. On peut ajouter une description.
variable "nom" {
type = "string"
default = "valeur"
description : "Un texte de description."
validation {
condition = condition
error_message = "message d'erreur."
}
}
L'étiquette se trouvant après le mot-clé variable est le nom de celle-ci, il doit être unique parmi toutes les variables d'un même module. Ce nom est utilisé pour lui affecter une valeur ou utiliser sa valeur dans la configuration.
ATTENTION
Une variable ne peut pas se nommer :source,version,providers,count,
for_each,lifecycle,depends_onou encorelocals.
Pour accéder à une variable dans les blocs, il suffit d'ajouter le préfixe var..
variable "hostname" {
type = string
default = "staticip"
}
resource "libvirt_volume" "os_image" {
name = "${var.hostname}-os_image"
pool = "default"
source = "bionic-server-cloudimg-amd64.img"
format = "qcow2"
}
Le type string est une série de caractères Unicode représentant un texte.
variable "nom_variable" {
type = "string"
default = "bonjour"
}
Le type number permet de spécifier une valeur numérique, qui peut être entière ou décimale (.)
variable "port" {
type = "number"
default = 80
}
Le type bool qui peut prendre les valeurs true ou false :
variable "delete_after" {
type = "bool"
default = true
}
Le type list est une séquence de valeurs :
variable "ma_liste" {
type = "list"
default = ["a", 15, true]
}
Pour accéder à un élément de la liste :
element = "$ {var.ma_liste [0]}"
Le type map est une structure de données composée de couple clé/valeur.
variable "user1" {
type = "map"
default = {
name = "John"
age = 52
}
}
Pour accéder à une clé de la map, deux écritures possibles :
nom = "$ {var.user1[var.name]}"
nom = "$ {var.user1["name"]}"
Pour de nombreuses variables, il est préférable de les déclarer dans un fichier de définitions de variables. Un fichier se terminant par l'extension .tfvars.
Par contre, ici pas besoin de spécifier qu'il s'agit d'un bloc de type variable. On les spécifie directement sous la forme nom = valeur.
port = 80
zones = [
"us-east-1a",
"us-west-1c",
]
Les fichiers .tfvars doit être chargé explicitement lors de l'application du plan. Pour le charger automatiquement, il suffit de l'appeler .auto.tfvars
Les variables de sortie permettent d'afficher les valeurs d'une variable à la sortie de l'application du plan, sauf si elle est déclarée sensitive.
output "ip" {
value = libvirt_domain.domain-ubuntu.*.network_interface.0.addresses
description : "The public IP address of the server instance."
sensitive = true
}
Une variable locale n'est accessible que dans les expressions du module où elle a été déclarée.
Les valeurs locales peuvent être utiles pour éviter de répéter plusieurs fois les mêmes valeurs ou expressions dans une configuration, mais si elles sont trop utilisées, elles peuvent également rendre la lecture d'une configuration difficile. On accède à une variable locale en la préfixant par local..
locals {
# Common tags to be assigned to all resources
common_tags = {
Service = local.service_name
Owner = local.owner
}
}
resource "aws_instance" "example" {
# ...
tags = local.common_tags
}
Les sources de données appelées data permettent d'extraire ou de calculer des données qui seront ensuite utilisées ailleurs dans votre configuration Terraform.
data "google_compute_instance" "appserver" {
name = "primary-application-server"
zone = "us-central1-a"
}
Comme dis précédemment cela peut être aussi une donnée calculée, par exemple l'utilisation d'un template pour générer un fichier qui sera ensuite utilisé par votre configuration :
data "template_file" "user_data" {
template = file("${path.module}/cloud_init.cfg")
vars = {
hostname = "${var.hostname}"
fqdn = "${var.hostname}.${var.domain}"
public_key = "${file("./id_ed25519.pub")}"
}
}
Le contenu (en partie) du template ou on voit l'utilisation des variables ${variable} :
hostname: ${hostname}
fqdn: ${fqdn}
manage_etc_hosts: true
...
C'est comme cela qu'on arrive à créer par exemple un inventaire Ansible qui sera ensuite utilisé par un provisionner local. Mais je préfère utiliser l'inventaire Ansible du provider en question.
On peut aussi faire appel à des programmes externes ou external :
data "external" "cars_count" {
program = ["python", "${path.module}/get_cool_data.py"]
query = {
thing_to_count = "cars"
}
}
output "cars_count" {
value = "${data.external.cars_count.result.cars}"
}
Le potentiel est énorme. On peut ainsi récupérer des données d'un vault Terraform. (Documentation Vault)
Les filtres permettent de faire le tri et de récupérer les informations nécessaires, utiles pour les données externes.
data "aws_ami" "example" {
executable_users = ["self"]
most_recent = true
name_regex = "^myami-\\d{3}"
owners = ["self"]
filter {
name = "name"
values = ["myami-*"]
}
filter {
name = "root-device-type"
values = ["ebs"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
Ici, on récupère toutes les images ami disponibles pour ne garder que celles :
La documentation de cette source de données
Comme pour les variables, il suffit d'ajouter le suffixe data devant le nom de celui :
resource "aws_instance" "app" {
ami = "${data.aws_ami.app_ami.id}"
instance_type = "t2.micro"
}
Les provisioner sont utilisés pour lancer des actions sur la machine locale ou sur une machine distante afin de préparer des serveurs ou d'autres objets d'infrastructure pour le service. Il existe des trois provisioner génériques : local_exec, remote_exec et file.
Il faut privilégier l'utilisation au maximum les provider plutôt que ces provisioner.
Pour exécuter une action locale vous pouvez utiliser le provisioner local_exec :
resource "null_resource" "example2" {
provisioner "local-exec" {
command = "Get-Date > completed.txt"
interpreter = ["PowerShell", "-Command"]
}
}
Du powershell...
Pour exécuter une action sur la ressource distante il faut utiliser le provisioner remote_exec :
resource "aws_instance" "web" {
# ...
provisioner "remote-exec" {
inline = [
"dnf -y install epel-release",
"dnf -y install htop",
]
}
}
Le provisioner file est utilisé pour copier des fichiers ou des répertoires de la machine exécutant Terraform vers la ressource nouvellement créée.
resource "aws_instance" "web" {
# ...
# Copies the myapp.conf file to /etc/myapp.conf
provisioner "file" {
source = "conf/myapp.conf"
destination = "/etc/myapp.conf"
}
Pour info : Ce provisioner prend en charge les connexions de type ssh ou et winrm.
Par défaut, un provisioner s'exécute lorsque la ressource dans laquelle il est défini est créée. Attention seulement pendant la création et non la mise à jour ! Cependant, on peut modifier ce comportement avec les arguments when = destroy et on_failure = continue.
when = destroy permet de lancer un provisioner au moment du décommissionnement d'une ressource.
resource "aws_instance" "web" {
# ...
provisioner "local-exec" {
when = destroy
command = "echo 'Destroy-time provisioner'"
}
}
En cas d'échec d'un provisioner la ressource associé est déclarée comme vérolée. On peut bypasser ce comportement en utilisant on_failure = continue.
resource "aws_instance" "web" {
# ...
provisioner "local-exec" {
command = "echo The server's IP address is ${self.private_ip}"
on_failure = continue
}
}
Tout d'abord, créez un fichier nommé main.tf. Ce fichier contiendra toute la configuration nécessaire pour déployer votre ressource. Ouvrez ce fichier dans votre éditeur de texte ou IDE préféré.
# main.tf
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "example" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
Déclaration du fournisseur : La première partie du script définit le fournisseur de cloud, ici AWS (provider "aws"). Vous devez spécifier la région où vous souhaitez déployer vos ressources, dans cet exemple, us-east-1.
Ressource : La seconde partie du script crée une ressource, spécifiquement une instance EC2 (resource "aws_instance" "example"). Vous devez fournir un AMI (Amazon Machine Image) et un type d'instance. L'AMI sert de modèle pour l'instance et le type d'instance détermine la puissance de calcul, la mémoire et d'autres caractéristiques de l'instance.
Avec votre script prêt, vous pouvez maintenant lancer les commandes Terraform pour déployer votre instance. Ouvrez un terminal, naviguez vers le répertoire contenant votre fichier main.tf et exécutez les commandes suivantes :
terraform init
terraform plan
terraform apply
terraform init initialise le répertoire de travail de Terraform et télécharge le plugin nécessaire pour interagir avec le fournisseur spécifié, dans ce cas, AWS.terraform plan affiche un plan d'exécution, montrant ce qui sera créé, modifié, ou détruit.terraform apply applique les modifications nécessaires pour atteindre l'état désiré défini dans votre configuration.Après l'exécution de terraform apply, Terraform vous fournira un résumé des actions réalisées et des ressources déployées. Vous pouvez vous connecter à votre console AWS pour vérifier que l'instance est bien en cours d'exécution.