ITSkillsCenter
Blog

Inventaire dynamique Ansible pour Hetzner Cloud pas à pas

12 min de lecture

📍 Article principal : Ansible 2026 : la stack pratique pour automatiser Linux et Windows

Vous avez déjà une structure en rôles propre et vos secrets sont chiffrés. Reste à éliminer la dérive entre l’inventaire et la réalité de l’infrastructure — c’est ce que fait l’inventaire dynamique.

Ce que vous aurez à la fin

Un inventaire qui se génère tout seul depuis l’API Hetzner Cloud à chaque exécution Ansible. Vos serveurs apparaissent et disparaissent automatiquement, regroupés par labels (environment, role, region), avec des variables d’hôte qui suivent la réalité plutôt que de la dupliquer dans Git.

Prérequis

  • ansible-core 2.20 ou supérieur installé.
  • Un compte Hetzner Cloud avec au moins deux serveurs créés et taggés via le panel (labels role=web, environment=staging).
  • Un projet Ansible avec la structure inventory/ conventionnelle.
  • Niveau attendu : avoir déjà manipulé l’API Hetzner ou tout autre API REST cloud.
  • Temps : 60 minutes en lecture active.

Étape 1 — Pourquoi un inventaire dynamique change la maintenance

Un inventaire statique est une liste figée. Quand vous provisionnez un nouveau serveur via le panel Hetzner ou un script Terraform, ce serveur n’existe pas pour Ansible tant que vous n’avez pas édité le fichier d’inventaire à la main. Sur un parc de quinze machines, ce n’est pas dramatique. Sur cinquante, ça devient une source d’erreurs : un serveur oublié dans l’inventaire ne reçoit pas les correctifs de sécurité, un serveur supprimé reste référencé pendant des semaines, deux développeurs poussent des modifications conflictuelles sur le même fichier.

L’inventaire dynamique inverse la logique. Au lieu d’un fichier qui décrit ce qu’on croit avoir, c’est un script — ou plus précisément un plugin d’inventaire — qui interroge l’API du fournisseur cloud à chaque exécution Ansible et reconstruit la liste réelle. Le fichier d’inventaire devient une simple configuration : « va chercher chez Hetzner avec ce token, organise les hôtes par labels ». Le contenu effectif vit dans le cloud, plus dans Git.

Étape 2 — Installer la collection Hetzner

Le plugin d’inventaire Hetzner s’appelle hetzner.hcloud.hcloud. Il fait partie de la collection hetzner.hcloud, maintenue par Hetzner eux-mêmes. On la déclare dans collections/requirements.yml :

---
collections:
  - name: hetzner.hcloud
    version: 6.8.0

L’épinglage de version est important : la collection a évolué plusieurs fois côté noms d’options et schémas de groupes. En mai 2026, la collection est en 6.8.0 — la branche 6.x est la branche stable activement maintenue. On installe :

ansible-galaxy collection install -r collections/requirements.yml

Le plugin a besoin du SDK Python hcloud. Selon la version d’ansible-core installée via pipx, il peut ne pas être disponible dans l’environnement Python isolé. La méthode propre consiste à l’installer dans l’environnement pipx d’ansible-core :

pipx inject ansible-core hcloud

pipx inject ajoute une dépendance Python à un environnement pipx existant sans casser son isolation. C’est exactement ce qu’on veut pour les bibliothèques nécessaires aux modules Ansible qui tournent côté contrôleur.

Étape 3 — Récupérer un token API Hetzner

L’authentification au plugin se fait via un token API généré dans le panel Hetzner Cloud. On le crée sous Cloud Console → Project → Security → API Tokens, avec un nom explicite (ansible-inventory-prod) et le rôle Read — c’est suffisant pour lire l’inventaire, pas besoin de droits d’écriture. Limiter les permissions au strict nécessaire est une habitude qui paie le jour où le token fuite.

Le token est affiché une seule fois. On le copie et on l’enregistre immédiatement dans Vault, jamais en clair :

