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>
This commit is contained in:
parent
60b7de980f
commit
4454e1e18b
2 changed files with 203 additions and 0 deletions
|
|
@ -3,6 +3,15 @@
|
|||
## 2026-07-02
|
||||
|
||||
### Décidé
|
||||
- **Bindings — conception des relations app/base/serveur/domaine (`docs/bindings-conception.md`).**
|
||||
Les relations service→service sont aujourd'hui codées en dur, éparpillées dans des group_vars
|
||||
(ex. Postfix→Dovecot/rspamd/LDAP), ce qui casse la portabilité multi-tenant. 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 qu'il accepte dans `meta/liens.yml` (comme
|
||||
`meta/empreinte.yml`) ; FQDN cible **dérivé de la nomenclature** (jamais codé en dur). Domaines
|
||||
publics traités comme lien `exposition` (écrit sur l'edge). Réconcilie l'existant (bases
|
||||
`consommateur`, `domaines.edge`). Preuve de migration ciblée : les 3 liens mail. Implémentation
|
||||
à suivre (phasée).
|
||||
- **Licence : passage de CC BY-NC-SA 4.0 à AGPLv3.** Les licences Creative Commons ne sont pas
|
||||
faites pour du logiciel (position de CC elle-même) et la clause **NonCommercial contredisait
|
||||
le principe fondateur « tout est libre »** — en plus de bloquer les artisans/coopératives
|
||||
|
|
|
|||
194
docs/bindings-conception.md
Normal file
194
docs/bindings-conception.md
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
# 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` :
|
||||
|
||||
```yaml
|
||||
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** :
|
||||
|
||||
```yaml
|
||||
# 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` :
|
||||
- `consommateur` → `host_vars` de l'hôte du consommateur (défaut) ;
|
||||
- `cible` / `edge` → `host_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`.
|
||||
|
||||
```yaml
|
||||
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.
|
||||
Loading…
Reference in a new issue