Comment utiliser Terraform dans Azure DevOps

Présentation de Terraform
Terraform est un outil open-source développé par HashiCorp, et utilisé pour provisionner et gérer des infrastructures IT dans le Cloud. Écrit en Go et fonctionnant en mode Infrastructure as a Code (IAC), il permet d’administrer une infrastructure grâce à du code au lieu de procéder à un ensemble d’opérations manuelles.
La force de Terraform est de reposer sur un langage de description d’infrastructure simple et lisible, on parle ici de HCL. L’approche IAC de Terraform permet de gérer le versioning d’une infrastructure en lui ajoutant ou retirant des composants.
Voici quelques articles pour débuter avec Terraform :
- Provisionner votre infrastructure Azure avec Terraform
- Comment déployer votre infrastructure Azure en toute sécurité avec Terraform
Objectif de cet article
Dans cet article, nous allons voir comment utiliser conjointement Terraform et Azure DevOps dans l’optique de déployer une infrastructure Azure, de manière automatique et continue.
Nos buts ici sont les suivants :
- Définir une stack Terraform simple,
- Intégrer Terraform dans un pipeline de Release continue de Azure DevOps.
Notre exemple portera sur les ressources suivantes :
- 1 Web API
- 1 SQL Database
- 1 KeyVault
La Web API aura besoin de connaître la chaîne de connexion de la base. Afin de ne pas exposer de données sensibles, cette chaîne de connexion sera stockée dans le KeyVault lors de l’exécution de Terraform.
Les environnements que nous mettrons en place seront : DEV, REC et PRD
L’ensemble du code source est disponible sur GitHub.
Initialisation du projet
Pour cette première étape de ce tutoriel, connectons-nous à Azure DevOps et créons un nouveau projet que nous nommons TerraDevOps, puis clonons-le pour commencer à travailler !
Voici la structure de fichier que nous allons adopter :
|-- src\ |-- terraform\ |-- README.md
- src\ contiendra les sources de la Web API,
- terraform contiendra l’ensemble des fichiers de la stack applicative.
Côté applicatif
Commençons par créer une API ASP.NET Core dans le répertoire src\
Afin de protéger la chaîne de connexion à la base, nous allons avoir recours au service KeyVault d’Azure. Pour pouvoir y accéder, ajoutons la dépendance : Microsoft.Extensions.Configuration.AzureKeyVault.
Ajoutons un fichier web.config afin d’éviter l’erreur suivante lors du build dans Azure DevOps :
No web project was found in the repository. Web projects are identified by presence of either a web.config file or wwwroot folder in the directory. Project file(s) matching the specified pattern were not found.
Éditons maintenant le fichier Program.cs :
Puis, éditons le fichier Startup.cs :
Ensuite, modifions le fichier ValuesController.cs :
Et enfin, éditons le fichier appsettings.json :
- KeyVaultName : Correspond au nom de la ressource KeyVault.
- AzureADApplicationId : GUID de l’application donnant l’accès au KeyVault.
- AzureADPassword : Est le Password de l’application donnant l’accès au KeyVault.
Voyons comment récupérer les valeurs des champs AzureADApplicationId & AzureADPassword.
Rendons-nous dans Azure Active Directory et allons dans la partie App registrations.
Ajoutons maintenant une seconde key :
Puis, mettons à jour le fichier appsettings.json avec la clé générée une fois l’action Save réalisée :
N’oublions pas de procéder à un push des modifications que nous venons de faire sur le repository.
À ce stade, l’API ne peut pas fonctionner car nous n’avons pas la ressource KeyVault de déployée.
Terraform / Infrastructure
Côté Terraform, nous allons procéder à la création des différents fichiers :
|-- src\ |-- terraform\ |-- main.tf |-- outputs.tf |-- provider.tf |-- variables.tf |-- variables.tfvars |-- README.md
Le fichier main.tf
Ce fichier définit l’ensemble des ressources que Terraform doit gérer.
Notons la syntaxe du nom de la ressource key_vault_secret_connectionstring : ConnectionString–Default. Lors de l’exécution de la Web Api, il sera possible d’accéder à la valeur via :
Comme nous l’avons vu précédemment, l’objet configuration est configuré pour récupérer des secrets dans le KeyVault.
outputs.tf
Ce fichier définit les outputs que retournera Terraform une fois les ressources créées.
provider.tf
La section backend permet de stocker le fichier tfstate sur un Storage Account (pour en savoir plus).
Notons les clés suivantes :
- storage_account_name = “shared__application__tfsa”
- key = “terraform-__environment__.tfstate”
- access_key = “__tf_storage_account_key__”
Les différents tokens __token_name__ seront remplacés automatiquement avant le déploiement de l’infrastructure.
variables.tf
Ce fichier permet de définir les différentes variables utilisées.
variables.tfvars
Ce fichier définit les valeurs des variables. Dans notre cas, nous utilisons des tokens qui seront automatiquement remplacés lors du déploiement, en fonction de l’environnement cible.
Certaines variables sont indépendantes de l’environnement (location, application) et d’autres non (environement, sql_server_*).
N’oublions pas, encore une fois, de pusher les modifications.
Création du pipeline dans Azure DevOps
Une fois la Web Api et le Terraform en place, nous pouvons mettre en place la CI/CD.
Build
Créons une nouvelle Build Pipeline.
Commençons par cliquer sur le lien Use the visual designer pour passer en mode visuel.
Afin de nous simplifier la tâche, restons sur la branche master.
Dans un second temps, sélectionnons une build de type ASP.NET Core pour compiler notre Web API.
Modifions uniquement au niveau du step Publish la valeur du champ Arguments :
Ensuite, ajoutons une étape de type Copy files avant le Publish Artifact. Cette étape sert à embarquer les fichiers de Terraform dans l’artifact.
Enfin, procédons au lancement de notre première build. Si tout se passe bien, nous obtenons l’artifact suivant :
Au final, on y retrouve bien les éléments suivants :
- drop\MyWebApi\MyWebApi.zip : zip contenant les binaires de la Web Api.
- drop\Terraform* : les fichiers de la stack Terraform.
Définition de la Release
Créons à présent une nouvelle Release Pipeline, pour se faire, nous partons du template Empty job.
Dans un second temps, renommons le premier stage DEV.
Puis, nous devons ajouter un artifact.
Activons le Continuous deployment trigger pour lancer une release sur DEV dès qu’une build a été exécutée (il faudra également, au niveau de la build, activer l’intégration continue pour qu’une build soit exécutée dès qu’un push est effectif sur le repository).
Pour utiliser Terraform, nous avons besoin de :
- créer son backend sur Azure Storage (Blob),
- récupérer la Key de ce Storage et de l’injecter dans les variables pour pouvoir écrire/lire dans le Blob.
Première étape, ajoutons une étape de type Azure CLI pour la création du backend :
Inline script :
Ce script permet de créer les élèments suivants :
- un Resoure Group, indépendant de l’environnement déployé,
- un Storage Account,
- un Blob nommé terraform qui contiendra les tfstates de chaque environnement déployé.
Ajoutons ensuite un second step de type Azure Powershell script (en version 4.* (preview) pour le support de l’extension Az).
Inline script :
Ce script récupère la Key du Storage Account et l’injecte dans la variable tf_storage_account_key (que nous ferons un peu plus loin).
Les 2 étapes suivantes permettent de substituer les tokens présents dans les fichiers Terraform.
Pour chacune de ces 2 étapes, n’oublions pas de changer les Token prefix et suffix dans la section Advanced (avec double-underscores).
Ajoutons les steps suivantes qui concernent l’exécution de Terraform :
- init
- apply (en approbation automatique)
Remarque : idéalement, nous devrions ajouter le step validate juste avant le step apply afin de valider la stack avant exécution de celle-ci.
Ajoutons finalement la step pour déployer la Web Api sans oublier la transformation du fichier appsettings.json.
Une fois le stage DEV finalisé, faisons-en un clone que l’on nomme REC. Réitérons l’opération REC → PRD.
Activons aux stages REC et PRD l’option Pre-deployment approvals :
Notre pipeline étant défini, il nous reste maintenant à définir les variables.
Les valeurs des clés KeyVaultName, AzureADApplicationId et AzureADPassword seront injectées dans la configuration de la Web Api lors du dernier step du pipeline.
Exécution du pipeline
Lançons finalement notre première Release !
Une fois déployée, nous nous retrouvons avec 2 nouveaux Resource Groups :
- dev-mywebapi
- shared-mywebapi
Dans shared-mywebapi, nous retrouvons le Storage Account et son Blob qui contient le tfstate du déploiement de l’environnement DEV.
dev-mywebapi contient quant à lui les ressources applicatives.
Reste à tester que notre Web Api fonctionne bien :
… et à déployer les environnements de REC et PRD 😃
Conclusion
Dans cet article, nous avons vu comment provisionner une infrastructure Azure avec Terraform, le tout, déployé de manière continue au travers d’Azure DevOps. Il est évident que tous les aspects n’ont pas été couverts, mais cela reste un point d’entrée pour notre démarche DevOps.