Tester l'infrastructure n'est plus optionnel. Les déploiements sans tests post-provisioning génèrent des incidents systématiquement le lundi matin : un service oublié, un firewall mal configuré, un fichier de conf avec une coquille. Goss, Inspec, Testinfra automatisent la validation. Chacun avec ses forces.
Testinfra est l'option Python pour les équipes qui maîtrisent déjà pytest. On écrit les tests d'infrastructure dans le même langage et avec les mêmes patterns que les tests applicatifs. Idéal pour les équipes qui versionnent leurs configs Ansible et veulent une boucle "écrire le test → modifier l'Ansible → faire passer le test" cohérente.
Pour les ops qui font du Python au quotidien, c'est l'outil naturel. Pour les autres, Goss reste plus simple.
Plan de l'article
- Pourquoi tester l'infrastructure
- Testinfra : architecture et fonctionnement
- Premiers tests : services, ports, fichiers
- Tests sur backends multiples (SSH, Docker, Ansible)
- Intégration avec Ansible et Molecule
- Tests réseau, firewall, et conformité
- Patterns pytest avancés : fixtures, parametrize
- Comparaison avec Goss et Inspec
Pourquoi tester l'infrastructure
Trois raisons concrètes pour valider l'état d'un serveur après provisioning.
Validation post-déploiement. Après ansible-playbook site.yml sur 50 serveurs, on veut vérifier que tout est conforme. Sans test, on découvre un service éteint sur 3 hôtes après mise en prod.
Régression sur configuration. Une PR Ansible modifie un rôle. Sans test, le commit casse silencieusement un autre rôle dépendant. Avec test, la CI bloque le merge.
Conformité et audit. CIS benchmark, PCI-DSS, ISO 27001 demandent preuves que les contrôles sont en place. Une suite de tests Testinfra exécutée régulièrement produit ces preuves automatiquement.
Documentation exécutable. Le test "le service nginx doit être actif et écouter sur 443" sert de doc en plus de garantie. Plus à jour que n'importe quel wiki.
C'est l'équivalent serveurs de la TDD applicative. La maturité de la pratique côté ops est en retard, mais les outils existent.
Testinfra : architecture et fonctionnement
Testinfra est un plugin pytest. Il fournit des modules pour interroger des serveurs depuis pytest et asserter des propriétés.
Backends supportés :
- SSH : tests via SSH, le mode classique.
- Docker : tests à l'intérieur d'un conteneur Docker.
- Podman : équivalent Podman.
- LXC : conteneurs LXC.
- Kubernetes : tests dans des Pods K8s.
- WinRM : Windows.
- Ansible : utilise un inventaire Ansible pour déterminer les hôtes cibles.
- Local : tests sur la machine qui exécute pytest.
Le module testinfra.host.Host fournit des propriétés de base :
host.service("nginx")retourne un objet Service avec.is_running,.is_enabled.host.file("/etc/nginx/nginx.conf")retourne un objet File avec.exists,.contains,.user,.group,.mode.host.package("openssl")retourne un Package avec.is_installed,.version.host.processpour interroger les processus.host.socket("tcp://0.0.0.0:443")pour les ports en écoute.host.user("nginx")pour les utilisateurs.host.command("uname -r")pour exécuter une commande arbitraire.
L'API est cohérente, type-friendly, intégrée à pytest.
Premiers tests : services, ports, fichiers
Setup minimal :
pip install pytest testinfra
mkdir tests
Premier test tests/test_baseline.py :
def test_nginx_service(host):
nginx = host.service("nginx")
assert nginx.is_running
assert nginx.is_enabled
def test_nginx_listening(host):
assert host.socket("tcp://0.0.0.0:80").is_listening
assert host.socket("tcp://0.0.0.0:443").is_listening
def test_nginx_config(host):
config = host.file("/etc/nginx/nginx.conf")
assert config.exists
assert config.user == "root"
assert config.mode == 0o644
assert config.contains("worker_processes auto;")
def test_no_anonymous_ftp(host):
vsftpd = host.service("vsftpd")
if vsftpd.exists:
config = host.file("/etc/vsftpd.conf")
assert config.contains("anonymous_enable=NO")
Exécution :
pytest --hosts="ssh://prod-web-01.exemple.fr,ssh://prod-web-02.exemple.fr" -v
Sortie pytest classique avec PASS/FAIL par test.
Tests sur backends multiples (SSH, Docker, Ansible)
Pour tester un parc Ansible :
pytest --hosts='ansible://all' --ansible-inventory=/etc/ansible/hosts -v
Testinfra parse l'inventaire Ansible, exécute les tests sur chaque host. Compatible avec les pratiques Ansible standards.
Pour tester une image Docker (ou un container Bootc à valider) :
pytest --hosts='docker://my-container' -v
Pour tester avec Molecule (qui orchestre tests Ansible roles), Testinfra est l'outil de validation par défaut. Molecule provisionne un container, lance le rôle, exécute les tests Testinfra, démolit.
Intégration avec Ansible et Molecule
Pattern Molecule + Testinfra :
# molecule/default/molecule.yml
driver:
name: docker
platforms:
- name: instance
image: debian:12
provisioner:
name: ansible
verifier:
name: testinfra
Tests Testinfra dans molecule/default/tests/test_default.py :
def test_role_applied(host):
# Vérifier les services et configs déployés par le rôle
assert host.service("postgresql").is_running
assert host.file("/etc/postgresql/15/main/pg_hba.conf").contains("hostssl")
def test_users_created(host):
user = host.user("appuser")
assert user.shell == "/bin/bash"
assert "developers" in user.groups
Cycle CI :
molecule test
# Provisionne le container, applique le rôle Ansible, exécute Testinfra, démolit
Pour les pipelines, intégrer dans GitLab CI ou GitHub Actions :
test-roles:
image: python:3.11
script:
- pip install molecule[ansible,docker] testinfra
- cd ansible/roles/postgres
- molecule test
Tests réseau, firewall, et conformité
Pour des tests réseau et firewall :
def test_firewall_ssh_only_internal(host):
# nftables policy
output = host.run("nft list ruleset")
assert "tcp dport 22 ip saddr 10.0.0.0/8 accept" in output.stdout
def test_no_listening_unexpected_port(host):
# Aucun service ne doit écouter sur des ports non documentés
expected_ports = [22, 80, 443]
output = host.run("ss -tlnp | awk '{print $4}' | grep -oE ':[0-9]+$'")
listening = {int(p[1:]) for p in output.stdout.splitlines() if p}
assert listening.issubset(set(expected_ports))
def test_pam_password_complexity(host):
pam_pwquality = host.file("/etc/security/pwquality.conf")
assert pam_pwquality.contains("minlen = 14")
assert pam_pwquality.contains("dcredit = -1")
Pour de la conformité CIS, voir cis-benchmark-audit qui couvre les contrôles à automatiser.
Patterns pytest avancés : fixtures, parametrize
Testinfra hérite de pytest, donc tous les patterns avancés sont disponibles.
Fixtures pour partager du setup :
import pytest
@pytest.fixture
def nginx_config(host):
return host.file("/etc/nginx/nginx.conf")
def test_nginx_config_exists(nginx_config):
assert nginx_config.exists
def test_nginx_worker_count(nginx_config):
assert nginx_config.contains("worker_processes auto;")
Parametrize pour tester plusieurs valeurs :
@pytest.mark.parametrize("service", ["nginx", "postgresql", "redis", "fail2ban"])
def test_critical_services_running(host, service):
s = host.service(service)
assert s.is_running
assert s.is_enabled
Markers pour grouper les tests :
@pytest.mark.security
def test_ssh_no_root_login(host):
sshd = host.file("/etc/ssh/sshd_config")
assert sshd.contains("PermitRootLogin no")
@pytest.mark.compliance
def test_audit_running(host):
assert host.service("auditd").is_running
Exécution sélective : pytest -m security pour ne lancer que les tests sécurité.
Comparaison avec Goss et Inspec
| Critère | Testinfra | Goss | Inspec |
| Langage | Python (pytest) | YAML | Ruby DSL |
| Cible primaire | Serveurs Ansible | Containers, images | Serveurs, conformité |
| Vitesse | Rapide | Très rapide | Moyenne |
| CIS profiles natifs | Non | Limité | Oui |
| Reporting | pytest standard | JSON, junit | Excellence (HTML, JSON, JUnit) |
| Courbe apprentissage | Faible si Python | Très faible | Moyenne |
| Communauté | Active | Active | Active (Chef) |
Testinfra gagne pour les équipes Python avec stack Ansible. Goss gagne pour les tests d'images container/Bootc rapides. Inspec gagne sur les profils CIS prêts à l'emploi et le reporting de conformité.
Sur les pipelines Packer + Ansible golden images, on voit souvent les trois cohabiter : Goss pour les images, Testinfra pour les tests post-Ansible, Inspec pour les rapports d'audit conformité formels.
Testinfra est l'outil par défaut quand l'équipe Ops a une appétence Python. Dans les autres contextes, Goss prend la place. L'objectif n'est pas l'outil mais l'existence de tests : 10 tests Goss valent mieux que 0 tests Testinfra parfaits jamais écrits. Écrire puis maintenir cette suite par-dessus l'exploitation courante demande du temps que peu d'équipes ont ; cette part se confie.
Sources
- Testinfra documentation officielle : référence install, modules, examples.
- GitHub pytest-dev/pytest-testinfra : code source.
- pytest documentation officielle : framework parent, fixtures, parametrize.
- Molecule documentation : framework de test pour rôles Ansible.
- Goss tool : alternative YAML pour images.
- Chef Inspec : alternative pour conformité formelle.


