Infrastructure as Code Inventory-Driven : Gérer des centaines d'applications sans perdre le contrôle

Introduction

Quand j’ai pris en charge l’infrastructure d’une grande entreprise française, j’ai découvert un système paralysé. Les déploiements prenaient des mois, parfois une année complète. Des consultants facturés à temps plein passaient leurs journées en réunions. Les documents étaient obsolètes avant même d’être terminés. Et tous les deux ans, les prestataires qui commençaient enfin à maîtriser le système arrivaient en fin de mission légale — emportant leur savoir avec eux.

Le problème n’était pas technique. C’était organisationnel et structurel. L’information était difficile à obtenir, volontairement non partagée (pour “protéger les emplois”), et chaque besoin nécessitait une demande formelle à une équipe dédiée. Un projet simple qui aurait dû prendre deux jours prenait un an. Il fallait payer chaque demande, payer des réunions où rien n’avançait.

J’ai développé une approche différente. Je l’appelle “inventory-driven” — pilotée par l’inventaire. Elle sépare radicalement ce qui change souvent (la liste de ce qu’on veut déployer) de ce qui change rarement (le code qui sait comment déployer). Dans notre cas, les déploiements sont passés de plusieurs mois à quelques jours. Plus important : les équipes projet peuvent désormais provisionner leurs ressources sans solliciter les DevOps.

Cet article explique pourquoi les approches classiques d’Infrastructure as Code ne scalent pas, et comment l’approche inventory-driven résout ce problème.


Pourquoi l’IaC classique ne scale pas

Les patterns habituels et leurs limites

La plupart des équipes qui adoptent Terraform ou Ansible commencent avec de bonnes intentions. Puis la réalité s’impose.

Un repo par application. Chaque équipe crée son propre dépôt Terraform. Au bout de 50 applications, vous avez 50 repos qui divergent, 50 façons différentes de faire la même chose, et personne ne sait ce qui est réellement déployé.

Un module par environnement. Pour gérer dev, staging et production, on duplique le code. Les environnements dérivent lentement. Un correctif appliqué en prod n’est jamais reporté en dev. Six mois plus tard, personne ne comprend pourquoi les environnements se comportent différemment.

Des variables partout. Les fichiers terraform.tfvars prolifèrent. Chaque cas particulier ajoute une variable. Le code devient un enchevêtrement de conditions if/else impossibles à maintenir.

Du code forké sans gouvernance. Les équipes copient-collent des modules trouvés sur internet ou chez le voisin. Sans bonnes pratiques partagées, chaque déploiement devient un cas unique. La dette technique s’accumule.

Le vrai problème

Toutes ces approches font la même erreur : elles mélangent deux choses fondamentalement différentes.

  1. Ce qui décrit l’infrastructure — la liste des applications, leurs configurations, leurs environnements. Ça change souvent : nouvelle app, nouveau serveur, modification d’un paramètre.

  2. Ce qui implémente l’infrastructure — le code Terraform, les rôles Ansible, les pipelines CI/CD. Ça change rarement : une fois que vous savez déployer une application Laravel, le code est le même pour toutes les applications Laravel.

Quand ces deux aspects sont mélangés, chaque ajout d’application ou d’environnement nécessite de toucher au code. Et toucher au code, c’est du risque, du temps, et de l’expertise.


L’idée centrale : séparer l’inventaire de l’implémentation

Une analogie simple

Pensez à un restaurant. Le chef a des recettes (l’implémentation). Le serveur a les commandes des clients (l’inventaire). Le chef ne réécrit pas la recette du risotto à chaque commande — il prend la commande et applique la recette.

L’approche inventory-driven fonctionne pareil :

  • L’inventaire décrit ce qu’on veut : quelles applications, sur quels serveurs, avec quels paramètres.
  • L’implémentation sait comment faire : provisionner une VM, déployer une app Laravel, configurer un certificat SSL.

Ajouter une application = ajouter une ligne dans l’inventaire. Pas de nouveau code. Pas de nouveau module. Pas de réunion.

Architecture globale

inventory/
├── terraform/
│   ├── dev.yaml      → VMs, réseaux, stockage pour l'env dev
│   ├── staging.yaml  → VMs, réseaux, stockage pour staging
│   └── prod.yaml     → VMs, réseaux, stockage pour prod
└── ansible/
    ├── dev.yaml      → Applications à déployer en dev
    ├── staging.yaml  → Applications à déployer en staging
    └── prod.yaml     → Applications à déployer en prod

