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.

# setup_prod_FINAL_v3_VRAIMENTFINAL.sh — ne pas toucher
scp app.tar.gz root@prod:/srv/        # étape 1 / 47
ssh root@prod 'tar xzf /srv/app.tar.gz && service app restart'
# étapes 2 à 47 : "demander à Jean-Michel"
  • 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 🛋️🔥

Pourquoi le YAML nous a trahis (et comment s'en remettre)

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     # false
answer: YES     # true
status: ON      # true

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 🧪

# 200 lignes de YAML pour tester 50 lignes de YAML, avec Tox et Molecule
# Plus Docker, plus Vagrant, plus un doctorat en souffrance

Yo mec, j'ai entendu que t'aimais le YAML alors j'ai mis du YAML dans tes tests YAML

# Pendant ce temps, en Python :
import pytest  # C'est tout.

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  # Enfin échapper à l'attraction du YAML
>>> 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

# Installer et configurer nginx
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

# Ne s'exécute que si le paquet n'est pas installé
apt.packages(packages=["nginx"])

# Ne s'exécute que si le service ne tourne pas
systemd.service(service="nginx", running=True)

# Ne s'exécute que si le contenu du fichier diffère
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 🎯

# SSH vers des serveurs (le classique)
pyinfra @ssh/web1.example.com deploy.py

# Exécuter en local (tester avant de casser)
pyinfra @local deploy.py

# Cibler des conteneurs Docker directement
pyinfra @docker/my-container deploy.py

# Mixer dans l'inventaire
pyinfra inventory.py deploy.py
# inventory.py — C'est juste du Python !
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

Performance : jusqu'à 10x plus rapide 🚀

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 🛤️

  1. Un playbook à la fois : prenez le pire
  2. Testez en local : pyinfra @local deploy.py
  3. 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  # La partie difficile. C'est fait.
# ou même pas : uvx pyinfra @localhost deploy.py
# deploy.py
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  # *chef's kiss*

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

Merci ! 🙏

return "Allez libérer votre infrastructure !"

🐍 pyinfra.com → Votre futur sans YAML commence maintenant

if encore_du_ansible_apres_ce_talk:
    raise FriendshipError("Faut qu'on parle")

On se retrouve au bar après ? Les bières sont offertes par Valentin. 🍻🧀

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 ?"