introduit le Test Driven Development sur du code d’infrastructure avec des outils tels que Molecule ou Terratest. Dans cet article, nous vous présenterons Kitchen-CI, un outil qui permet, avec l’aide de bibliothèques de test comme InSpec ou ServerSpec, de tester les différentes briques de son infrastructure.
Kitchen-CI (de son vrai nom) est un outil écrit en Ruby par les développeurs de Chef. À l’aide de plugins spécifiques, il permet d’effectuer des tests pour vérifier la conformité des ressources générées par notre code.
Grâce à Kitchen, il est possible de tester le résultat de l’exécution de tout un panel d’outils tels que Terraform ou Ansible. Cela permet d’avoir une boucle de retour automatisée qui est plus efficace qu’une vérification manuelle et de n’avoir qu’un seul outil pour tester toutes les briques de déploiement de notre infrastructure.
La plus grande force de Kitchen-CI est sa modularité. Il dispose de divers modules lui permettant de s’adapter à la plupart des plateformes cloud (Amazon Web Service, Microsoft Azure…) et de virtualisation (Docker, Vagrant…) afin de mettre en place une infrastructure, la configurer et la tester.
Image tirée du site de Kitchen-CI
Le fonctionnement de Kitchen est très proche de celui de Molecule. Il se décompose en six étapes qui sont toutes jouées lors de l'exécution de la commande kitchen test :
Il faut savoir qu’il est possible de jouer chacune de ces étapes individuellement à l’aide des commandes :
**$** kitchen create **$** kitchen converge **$** kitchen setup **$** kitchen verify **$** kitchen destroy
Il faut également savoir que, contrairement à Molecule, Kitchen n’embarque pas nativement de bibliothèque de tests comme TestInfra. Par conséquent, il faut en ajouter une manuellement. Parmi les plus utilisées, on retrouve notamment InSpec et ServerSpec, deux bibliothèques mise en avant par les équipes de Chef dans la documentation et très appréciés par la communauté (respectivement 1638 et 2219 Stars sur leurs Github respectifs à l'heure où ces lignes sont écrites).
Pour illustrer le fonctionnement de Kitchen, essayons de construire l’architecture suivante sur Azure en testant à la fois la création de l’infrastructure (Terraform) et la configuration de la machine (Ansible) avec Kitchen :
Vous trouverez à cette adresse un dépôt git qui contient l’intégralité du projet. Essayons de comprendre comment chaque brique a été développée.
Voyons maintenant comment fonctionne Kitchen avec Terraform. Pour cela, je vous propose de jeter un œil au dossier terraform présent dans le dépôt git.
Lorsque l’on regarde l’architecture d’un projet Terraform qui utilise Kitchen, on obtient généralement une architecture qui ressemble à ça :
Jetons maintenant un œil aux fichiers nécessaires au bon fonctionnement de Kitchen afin de comprendre son fonctionnement.
Tout d’abord, le fichier kitchen.yml. C’est lui qui permet à Kitchen de connaître le module qu’il devra utiliser lors de chacune des grandes étapes de son fonctionnement (create, converge, verify…). Ouvrons le fichier et regardons son contenu : --- driver:
name: **terraform**
root_module_directory: **test/tf_module**
variable_files:
- **test/tf_module/testsuit.tfvars**
provisioner:
name: **terraform**
verifier:
name: **terraform**
systems:
- name: **azurerm**
backend: **azure**
controls:
- **resource_group**
- **network-vnet**
- **network-subnet**
- **virtual-machine**
platforms:
- name: **terraform**
suites:
- name: **default**
On y retrouve cinq grandes catégories :
On retrouve ensuite le fichier chefignore qui est utile si Kitchen est utilisé avec Chef afin de spécifier quel fichier doit être ignoré lors de l’envoi de donnée à un serveur Chef. Dans notre cas, ce fichier n’est pas utile.
Enfin, on retrouve le dossier test. C’est lui qui contient tout ce dont Kitchen a besoin pour jouer ses tests. Dans celui-ci, nous retrouvons tout d’abord le dossier integration qui contient toutes les suites de tests. Comme dit plus haut, Kitchen ne dispose pas de bibliothèque de tests native telle que TestInfra. Il faut donc en mettre une en place. Dans cet exemple, Inspec a été utilisé afin de mettre en place de pouvoir écrire des tests.
Inspec est une bibliothèque créée par les équipes de Chef afin de pouvoir tester son infrastructure. Elle dispose d’un nombre grandissant de ressources de test sur les principaux cloud provider (telle que Amazon Web Service, Google Cloud Platform et Microsoft Azure).
En utilisant Inspec, plusieurs fichiers sont importants :
Le fichier inspec.yml qui contient les informations relatives au cloud provider que l’on souhaite utiliser. Dans notre cas, avec Microsoft Azure, nous voulons utiliser inspec-azure. Afin de l’utiliser, le fichier doit ressembler à ça : ---
name: **test-infrastructure**
title: **Le nom de ma suite de test**
version: 0.1.0
inspec_version: '>= 2.2.7'
depends:
- name: **inspec-azure**
url: https://github.com/inspec/inspec-azure/archive/master.tar.gz
supports:
- platform: **azure**
On trouve ensuite les controls. Il s’agit de l’endroit où vont se situer nos différents tests. Un des avantages d’InSpec est que les tests sont écrit en Ruby ce qui leur accorde une grande lisibilité. Prenons par exemple d’un test du fichier network.rb : control **'network-vnet'** do
title **'Validation of the VNet properties.'**
desc **"The vnet is created and is placed in Amsterdam region."**
describe azurerm_virtual_network(**resource_group: 'KitchenDemoEnvironment'**, **name: 'MyVnet'**) do
it { should exist }
its(**'location'**) { should eq **'westeurope'** }
end
end
On retrouve le nom du control qui a été déclaré dans le verifier du fichier kitchen.yml, ainsi que les tests qui lui sont associés.
Maintenant que nous avons vu comment le code est organisé, voyons comment kitchen-terraform lance les tests ?
Dans notre dépôt, le code Terraform qui correspond à notre infrastructure correspond aux trois fichiers suivants qui se trouvent à la racine du dossier Terraform :
On a ensuite un dossier test/tf_module qui contient la déclaration du module pointant sur le code que Kitchen va tester. À l’aide de cette interface et du fichier de variable spécifique présent dans le même dossier, il génère une infrastructure spécifique pour l’exécution des tests.
Dans le code de l’exemple, nous avons choisi d’effectuer les tests dans un autre ResourceGroup afin que cela n’ait pas d’impact sur l’environnement principal. On obtient donc un fonctionnement qui ressemble à ça :
On se retrouve donc avec 2 flux qui permettent la mise en place de deux environnements différents. Le premier flux est celui de Kitchen qui effectue les tests sur une infrastructure temporaire qui est détruite à chaque exécution de Kitchen. Une fois la validation effectuée, on peut finalement appliquer le changement sur un environnement qui a vocation à être plus stable (zone de test de développeur, pré-production...).
Si on veut appliquer ça au Test Driven Development, notre cycle de développement se décompose en 4 étapes :
Ce genre de cycle de développement répond à des objectifs de déploiement continu et on se retrouve ainsi à déployer une infrastructure fonctionnelle bien plus souvent et de manière plus sécurisée.
Maintenant que nos machines ont été créées, on cherche à installer notre serveur Nginx dessus. Pour ce faire, on utilise Ansible pour configurer notre machine créée précédemment et nous allons utiliser le module kitchen-ansible pour effectuer nos tests sur cette partie.
Jetons un œil au dossier ansible dans le dépôt git.
Tout d’abord, regardons l’architecture du dossier :
Nous retrouvons l’architecture classique d’un dépôt Ansible. Notre dossier inventories contient un inventaire kitchen-demo avec les caractéristiques de la machine déployée lors de la partie Terraform. On retrouve également un playbook deploy.yml qui joue des rôles (présent dans le dossier éponyme). Enfin, notre but étant d’installer Nginx sur la machine créée plus tôt, nous avons un rôle install-nginx-server.
Dans le cas où nous avons un rôle à tester, Kitchen a un comportement similaire à celui de Molecule. Voyons comment celui-ci fonctionne. Le premier fichier que nous allons vouloir voir est le fichier kitchen.yml. Comme pour la partie Terraform, celui-ci comprend les instructions permettant à Kitchen d’effectuer ces tests. ---
driver:
name: **azurerm**
subscription_id: **'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'**
location: **'West Europe'**
machine_size: **'Standard_B2s'**
provisioner:
name: **ansible_playbook**
hosts: **all**
require_chef_for_busser: false
playbook: **kitchen-playbook.yml**
idempotency_test: true
verifier:
name: **inspec**
platforms:
- name: **ubuntu-18.04**
driver:
image_urn: Canonical:UbuntuServer:18.04-LTS:latest
vm_name: **Kitchen-Test-Machine**
vm_tags:
Purpose: **KitchenDemo**
suites:
- name: **default**
On retrouve ici les cinq parties vu précédemment avec quelques changements :
Contrairement aux tests avec Terraform, on a pas besoin de spécifier à InSpec comment jouer ces tests. La vérification se fera à l’aide des informations définies dans la partie platforms et il ira automatiquement récupérer les tests présents dans le dossier tests/integration/default/. Comme pour la partie Terraform, on retrouve des tests écrits en Ruby :
describe package(**'nginx'**) do
it { should be_installed }
end
Pour l’exécution de tests, il procède de la même façon que Molecule. Il utilise le playbook kitchen-playbook.yml pour exécuter le rôle sur l’environnement de tests lors de la phase de configuration des machines (converge). Une fois fait, il joue les tests sur l’environnement puis le détruit.
On obtient donc un cycle de développement en 4 étapes similaires qui est le même que celui de kitchen-terraform. La seule différence réside dans la construction de l’infrastructure qui a lieu avec azurerm et qui se fait via un ResourceGroup dédié.
Dans notre exemple, nous aurions pu rajouter également une série de tests Inspec sur le playbook situé à la racine du dossier Ansible pour valider que nos services tournent convenablement et que nos rôles n’ont pas eu d’effet de bord les uns sur les autres.
Finalement, on obtient une série de tests qui, s’ils sont automatisés, permettent de couvrir à la fois le code Terraform et les différentes parties du code Ansible. Si on devait automatiser les tests, on obtiendrait une pipeline ressemblant à ça :
Dans le cas de notre exemple, nous devons cependant nuancer nos résultats. En testant les différentes strates de code individuellement, nous avons la garantie que notre code est fonctionnelle… individuellement. Rien ne nous dit que Ansible pourra tourner sur l’infrastructure générée par Terraform.
Kitchen-CI est un outil puissant. Il permet de réaliser un grand nombre de tests sur divers environnements que ce soit du code de construction d’infrastructure (Terraform…) ou du code de gestion de configuration (Ansible).
Concernant la partie construction d’infrastructure, l’utilisation de Kitchen-CI est pratique afin de valider et d’éviter une régression sur une infrastructure qui marche. Lors de l’écriture des tests et du code Terraform, on retrouve une certaine redondance liée à la nature “descriptive” du HCL et cela donne l’impression d’écrire du code en double. Dans notre cas, nous avons dû décrire le CIDR à la fois lors de l’écriture du Vnet et lors de l’écriture du test associé à ce Vnet par exemple. De plus, en fonction du cloud provider, les ressources de tests mises à disposition par InSpec sont pour le moment plutôt limitées (vous trouverez la liste ici). Actuellement, je recommanderais l’utilisation de Kitchen-CI avec Terraform dans deux cas d’usage :
1 - Écriture de code de validation d’infrastructure
2 - Écriture de module logique (ex : Module de convention de nommage…)
Dans le cas de Ansible, Kitchen-CI a un rôle similaire à celui de Molecule. Il permet de mettre en place des tests sur :
Les plus |
Forte compatibilité avec InSpec<br><br> Écriture de test en Ruby (très bonne lisibilité).<br><br> Forte modularité<br><br> Peu de dépendance à installer. |
Les moins |
Mise en place complexe<br><br> Sensible au temps de création de l’infrastructure des divers cloud provider. |