implementation/
├── terraform/        → Modules génériques (VM, réseau, SG...)
└── ansible/          → Rôles par framework (laravel, flask, react...)

L’inventaire est la source de vérité unique. Le code d’implémentation est identique quel que soit l’environnement.


Structure d’un inventaire applicatif (Ansible)

Voici à quoi ressemble un inventaire pour le déploiement d’applications :

# inventory/ansible/dev.yaml
portal:
  - name: app-frontend
    port_host: 8081
    git_url: https://github.com/example/frontend.git
    git_version: master
    framework: flask
    ssl_enabled: false
    status: active

  - name: app-backend
    port_host: 8082
    git_url: https://github.com/example/backend.git
    git_version: master
    framework: laravel
    stack: livewire
    ssl_enabled: true
    app_url: https://app.example.com:8082
    status: active

  - name: legacy-app
    port_host: 8083
    git_url: https://github.com/example/legacy.git
    framework: php
    status: manual  # Géré manuellement, Ansible ne touche pas

Les principes de conception

Déclaratif, pas impératif. L’inventaire décrit l’état souhaité, pas les étapes pour y arriver. “Je veux cette application sur ce port” — pas “installe PHP, puis clone le repo, puis configure Apache”.

Valeurs par défaut intelligentes. Le framework détermine automatiquement ce qu’il faut faire. Une app Laravel déclenche composer install, npm install, les migrations. Pas besoin de le spécifier.

Statuts pour le cycle de vie. Trois états possibles :

  • active : l’application est déployée et maintenue
  • inactive : l’application est désinstallée proprement
  • manual : Ansible ignore cette entrée (géré par d’autres moyens)

Ce que l’inventaire ne contient PAS. Pas de logique. Pas de conditions. Pas de références à des chemins de fichiers ou des détails d’implémentation. Juste des données.


Structure d’un inventaire infrastructure (Terraform)

Pour le provisioning des ressources cloud, un inventaire séparé :

# inventory/terraform/dev.yaml
environment: "dev"

defaults:
  ebs_size: 60  # Disque additionnel par défaut

categories:
  - name: IAC_BAS_SPI
    description: "Infrastructure as Code - Bac à sable pour tests"
    subnet_id: "EXPOSURE"
    security_groups: ["SG_BASE_1", "SG_BASE_2"]
    cmdb_project_name: "PROJET POC"
    instances:
      - name: server01
      
  - name: KUBERNETES
    description: "Cluster Kubernetes de développement"
    subnet_id: "COMPUTE"
    security_groups: ["SG_BASE_1", "SG_K8S"]
    cmdb_project_name: "Kubernetes Dev"
    instances:
      - name: k8s-master-01
      - name: k8s-worker-01
      - name: k8s-worker-02

Organisation par catégories

Les instances sont regroupées par projet ou fonction. Chaque catégorie porte :

  • Les métadonnées projet (nom, description, référence CMDB)
  • La configuration réseau (subnet, security groups)
  • La liste des instances à créer

Cette structure permet de provisionner 3 ou 300 serveurs avec le même code Terraform — seul l’inventaire change.


Implémentation Terraform

Le code Terraform qui consomme l’inventaire :

# main.tf
locals {
  config = yamldecode(file("inventory/${var.environment}.yaml"))
  
  # Aplatir les instances avec leur contexte catégorie
  instances = flatten([
    for cat in local.config.categories : [
      for instance in cat.instances : {
        name            = instance.name
        category        = cat.name
        description     = cat.description
        subnet_id       = cat.subnet_id
        security_groups = cat.security_groups
        cmdb_project    = cat.cmdb_project_name
        ebs_size        = try(instance.ebs_size, local.config.defaults.ebs_size)
      }
    ]
  ])
}

module "instance" {
  for_each = { for inst in local.instances : inst.name => inst }
  source   = "./modules/ec2-instance"
  
  name            = each.value.name
  subnet_id       = local.subnets[each.value.subnet_id]
  security_groups = [for sg in each.value.security_groups : local.security_groups[sg]]
  ebs_size        = each.value.ebs_size
  
