Set-OPS-Public/docs/bindings-conception.md
Daniel Allaire 4454e1e18b docs: conception des bindings (relations app/base/serveur/domaine)
Note d'architecture. Direction retenue : liens déclarés côté application
(liens: [{vers, role}]), résolus par instancier.py en variables Ansible ;
chaque rôle décrit les liens acceptés dans meta/liens.yml ; FQDN cible
dérivé de la nomenclature. Domaines = lien exposition (écrit sur l'edge).
Preuve de migration ciblée : les 3 liens mail. Implémentation phasée à suivre.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 05:29:24 -04:00

8.3 KiB

Bindings — conception des relations entre applications, bases, serveurs et domaines

Note de conception, 2026-07-02. Décision d'architecture à valider avant implémentation. Direction retenue : liens déclarés côté application (le consommateur déclare ses besoins).

1. Problème

Les relations service → service ne sont nulle part dans le plan : elles sont codées en dur, éparpillées dans des group_vars / defaults. Exemples réels, produits à la main pour la messagerie :

  • serveur_postfix_mailstore_hote: "infra-mail-01.lab.chezlepro.internal" (Postfix → Dovecot) ;
  • serveur_postfix_rspamd_milter: true (Postfix → rspamd) ;
  • id-ldap-01 codé en dur dans les defaults (Postfix / Dovecot → OpenLDAP).

Conséquence : la topologie n'est pas déclarative. Déplacer Dovecot sur un autre hôte oblige à éditer des variables à la main. Cela échoue à l'épreuve de la portabilité multi-tenant — pourtant au cœur de la mission.

2. Ce qui existe déjà (partiel)

Le concept est à moitié né, mais éclaté et non câblé :

  • Bases (plan/bases-donnees.yml) : consommateur + portee (groupe|hote|application) + secret (Vault) + usage + proprietaire. Un vrai binding base → consommateur, mais vide et non injecté dans l'inventaire.
  • Domaines (plan/domaines.yml) : un domaine se lie à un edge ; scripts/domaines.py sait déjà lister les expositions déclarées par les applications — mais aucune app n'en déclare.
  • Applications (plan/applications.yml) : seulement groupe + hote. Aucun lien app → app.
  • Rôles : portent déjà une méta auto-descriptive (meta/empreinte.yml).

3. Modèle proposé

Un binding est une arête typée et dirigée, d'un consommateur (une application) vers une cible (une autre application, une base, ou un domaine public). scripts/instancier.py le résout en variables Ansible. On déclare la relation ; l'outil dérive le câblage.

3.1 Où vivent les liens : côté application (décision)

Le consommateur déclare ses besoins, dans plan/applications.yml :

applications:
  postfix:
    groupe: serveur_postfix
    hote: edge-mta-01
    liens:
      - vers: dovecot      # autre application
        role: mailstore    # nature du lien
      - vers: rspamd
        role: milter
      - vers: openldap
        role: annuaire

Rationale : localité et lisibilité (on lit une app et on voit de quoi elle dépend), et symétrie avec le déploiement (on pousse une app → ses liens sont là). Les entités cibles restent définies dans leur propre registre (bases-donnees.yml, domaines.yml) ; seules les arêtes vivent sur le consommateur.

3.2 Les rôles décrivent les liens qu'ils acceptent

Chaque rôle Ansible porte un meta/liens.yml (comme meta/empreinte.yml) qui dit quels liens il accepte et quelles variables ils remplissent :

# roles/serveur_postfix/meta/liens.yml
---
setops_liens:
  accepte:
    mailstore:
      cible: application
      injecte_sur: consommateur
      variables:
        serveur_postfix_mailstore_hote: "{cible.fqdn}"
    milter:
      cible: application
      injecte_sur: consommateur
      variables:
        serveur_postfix_rspamd_milter: true
    annuaire:
      cible: application
      injecte_sur: consommateur
      variables:
        serveur_postfix_ldap_serveur: "ldaps://{cible.fqdn}:636"

Le moteur reste générique ; la connaissance « lien → variables » reste dans le rôle.

3.3 Attributs de la cible (substitution)

Les gabarits de variables utilisent les attributs résolus de la cible :

Placeholder Application Base Domaine
{cible.hote} nom de VM hôte de la base
{cible.fqdn} FQDN interne dérivé FQDN interne
{cible.ip} IP dérivée IP
{cible.port} port
{cible.secret} nom de la variable Vault
{cible.base} nom de la base
{cible.domaine} nom public
{cible.edge} edge qui sert

Le FQDN interne est dérivé de la nomenclature (hote + domaine_interne) — jamais codé en dur. C'est ce qui rend la topologie portable.

3.4 Algorithme de résolution (instancier)

Pour chaque application portant des liens, pour chaque lien {vers, role} :

  1. résoudre vers → entité cible (app / base / domaine) et ses attributs ;
  2. charger le meta/liens.yml du rôle du consommateur ; trouver accepte[role] ;
  3. valider que le genre de la cible == cible attendue ;
  4. substituer les attributs dans chaque gabarit de variable ;
  5. injecter selon injecte_sur :
    • consommateurhost_vars de l'hôte du consommateur (défaut) ;
    • cible / edgehost_vars de l'hôte cible / de l'edge (cas exposition).

4. Les domaines : un genre de lien distinct

À considérer avec les bindings, sans les confondre :

  • FQDN interne (service → service) : dérivé de la nomenclature. Pas de domaines.yml.
  • Domaine public (exposition) : espace d'adressage + edge. Modélisé comme un lien role: exposition d'une app vers un domaine de domaines.yml. Sa résolution écrit sur l'edge (injecte_sur: edge) : un vhost/upstream nginx git.chezlepro.ca → forgejo-fqdn:port.
applications:
  forgejo:
    groupe: serveur_forgejo
    hote: git-01
    liens:
      - vers: git.chezlepro.ca   # domaine public (domaines.yml)
        role: exposition
      - vers: postgresql-forgejo # base (bases-donnees.yml)
        role: base

domaines.yml reste le registre des domaines publics (autorité, DNSSEC, secondaires, edge) ; le lien app ↔ domaine relie enfin app + domaine + edge, aujourd'hui séparés.

5. Réconcilier l'existant (bases)

Le binding base existe aujourd'hui côté base (consommateur pointe l'app). On l'aligne sur la direction retenue :

  • bases-donnees.yml garde la définition de la base (type, hôte, port, secret, proprietaire) — la base reste une entité à part entière ;
  • le lien de consommation passe côté app (role: base) ; la résolution injecte chez le consommateur la chaîne de connexion + le nom de la variable Vault (jamais le secret) ;
  • consommateur / portee sur la base deviennent une rétro-référence calculée (ou sont dépréciés). Migration douce.

