Ansible & KitchenCI : Infrastructure As Code guidée par les Tests - Part I
Sommaire
En effet, il permet entre autres d'éliminer l’effet "snowflake server", vous savez, ce serveur présent dans le data center, assez unique en son genre et pas vraiment reproductible pour x raisons historiques que personne ne saurait vraiment expliquer. On parle alors "d’Infrastructure as Code" car le code Ansible permet d’envisager l’infrastructure comme une stack logicielle avec un cycle de vie propre et une évolution itérative.
Peut-on, par conséquent, appliquer certaines méthodes de l’usine logicielle au développement du code d’infrastructure Ansible ? C’est ce que nous verrons dans cette série d’article en deux parties.
Le focus du présent billet concerne la mise en place de tous les composants nécessaires à l’écriture, la validation et l'exécution des tests.
La démarche "Infrastructure as Code" a un double avantage. Lorsqu’elle s’intègre dans un pipeline CI/CD, elle permet une réduction des risques lors des déploiements et une réduction du temps pour passer de l’idée fonctionnelle à la production. Le cycle de vie de l’infrastructure devient similaire à celui d’une stack logicielle comme illustré sur le schéma ci-dessous :
Framework de test
Comme souvent, nous avons besoin d’outils. L’un de ces outils est KitchenCI (Test-kitchen). KitchenCi fournit un "runner" pour l'exécution des tests d’infrastructure de manière isolée et une architecture de "driver" afin de cibler différentes plateformes, telles que Vmware, Amazon EC2, Vagrant, Docker et bien d’autres. De plus, grâce à la notion de "plugin framework" on peut par exemple utiliser son framework de test préféré. Parmi ceux supportés en standard, il y a par exemple :
InSpec
ServerSpec
Bats
Dans notre cas, nous utiliserons ServerSpec, mais les mêmes principes s’appliquent à InSpec et Bats ou autres.
Etant donné le fait que nous développons sur notre workstation ou laptop, nous utiliserons le driver Docker, cela permet entre autres d’avoir un retour plus rapide du résultat de nos tests.
Pré-requis
Pour avoir une stack complète permettant l’écriture de code Ansible guidé par les tests, il est nécessaire d’avoir sur son laptop les composants suivants :
Docker : 18.06 (ma version actuelle, mais toute autre version relativement récente sera suffisante)
KitchenCI : un fichier Gemfile à la racine du projet inclus Test-Kitchen et toutes les dépendances
Ruby : 2.4 ou supérieure
System d’exploitation : Linux/MacOS X
Le schema ci-dessous donne une idée de la cinématique de l’execution de Test-kitchen ou KitchenCi.
Exemple
Prenons pour l’exemple l’implémentation d’un rôle Ansible pour l’installation du serveur web nginx, que nous souhaitons utiliser comme reverse proxy dans notre infrastructure. On commence par récupérer les sources depuis Github comme indiqué ci-dessous.
Pour information, ce rôle Ansible est disponible dans la librairie de rôles Ansible Galaxy.
---
git clone https://github.com/jamroks/ansible-infra-as-code.git
---
Vous devriez obtenir l’aborescence suivante:
.
├── Gemfile
├── Jenkinsfile
├── LICENSE
├── README.md
├── defaults
├── handlers
|── .kitchen.yml
├── kitchen-hosts
├── meta
├── plays
├── site.yml
├── spec
├── tasks
├── templates
└── vars
Nous allons voir dans la section suivante le fichier de configuration principal que nous devons modifier pour mettre en place les bases pour l'exécution de nos tests. Notez également que cette arborescence est la structure standard d’un rôle Ansible, à l’exception de:
spec
Gemfile
Jenkinsfile
plays
site.yml
Nous verrons pourquoi dans le deuxième volet, mais pour l’instant je vais vous décrire le fichier ".kitchen.yml" dans ce qui suit.
Configuration
L'exécuteur ou le Runner Test-Kitchen se pilote à partir d’un fichier de configuration unique nommé ".kitchen.yml". Ci-dessous mon fichier .kitchen.yml pour le rôle ansible nginx.
# Requirements :
# gem install kitchen-docker kitchen-ansible
driver:
name: docker
use_sudo: false
require_chef_omnibus: false # No need of chef
require_ruby_for_busser: true
private_key: spec/id_rsa
public_key: spec/id_rsa.pub
provisioner:
name: ansible_playbook
#roles_path: .
ansible_version: latest
require_ansible_repo: false
ansible_connection: ssh
#group_vars_path: ./group_vars
ansible_host_key_checking: false
playbook: playbook.yml
# list of requirements role
# requirements_path: requirements.yml
private_key: spec/id_rsa # -------#################--- Not needed ?
require_chef_for_busser: false
ansible_inventory: ./kitchen-hosts
# e.g.: 1 => '-v', 2 => '-vv', 3 => '-vvv", ...
ansible_verbose: true
ansible_verbosity: 2
# Add some random variables
# extra_vars:
# copy additional playbook dir
additional_copy_path:
- plays
#http_proxy: http://xxxxx@xxxxx:8080
#https_proxy: http://xxxxx@xxxxx:8080
#no_proxy: localhost,xxxx,xxxx
#ansible_extra_flags: '--tags=debug,add_vhost,apache'
#ansible_extra_flags: '--skip-tags=mytag1,mytag2 --limit=web'
# extra_vars:
# version: '0.0.1-SNAPSHOT'
# env: staging
# repository: snapshots
ignore_extensions_from_root: [".git"]
ignore_paths_from_root: [".git",".kitchen","bin"]
transport:
max_ssh_sessions: 5
verifier:
name: serverspec
sudo_path: true
platforms:
- name: centos-kitchen
driver_config:
image: 'kemet/centos7-spec'
suites:
- name: case1
provisioner:
ansible_playbook_command: echo 'NOOOP FOR ANSIBLE For This Container'
require_ansible_omnibus: false
require_ansible_source: false
require_ansible_repo: false
require_ruby_for_busser: true
driver:
provision_command:
- "yum -y install iproute"
- "yum -y install net-tools"
- "yum -y install vim"
- "yum -y install unzip"
run_command: '/usr/sbin/init'
driver_config:
hostname: case1
instance_name: 'case1'
privileged: true
volume: /sys/fs/cgroup:/sys/fs/cgroup
- name: ansible
provisioner:
require_ansible_omnibus: false
require_ansible_repo: true
require_chef_for_busser: false
require_ruby_for_busser: false
private_key: spec/id_rsa
ansible_verbosity: 2
driver:
provision_command:
- "yum -y install iproute"
- "yum -y install net-tools"
- "yum -y install vim"
- "sudo -H pip install ansible==2.5.4"
- "sudo -H pip install Jinja2==2.10"
- "sudo -H pip install jmespath"
- "sudo -H pip install lxml"
run_command: '/usr/sbin/init'
driver_config:
hostname: ansible
instance_name: 'ansible'
privileged: true
volume: /sys/fs/cgroup
links:
- case1:case1
✓ Driver : ici j’ai choisi Docker, j’effectuerai des tests (unitaires en quelque sorte) en local. D'autre driver existe tel que Vagrant, kitchen-scaleway, kitchen-cloudstack, kitchen-vcenter, kitchen-vsphere etc ...
✓ Provisioner : evidement j'ai choisi Ansible. Cela aurait pu être "Chef" ou un autre outil de configuration management supporté par Test-kichen
✓ Transport : cette section concerne le nombre de sessions de connexion, le protocole étant ssh. Nous aurons rarement besoin d'y toucher
✓ Verifier : J'ai fais le choix de ServerSpec. Cela aurait pu être Inspec ou Bats, testinfra etc…
✓ Platforms : permet de définir l’image de machine virtuelle ou l’image Docker, dans notre cas il s’agit de ma "Golden image" Docker que j’ai construit spécifiquement pour les tests de type Ansible role et playbook (disponible ici sur dockerhub centos7-spec)
✓ Suites : ce paramètre permet de configurer une instance particulière, c’est à dire que vous pouvez avoir plusieurs conteneurs (ou machines virtuelles) dans votre configuration et leur spécifier des paramètres différents selon les besoins. Ici j’ai défini deux instances, la première est appelée "case1" et la deuxième qui me sert de bastion est “brillamment” appelée "ansible".
J’ai donc 2 conteneurs, "case1" est la cible d’installation pour nginx, et "ansible" est le bastion (ou contrôleur) Ansible qui exécute le playbook d’installation, comme illustré sur le schéma ci-dessous.
Je reviendrai plus en détails sur les différentes options ou paramètres mais pour l’instant, essayons de vérifier que nos briques de base sont fonctionnelles, c’est à dire que nous arrivons bien à exécuter KitchenCI, que celui-ci déclenche bien l'exécution du playbook Ansible et que l’instance nginx est bien démarrée à l’intérieur du conteneur "case1".
Hands-On
- Premièrement, nous installons Test-Kitchen. A la racine du projet, j’exécute les commandes suivantes. Cette commande va installer tous les ruby Gems qui sont listés dans le fichier Gemfile :
Installez le gestionnaire de dependence ruby "bundler"
gem install bundler # ou bien sudo gem install bundler
ensuite installez les dépendences ruby via le package manager bundler
bundle install
- deuxiemement nous verifions que Test-Kitchen est bien installé.
kitchen list
la commande ci-dessus devrait renvoyé ce qui suit:
Instance Driver Provisioner Verifier Transport Last Action Last Error
case1-centos-kitchen Docker AnsiblePlaybook Serverspec Ssh <Not Created> <None>
ansible-centos-kitchen Docker AnsiblePlaybook Serverspec Ssh <Not Created> <None>
- Troisième étape, nous allons creer nos deux instances de conteneur.
kitchen create
on devrait obtenir ceci:
Instance Driver Provisioner Verifier Transport Last Action Last Error
case1-centos-kitchen Docker AnsiblePlaybook Serverspec Ssh Created <None>
ansible-centos-kitchen Docker AnsiblePlaybook Serverspec Ssh Created <None>
- Quatrieme étape, nous allons déclenché l’execution du playbook
kitchen converge
On devrait obtenir le résultat ci-dessous, après avoir vu défiler dans
la sortie standard les output "ansible run" habituelle:
PLAY RECAP *********************************************************************
case1 : ok=8 changed=3 unreachable=0 failed=0
Après l’execution d’un “kitchen converge”, on peut afficher l’état des containers cibles.
kitchen list
Ce qui nous donne le résultat ci-dessous, ou l'on vois que les containers sont dans un état "Converged".
Instance Driver Provisioner Verifier Transport Last Action Last Error
case1-centos-kitchen Docker AnsiblePlaybook Serverspec Ssh Converged <None>
ansible-centos-kitchen Docker AnsiblePlaybook Serverspec Ssh Converged <None>
Conclusion :
Pour avoir un socle d'infrastructure fiable et répétable, mais aussi pour faciliter la collaboration dans les équipes DevOps, il est impératif de pouvoir tester continuellement ce code d'infra, et faire rentrer l'Infrastructure as Code dans les pratiques de CI/CD. Dans la deuxième partie de l'article nous verrons comment écrire nos cas de tests d'infrastructure et leur mise en place dans l'intégration continue (CI) grâce à Docker et Jenkins.