ansible-vault encrypt_string \
  --vault-password-file ~/.ansible/vault-pass-dev \
  --name 'hcloud_api_token' \
  'votre-token-collé-ici-une-seule-fois'

On colle le bloc obtenu dans group_vars/all/secrets.yml. Le token n’apparaîtra plus jamais en clair sur le disque ou dans Git.

Étape 4 — Le fichier d’inventaire dynamique

Le plugin lit son nom et ses options depuis un fichier YAML dont le nom doit se terminer par .hcloud.yml ou .hcloud.yaml — c’est cette extension qui permet à Ansible de l’identifier comme un fichier de configuration de plugin d’inventaire et non comme un inventaire statique.

On crée inventory/hcloud.yml :

plugin: hetzner.hcloud.hcloud
token: "{{ hcloud_api_token }}"

keyed_groups:
  - key: labels.role
    prefix: role
    separator: "_"
  - key: labels.environment
    prefix: env
    separator: "_"
  - key: location
    prefix: loc
    separator: "_"

hostvars:
  ansible_host: ipv4_address
  ansible_user: ansible

compose:
  ansible_python_interpreter: "'/usr/bin/python3'"

Ce fichier mérite d’être lu ligne par ligne. La directive plugin identifie le plugin à utiliser. Le token consomme la variable Vault chiffrée — on ne met pas la valeur en dur ici, sinon le contenu du fichier serait sensible alors qu’il vit dans Git en clair.

La directive keyed_groups est ce qui rend l’inventaire utilisable. Elle dit à Ansible : « pour chaque serveur, regarde la valeur de labels.role et place-le dans un groupe role_<valeur> ». Si vous avez taggé deux serveurs avec role=web et trois avec role=db dans le panel Hetzner, vous obtenez automatiquement deux groupes role_web et role_db que vos playbooks peuvent cibler.

hostvars définit les variables Ansible essentielles depuis les attributs du serveur Hetzner. ansible_host: ipv4_address dit à Ansible de se connecter à l’adresse IPv4 publique du serveur, et non à son nom — c’est ce qu’il faut tant que vous n’avez pas un DNS interne fiable.

La directive compose permet de calculer des variables à partir d’expressions Jinja2 ; ici on force le chemin de l’interpréteur Python sur les cibles, mais on pourrait aussi calculer un nom complet, déduire un environnement, etc.

Étape 5 — Vérifier l’inventaire généré

Avant tout playbook, on vérifie que le plugin retourne ce qu’on attend. La commande ansible-inventory sait afficher l’inventaire en mode arbre ou en mode liste :

ansible-inventory -i inventory/hcloud.yml --graph \
  --vault-password-file ~/.ansible/vault-pass-dev

La sortie attendue ressemble à :

@all:
  |--@ungrouped:
  |--@role_web:
  |  |--web-fra1
  |  |--web-fra2
  |--@role_db:
  |  |--db-nbg1
  |--@env_staging:
  |  |--web-fra1
  |  |--db-nbg1
  |--@env_production:
  |  |--web-fra2
  |--@loc_fsn1:
  |  |--web-fra1
  |  |--web-fra2

Chaque serveur apparaît dans plusieurs groupes — c’est exactement ce qu’on veut. Un playbook qui cible role_web traite tous les web, peu importe l’environnement ; un playbook qui cible env_production traite la prod, tous rôles confondus ; un playbook qui cible l’intersection (par exemple role_web:&env_production) traite uniquement les web de prod.

Pour inspecter les variables d’un hôte précis :

ansible-inventory -i inventory/hcloud.yml --host web-fra1 \
  --vault-password-file ~/.ansible/vault-pass-dev

La sortie liste tous les hostvars calculés : ansible_host, ansible_user, ansible_python_interpreter, plus tous les attributs Hetzner (ipv4_address, ipv6_address, server_type, image_name, labels, created). Vous pouvez consommer n’importe lequel dans un playbook ou un gabarit.

Étape 6 — Variables d’inventaire qui suivent

L’intérêt des groupes générés automatiquement, c’est qu’on peut leur associer des group_vars. On crée group_vars/role_web.yml :