6. Règles de validation

  • tout vers résout vers exactement une entité (sinon erreur ; désambiguïser par registre) ;
  • tout role est accepté par le rôle du consommateur (meta/liens.yml) ;
  • le genre de la cible correspond à cible ;
  • pour une base, le secret nommé existe dans la voûte ;
  • cycles tolérés s'ils sont légitimes (ex. mTLS mutuel), signalés sinon.

7. Interface (GUI)

  • vue « Liens » / éditeur de bindings par application ;
  • graphe des liens — visualisation naturelle (le GUI a déjà un graphe d'inventaire), très parlante pour la démo de portabilité (déplacer un nœud, voir les arêtes suivre).

8. Preuve de migration (les 3 liens mail)

Premier cas concret, à faire en régression (les mêmes variables doivent être générées) :

Avant (codé en dur) Après (déclaratif)
serveur_postfix_mailstore_hote (group_var) postfix.liens: [mailstore → dovecot]
serveur_postfix_rspamd_milter (group_var) postfix.liens: [milter → rspamd]
id-ldap-01 (defaults) postfix.liens: [annuaire → openldap], dovecot.liens: [annuaire → openldap]

9. Phasage

  • Phase 0 : cette note + décision. (direction : liens côté app)
  • Phase 1 : résolveur de liens dans instancier.py + meta/liens.yml des rôles mail ; migrer les 3 liens mail ; prouver que le déploiement reste identique (régression).
  • Phase 2 : réconcilier les bases (consommateur → liens app ; chaîne de connexion).
  • Phase 3 : exposition / domaines (vhosts nginx dérivés des liens).
  • Phase 4 : vue GUI des liens + graphe.

10. Décisions ouvertes

  • désambiguïsation de vers entre registres (app / base / domaine) ;
  • host_vars du consommateur vs group_vars du groupe (défaut : host_vars, plus précis) ;
  • format exact des gabarits ({cible.fqdn}) et jeu d'attributs figés ;
  • compat ascendante du consommateur des bases pendant la migration.