Previous slide Next slide Toggle fullscreen Toggle overview view Open presenter view
YAML, l'ennemi silencieux de tes soirées
Apéros Web Nancy - 27 mai 2026
Loïc "wowi42" Tosser
github.com/wowi42
codeberg.org/wowi42
@wowi42
Partie 0 : Avant le YAML, il y avait... pire
Une brève histoire de l'Infrastructure as Code
Racontée par quelqu'un qui a survécu aux trois guerres
Accrochez-vous, on remonte en 1993
L'âge des ténèbres : le script bash sacré
Avant l'IaC, il y avait Jean-Michel et son script.
scp app.tar.gz root@prod:/srv/
ssh root@prod 'tar xzf /srv/app.tar.gz && service app restart'
Documentation : un post-it
Le post-it : a disparu
Jean-Michel : est parti chez un concurrent
"Works on my machine", sauf que la machine c'est la prod, et on ne la reboot jamais.
1993 : CFEngine, le grand-père
Mark Burgess , physicien norvégien, en a marre de réparer les mêmes serveurs à la main.
Son idée révolutionnaire :
Ne décris pas les étapes. Décris l'état désiré. La machine converge toute seule.
L'idempotence avant que ce soit à la mode
Le serveur se répare pendant que tu dors
Si austère que la doc ressemblait à une thèse de doctorat (parce que c'en était une)
Tout le monde a dit "génial". Personne ne l'a lu.
2005 : Puppet, le DSL qui se prenait pour un langage
Luke Kanies en a marre de CFEngine. Il invente un DSL Ruby déclaratif :
package { 'nginx' :
ensure => installed,
}
service { 'nginx' :
ensure => running ,
enable => true ,
require => Package['nginx' ],
}
Un Puppet master , des agents sur chaque serveur
Des certificats SSL à signer à la main, un par machine
Un graphe de dépendances que personne ne maîtrise vraiment
Ni tout à fait du code, ni tout à fait de la config. Le mulet de l'infra.
2009 : Chef, "et si on écrivait l'infra en VRAI code ?"
Adam Jacob a la bonne intuition. Des recipes , des cookbooks , du vrai Ruby :
package 'nginx' do
action :install
end
service 'nginx' do
supports status: true
action [:enable , :start ]
end
Chef avait raison : l'infrastructure, c'est du code.
Mais : du Ruby que les ops ne connaissaient pas, un serveur Chef, un agent partout, un bootstrap pénible. La bonne idée, au mauvais moment, beaucoup trop lourde. Retenez ça.
2012 : Ansible, la simplicité a gagné
Michael DeHaan : pas d'agent, juste SSH. Pas de Ruby, juste... du YAML.
Les ops pouvaient l'utiliser sans apprendre un langage
Pas de master, pas d'agent, pas de certificats
Installation en 30 secondes
Tout le monde a adoré. Enfin un truc simple.
On a échangé "apprendre un vrai langage" contre "ne jamais pouvoir débuguer".
À l'époque, ça semblait un bon deal.
Pendant ce temps : la guerre Dev vs Ops
Deux camps, deux objectifs incompatibles, un mur entre les deux.
Les Devs veulent livrer 10 fois par jour, aller vite, casser des trucs
Les Ops veulent que RIEN ne bouge, jamais, sous aucun prétexte
Le dev lance son code par-dessus le mur. L'ops le reçoit sur la tête.
Dev : "Ça marche sur ma machine."
Ops : "Parfait. On déploie ta machine, alors."
Petit detail : Jean-Michel etait ops. Son script etait le mur.
2009 : on signe la paix, ça s'appelle DevOps
Velocity 2009 : Allspaw & Hammond (Flickr) présentent "10+ deploys per day: Dev and Ops cooperation" . La salle est sous le choc.
Patrick Debois , excédé, organise les premiers DevOpsDays à Gand, en Belgique.
Le hashtag #DevOps est resté parce que #DevOpsDays était trop long pour Twitter.
Dev + Ops réconciliés : mêmes outils, même langage , responsabilité partagée.
La paix était belle. Le traité de paix, lui, était écrit en YAML.
Partie I : Thérapie de groupe
Vous vous souvenez de cette promesse ?
Infrastructure as Code → Infrastructure as YAML
On nous a menti
Code : "Un langage de programmation lisible par l'humain"
YAML : "Un fichier de définition lisible par la machine" (à peine)
L'un est du code, l'autre est un appel à l'aide.
L'état actuel du DevOps
- name: Deploy application
hosts: all
vars:
deploy_state: "{{ lookup('env', 'STATE') |
default('present') }} "
tasks:
- name: Ensure application is {{ deploy_state }}
when: ansible_os_family == "Debian" and
not (ansible_distribution == "Ubuntu" and
ansible_distribution_version is
version('20.04', '>=' ))
Voilà ce qui arrive quand on demande à un fichier de config d'être un langage de programmation
Le cauchemar du développeur
DevOps devait rapprocher les deux mondes. Le YAML a fait l'inverse.
Les devs sont devenus de mauvais ops : ils écrivent du YAML qu'ils ne savent pas débuguer, sur des serveurs qu'ils ne connaissent pas.
Les ops sont devenus de mauvais devs : ils font du templating, des conditions, des boucles, sans pdb, sans tests, sans IDE.
Personne n'est content. Tout le monde est fatigué. Le YAML, lui, se porte bien.
Le mur n'a pas disparu. On l'a juste repeint en jaune Ansible.
L'enfer du templating : inception
Du Jinja2 dans du YAML dans du Jinja2 :
value: "{{ '{{' }} {{ '{{' }} variable {{ '}} ' }} {{ '}} ' }}"
C'est comme ecrire du HTML dans du PHP dans du JS. Mais en moins lisible.
Le YAML contre la geographie
country: NO
answer: YES
status: ON
La Norvege n'existe plus dans votre config. Le "non" non plus. Le pays ON demain.
Les messages d'erreur Ansible
TASK [Deploy app] *******************
fatal: [web01]: FAILED! => {"msg": "The task includes
an option with an undefined variable. The error was:
'dict object' has no attribute 'nonexistent'\n\nThe
error appears to be in '/playbook.yml': line 42,
column 3, but may be elsewhere in the file depending
on the exact syntax problem."}
"quelque part dans le fichier, ou dans un autre playbook, peut-etre que c'est mardi. On ne sait pas." — Merci, super utile !
Ajouter -vvvv et scroller 1000 lignes d'output ?
Les tests Ansible
Yo mec, j'ai entendu que t'aimais le YAML alors j'ai mis du YAML dans tes tests YAML
import pytest
Pas de pdb. Pas de breakpoint(). Juste... debug: msg="{{ variable }}".
Ton copilote IA aime le Python
Stack trace Python :
KeyError: 'nonexistent' at deploy.py:42
Ton LLM : "Ligne 42, ajoute la clé. Voilà le diff."
Erreur Molecule :
TASK [include_role] FAILED! => msg: "..."
Ton LLM : "Essaie -vvvv. Ou demande à un humain."
Diagnostic medical : YAML fatigue chronique
Reconnaissez-vous ces symptomes ?
Tremblements en voyant une extension .yml
Vous indentez votre liste de courses
Vous hurlez "C'est pas un bug, c'est un espace !" à 3h du matin
Dr PyInfra : 200mg de Python matin et soir.
Jean-Michel a fini le traitement. Il fait de la poterie maintenant.
Partie II : Pyinfra, Magie noire (mais propre)
Du code Python sur des centaines de serveurs. Sans agent. Juste SSH.
Tout est en Python (y compris votre santé mentale)
>>> import antigravity
>>> from yaml import pain
ImportError: No more. We're done here.
>>> import pyinfra
>>> # *chef' s kiss*
La synthèse : Chef avait raison, Ansible aussi
Souvenez-vous de la Partie 0 :
Chef (2009) : l'infra, c'est du vrai code . Idée géniale, trop lourde (Ruby + serveur + agent sur chaque machine).
Ansible (2012) : sans agent , juste SSH. Simple, mais payé en YAML indébugable.
PyInfra = l'intuition de Chef + la simplicité d'Ansible.
Du vrai code. Sans agent. Juste SSH. 13 ans plus tard, on a enfin les deux en même temps.
C'est quoi PyInfra ?
Du Python en entrée. Du shell en sortie.
Commandes ad-hoc ou opérations déclaratives
SSH, local, Docker. De un à mille serveurs.
Zéro Python sur la cible. Juste un shell POSIX.
Phase 1 : la collecte des faits
SSH, puis PyInfra regarde l'état réel :
Paquets, services, checksums
OS, réseau, utilisateurs
Il ne suppose rien. Il va voir.
Contrairement à Jean-Michel.
Phase 2 : l'exécution
PyInfra compare l'état souhaité aux faits collectés.
Génère les commandes nécessaires, rien de plus
Exécute en parallèle sur tous les hôtes
Vraie idempotence. Vous savez exactement ce qui a changé.
Le concept de base
from pyinfra.operations import apt, systemd
apt.packages(
name="Install nginx" ,
packages=["nginx" ],
)
systemd.service(
name="Restart and enable the nginx service" ,
service="nginx.service" ,
running=True ,
restarted=True ,
enabled=True ,
)
Du Python en entrée. Du shell en sortie. Magique ? Non. Normal.
Même Jean-Michel comprend. C'est presque vexant.
Attendez... C'est juste un VRAI langage !
EXACTEMENT.
Pas un DSL. Pas un format de config qui fait semblant d'être un langage. Un vrai langage de programmation , avec tout ce qui va avec :
De vraies def fonctions() qui marchent vraiment
De vraies boucles for, sans gymnastique with_items
De vrais if/else, sans spaghetti Jinja2
De vrais import, de vrais tests, un vrai debugger
Autocomplétion, type hints, refactoring → l'IDE bosse pour vous
Pas de Python tous les jours ? Ça s'apprend en une après-midi.
Le YAML, lui, ne se débugue jamais. Même au bout de dix ans.
Opérations déclaratives
Ne faire des changements que quand c'est nécessaire
PyInfra intègre la vérification de diff et des opérations idempotentes
apt.packages(packages=["nginx" ])
systemd.service(service="nginx" , running=True )
files.put(src="config.j2" , dest="/etc/nginx/nginx.conf" )
Déclaratif. Idempotent. Lisible par un humain. Trois pour le prix d'un.
Architecture sans agent
Zéro dépendance sur les systèmes cibles
Accès shell POSIX suffit
Pas d'agent. Pas de démon. Pas de certificat à renouveler à 4h du mat'.
Pas de bootstrap. Pas de poule, pas d'œuf.
Juste SSH et un shell. Comme en 1995, mais en mieux.
Linux, macOS, BSD, conteneurs, VMs cloud, bare metal, routeurs. Si ça parle shell, ça nous parle.
Connecteurs : ciblez n'importe quoi
pyinfra @ssh/web1.example.com deploy.py
pyinfra @local deploy.py
pyinfra @docker/my-container deploy.py
pyinfra inventory.py deploy.py
ssh_hosts = ["web1" , "web2" , "db1" ]
docker_hosts = [("@docker/app" , {"env" : "staging" })]
local = ["@local" ]
Un outil. Toutes les cibles. Zéro stagiaire en pleurs.
Totalement extensible
Profitez de tout l'écosystème de packages Python
import requests
hosts = requests.get("https://api/hosts" ).json()
import boto3
instances = boto3.client('ec2' ).describe_instances()
from slack_sdk import WebClient
slack = WebClient(token=TOKEN)
slack.chat_postMessage(channel="#ops" , text="Deploy complete!" )
Pas de plugin custom à maintenir. uv add et basta. 400 000 packages PyPI, en libre service.
Intégrez PyInfra partout
C'est une bibliothèque, pas juste un CLI
from pyinfra.api import Config, Inventory, State
from pyinfra.api.operations import run_ops
from pyinfra.operations import server
inventory = Inventory((["web1.example.com" ], {}))
state = State(inventory, Config())
server.shell(commands=["echo 'Deployed from my Flask app!'" ])
run_ops(state)
Votre infra dans votre code. Pas dans un Jenkins de 2015 que personne ne touche.
Intégration transparente
Inventaire depuis Terraform, Coolify, ou n'importe quelle API
Conteneurs Docker, sans plugin
Django, FastAPI, votre CLI maison
import requests
hosts = requests.get("https://api/hosts" ).json()
from datetime import datetime
now = datetime.now()
if now.weekday() == 4 and now.hour >= 17 :
print ("Vendredi 17h. Non. Valentin a déjà payé les bières." )
exit(1 )
Partie III : Duel à mort, Ansible vs PyInfra
On compte les lignes, et c'est sans pitié
Spoiler : PyInfra gagne par KO technique
Ansible :
Séquentiel. Allez vous chercher un café. Et un sandwich.
Des couches d'abstraction sur des couches d'abstraction.
Python obligatoire sur la cible. Sinon, débrouille-toi.
PyInfra :
Multiplexage SSH. Le déploiement finit avant votre café.
Parallèle de base. Comme un adulte.
Shell direct. Pas de magie. Pas de drame.
10x plus rapide. Et 10x moins de réunions pour expliquer pourquoi c'est lent.
Face à face : installation de paquets
Ansible (YAML verbeux)
- name: Install packages
hosts: webservers
become: yes
tasks:
- name: Update apt cache
apt:
update_cache: yes
- name: Install packages
apt:
name: "{{ item }} "
state: present
with_items:
- nginx
- postgresql
- redis
18 lignes de YAML pour installer 3 paquets. Bravo.
Face à face : installation de paquets
PyInfra (du Python propre)
from pyinfra.operations import apt
apt.packages(
packages=["nginx" , "postgresql" , "redis" ],
update=True ,
)
6 lignes. Vous pouvez les lire en une seule respiration.
Face à face : logique conditionnelle
Ansible
- name: Configure based on environment
template:
src: "{{ 'prod.j2' if env == 'production'
else 'dev.j2' }} "
dest: /etc/app/config
when: env is defined
PyInfra
template = "prod.j2" if env == "production" else "dev.j2"
files.template(src=template, dest="/etc/app/config" )
Juste du Python. La gymnastique Jinja2, on la garde pour les JO 2028.
Face à face : les boucles
Ansible
- name: Create users
user:
name: "{{ item.name }} "
groups: "{{ item.groups }} "
with_items:
- { name: alice , groups: sudo }
- { name: bob , groups: developers }
when: item.name != 'root'
Face à face : les boucles
PyInfra
users = [
{"name" : "alice" , "groups" : ["sudo" ]},
{"name" : "bob" , "groups" : ["developers" ]}
]
for user in users:
if user["name" ] != "root" :
server.user(user.name, groups=user.groups)
for x in y: appris en CP. with_items appris en thérapie.
Debug : retour instantané
Ansible
-vvvv, scroller 10 000 lignes, prier
Coller des debug: partout. Genre, partout.
Quelque part, la vraie erreur. Bonne chasse.
PyInfra
Output shell en direct. Comme un être humain.
breakpoint() marche. Comme dans tous les langages, depuis 30 ans.
Le debugger de l'IDE marche aussi. Quelle nouveauté.
Messages d'erreur : de vraies stack traces
Traceback (most recent call last):
File "deploy.py" , line 42 , in <module>
config["nonexistent" ]
KeyError: 'nonexistent'
Fichier exact, ligne, et erreur. Fini le "may be elsewhere".
Tests : de vrais tests unitaires
PyInfra
import pytest
from deploy import setup_nginx
def test_nginx_config ():
config = setup_nginx("production" )
assert "ssl" in config
assert config["worker_processes" ] == 4
pytest. Pas de Molecule, pas de Test Kitchen, pas de Vagrant, pas de larmes.
Support IDE : on est en 2026, quand même
Ansible
De la couleur sur du YAML. Bravo.
Du linting. Parfois. Si l'extension est de bonne humeur.
Refactoring ? sed -i. Et une prière.
PyInfra
Autocomplétion + type hints. L'IDE tape pour vous.
Doc inline. F1, c'est plié.
Renommer en un clic sur 500 fichiers, sans rien casser.
Aller à la définition. Pas à la légende urbaine.
Réutilisation de code : modules Python
Ansible
Cloner un rôle Galaxy. Maintenu en 2019. Par un inconnu.
5 répertoires pour personnaliser une ligne
Résoudre les dépendances. À la main. À 3h du matin.
PyInfra
import. Comme tout le monde, depuis Python 1.0.
400 000 packages PyPI. uv add. Fini.
Du code que vous oserez relire dans 6 mois.
from my_company.infra import setup_monitoring, configure_firewall
Bénéfices en production
Jusqu'à 10x plus rapide qu'Ansible
70% de code en moins à maintenir
Vrais tests unitaires avec pytest
Les juniors comprennent : c'est un vrai langage
Mais et si...
"Ansible a des milliers de modules !"
PyInfra a 400 000 packages PyPI. uv add requests > module custom.
"Mon manager veut du Ansible, c'est standard !"
Votre manager voulait aussi Windows XP. On fait quoi ?
Mais et si... (suite)
"Et les dépendances sur les cibles ?"
Zéro. Sans agent. Juste un shell POSIX.
"Le changement fait peur !"
Votre playbook de 5000 lignes que personne n'ose toucher aussi. Demandez à Jean-Michel.
Chemin de migration
Un playbook à la fois : prenez le pire
Testez en local : pyinfra @local deploy.py
Dormez mieux : fini le debug à 3h du mat'
De nombreuses équipes ont migré avec succès d'Ansible vers pyinfra.
Prêt pour la production ?
Prêt pour la production
Tourne en prod ailleurs. Vraiment. Pas que sur un blog Medium.
APIs stables. Pas de "breaking change surprise" un mardi.
MIT. Gratuit, et le restera. Pas de "open core" enterprise.
Maintenu. Releases régulières. Pas un fantôme depuis 2021.
Cas d'usage
Provisionnement de serveurs
Déploiement d'applications
Conteneurs Docker, infra cloud
Si ça a SSH, PyInfra peut lui parler.
Lancez-vous aujourd'hui !
uv tool install pyinfra
from pyinfra.operations import apt, systemd
apt.packages(name="Install nginx" , packages=["nginx" ])
systemd.service(
name="Start nginx" ,
service="nginx.service" ,
running=True ,
enabled=True ,
)
pyinfra @localhost deploy.py
C'est tout. De l'IaC avec du vrai code.
Arrêtez le YAML, passez au Python
Venez me voir après pour discuter de :
Vos histoires d'horreur Ansible (thérapie de groupe )
Stratégies de migration
Pourquoi le YAML vous a fait du mal
Comment ne pas devenir le Jean-Michel de demain
Comment import pyinfra peut vous guérir
Et surtout : on continue la thérapie au bar. Les bières sont offertes par Valentin
Contribuez
resources = {
"docs" : "docs.pyinfra.com" ,
"github" : "github.com/pyinfra-dev/pyinfra" ,
"install" : "uv tool install pyinfra" ,
"examples" : "github.com/pyinfra-dev/pyinfra/tree/3.x/examples" ,
}
Starrez le repo • Essayez aujourd'hui • Ne touchez plus jamais au YAML
SCRIPT: "PyInfra n'est pas qu'un CLI — c'est une bibliothèque. Intégrez-la dans Django, FastAPI, n'importe où."
SCRIPT: "C'est juste du Python. Récupérez l'inventaire depuis Terraform, ajoutez des freezes de déploiement, bloquez les déploiements du vendredi — tout en Python standard."
SCRIPT: "PyInfra versus Ansible. Comptons les avantages..."
SCRIPT: "Jusqu'à 10x plus rapide. Multiplexage SSH, exécution parallèle, commandes shell directes."
SCRIPT: "18 lignes de YAML pour installer 3 paquets."
SCRIPT: "PyInfra : 6 lignes. Un import, un appel de fonction, terminé."
SCRIPT: "Logique conditionnelle : du Jinja2 dans du YAML vs une expression ternaire."
SCRIPT: "Les boucles. En Ansible : with_items, with_dict, with_file, with_nested... En Python : une boucle for."
SCRIPT: "En PyInfra : une boucle for. Celle que vous avez apprise la première semaine de Python."
SCRIPT: "Debug : -vvvv et scroller des milliers de lignes, ou utiliser pdb comme une personne civilisée."
SCRIPT: "PyInfra : fichier, numéro de ligne, problème exact. KeyError à la ligne 42. Terminé."
SCRIPT: "Tests : import pytest, écrire des tests. Pas de molecule, pas de test kitchen."
SCRIPT: "Support IDE : coloration YAML vs autocomplétion complète, refactoring, aller à la définition."
SCRIPT: "Réutilisation de code : copier des rôles depuis Galaxy ou juste importer un module."
SCRIPT: "10x plus rapide. 70% de code en moins. Les juniors comprennent parce qu'ils ont appris Python."
SCRIPT: "Objections : Ansible a des modules ? PyInfra a 400k packages. Tout le monde connaît Ansible ? Tout le monde déteste le debug."
SCRIPT: "Zéro dépendance sur les cibles. Intégration Terraform et Docker. Le changement fait peur — le playbook de 5000 lignes aussi."
SCRIPT: "Commencez petit. Prenez un playbook. Convertissez-le. Savourez."
SCRIPT: "Prêt pour la production. APIs stables. Maintenance active. Licence MIT."
SCRIPT: "Prêt pour la production. APIs stables. Maintenance active. Licence MIT."
SCRIPT: "Cas d'usage : provisionnement de serveurs, déploiement, configuration, conteneurs. Si ça a SSH, PyInfra peut lui parler."
SCRIPT: "uv tool install pyinfra. Écrivez deploy.py. Lancez. Terminé. Ou même pas d'install avec uvx. Essayez pendant les questions."
SCRIPT: "Arrêtez le YAML. Passez au Python. Venez me voir après pour une thérapie de groupe."
SCRIPT: "Starrez le repo. Essayez aujourd'hui. Ne touchez plus jamais au YAML."
SCRIPT: "Merci ! Allez libérer votre infrastructure ! On se retrouve au bar après ? Des questions ?"