---
nginx_worker_processes: 4
firewall_allowed_ports:
  - 80
  - 443

Et group_vars/env_production.yml :

---
ansible_become_method: sudo
backup_retention_days: 30
log_level: warn

Ces fichiers s’appliquent automatiquement aux hôtes qui appartiennent à ces groupes. Un serveur de prod taggé role=web cumule les variables des deux groupes ; un serveur de staging taggé role=web n’hérite que des variables web. La configuration suit la topologie sans manipulation manuelle.

Étape 7 — Fusion plusieurs sources d’inventaire

En production, un projet a souvent plusieurs sources : l’inventaire Hetzner pour les VPS cloud, mais aussi quelques machines on-premise déclarées en statique, et peut-être un autre cloud. Ansible sait fusionner les sources naturellement. On range les fichiers dans inventory/ et on cible le répertoire entier :

inventory/
├── hcloud.yml          # plugin dynamique Hetzner
├── on-premise.yml      # inventaire statique pour le datacenter local
└── group_vars/
    ├── all.yml
    ├── role_web.yml
    └── env_production.yml
ansible-playbook -i inventory/ playbooks/site.yml \
  --vault-password-file ~/.ansible/vault-pass-dev

Les hôtes de toutes les sources se retrouvent dans la même structure de groupes. Un même playbook peut donc orchestrer le mix cloud + on-premise sans rien savoir de la nature des machines.

Étape 8 — Cache et performance

Chaque appel à l’API Hetzner ajoute deux secondes au démarrage du playbook. Sur un poste développeur qui relance des plays cinquante fois par jour, c’est tolérable. Sur un runner CI qui doit boucler vite, on active le cache d’inventaire :

plugin: hetzner.hcloud.hcloud
token: "{{ hcloud_api_token }}"
cache: true
cache_plugin: jsonfile
cache_timeout: 300
cache_connection: /tmp/ansible_inventory_cache

Avec ces options, le résultat de l’API est mis en cache pour cinq minutes. Les exécutions consécutives lisent le cache au lieu d’appeler Hetzner. Pour forcer un rafraîchissement, on lance ansible-inventory --flush-cache -i inventory/hcloud.yml.

Le cache est invalidé automatiquement quand les options du plugin changent. Si on ajoute un keyed_groups ou qu’on change le filtrage, le prochain run recalcule sans intervention.

Étape 9 — Filtrer et grouper finement

L’API Hetzner retourne tous les serveurs du projet. Sur un compte qui héberge plusieurs équipes ou plusieurs environnements distincts, on veut souvent restreindre la portée du plugin. Trois mécanismes coexistent et savent s’enchaîner.

Le filtrage par label. Le plus simple — limiter le plugin aux serveurs portant un label précis. Si votre équipe utilise team=infra sur ses propres serveurs et team=apps pour ceux d’une autre équipe, vous filtrez :

label_selector: "team=infra"

La syntaxe accepte aussi les opérateurs !=, in, notin et la combinaison via virgules. label_selector: "team=infra,environment!=archive" retourne les serveurs de l’équipe infra qui ne sont pas en archive.

Les groupes conditionnels avec groups. Là où keyed_groups crée un groupe par valeur d’attribut, groups permet d’écrire une condition Jinja2 arbitraire :

groups:
  large_servers: server_type.cores >= 8
  ipv6_only: ipv4_address is none
  recently_created: created > '2026-01-01'

Ces groupes apparaissent dans ansible-inventory --graph exactement comme les groupes statiques. On y attache des group_vars qui s’appliqueront à tous les serveurs satisfaisant la condition.

L’attribution explicite via compose. Pour les variables calculées plus complexes — concaténer un préfixe de label et une zone, dériver un nom DNS, exposer le réseau privé en plus du public — la directive compose évalue des expressions Jinja2 par hôte :

compose:
  fqdn: "name + '.' + (labels.environment | default('dev')) + '.example.com'"
  internal_ip: "(private_net | first).ip if private_net else ipv4_address"

