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

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

Tester son code Ansible et de manière plus générale le code
d’infrastructure (IaC), présente quelques avantages notamment
lorsqu’on est bien outillé. La première partie de cet article en dévoile quelques usages et pose les bases de l’outillage nécessaire à cette pratique dans une architecture d’infrastructure à large échelle: du développement, où les tests peuvent être utilisés pour mieux comprendre l’impact des modifications et explorer des cas particuliers, à la documentation des cas critiques et leur comportement, en passant par la création d’une confiance partagée dans la base de code d’infrastructure. Le principe est le même pour d’autres outils qu’ansible, par exemple cet article à lire ou à relire Tests d’intégration SaltStack avec docker, de Séven Le Mesle notre CEO, traite des mêmes idées générales appliquées à SaltStack.

caution caution

I. L’intrigue

L’exposition de la situation

Vous avez atteint le nirvana du “management de la configuration”. Vous effectuez des déploiements “from scratch” d’une architecture en quelques minutes, c’est “automagique”. Le tout est répétable, permet de gagner du temps, augmente l’efficience et réduit les erreurs humaines au passage. Mais ce n’est pas “bulletproof” pour autant.

Le coeur du problème

Lorsqu’on écrit du code, il est presque certain d’avoir des bugs ou des cas particuliers qui ont été négligés tout au long du cycle de vie de cette base de code. Automatiser une petite infrastructure permet certainement de s’en sortir avec des vérifications manuelles. Mais voilà le succès est au rendez-vous et à mesure que l’équipe et l’architecture grandissent on commence à prendre des raccourcis et on souhaite livrer ce qu’on a pour la prochaine release sans plus attendre.

Les péripéties

Les déploiements sont automatisés mais pas les tests. Des erreurs apparaissent alors dans les scripts et auront le même impact que celles que l’on retrouve lors de déploiements manuels. Pire, elles vont se répéter encore et encore à grande échelle.

Le dénouement ou l’âge de maturité

Prenant un peu de recul, il est temps d’examiner de plus près la façon dont on teste nos configurations. Et comme je dis bien souvent: entrons dans le “code testing paradox” ou quand ralentir un peu, permet d’aller plus vite et plus loin.

II. Les principes:

Méthodiquement parlant, les principes appliqués seront les suivants :

  • ✓ Documentation : le besoin est recueilli, détaillé ou bien une vue d’ensemble des attendus est fourni.
  • ✓ Test : écritures de tests qui décrivent le comportement attendu.
  • ✓ Script : écriture du code qui implémente le comportement voulu.
  • ✓ Versionning : utilisation de git ou d’autres outils de gestion de versions.
  • ✓ Intégration : pratique de l’intégration continue avec Jenkins ou un autre outil de CI.

L’ordre spécifique d’application de ces principes est discutable, mais ils sont la clé de l’infrastructure as code guidée par les tests.

III. Exemple:

La plupart du temps le TDD (test driven development) implique d’écrire les tests d’abord, avant d’implémenter le code. Il est clair que cela n’est pas toujours possible ou applicable, mais dans l’ensemble la démarche reste valable. Supposons que nous voulions tester entièrement notre système dans ce cas nous aurions à écrire :

  • ✓ Tests unitaires : test de tâche spécifique affectant le “filesystem”, “users”, “services”… etc.
  • ✓ Tests d’intégration : test de modules en coordination (plusieurs rôles Ansible).
  • ✓ Tests système: se rapproche le plus de Test “End To End” (de bout en bout) sur tout le système.

Dans notre exemple, il s’agit simplement de présenter la démarche, on s’attachera juste à illustrer des exemples de tests unitaires. Notre objectif est d’obtenir le workflow automatique suivant :

Cinematique-1

Reprenant notre exemple basique d’installation du serveur nginx, disponible sur gitansible-nginx. Pour installer la stack ansible/kitchenCi, reportez-vous à l’article précédent Ansible & KitchenCI : Infrastructure As Code guidée par les Tests Part I

Les "spécifications" étant les suivantes :

  • ✓ server web: nginx

  • ✓ démarrage du serveur: activé

  • ✓ port d'ecoute http : 80

  • ✓ etc …

Le test unitaire pour vérifier l’installation des binaires nginx sur le serveur donnerait :

require 'serverspec'

# Required by serverspec
set :backend, :exec

require 'spec_helper'

describe package('nginx'), :if => os[:family] == 'redhat' do
  it { should be_installed }
end

describe service('nginx'), :if => os[:family] == 'redhat' do
  it { should be_enabled }
  it { should be_running }
end

À noter, la syntaxe des tests serverSpec.

L’exécution des commandes suivantes (à la racine du répertoire de code), nous permettra de vérifier si le test passe ou bien s’il y a une erreur.

kitchen converge

kitchen verify case1
  • Converge : install nginx via le role ansible-nginx.

  • Verify : exécute la suite de tests pour valider l’installation.

Nous devrions avoir un message indiquant que les tests ont échoué à l’étape “verify”. Implémentons le code nécessaire afin que les tests passent. Pour ce faire nous allons modifier des tasks Ansible dans deux fichiers setup-RedHat.yml et main.yml, respectivement, pour installer les binaires et pour s’assurer que le service est bien démarré au boot.
Si vous utilisez le repository git fourni, à la fin de chaque fichier indiqué plus haut il suffit de décommenter les deux tasks ansible ci-dessous :

  • ✓ Ensure nginx is installed ( setup-RedHat.yml )

  • ✓ Ensure nginx is started/enabled at boot ( main.yml )

 - name: Ensure nginx is installed
   yum:
         name: nginx
         state: installed
   when: nginx_is_wanted | bool


 - name: Ensure nginx is started/enabled at boot
   service:
         name: nginx
         state: started
         enabled: yes
   notify:
         - reload nginx
   when: nginx_is_wanted | bool

A nouveau, exécutons les commandes :

kitchen destroy
kitchen converge
kitchen verify case1

Notez les tests qui passent indiquant que les binaires Nginx ont bien été installés. Mais nous avons toujours un test en échec pour la vérification du port d’écoute.

De la même manière, notre test unitaire pour vérifier qu’nginx est en écoute sur le port 80 est le suivant, il suffit de changer le port dans le test 8082→80 dans le fichier spec/nginx/nginx_spec.rb comme indiqué ci-dessous :

describe port(80) do
  it { should be_listening }
end

En exécutant de nouveau les commandes :

kitchen destroy
kitchen converge
kitchen verify case1

Les 3 tests unitaires passent et valident que notre installation d’Nginx est conforme aux “spécifications”.

Notre but étant de continuellement tester notre code Ansible Nginx, un pipeline Jenkins automatise ces tâches sous forme de Jenkinsfile, ci-dessous un exemple :

Conclusion

Un job Jenkins testant continuellement notre code, la collaboration facilite et améliore nos tests, qui valident notre démarche Infra As Code. Notre infrastructure peut évoluer plus sereinement.

Happy Testing !