Azure Devops + Terraform = Change Management

Pour ce nouvel article, je vous parle InfraOps avec Terraform et Azure Devops.

Je vous partage donc la manière dont j’ai implémenté un processus de Change Management de l’infrastructure d’un projet au travers de Azure Devops.

Le processus que je propose ici peut être décris comme suit :

  • Création d’une demande de changement (Work Item)
  • Réalisation de l’implémentation dans le repository et création d’une Pull Request
    • La branche de destination possède plusieurs règles permettant de garantir le processus (Reviewers, Linked items, …)
  • La génération d’un build crée le plan de migration qui sera effectué
    • Le plan de migration est sauvegardé dans un artefact
  • La validation de la Pull Request génère un release de celle-ci basée sur les plans prévus

De cette manière nous pouvons garantir une bonne exécution du pipeline et des changements qui seront opérés.

Cependant, je tiens à préciser que réaliser cette solution de cette manière ne permet pas de garantir l’idempotence de l’application de Terraform. En effet, lorsque vous demanderez à Terraform d’appliquer le plan prévu, il refusera de s’exécuter une deuxième fois où si l’état de votre architecture a changé depuis la définition de ce plan. La solution que je propose ici est bien de permettre de s’assurer que les changements qui ont été prévus seront appliqués, ni plus ni moins.

Pour plus d’informations sur ce que permet la pull request: Get feedback with pull requests – Azure DevOps | Microsoft Docs

Arborescence projet

Avec Terraform, il y a plusieurs manières d’organiser la structure de votre projet, en fonction de la complexité/généricité de votre solution. Certaines équipes peuvent opter pour le stockage des scripts Terraform avec le code source applicatif ou dans un repository à part. Cela dépend évidemment de l’organisation de l’équipe en charge du développement et du déploiement de la solution

Dans mon cas, j’ai hébergé les scripts Terraform dans un repository spécifique avec ce type d’arborescence :

 │   .gitignore
 │   azure-pipelines.yml
 │   README.md
 │   terraform-plan.yml
 │
 └───src
     │   main.tf
     │   variables.tf
     │
     ├───env
     │       production.tfvars
     │       tests.tfvars
     │
     └───modules
         ├───data
         │       main.tf
         │       output.tf
         │       variables.tf
         │
         ├───monitoring
         │       main.tf
         │       variables.tf
         │
         ├───shared
         │       main.tf
         │       output.tf
         │       variables.tf
         │
         └───website
         │       main.tf
         │       output.tf
         │       variables.tf

Azure Devops Pipelines

Les pipelines, comme le nom l’indique permettent de définir le process de l’intégration de votre code applicatif (et ici de vos script Terraform). La définition du pipeline est lui-aussi présent dans le repository, dans le fichier azure-pipelines.yml.

Dans notre cas, je propose de réaliser les plans de migration Terraform durant cette phase de pipeline, pour les environnements que nous ciblons. Ces plans de migration seront ensuite stockés dans les artefacts de déploiement de Azure Devops.

Enfin l’état de l’environnement est quant à lui stocké dans un Storage Account Azure accessible par le Service Principal utilisé par Azure Devops lors du lancement de Terraform.

La génération du plan Terraform est assuré par un template Azure Devops Pipeline définit dans le fichier terraform-plan.yml :

parameters:
- name: environment
  default: ''
  
jobs:
- job:
  steps: 
  - task: TerraformInstaller@0
    inputs:
      terraformVersion: $(terraform_version)
      
  - task: TerraformTaskV1@0
    displayName: "Init Terraform"
    inputs:
      provider: 'azurerm'
      command: 'init'
      workingDirectory: $(workingDirectory)
      backendServiceArm: $(backendServiceArm)
      backendAzureRmResourceGroupName: $(backendAzureRmResourceGroupName)
      backendAzureRmStorageAccountName: $(backendAzureRmStorageAccountName)
      backendAzureRmContainerName: $(backendAzureRmContainerName)
      backendAzureRmKey: ${{parameters.environment}}.tfstate

  - task: TerraformTaskV1@0
    displayName: "Validates the Terraform scripts"
    inputs:
      provider: 'azurerm'
      command: 'validate'
      workingDirectory: $(workingDirectory)

  - task: TerraformTaskV1@0
    displayName: "Plan the Terraform changes"
    inputs:
      provider: 'azurerm'
      command: 'plan'
      workingDirectory: $(workingDirectory)
      commandOptions: '-var-file ./env/${{parameters.environment}}.tfvars -out=$(Build.ArtifactStagingDirectory)/${{parameters.environment}}.zip'
      environmentServiceNameAzureRM: 'MGM'

  - task: PublishBuildArtifacts@1
    displayName: "Publish the plan to build artifacts"
    inputs:
      PathtoPublish: '$(Build.ArtifactStagingDirectory)'
      ArtifactName: 'tf-plans'
      publishLocation: 'Container'

Et enfin ce template sera appelé par le pipeline lors de son déclenchement pour les environnements que nous spécifierons:

pool:
  vmImage: 'windows-2019'

variables:
  terraform_version: '0.13.3'
  workingDirectory: '$(System.DefaultWorkingDirectory)/src'
  backendServiceArm: '*******'
  backendAzureRmResourceGroupName: '*******'
  backendAzureRmStorageAccountName: '*******'
  backendAzureRmContainerName: 'tfstate'