Ces variables sont disponibles dans tous les playbooks et gabarits sans manipulation supplémentaire. C’est le mécanisme qui transforme un inventaire dynamique brut en une vraie source de vérité enrichie.

Étape 10 — Diagnostic quand l’inventaire ne sort pas ce qu’on attend

L’erreur la plus pénible est silencieuse : le plugin retourne moins d’hôtes que prévu, ou les place dans les mauvais groupes. Trois outils pour la débusquer rapidement.

Premier : ansible-inventory --list sort le JSON brut de l’inventaire. On peut le piper dans jq pour ne voir que la liste des groupes ou les variables d’un hôte particulier :

ansible-inventory -i inventory/hcloud.yml --list \
  --vault-password-file ~/.ansible/vault-pass-dev \
  | jq '._meta.hostvars["web-fra1"]'

Deuxième : le drapeau -vvv sur ansible-inventory affiche les appels API et les transformations en détail. Utile pour vérifier que les filtres atteignent bien Hetzner et que le cache fonctionne.

Troisième : la commande hcloud server list -o columns=name,labels,status du CLI officiel Hetzner liste les serveurs vus côté API, indépendamment d’Ansible. Si un serveur n’apparaît pas avec hcloud server list, il ne pourra pas apparaître dans l’inventaire Ansible non plus — le bug est ailleurs (mauvais projet, mauvais token, serveur supprimé).

Erreurs fréquentes

Erreur Cause Solution
No inventory was parsed Nom du fichier n’a pas le suffixe .hcloud.yml Renommer ou déclarer explicitement plugin: hetzner.hcloud.hcloud en première ligne
Authorization required côté API Token expiré ou révoqué Régénérer dans le panel et re-chiffrer dans Vault
module hcloud not found SDK Python hcloud manquant dans l’env d’ansible-core pipx inject ansible-core hcloud
Groupes générés mais vides Labels mal nommés côté Hetzner ou key écrite avec une typo Lister les labels avec hcloud server list -o columns=name,labels
Inventaire affiche les hôtes mais SSH refuse ansible_host mal calculé (privé vs public) Mettre ipv4_address pour public ou private_net.ip pour réseau interne

Tutoriels frères

Lectures complémentaires

FAQ

Faut-il un plugin différent par fournisseur cloud ?
Oui. Chaque collection cloud expose son plugin (amazon.aws.aws_ec2, community.digitalocean.digitalocean, scaleway.scaleway.scaleway). On peut empiler plusieurs sources dans le même répertoire inventory/ et Ansible les fusionne automatiquement.

Peut-on filtrer les serveurs retournés par le plugin ?
Oui. La directive filters du plugin Hetzner accepte des conditions sur les labels ou le statut. Pratique pour exclure les serveurs en état off ou ceux d’un projet annexe.

Comment gérer les hôtes Windows dans un inventaire dynamique mixte ?
On les distingue par un label (os=windows) et on associe les variables WinRM dans group_vars/os_windows.yml. Le même fichier hcloud.yml sert pour les deux familles d’OS — c’est le tagging cloud qui dicte la connectivité.

Le token API doit-il avoir les droits d’écriture pour l’inventaire ?
Non. Pour la lecture d’inventaire seule, le rôle Read suffit. Si vous utilisez aussi des modules Hetzner pour créer/détruire des serveurs depuis Ansible, alors là un token Read & Write est nécessaire — mais on le sépare du token d’inventaire.

Quelle différence entre keyed_groups et groups ?
keyed_groups crée des groupes nommés dynamiquement à partir de la valeur d’un attribut (un groupe par valeur distincte). groups crée un groupe statique dont l’appartenance est conditionnée par une expression Jinja2 (par exemple, regrouper tous les hôtes dont la RAM dépasse 16 Go).

L’inventaire dynamique fonctionne-t-il avec ansible-pull ?
Oui. Le mode pull tire le code depuis Git, calcule l’inventaire localement, et applique les playbooks. C’est utile pour les architectures à haute échelle où chaque cible se configure elle-même au boot.

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité