BureauTerraformScaleway

Terraformer une infra Scaleway avec du Continuous Delivery

Infrastructure as code

L'infrastructure as code est une pratique qui consiste à déclarer sous forme de fichiers de code les éléments de son infrastructure (ressource de calcul, stockage, règles de flux etc.). Les avantages de cette méthode sont multiples :

  • Tout changement est traçable (via un système de gestion de version comme git)
  • Cela peut permettre de maintenir un état cohérent de l'infrastructure en s'assurant que la configuration ne change pas avec le temps
  • Le retour vers une infrastructure fonctionnelle en cas de problème est facilité
  • Dans une certaine mesure, le code fait aussi office de documentation de l'infrastructure
  • Il est plus facile de partager un code source d'infrastructure qu'un ensemble de documents (pas nécessairement à jour) décrivant comment a été construite une infrastructure, un environnement

Cette pratique est de plus en plus courante dans le monde qui m'entoure. C'est une bonne chose et j'essaie de la répandre autour de moi également.

Dans la suite de cet article, Terraform sera notre outil, notre langage pour faire de l'infrastructure as code.

Terraform

Pour commencer, voici un petit extrait de Wikipedia :

Terraform est un environnement logiciel d'« infrastructure as code » publié en open-source par la société HashiCorp. Cet outil permet d'automatiser la construction des ressources d'une infrastructure de centre de données comme un réseau, des machines virtuelles, un groupe de sécurité ou une base de données.

En complément de cette première définition, ajoutons que Terraform est un outil descriptif. On y décrit ce qu'on le veut comme ressources, comme paramètres. En revanche, à aucun moment on n'écrit comment les choses doivent être créées.

Une des grandes forces de Terraform est qu'il fonctionne avec beaucoup de fournisseurs (dans l'outil, il est question de provider) connus : Amazon AWS , Google GCP, Microsoft Azure, IBM, VMware, OVH ou encore Scaleway. C'est ce dernier qui va nous intéresser dans la suite de cet article.

Pour la petite histoire, Terraform est écrit en Go. Pour décrire notre infrastructure as code, nous utilisons un langage de configuration propre à HashiCorp (éditeur de la solution) : HCL (Hashicorp Configuration Language).

Dernier point important à souligner, Terraform est idempotant. Une première application du code Terraform effectue les modifications nécessaires sur l'infrastructure. Une seconde indique qu'il n'y a rien à faire, l'infrastructure est déjà dans l'état cible.

Arrêtons ici l'introduction et passons plutôt dans le vif du sujet.

Attention, cet article n'est pas une introduction à Terraform. Pour mieux saisir ce qui est exposé par la suite, il est préférable d'avoir des connaissances basiques sur l'utilisation et le fonctionnement de Terraform. Voici un lien vers le tutoriel officiel Hashicorp : https://learn.hashicorp.com/terraform

Terraform en Continuous Delivery

Comme évoqué dans l'introduction de cet article, nous voulons versionner le code Terraform. Pour cela, nous allons utiliser GitLab et gitlab-ci pour la partie automatisation.

