[TECH] Terraform introduction à l’infrastructure-as-code

Terraform est un outil qui permet de rendre possible l’Infrastructure as Code. C’est à dire qu’il permet de construire, modifier et versioner l’infrastructure que vous souhaitez déployer à l’aide de simple fichiers texte. Autrement dit, vous codez votre infrastructure, et vous pouvez suivre les mêmes bonnes pratiques de développement: versioning, tests, etc.

Les concepts principaux

  • Providers

Les providers sont l’élément essentiel de terraform. Il faut voir un provider comme une interface vers différents fournisseurs de IaaS, PaaS, SaaS ou même des logiciels comme RabbitMQ, et bien d’autres.

  • Provisioners

Les provisioners sont exécutés après le démarrage ou la mise à disposition d’une machine virtuelle. Cela permet par exemple de lancer des commandes sur une instance EC2 fraichement déployée, ou encore de bootstraper un agent Chef . Et pourquoi pas les deux en même temps !

  • Backend

Les backends de terraform sont utilisées pour stocker le fichier appelé “tfstate” qui résulte du lancement de terraform. Cela permet de stocker les informations de déploiement nécessaire au bon fonctionnement de terraform à différents endroits (en local, sur un bucket S3, sur Consul, etc). L’avantage est que certains backends permettent le travail collaboratif avec des systèmes de “lock” permettant de s’assurer qu’un seul processus d’exécution de terraform ne s’applique à un instant T.

  • Module

