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.
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.
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 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.
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.
Méthodiquement parlant, les principes appliqués seront les suivants :
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.
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 :
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 :
Reprenant notre exemple basique d’installation du serveur nginx, disponible sur ansible-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 :
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 !