Le Continuous Delivery, aussi abrégé CD, est une pratique qui consiste à automatiser un certain nombre d'actions autour d'un répertoire, d'un projet de code. Chaque fois qu'un nouveau code est déployé, des actions se lancent automatiquement : build si besoin, des tests (unitaires, fonctionnels, d'intégration etc. ). Tout ce qu'il faut pour préparer et valider que l'application est prête à être déployée. Cette dernière action reste manuelle. Dans ce mode de fonctionnement, le but recherché est d'automatiser tout sauf la mise en production que l'on garde en déclenchement manuel afin de continuer à la maîtriser.

Il existe aussi le Continuous Deployment, qui vise à tout automatiser, y compris l'étape de déploiement.

Ici, c'est bien le Continuous Delivery qui nous intéresse. Il nous faut rester maître des applications du code Terraform sur notre infrastructure.

Un autre avantage de la mise en place d'un tel système avec GitLab est la simplification du poste utilisateur. Il n'est plus nécessaire d'installer terraform sur son poste de travail. Tout est déclenché et exécuté depuis le runner gitlab-ci. Cette machine utilise des conteneurs pour exécuter les tâches qui lui sont attribués. En cas de besoin ponctuel pour investiguer, comprendre ou analyser un problème, il est possible de faire la même chose sur son poste : lancer des commandes terraform depuis un conteneur. Ceci offre la possibilité de se concentrer un peu plus sur le code et non sur le lancement des commandes terraform.

Un brin de technique pour la mise en place

Terraform stocke l'état des infrastructures et sa configuration. Le sujet a été abordé précédemment : les commandes sont exécutées depuis des conteneurs. Il faut donc trouver un endroit pour stocker et partager ce fichier avec les conteneurs successifs. Plusieurs solutions sont possibles et ici nous avons fait le choix arbitraire de stocker le fichier sur le service stockage objet de Scaleway. Le service se base sur le protocole S3 d'Amazon. Dans la suite de cet article, nous aborderons la mise en place de la configuration pour ce fichier d'état.

Pour l'intégration GitLab, il n'est pas utile de réinventer la roue, un modèle existe et nous allons nous en inspirer fortement. C'est parti !

Exemple concret

Dans l'exemple suivant, nous travaillons sur la déclaration d'une instance security group. Il s'agit de règles de flux, entrants et sortants pour un ou plusieurs instances.

Sans plus attendre, voici l'arborescence de notre mini projet.

.
+-- .gitlab-ci.yml
+-- backend.tf
+-- provider.tf
+-- security-group.tf

Pour le fichier .gitlab-ci.yml, un modèle est fourni par GitLab, il ne reste plus qu'à le personnaliser un peu et l'utiliser. Nous y reviendrons. Voici le fichier fourni :

# This file is a template, and might need editing before it works on your project.
# Official image for Hashicorp's Terraform. It uses light image which is Alpine
# based as it is much lighter.
#
# Entrypoint is also needed as image by default set `terraform` binary as an
# entrypoint.
image:
  name: registry.gitlab.com/gitlab-org/gitlab-build-images:terraform
  entrypoint:
    - '/usr/bin/env'
    - 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'

# Default output file for Terraform plan
variables:
  PLAN: plan.tfplan
  JSON_PLAN_FILE: tfplan.json

cache:
  paths:
    - .terraform

before_script:
  - alias convert_report="jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create\":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'"
  - terraform --version
  - terraform init

stages:
  - validate
  - build
  - test
  - deploy

validate:
  stage: validate
  script:
    - terraform validate

plan:
  stage: build
  script:
    - terraform plan -out=$PLAN
    - "terraform show --json $PLAN | convert_report > $JSON_PLAN_FILE"
  artifacts:
    paths:
      - $PLAN
    reports:
      terraform: $JSON_PLAN_FILE

# Separate apply job for manual launching Terraform as it can be destructive
# action.
apply:
  stage: deploy
  environment:
    name: production
  script:
    - terraform apply -input=false $PLAN
  dependencies:
    - plan
  when: manual
  only:
    - master

Le fichier backend.tf contient les informations nécessaires pour accéder au tfstate. Pour rappel, il s'agit du fichier d'état Terraform qui contient des informations sur l'état actuel des infrastructures déclarées dans le code etc. Dans la démarche de CD que nous menons, il faut le stocker à part. En effet, chaque lancement peut tomber sur un exécuteur différent. Il faut donc que cet état soit conservé dans un endroit accessible à tous. Dans notre cas, nous avons fait le choix du stockage objet Scaleway. Il est considéré comme du stockage S3 par terraform :

terraform {
  backend "s3" {
    bucket                      = "mybucket"
    key                         = "terraform.tfstate"
    region                      = "fr-par"
    endpoint                    = "https://s3.fr-par.scw.cloud"
    access_key                  = "__BACKEND_ACCESS_KEY__"
    secret_key                  = "__BACKEND_SECRET_KEY__"
    skip_credentials_validation = true
    skip_region_validation      = true
  }
}

Il est important de noter que l'access_key et la secret_key ne sont pas données directement dans le code. Il s'agit d'information sensible que nous remplaçons à la volée afin qu'elle n'apparaissent pas dans le code source. C'est dans le fichier .gitlab-ci.yml que ce travail est effectué grâce à l'outil sed :

(...)
before_script:
  - alias convert_report="jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create\":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'"
  - sed -i "s~__BACKEND_ACCESS_KEY__~${BACKEND_ACCESS_KEY}~" backend.tf
  - sed -i "s~__BACKEND_SECRET_KEY__~${BACKEND_SECRET_KEY}~" backend.tf
  - terraform --version
  - terraform init
(...)

Comme expliqué en introduction, Terraform est un outil très puissant. Il permet de faire beaucoup avec un grand nombre de providers. Il est donc impératif de lui signaler quels sont ceux que nous souhaitons utiliser. C'est le rôle du fichier provider.tf.

terraform {
    required_providers {
        scaleway = {
            source  = "scaleway/scaleway"
            version = "~> 1.17"
        }
    }
}

provider "scaleway" {
    organization_id = "00000000-0000-0000-0000-000000000000"
    zone            = "fr-par-1"
    region          = "fr-par"
}

C'est donc dans ce fichier qu'est déclaré le provider Scaleway et les informations nécessaires à son bon fonctionnement. Ici le projet est assez simple donc nous avons fait le choix d'écrire en dur l'organization_id. En revanche, à l'image de ce qui a été fait pour le backend, les informations de l'API (access_key et secret_key) ne sont pas visibles ici. Ces informations sensibles sont stockées dans des variables qui sont injectées à la volée lors de l'exécution du pipeline.

Le dernier fichier (security-group.tf) est celui qui contient la déclaration de la ressource qui nous intéresse : les règles de flux, ou security group chez Scaleway.

resource "scaleway_instance_security_group" "my_instance_security_group" {
    inbound_default_policy  = "drop"
    outbound_default_policy = "accept"

    inbound_rule {
        action = "accept"
        ip     = "1.2.3.4"
    }

    inbound_rule {
        action = "accept"
        port   = 80
    }

    inbound_rule {
        action = "accept"
        port   = 443
    }
}

Les propriétés par défaut sont déclarées tout en haut. Dans l'exemple, 3 règles sont déclarées dont une avec une adresse ip et tous les ports. Sur les autres, ce sont des ports qui sont précisés mais plus d'adresse, c'est tout internet qui est autorisé.

pipeline

Dès qu'on pousse du code, le pipeline se déclenche sur GitLab avec 3 étapes:

  • Validate : Lance la commande terraform validate pour faire une vérifcation de la bonne syntaxe du projet.
  • Plan : terraform plan permet de simuler un lancement et de montrer tout ce qui serait réalisé (création, modification et destruction) par Terraform.
  • Apply : Dernière étape. Celle-ci est manuelle. Une fois que le plan est validé, il est possible de la déclencher et cela exécute la commande terraform apply.

Ajout de code

Afin de poursuivre dans le concret, ajoutons une ressource Object Storage dans la base de code et étudions ce qu'il se passe.

La première étape consiste à créer une issue sur le projet.

issue creation

Une fois l'issue créée, l'étape logique suivante est de proposer une Merge Request ou MR. Une nouvelle branche git permet de proposer notre code.

Petite parenthèse : dans le monde de l'open source, pour proposer une contribution à un projet, il faudrait créer une copie (ou fork) du projet, travailler dessus et proposer les modifications via une Merge Request. Dans l'exemple de cet article, une simplification a été faite. Il n'y a pas de copie du projet car nous avons les droits dessus.

C'est dans cette branche que nous allons rajouter un nouveau fichier storage.tf. Le fichier contient les lignes suivantes :

resource "scaleway_object_bucket" "test_bucket" {
  name = "scw-area51-bucket"
  acl  = "private"
  tags = {
    key = "test"
  }
}

Une nouvelle ressource scaleway_object_bucket est déclarée. Il est nécessaire de lui fournir un nom, les autres propriétés sont optionnelles.

mr overview

Le code est poussé sur le projet, dans sa branche distincte. Voici l'aperçu de la MR. Plusieurs onglets sont visibles en haut avec Overview, Commits, Pipelines et Changes. Le premier présente une vue d'ensemble de la proposition de code. L'onglet Changes est très intéressant.

mr changes

Comme le montre la capture précédente, il est possible de visualiser rapidement tous les changements proposés. Dans l'exemple, un nouveau fichier est ajouté, avec les lignes de déclaration de l'Object Storage.

Lorsque le code a été poussé sur le projet, un pipeline s'est automatiquement déclenché.

pipeline validate

La première étape est un validate qui vérifie la bonne syntaxe des fichiers terraform du projet. Dans notre exemple, la tâche est un succès, le pipeline peut donc continuer son exécution avec l'étape suivante.

pipeline plan

L'étape montrée ci-dessus est un plan. Une liste détaillée des actions à réaliser est affiché. Cette liste est complétée par un résumé des actions : nombre d'objet à créer, modifier ou détruire.

Ce résumé est très pratique. Lors d'un ajout d'Object Storage, comme dans l'exemple, il n'y a pas de raison pour que terraform veuille détruire des ressources. Sur l'aperçu de la MR, un bloc est visible affichant ce résumé et un lien permet d'aller directement voir la tâche plan

mr overview report

Le plan n'a pas remonté d'erreur et indiqué ce qu'il voudrait faire sur notre infrastructure.

Le code proposé peut être mergé dans la branche principale du projet. Ceci déclenche automatiquement un nouveau pipeline :

mr overwiew pipeline master

Le pipeline a lancé les tâches validate et plan. Le plan Terraform est exactement le même que celui détaillé plus haut. Une dernière et nouvelle étape est visible sur le pipeline. Celle-ci ne s'est pas lancée automatiquement. En effet, comme expliqué précédemment, nous sommes ici dans une démarche de Continuous Delivery. Nous souhaitons conserver une maîtrise totale sur le déploiement en production.

Ici, tout a été validé en amont donc nous pouvons appliquer les modifications.

pipeline master apply

Tout s'est bien passé et notre nouvel Object Storage a été déployé !

Conclusion

Voilà c'est la fin de cet article un peu long. Nous avons vu dans les grandes lignes comment fonctionne un tel projet et également comment y contribuer pour ajouter du contenu.

C'est une bonne base mais il reste néanmoins des points d'amélioration. En particulier, la gestion des secrets dans GitLab a ses limites. Il serait mieux d'utiliser un coffre-fort comme Vault. Mais c'est une autre histoire...

En l'état, le projet reste fonctionnel. Il permet de créer et gérer des infrastructures avec Terraform. Et vous, comment faites vous ?

{{ message }}

{{ 'Comments are closed.' | trans }}