  tags = {
    Project     = each.value.category
    Description = each.value.description
    Environment = var.environment
    CMDB        = each.value.cmdb_project
  }
}

Points clés

yamldecode() pour lire l’inventaire. Terraform parse directement le YAML. Pas de génération de tfvars, pas d’étape intermédiaire.

for_each sur les données aplaties. Une seule ressource module qui s’instancie autant de fois que nécessaire.

Defaults avec try(). Les valeurs par défaut sont dans l’inventaire, appliquées proprement.

State séparé par environnement. Chaque environnement a son propre state backend. Pas de workspaces Terraform — trop de confusion possible.


Implémentation Ansible

Côté déploiement applicatif, un playbook maître qui orchestre :

# deploy.yaml
- name: Deploy applications from inventory
  hosts: "{{ target_host }}"
  vars:
    inventory: "{{ lookup('file', 'inventory/' + env + '.yaml') | from_yaml }}"
  
  tasks:
    - name: Process each application
      include_role:
        name: "deploy-{{ item.framework }}"
      vars:
        app: "{{ item }}"
      loop: "{{ inventory[target_host] }}"
      when: item.status == 'active'
    
    - name: Remove inactive applications
      include_role:
        name: "remove-app"
      vars:
        app: "{{ item }}"
      loop: "{{ inventory[target_host] }}"
      when: item.status == 'inactive'

Un rôle par framework

Chaque technologie a son rôle dédié :

roles/
├── deploy-laravel/    → Clone, composer, npm, migrations, Apache/Nginx
├── deploy-flask/      → Clone, venv, pip, Gunicorn
├── deploy-react/      → Clone, npm, build, serveur statique
├── deploy-html/       → Clone, copie fichiers, configuration serveur
└── remove-app/        → Désinstallation propre

Le rôle deploy-laravel sait tout ce qu’il faut pour déployer une application Laravel : installer les dépendances Composer, compiler les assets npm, exécuter les migrations, configurer le serveur web. Ces bonnes pratiques sont codifiées une fois, appliquées partout.

Déclenchement

Deux modes de déclenchement possibles :

  1. Push depuis l’inventaire. Un commit sur le fichier d’inventaire déclenche la pipeline GitLab CI qui exécute Ansible.

  2. Pull depuis l’application. Le repo de l’application contient un hook qui appelle le déployeur avec ses paramètres. L’application “demande” son déploiement.


Orchestration CI/CD

Une pipeline GitLab CI typique :

# .gitlab-ci.yml
stages:
  - validate
  - plan
  - apply

variables:
  TF_ROOT: ${CI_PROJECT_DIR}/terraform

validate:
  stage: validate
  script:
    - yamllint inventory/
    - terraform -chdir=${TF_ROOT} init
    - terraform -chdir=${TF_ROOT} validate
  rules:
    - changes:
        - inventory/**/*
        - terraform/**/*

plan:
  stage: plan
  script:
    - terraform -chdir=${TF_ROOT} plan -var="environment=${CI_ENVIRONMENT_NAME}" -out=plan.tfplan
  artifacts:
    paths:
      - ${TF_ROOT}/plan.tfplan
  environment:
    name: ${CI_ENVIRONMENT_NAME}
    action: prepare

apply:
  stage: apply
  script:
    - terraform -chdir=${TF_ROOT} apply plan.tfplan
  dependencies:
    - plan
  environment:
    name: ${CI_ENVIRONMENT_NAME}
  when: manual  # Validation humaine requise
  rules:
    - if: $CI_ENVIRONMENT_NAME == "prod"

Validation de l’inventaire

La première étape valide le YAML avant tout déploiement. Une erreur de syntaxe ou une valeur invalide bloque la pipeline immédiatement — pas de déploiement cassé.

Apply manuel pour la production

Le déploiement en production nécessite une action manuelle. Le plan est visible, reviewable, avant d’être appliqué.


L’interface web : démocratiser l’accès

Remplir un fichier YAML et commit, c’est déjà simple. Mais pour les équipes non techniques, nous avons ajouté une interface web en React.

Fonctionnalités

  • Formulaire guidé pour ajouter une application : URL du repo, framework, port, options
  • Visualisation de l’inventaire actuel par environnement
  • Déclenchement du déploiement en un clic
  • Logs de déploiement en temps réel