stages:
- stage: Tests_Plan
  jobs:
  - template: terraform-plan.yml
    parameters:
      environment: tests

- stage: Production_Plan
  jobs:
  - template: terraform-plan.yml
    parameters:
      environment: production

Une fois ces fichiers sauvegardés dans votre repository à côté de vos scripts Terraform, vous pourrez alors configurer vos policies sur votre branche par défaut de Azure Devops.

Banch Policies

Une fois votre pipeline défini vous pourrez alors configurer les règles de commit sur votre branche afin de respecter le processus que vous aurez déterminé (c.f: Protect your Git branches with policies – Azure Repos | Microsoft Docs).

Minimum reviewers

En fonction de l’organisation de l’équipe, de la taille, etc, vous pouvez définir un certain nombre de règles concernant la relecture de votre PR :

De cette manière vous pouvez vous assurer que le changement demandé sera relu par au moins une personne (autorisée de préférence).

Linked Work Item

Cette fonctionnalité permet de s’assurer que la demande de changement est bel et bien tracée et que la Pull Request est dument requise :

Comment Resolution

La pull request étant un endroit propice à la relecture et au commentaire des paires sur ce qui est proposé, il parait de bon sens de s’assurer que les commentaires sur les modifications soient résolues avant l’approbation de ce changement :

Build Validation

C’est ici que nous allons déclencher notre pipeline Terraforme afin de générer les plans de migrations. Nous allons donc demander à Azure Devops de démarrer automatiquement un build du pipeline défini dès la création de la pull request :

Automatically included reviewers

Cette partie permet de définir les membres de l’équipe unitairement ou le groupe Azure Devops auquel ils appartiennent en tant que relecteur. Ces relecteurs peuvent être optionnels ou requis en fonction du processus que vous souhaitez :

Exécutez maintenant une mise à jour de votre script terraform et effectuez une demande de pull request. Le pipeline sera alors déclenché et il sera possible de voir attaché son résultat d’exécution :

Une fois toutes ces règles édités sur votre branche et votre pipeline fonctionnel, il est maintenant temps de définir le processus de release.

Release Pipeline

Votre pipeline de build a généré des artifacts contenant le plan de migration de votre infrastructure. Vous devez maintenant définir un processus de release de ces changements.

Nous allons donc créer un nouveau pipeline lié au précédent.

Votre pipeline doit récupérer les artefacts générés par le pipeline de build et se déclencher lorsque le build a été déclenché par une Pull Request.

Les stages de déploiements reprendrons alors les étapes suivantes :

Installation de Terraform

steps:
- task: ms-devlabs.custom-terraform-tasks.custom-terraform-installer-task.TerraformInstaller@0
  displayName: 'Install Terraform 0.14.5'
  inputs:
    terraformVersion: 0.14.5

Initialisation de Terraform

steps:
- task: ms-devlabs.custom-terraform-tasks.custom-terraform-release-task.TerraformTaskV1@0
  displayName: 'Terraform : init'
  inputs:
    workingDirectory: '$(System.DefaultWorkingDirectory)/_Terraform Infrastructure/tf-scripts/src/infrastructure'
    backendServiceArm: *********
    backendAzureRmResourceGroupName: '*********'
    backendAzureRmStorageAccountName: *********
    backendAzureRmContainerName: infrastructure
    backendAzureRmKey: *****.tfstate

Application du plan

steps:
- task: ms-devlabs.custom-terraform-tasks.custom-terraform-release-task.TerraformTaskV1@0
  displayName: 'Terraform : apply'
  inputs:
    command: apply
    workingDirectory: '$(System.DefaultWorkingDirectory)/_Terraform Infrastructure/tf-scripts/src/infrastructure'
    commandOptions: '-var-file "$(System.DefaultWorkingDirectory)/_Terraform Infrastructure/tf-scripts/src/***********.tfvars" -auto-approve'
    environmentServiceNameAzureRM: ********
  continueOnError: true

Notez ici que nous avons appliqué le paramètre auto-approve de terraform puisque le plan sera approuvé par les outils Azure Devops (par la merge Request), et également au travers de conditions d’approbation de pré-déploiement (que je ne détaille pas ici).

Conclusions

Nous avons vu ensemble comment nous pouvons créer un pipeline de déploiement d’infrastructure prenant en charge les principes de revue des changements via les Merge Requests.

Lors de ces merge requests, il nous est alors possible de contrôler ce que le terraform va réaliser et de sauvegarder ce plan.

Finalement, lors du déploiement de nos changements, nous sommes garantis que seuls les changements demandés lors du plan qui a été validé lors de la merge request auront été effectués, en effet si l’infrastructure a changé depuis et/ou si un autre déploiement a été réalisé avant (ce qui ne devrait pas être le cas dans un mode agile), terraform vous informera qu’il ne peut exécuter le plan demandé puisque les conditions ont changé …

Votre commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l’aide de votre compte WordPress.com. Déconnexion /  Changer )

Image Twitter

Vous commentez à l’aide de votre compte Twitter. Déconnexion /  Changer )

Photo Facebook

Vous commentez à l’aide de votre compte Facebook. Déconnexion /  Changer )

Connexion à %s