Ansible & KitchenCI : Infrastructure As Code guidée par les Tests Part I

Ansible & KitchenCI : Infrastructure As Code guidée par les Tests Part I

Ansible est un outil qui a fait ses preuves dans la chaîne d’outillage DevOps, tant pour faire du "configuration management" que pour l’automatisation des installations des stacks serveurs, au même titre que Puppet, Chef, etc …​
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 :

LifeCycleDev1

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.

KitchenAnsibleDockerServerSpec

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  git@github.com:jamroks/ansible-infra-ascode.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.

AnsibleSpecRunLarge

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:

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.