L’interface génère le YAML, commit dans le repo, et déclenche la pipeline. L’utilisateur n’a pas besoin de connaître Git, YAML, ou Terraform.

Garde-fous

  • Validation côté IHM : les champs obligatoires sont vérifiés, les formats validés
  • Droits par environnement : seules certaines personnes peuvent toucher à la production
  • Audit trail : chaque modification est tracée (qui, quand, quoi)

Documentation auto-générée

Un problème récurrent dans les grandes organisations : la documentation est obsolète. Elle est écrite une fois, jamais mise à jour, et personne ne lui fait confiance.

Notre solution : générer la documentation depuis l’inventaire.

MkDocs intégré

Un site MkDocs est généré automatiquement et expose :

  • La liste des applications déployées par environnement
  • Les configurations de chaque application
  • Les procédures standard (comment ajouter une app, comment debugger)

Édition collaborative

Un bouton “Éditer” sur chaque page permet de modifier la documentation directement. Le clic ouvre l’éditeur GitLab sur le fichier Markdown correspondant. Commit, merge request, déploiement automatique du site.

La documentation reste vivante parce qu’elle est facile à modifier.


Résultats concrets

Avant / Après

MétriqueAvantAprès
Temps pour déployer une nouvelle app3-12 mois1 jour
Expertise requiseDevOps seniorFormulaire web
DocumentationObsolète, éparpilléeGénérée, centralisée
ReproductibilitéFaible (code forké)Totale (même code partout)
Onboarding nouveau DevOpsSemainesJours

Ce qui a vraiment changé

Les DevOps ne codent plus pour chaque projet. Ils maintiennent les rôles et modules génériques. Leur expertise est capitalisée, pas réinventée à chaque mission.

Les équipes projet sont autonomes. Elles remplissent un formulaire ou un YAML, sans dépendre d’une équipe centrale pour chaque action.

L’information est partagée par défaut. L’inventaire est visible, versionné, documenté. Quand un prestataire part, le savoir reste.

Les bonnes pratiques sont appliquées automatiquement. Elles sont dans le code des rôles Ansible, pas dans la tête des gens. Impossible de déployer “sans les bonnes pratiques” — le système ne sait faire qu’avec.


Limites et cas où ne pas utiliser cette approche

Ça fonctionne bien pour

  • Beaucoup d’applications similaires. Si vous déployez 50 applications Laravel, l’approche est redoutablement efficace.
  • Environnements multiples. Dev, staging, prod avec la même structure — l’inventaire gère les différences.
  • Équipes qui veulent de l’autonomie encadrée. Liberté de déployer, dans un cadre qui garantit les bonnes pratiques.

Ça fonctionne moins bien pour

  • Infrastructure très hétérogène. Si chaque application est un cas unique avec des besoins spécifiques, le gain est limité.
  • Petits projets. Pour 3 applications, l’overhead de mise en place n’est pas justifié.
  • Équipes sans culture GitOps. Si personne ne veut versionner ou utiliser des pipelines, l’approche ne prendra pas.

Complexité à prévoir

  • Conception initiale de l’inventaire. Le schéma YAML doit être bien pensé dès le départ. Le modifier après coup impacte tous les déploiements.
  • Rôles génériques de qualité. Les rôles Ansible doivent couvrir tous les cas d’usage. Ça demande de l’investissement initial.
  • Formation des équipes. Même simplifié, le système nécessite une prise en main.

Conclusion

L’approche inventory-driven n’est pas une révolution technique. C’est une discipline : séparer ce qui change de ce qui ne change pas, et traiter l’inventaire comme une donnée de première classe.

Le bénéfice principal n’est pas la vitesse de déploiement — même si passer d’un an à un jour est appréciable. C’est la persistance du savoir. Quand les bonnes pratiques sont dans le code et que l’état du système est dans un fichier YAML versionné, le turnover des équipes ne détruit plus la connaissance.

Dans un contexte où les consultants changent tous les deux ans et où l’information est traditionnellement un pouvoir qu’on garde pour soi, c’est peut-être le changement le plus important.


Ressources


Cet article est basé sur une expérience de mise en œuvre dans un contexte de 400+ applications. Les exemples de code sont simplifiés pour la clarté.