Infrastructure automatisée avec PyInfra
Mise à jour :
PyInfra est un outil d’automation d’infrastructure écrit en Python, conçu pour gérer des serveurs via SSH de manière simple, rapide et idempotente. Contrairement à des solutions comme Ansible qui utilisent YAML, PyInfra propose une approche déclarative en Python, permettant une meilleure intégration dans des workflows DevOps existants. Il s’adresse à ceux qui veulent automatiser le déploiement, la configuration et la gestion de serveurs sans agents, avec une syntaxe claire et des performances optimisées.
Concepts clés de PyInfra
PyInfra repose sur une architecture modulaire et une approche déclarative pour gérer des serveurs à distance. Son fonctionnement s’articule autour de plusieurs concepts fondamentaux : l’inventaire, les opérations, les facts et les connecteurs. Ces éléments permettent de modéliser une infrastructure de manière structurée tout en garantissant l’idempotence des actions.
- Inventaire : L’inventaire est au cœur de PyInfra. Il définit les hôtes que l’on souhaite gérer, qu’il s’agisse de serveurs physiques, virtuels, conteneurs ou autres. Cet inventaire peut être statique (fichier texte ou Python) ou dynamique (généré à partir d’une API ou d’un script). Il permet aussi de regrouper les hôtes par environnement ou rôle, facilitant la réutilisation des configurations. Chaque hôte peut contenir des données spécifiques (comme des variables d’environnement ou des secrets), ce qui permet de personnaliser les opérations en fonction du contexte.
- Opérations : Les opérations sont les actions exécutées sur les hôtes, comme installer un paquet, modifier un fichier, ou créer un utilisateur. Elles sont déclarées de manière déclarative, c’est-à-dire que l’on décrit l’état désiré plutôt que les étapes pour y arriver. Chaque opération est conçue pour être idempotente : elle ne modifie le système que si nécessaire. Cela permet d’exécuter plusieurs fois un même déploiement sans provoquer d’effets secondaires.
- Facts : Les facts sont des données collectées depuis les hôtes avant l’exécution des opérations. Par exemple, la version d’un paquet, le type de distribution Linux, ou la présence d’un fichier. Ces informations permettent d’adapter dynamiquement les actions à l’état réel du système distant. PyInfra fournit une collection de facts intégrés, mais il est également possible d’en définir sur mesure, pour des besoins spécifiques.
- Connecteurs : PyInfra fonctionne sans agent installé sur les machines gérées. Il utilise des connecteurs pour interagir avec elles. Le plus courant est le connecteur SSH, qui permet de piloter n’importe quel serveur disposant d’un accès SSH. D’autres connecteurs existent, comme ceux pour Docker ou Chroot, élargissant les possibilités à d’autres types d’environnements.
Comment fonctionne PyInfra ?
PyInfra utilise des connecteurs pour se connecter aux hôtes et exécuter des commandes déclarées dans des fichiers Python. Il suit un processus en plusieurs étapes :
- Chargement de l’inventaire : PyInfra charge l’inventaire défini dans un fichier Python, qui peut contenir des groupes d’hôtes et des données spécifiques.
- Collecte des facts : Avant d’exécuter les opérations, PyInfra collecte les facts de chaque hôte. Cela permet de connaître l’état actuel du système et d’adapter les actions en conséquence.
- Exécution des opérations : Les opérations sont exécutées de manière déclarative. PyInfra compare l’état actuel du système avec l’état désiré défini dans les opérations. Si l’état est déjà conforme, aucune action n’est effectuée. Sinon, les modifications nécessaires sont appliquées.
- Affichage des résultats : Après l’exécution, PyInfra affiche un résumé des actions effectuées, indiquant les changements appliqués et ceux qui n’ont pas nécessité de modifications. Cela permet de suivre l’évolution de l’infrastructure et de vérifier que les opérations ont été appliquées correctement.
Installation de PyInfra
L’installation de PyInfra est simple et rapide grâce à sa distribution via pip. Il peut être installé globalement, dans un environnement virtuel, ou via pipx pour un usage isolé sans perturber les dépendances du système.
L’utilisation de pipx est particulièrement adaptée pour les outils en ligne de commande Python. Elle permet d’installer PyInfra dans un environnement isolé tout en gardant la commande disponible globalement.
-
Installer
pipx
(si ce n’est pas déjà fait) :Terminal window python3 -m pip install --user pipxpython3 -m pipx ensurepath -
Installer
pyinfra
avecpipx
:Terminal window pipx install pyinfra -
Vérifier l’installation :
Terminal window pyinfra --versionpyinfra: v3.3.1
Cette méthode assure une séparation propre entre les outils et les bibliothèques Python du système, ce qui évite les conflits de dépendances.
Premiers pas avec PyInfra
PyInfra est conçu pour être simple à prendre en main, même pour ceux qui n’ont pas d’expérience préalable en automation d’infrastructure. Voici les étapes pour commencer à utiliser PyInfra efficacement.
Création d’un inventaire
Un inventaire est un fichier Python qui liste les hôtes à gérer. Il peut être statique (une liste d’adresses IP ou de noms de domaine) ou dynamique (généré par un script ou une API). Voici un exemple d’inventaire statique :
my_hosts = [ "my-server.net", "@docker/ubuntu:18.04", # Conteneur Docker "@local", # Machine locale]
Cet inventaire peut être utilisé pour exécuter des commandes ou des opérations sur les hôtes listés. Les hôtes peuvent être regroupés par environnement ou par rôle, facilitant ainsi la gestion de configurations complexes. Nous verrons plus loin comment structurer les inventaires pour différents environnements.
Vous remarquerez que les hôtes peuvent être désignés par leur nom de domaine,
leur adresse IP, ou par des alias comme @docker/ubuntu:18.04
pour indiquer un
conteneur Docker ou @local
pour la machine locale. Ces alias utilisent des
connecteurs spécifiques pour interagir avec les cibles. Parmis les connecteurs
disponibles, on trouve :
- @ssh : pour les serveurs distants accessibles via SSH.
- @docker : pour les conteneurs Docker.
- @local : pour exécuter des commandes sur la machine locale.
- @terraform : pour exécuter des commandes dans un environnement Terraform.
- @vagrant : pour les machines virtuelles gérées par Vagrant.
Il est possible d’écrire son propre connecteur pour des besoins spécifiques, mais les connecteurs fournis couvrent déjà la plupart des cas d’usage courants.
Exécution de commandes simples (ad-hoc)
Pour exécuter des commandes simples sur des hôtes distants, PyInfra propose
la commande exec
.
pyinfra INVENTAIRE exec -- commande
Elle permet d’envoyer des commandes shell à un ou plusieurs hôtes définis dans un inventaire. Voici quelques exemples :
# Exemple : via SSHpyinfra my-server.net exec -- echo "hello world"
# Exemple : dans un conteneur Dockerpyinfra @docker/ubuntu:18.04 exec -- echo "hello world"
# Exemple : sur la machine localepyinfra @local exec -- echo "hello world"
Cette méthode utilise le connecteur SSH, Docker ou local selon la cible et affiche le résultat en temps réel.
Utilisation des opérations
Les opérations sont le cœur de PyInfra. Elles permettent de déclarer des actions à exécuter sur les hôtes, comme installer des paquets, modifier des fichiers, ou gérer des utilisateurs. Contrairement aux commandes ad-hoc, les opérations sont déclaratives et idempotentes, ce qui signifie qu’elles ne modifient l’état du système que si nécessaire.
Voici un exemple d’opération pour installer un paquet avec PyInfra :
# Installer le paquet vim sur une machine distantepyinfra dev apt.packages vim _sudo=True
Dans cet exemple, dev
est l’hôte défini dans l’inventaire, apt.packages
est
l’opération pour gérer les paquets sur les systèmes basés sur Debian/Ubuntu, et
_sudo=True
indique que l’opération doit être exécutée avec des privilèges
élevés (sudo).
A la première exécution, PyInfra va installer le paquet vim
si nécessaire.
Si le paquet est déjà installé, l’opération ne fera rien, ce qui garantit
l’idempotence.
Il existe de nombreuses opérations prédéfinies pour gérer les paquets, les utilisateurs, les fichiers, les services, etc.
Vous pouvez consulter les opérations disponibles dans la documentation officielle de PyInfra ↗.
Arguments Globaux
PyInfra propose plusieurs arguments globaux pour personnaliser le comportement des opérations :
--sudo
: exécute les opérations avec des privilèges élevés (sudo).--data
: permet de passer des données supplémentaires aux opérations.--chdir
: change le répertoire de travail avant d’exécuter les opérations.--group-data
: spécifie un répertoire contenant des données de groupe supplémentaires.
Ces arguments peuvent être combinés pour adapter le comportement des opérations à vos besoins spécifiques. Par exemple, pour exécuter des opérations avec sudo et passer des données personnalisées :
pyinfra inventory.py deploy.py --sudo --data key=value
Créer un déploiement
Un deploy regroupe inventaires et opérations dans des fichiers Python pour être versionné et réutilisé :
my_hosts = ["dev"]
from pyinfra.operations import apt
apt.packages( name="Installer le paquet vim", packages=["vim"], _sudo=True,)
Exécution :
pyinfra inventory.py deploy.py
--> Loading config...--> Loading inventory...--> Connecting to hosts... [dev] Connected
--> Preparing operation files... Loading: deploy.py [dev] Ready: deploy.py
--> Detected changes: Operation Change Conditional Change Installer le paquet vim - -
Detected changes may not include every change pyinfra will execute. Hidden side effects of operations may alter behaviour of future operations, this will be shown in the results. The remote state will always be updated to reflect the state defined by the input operations.
Detected changes displayed above, skip this step with -y
--> Beginning operation run...--> Starting operation: Installer le paquet vim [dev] No changes
--> Results: Operation Hosts Success Error No Change Installer le paquet vim 1 - - 1 Grand total 1 - - 1
--> Disconnecting from hosts...
Cette approche, similaire à un playbook Ansible, facilite la maintenance et le partage de vos procédures.
Si vous souhaitez désinstaller un paquet, vous pouvez utiliser l’option
present=False
:
apt.packages( name="Désinstaller le paquet vim", packages=["vim"], _uninstall=True, present=False,)
Chaque opération possède différents arguments pour personnaliser son
comportement, comme mode
pour définir les permissions d’un fichier.
Référez-vous à la documentation officielle de
PyInfra ↗ pour une liste
complète des opérations et de leurs arguments.
Syntaxe Avancée de PyInfra
Pour aller plus loin, explorons les mécanismes avancés de PyInfra qui offrent un contrôle précis sur le déroulement des opérations et la gestion de l’inventaire.
Arguments Globaux
PyInfra propose plusieurs arguments globaux pour personnaliser le comportement d’une opération.
_sudo
: exécute les opérations avec des privilèges élevés (sudo)._sudo_user
: spécifie un utilisateur pour sudo._chdir
: change le répertoire de travail avant d’exécuter les opérations._env
: permet de passer des variables d’environnement aux opérations._timeout
: spécifie un délai d’attente pour les opérations._ignore_errors
: ignore les erreurs lors de l’exécution des opérations._if
: conditionne l’exécution d’une opération en fonction du résultat d’une autre opération.
Ces arguments peuvent être combinés pour adapter le comportement des opérations à vos besoins spécifiques. Par exemple, pour exécuter des opérations avec sudo et passer des données personnalisées :
server.user( name="Create pyinfra user using sudo", user="pyinfra", _sudo=True, _sudo_password="my-secret-password",)
Pour plus de détails sur les arguments globaux, consultez la documentation officielle de PyInfra ↗.
L’objet host
et inventory
Comment récupérer des données dynamiques ? Grâce à l’objet host
, vous
pouvez interroger :
host.name
ethost.groups
pour cibler des machines préciseshost.data
pour accéder aux variables d’inventairehost.get_fact(...)
pour obtenir des facts (distribution, version, etc.)
from pyinfra import host, inventoryfrom pyinfra.facts.server import LinuxNamefrom pyinfra.operations import server
if host.get_fact(LinuxName) == "Ubuntu": server.apt.update( name="Mettre à jour les paquets sur Ubuntu", _sudo=True, ) server.apt.upgrade( name="Mettre à niveau les paquets sur Ubuntu", _sudo=True, )
Pour plus d’informations sur les facts disponibles, consultez la documentation officielle de PyInfra ↗.
De son côté, l’objet inventory
facilite l’accès aux autres hôtes, idéal
pour générer des configurations croisées.
from pyinfra import inventoryfrom pyinfra.operations import server
# Créer un utilisateur sur tous les hôtes du groupe "web"for host in inventory.get_group("web"): server.user( name="Créer l'utilisateur web sur {}".format(host.name), user="web", shell="/bin/bash", home="/home/web", _sudo=True, )
Opérations conditionnelles
Les opérations conditionnelles permettent de n’exécuter une action que si certaines conditions sont remplies. Par exemple, vous pouvez conditionner l’exécution d’une opération en fonction du résultat d’une autre opération.
from pyinfra import hostfrom pyinfra.operations import server, files
template = files.template( name="Déployer le fichier de configuration", src="templates/myapp.conf.j2", dest="/etc/myapp/myapp.conf", mode="644", user="root", group="root", _sudo=True,)
server.service( name="Redémarrer le service si le fichier a changé", service="myapp", _if=template.did_change, _sudo=True,)
Dans cet exemple, le service myapp
ne sera redémarré que si le fichier de
configuration a été modifié par l’opération files.template
. Cela permet
d’éviter des redémarrages inutiles et de gagner du temps lors des déploiements.
Inclusion de fichiers multiples
Pour un déploiement modulaire, local.include
permet d’importer d’autres
scripts pyinfra et de leur passer des données via l’argument data
. Cela
facilite les rôles et les tâches partagées :
from pyinfra import local
local.include("tasks/setup_db.py", data={"db_user": "app"})
Objet config
Enfin, l’objet config
offre des defaults globaux pour toutes les
opérations du deploy :
from pyinfra import config
config.SUDO = True
Tous les appels suivants utiliseront sudo par défaut et vérifieront la version de pyinfra exigée.
Structure d’un projet PyInfra
Un projet PyInfra se compose de quelques fichiers et répertoires bien identifiés ; leur agencement conditionne la façon dont l’outil charge les cibles, les variables et les opérations.
Arborescence minimale conseillée
proj_PyInfra/├─ deploy.py # scénario principal (opérations)├─ inventories/│ ├─ production.py # inventaire statique ou dynamique│ └─ staging.py└─ group_data/ ├─ all.py # variables communes ├─ production.py # données propres à l’environnement de production └─ staging.py # données propres à l’environnement de staging
Cet exemple, repris de la documentation officielle, illustre la séparation entre
inventaires et données de groupe pour plusieurs environnements ; chaque
fichier d’inventaire devient automatiquement un groupe du même nom (
production
, staging
) en plus du groupe spécial all qui englobe toutes
les machines.
Inventaires : fichiers Python
- Les fichiers d’inventaire sont des scripts Python qui définissent les hôtes à gérer.
- Ils sont placés dans le répertoire
inventories/
et peuvent être nommés commeproduction.py
,staging.py
, etc. - Chaque fichier d’inventaire peut contenir des groupes d’hôtes, des variables spécifiques, et des données d’environnement.
- Il est possible de spécifier un inventaire différent avec l’option
--inventory
ou-i
group_data : variables par groupe
- Tout fichier
group_data/<nom_du_groupe>.py
expose des variables accessibles viahost.data
dans les opérations. - Le répertoire
group_data
est résolu relativement au répertoire courant ; il peut être déplacé avec l’option--group-data
ou--chdir
.
Hiérarchie des données
Lorsqu’une clé est définie à plusieurs niveaux, PyInfra applique cette priorité (de la plus forte à la plus faible) :
- Override fourni en ligne de commande (
--data key=value
) - host_data dans l’inventaire
- group_data normaux
- all.py (valeurs par défaut)
Ainsi, une même variable peut être définie globalement puis surchargée finement par environnement ou par hôte.
Comparaison avec Ansible
- Langage et flexibilité
- pyinfra : tout l’inventaire et les opérations sont écrits en Python, permettant d’intégrer boucles, conditions et fonctions complexes au besoin.
- Ansible : playbooks en YAML enrichis de Jinja2, plus accessible pour les débutants mais moins souple dès qu’il faut gérer de la logique avancée.
- Performance
- pyinfra exécute les commandes shell en parallèle, sans couche JSON intermédiaire, et ne modifie l’hôte que si nécessaire (idempotence).
- Transparence et débogage
- pyinfra affiche en temps réel la sortie des commandes et propose un diff avant/après chaque opération.
- Ansible passe par des modules encapsulés et un échange JSON, rendant parfois le débogage moins direct
- Modèle déclaratif vs impératif
- Ansible impose un modèle strictement déclaratif (playbooks YAML).
- pyinfra reste déclaratif pour la plupart des opérations, mais autorise
l’insertion de code Python impératif (via
host.get_fact
, callbacks, etc.) pour les cas complexes
- Inventaire et variables
- pyinfra : inventaire en Python (listes/tuples), enrichi par
host_data
etgroup_data
pour définir variables et secrets. - Ansible : inventaire en INI ou YAML, variables réparties dans les
répertoires
group_vars/
ethost_vars/
- pyinfra : inventaire en Python (listes/tuples), enrichi par
- Écosystème et modules
- Ansible dispose de milliers de modules officiels (
ansible-doc -l
) couvrant une large gamme de cas d’usage. - pyinfra s’appuie sur ses operations Python et la richesse de l’écosystème des packages Python pour étendre ses fonctionnalités
- Ansible dispose de milliers de modules officiels (
Conclusion
Comparé à Fabric, PyInfra offre une approche plus moderne et performante pour l’automatisation d’infrastructure, tout en restant accessible aux développeurs Python. Contrairement à Fabric, les modules Pyinfra garantissent l’idempotence.
PyInfra est donc un outil puissant et flexible pour l’automatisation d’infrastructure écrit en Python. Sa syntaxe claire et sa capacité à gérer des opérations idempotentes en font un choix idéal pour les équipes DevOps qui cherchent à simplifier la gestion de leurs serveurs.