Toutes les intégrations existantes de Terraform avec différents providers (AWS, Azure et beaucoup d’autres sont réalisées sous la forme de plugins. L’outil étant open-source il est donc assez aisé (pour peu que l’on sache développer en Go) de développer son propre plugin pour des besoins spécifiques ou pas encore intégrés dans l’outil. Ces modules peuvent concerner, les provisioners et les provisioners.

Pour résumer, Terraform se rapproche fortement d’outils comme Amazon CloudFormation ou encore Azure Templates. Il permet donc de décrire des composants d’une infrastructure à déployer. Sa grande force est d’être agnostique de la technologie utilisée. Et de permettre le travail collaboratif assez simplement tout en étant facilement extensible.

Terraform bénéficie aussi d’une communauté très active et les releases sont très fréquentes.

Exemple d’utilisation simple

Voyons un exemple rapide avec un déploiement d’une instance sur Amazon EC2. Nous allons avoir un dossier avec les fichiers suivants :

.
├── instance.tf
└── terraform.tfvars

 

instance.tf

// Declaration of the provider you want to use, here AWS
provider "aws" {
  region = "eu-west-1"
  access_key = "[my-access-key]"
  secret_key = "[my-secret-key]"
}

// We say the user needs to provide a variable , manually
//  or in a .tfvars file
variable "instance_type" { type = "string" }

// Here is the EC2 instance definition (it is very simple here on purpose)
resource "aws_instance" "myserver" {
  ami           = "ami-2757f631"
  instance_type = "${var.instance_type}"
}

 

terraform.tfvars

instance_type = "t2.micro"

Plan

Si l’on joue la commande terraform plan , nous allons obtenir la sortie suivante :

$ terraform plan
+ aws_instance.myserver
    ami:                         "ami-2757f631"
    associate_public_ip_address: "<computed>"
    availability_zone:           "<computed>"
    ebs_block_device.#:          "<computed>"
    ephemeral_block_device.#:    "<computed>"
    instance_state:              "<computed>"
    instance_type:               "t2.micro"
    key_name:                    "<computed>"
    network_interface_id:        "<computed>"
    placement_group:             "<computed>"
    private_dns:                 "<computed>"
    private_ip:                  "<computed>"
    public_dns:                  "<computed>"
    public_ip:                   "<computed>"
    root_block_device.#:         "<computed>"
    security_groups.#:           "<computed>"
    source_dest_check:           "true"
    subnet_id:                   "<computed>"
    tenancy:                     "<computed>"
    vpc_security_group_ids.#:    "<computed>"


Plan: 1 to add, 0 to change, 0 to destroy.

 

Nous voyons ce que terraform prévoit de faire : en l’occurence l’ajout de une instance avec un certains nombre de paramètres. La plupart sont en “computed” , et obtiendront une valeur par défaut. On retrouve en revanche l’instance-type et l’AMI renseignée dans nos fichiers (.tf et .tfvars).

Apply

Nous passons à l’étape apply si tout est ok :

$  terraform apply
aws_instance.myserver: Creating...
  ami:                         "" => "ami-8f043ee9"
  associate_public_ip_address: "" => "<computed>"
  availability_zone:           "" => "<computed>"
  ebs_block_device.#:          "" => "<computed>"
  ephemeral_block_device.#:    "" => "<computed>"
  instance_state:              "" => "<computed>"
  instance_type:               "" => "t2.micro"
  key_name:                    "" => "<computed>"
  network_interface_id:        "" => "<computed>"
  placement_group:             "" => "<computed>"
  private_dns:                 "" => "<computed>"
  private_ip:                  "" => "<computed>"
  public_dns:                  "" => "<computed>"
  public_ip:                   "" => "<computed>"
  root_block_device.#:         "" => "<computed>"
  security_groups.#:           "" => "<computed>"
  source_dest_check:           "" => "true"
  subnet_id:                   "" => "<computed>"
  tenancy:                     "" => "<computed>"
  vpc_security_group_ids.#:    "" => "<computed>"
aws_instance.myserver: Still creating... (10s elapsed)
aws_instance.myserver: Still creating... (20s elapsed)
aws_instance.myserver: Still creating... (30s elapsed)
aws_instance.myserver: Creation complete

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

 

Changements

Terraform est un très bon outil pour gérer les changements. Notamment grâce à sa commande “plan” qui permet d’anticiper les actions que l’outil va faire pour nous.

Voyons un example concret en ajoutant un “security group” pour notre instance autorisant le SSH depuis une IP bien précise (1.2.3.4), nous allons ajouter au fichier instance.tf le contenu suivant :

resource "aws_security_group" "mysecuritygroup" {
  name = "MySecurityGroup"
  description = "Allow SSH FROM IP : 1.2.3.4"

  ingress {
    from_port = 22
    to_port   = 22
    protocol  = "tcp"
    cidr_blocks = ["1.2.3.4/32"]
  }
  egress {
    from_port = 0
    to_port   = 0
    protocol  = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

 

Et ajouter la variable suivante dans la ressource myserver :

resource "aws_instance" "myserver" {
   ...
   security_groups = ["${aws_security_group.mysecuritygroup.name}"]
}

 

Passons à l’étape plan :

$ terraform plan
-/+ aws_instance.myserver
    ami:                         "ami-8f043ee9" => "ami-8f043ee9"
    associate_public_ip_address: "true" => "<computed>"
    availability_zone:           "eu-west-1a" => "<computed>"
    ebs_block_device.#:          "0" => "<computed>"
    ephemeral_block_device.#:    "0" => "<computed>"
    instance_state:              "running" => "<computed>"
    instance_type:               "t2.micro" => "t2.micro"
    key_name:                    "" => "<computed>"
    network_interface_id:        "eni-04cff30b" => "<computed>"
    placement_group:             "" => "<computed>"
    private_dns:                 "ip-172-31-32-101.eu-west-1.compute.internal" => "<computed>"
    private_ip:                  "172.31.32.101" => "<computed>"
    public_dns:                  "ec2-34-249-87-185.eu-west-1.compute.amazonaws.com" => "<computed>"
    public_ip:                   "34.249.87.185" => "<computed>"
    root_block_device.#:         "1" => "<computed>"
    security_groups.#:           "0" => "<computed>" (forces new resource)
    source_dest_check:           "true" => "true"
    subnet_id:                   "subnet-ea4553b3" => "<computed>"
    tenancy:                     "default" => "<computed>"
    vpc_security_group_ids.#:    "1" => "<computed>"

+ aws_security_group.mysecuritygroup
    description:                          "Allow SSH FROM IP : 1.2.3.4"
    egress.#:                             "1"
    egress.482069346.cidr_blocks.#:       "1"
    egress.482069346.cidr_blocks.0:       "0.0.0.0/0"
    egress.482069346.from_port:           "0"
    egress.482069346.prefix_list_ids.#:   "0"
    egress.482069346.protocol:            "-1"
    egress.482069346.security_groups.#:   "0"
    egress.482069346.self:                "false"
    egress.482069346.to_port:             "0"
    ingress.#:                            "1"
    ingress.1497284064.cidr_blocks.#:     "1"
    ingress.1497284064.cidr_blocks.0:     "1.2.3.4"
    ingress.1497284064.from_port:         "22"
    ingress.1497284064.protocol:          "tcp"
    ingress.1497284064.security_groups.#: "0"
    ingress.1497284064.self:              "false"
    ingress.1497284064.to_port:           "22"
    name:                                 "MySecurityGroup"
    owner_id:                             "<computed>"
    vpc_id:                               "<computed>"


Plan: 2 to add, 0 to change, 1 to destroy.

 

Terraform va ajouter la ressource “aws_security_group.mysecuritygroup” (on le voit rapidement avec le petit + devant la ligne). On voit aussi que le changement de security group oblige la recréation de l’instance , via cette ligne :

security_groups.#:           "0" => "<computed>" (forces new resource)

Je sais donc que si je fais un apply mon security group devrait être ajouté et mon instance va être détruite puis recréer avec le nouveau security group. C’est ce que nous allons faire. Je vous passe le log de création.

Dernier use-case , je souhaite changer le type d’instance de mon instance “myserver” car elle est trop légère en CPU et mémoire. Pour cela, nous allons modifier le paramètre dans le fichier terraform.tfvars (ce fichier est lu par défaut par le cli, mais on peut en passer d’autres). Passons à une instance de type t2.large par exemple; Je vous laisse faire la modification.

Un nouveau plan devrait vous montrer que le serveur doit être supprimé puis récréé avec le nouveau type d’instance. On le voit avec cette ligne dans le log terraform :

instance_type:               "t2.micro" => "t2.large" (forces new resource)

Les personnes connaissant bien AWS diront qu’il n’est pourtant pas nécessaire de re-créer l’instance pour changer son type. En effet il est possible de le faire dans la console AWS via un simple “Stop > Change inst. type > Start” C’est une limitation de l’outil dans ce cas, qui ne sait pas vraiment réaliser cette action comme on le voudrait.

Cependant, vous pouvez faire cette opération manuellement et impacter le changement dans le fichier terraform.tfvars. Puis si vous faites un plan , terraform detectera le changement de type d’instance via l’API AWS, et vous diras qu’il n’y a aucun changement à faire.

J’espère que vous comprenez mieux en quoi ce genre d’outil permettant l’ Infrastructure as Code est utile, et permet d’accélérer les déploiements.

Dans un prochain article sur le sujet nous verrons comment étendre les capacités de terraform pour l’adapter finement à un besoin particulier. Ou encore comment utiliser les modules. Stay tuned !

Laisser un commentaire