docs(runbook) — chapitre « service compagnon » (namespace + stack partagés)
Le runbook new-web-app couvre l'app autonome (dépôt/base/Vault/namespace propres, tout nommé <app>). Il manquait le cas du SERVICE COMPAGNON : un second service qui partage le namespace — et parfois le stack Vault/DB — d'une app existante (API cœur à côté de son front, façade d'analyse). Deux précédents vivants non documentés : kadans-jobs (namespace seul) et kadans-api (namespace + base + Vault). - Nouvelle page 09-service-compagnon.md : compagnon vs app autonome ; les deux formes (sans état / partage Vault+DB) ; le PIÈGE du VaultAuth manquant quand l'app primaire ne consomme pas Vault (front statique) → le compagnon pose son propre VaultAuth mais avec le rôle+SA du PRIMAIRE ; carte, précédents, delta de checklist. - 07-argocd-register.md : ajoute la ligne `namespace:` aux options (elle existait dans values.yaml — kadans-jobs — mais n'était pas documentée) ; corrige le callout qui affirmait le namespace « non configurable ». - conventions.md : note l'exception compagnon à la règle « tout est <app> ». - README.md : entrée 09 dans l'index + Last Updated. Vérifié : VaultAuth erp nommé `auth` ; connexion via pgbouncer.tools ; liens internes tous résolus. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_013ws8L74dVZmp97Wu36fm8j
This commit is contained in:
@@ -39,6 +39,7 @@ Options supplémentaires :
|
||||
| Champ | Quand l'utiliser | Effet |
|
||||
|---|---|---|
|
||||
| `org: arcodange` | dépôt hors `arcodange-org` | change le `repoURL` (défaut `arcodange-org`) |
|
||||
| `namespace: <autre>` | **service compagnon** partageant le namespace d'une app existante | déploie hors du namespace `<app>` (défaut = nom de l'app) — voir [9. Service compagnon](09-service-compagnon.md) |
|
||||
| `syncPolicy: …` | contrôle manuel | surcharge la policy (défaut : `automated {prune, selfHeal}`) |
|
||||
|
||||
## Ce que ça génère
|
||||
@@ -79,7 +80,7 @@ flowchart LR
|
||||
## Notes / contraintes
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `path: chart` et `namespace: <app>` sont **déduits du nom**, pas configurables par entrée. C'est pourquoi le dossier doit s'appeler `chart/` ([étape 1](01-gitea-repo.md)) et le nom doit être cohérent partout ([conventions](conventions.md)).
|
||||
> `path: chart` est **fixe** (jamais configurable) : c'est pourquoi le dossier doit s'appeler `chart/` ([étape 1](01-gitea-repo.md)) et le nom doit être cohérent partout ([conventions](conventions.md)). Le `namespace` vaut **le nom de l'app par défaut**, mais se surcharge via `namespace:` — utilisé par les [services compagnons](09-service-compagnon.md) qui partagent le namespace d'une app existante.
|
||||
|
||||
- Le chart `factory/argocd` est lui-même réconcilié par ArgoCD (app-of-apps racine) : committer `values.yaml` sur `main` suffit à faire apparaître/synchroniser la nouvelle `Application`. Pas de `kubectl apply` manuel.
|
||||
- `prune: true` + `selfHeal: true` : ArgoCD supprime ce qui n'est plus dans le chart et réécrase les dérives manuelles. En tenir compte avant tout `kubectl edit`.
|
||||
@@ -88,4 +89,5 @@ flowchart LR
|
||||
|
||||
- [4. Chart Helm](04-helm-chart.md) — le contenu déployé (le dossier `chart/`).
|
||||
- [6. Workflows CI](06-ci-workflows.md) — les annotations `argocd-image-updater` collaborent avec l'image poussée.
|
||||
- [9. Service compagnon](09-service-compagnon.md) — le champ `namespace:` pour déployer dans le namespace d'une app existante.
|
||||
- [8. Checklist](08-checklist.md) — vérifier que l'`Application` passe `Healthy`/`Synced`.
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
[Factory](../../../README.md) > [Doc](../../README.md) > [Runbooks](../README.md) > [Nouvelle application web](README.md) > **9. Service compagnon**
|
||||
|
||||
# 9. Service compagnon (namespace partagé)
|
||||
|
||||
> **Status:** ✅ Active
|
||||
> **Upstream:** [7. Enregistrement ArgoCD](07-argocd-register.md) (le champ `namespace:` utilisé ici)
|
||||
> **Related:** [Conventions de nommage](conventions.md) · [4. Chart Helm](04-helm-chart.md) · [2. Base de données](02-database.md) · [Checklist](08-checklist.md)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Tous les autres chapitres décrivent une app **autonome** : son dépôt, sa base, son stack Vault, son namespace, son ServiceAccount — tout porte le même nom `<app>`. Mais certains services ne sont pas une app à part entière : ce sont des **compagnons** d'une app existante. Une **API cœur** à côté de son front, une **façade d'analyse** qui sert une app — ils vivent dans le **même namespace** que l'app qu'ils servent et **réutilisent son identité** (Vault, base, ServiceAccount) plutôt que d'en provisionner une nouvelle.
|
||||
|
||||
Ce chapitre décrit ce raccourci et son **piège principal** : la convention « tout est nommé `<app>` » ([conventions](conventions.md)) **ne tient plus** pour un compagnon — ses identités Vault/DB/SA restent celles de l'app **primaire**, pas les siennes.
|
||||
|
||||
## Compagnon ou app autonome ?
|
||||
|
||||
Fais un **compagnon** quand le service partage réellement l'identité et les données de l'app primaire. Fais une **app autonome** (chapitres 1→8) dès qu'il lui faut sa propre base ou ses propres accès.
|
||||
|
||||
| Prends un compagnon si… | Prends une app autonome si… |
|
||||
|---|---|
|
||||
| Il lit/écrit **la base de l'app primaire** (même données) | Il lui faut **sa propre base** |
|
||||
| Il partage le cycle de vie de l'app (déployé avec, pour elle) | Il a un cycle de vie indépendant |
|
||||
| Une seule origine CORS / un seul domaine logique | Domaine et exposition propres |
|
||||
|
||||
Un compagnon garde **son propre dépôt Gitea et son propre chart** (donc sa propre image, sa CI de build, son ingress). Ce qu'il **ne** refait pas : base, rôles Vault, ServiceAccount, namespace.
|
||||
|
||||
## Les deux formes de compagnon
|
||||
|
||||
### A. Compagnon sans état — juste le namespace partagé
|
||||
|
||||
Le service n'a **ni base ni secret Vault** (ex. façade d'analyse `kadans-jobs`). Il suffit de le déployer dans le namespace de l'app primaire. Une seule chose le distingue d'une app normale à l'[étape 7](07-argocd-register.md) : la clé **`namespace:`**.
|
||||
|
||||
```yaml
|
||||
# factory/argocd/values.yaml
|
||||
kadans-jobs:
|
||||
org: arcodange
|
||||
namespace: kadans # ← sinon ArgoCD déduirait « kadans-jobs »
|
||||
annotations:
|
||||
argocd-image-updater.argoproj.io/image-list: kadans-jobs=…/kadans-jobs:latest
|
||||
argocd-image-updater.argoproj.io/kadans-jobs.update-strategy: digest
|
||||
```
|
||||
|
||||
Son chart ne contient que `deployment` / `service` / `ingress`. **Pas d'`iac/`, rien dans `postgres/iac/terraform.tfvars`, pas de CRD Vault.**
|
||||
|
||||
### B. Compagnon partageant le stack Vault/DB de l'app primaire
|
||||
|
||||
Le service lit la **base de l'app primaire** avec **ses** creds dynamiques (ex. API cœur `kadans-api` sur la base `kadans`). Il réutilise, **à l'identique**, tout ce qui a été provisionné pour le primaire :
|
||||
|
||||
| Ressource | Elle porte le nom du **primaire**, jamais du compagnon |
|
||||
|---|---|
|
||||
| Base PostgreSQL | `kadans` (pas `kadans-api`) |
|
||||
| Rôle DB dynamique Vault | `postgres/creds/kadans` |
|
||||
| Rôle d'auth K8s Vault | `kadans` (bound au SA `kadans` / ns `kadans`) |
|
||||
| Policy KV runtime | `kadans` (accès `kvv2/kadans/*`) |
|
||||
| ServiceAccount K8s | `kadans` (créé par le chart du **primaire**) |
|
||||
|
||||
Donc le compagnon **ne fait PAS** l'[étape 2](02-database.md) (pas de nouvelle base), **PAS** l'[étape 5](05-app-terraform.md) (pas de nouvel `app_roles`, pas d'`iac/`), et **ne s'ajoute PAS** à la liste `applications` de `postgres` / `tools`. Son `VaultDynamicSecret` pointe simplement le mount/chemin du primaire :
|
||||
|
||||
```yaml
|
||||
# chart du compagnon — vaultdynamicsecret.yaml
|
||||
spec:
|
||||
mount: postgres
|
||||
path: creds/kadans # = le rôle DB du PRIMAIRE
|
||||
vaultAuthRef: kadans # cf. le VaultAuth ci-dessous
|
||||
```
|
||||
|
||||
Le host DB reste **`pgbouncer.tools`**, base = celle du primaire ([étape 4](04-helm-chart.md) « via pgbouncer, jamais en direct »).
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Le piège du VaultAuth manquant.** Le `VaultDynamicSecret` a besoin d'un CR **`VaultAuth`** dans le namespace (VSO le résout par nom, dans le même namespace). Deux cas :
|
||||
>
|
||||
> - **Le primaire consomme déjà Vault** → son chart a déjà posé un `VaultAuth` (nommé `auth` par convention, [étape 4](04-helm-chart.md)). Le compagnon **le référence** (`vaultAuthRef: auth`) et ne crée rien.
|
||||
> - **Le primaire ne consomme PAS Vault** (front statique, aucune base — cas de `kadans`) → **personne** n'a créé de `VaultAuth` dans le namespace. Le compagnon doit alors **poser le sien**, mais pointant le rôle et le SA du **primaire** :
|
||||
>
|
||||
> ```yaml
|
||||
> # chart du compagnon — vaultauth.yaml (cas « primaire sans Vault »)
|
||||
> apiVersion: secrets.hashicorp.com/v1beta1
|
||||
> kind: VaultAuth
|
||||
> metadata:
|
||||
> name: kadans # ou « auth » ; l'important est spec.kubernetes.*
|
||||
> namespace: {{ .Release.Namespace }}
|
||||
> spec:
|
||||
> vaultConnectionRef: default # VaultConnection cluster-wide (VSO)
|
||||
> method: kubernetes
|
||||
> mount: kubernetes
|
||||
> kubernetes:
|
||||
> role: kadans # ← rôle K8s Vault du PRIMAIRE
|
||||
> serviceAccount: kadans # ← SA du PRIMAIRE (créé par SON chart)
|
||||
> audiences: [vault]
|
||||
> ```
|
||||
>
|
||||
> C'est la seule raison pour laquelle le SA et le rôle du VaultAuth ne portent **pas** le nom du service qui le déploie. Ne crée **pas** un second SA `kadans-api` : le rôle K8s Vault `kadans` n'accepte que le SA `kadans`.
|
||||
|
||||
## Carte
|
||||
|
||||
```mermaid
|
||||
%%{init: {'theme': 'base'}}%%
|
||||
flowchart TB
|
||||
classDef prim fill:#059669,stroke:#047857,color:#fff
|
||||
classDef comp fill:#b45309,stroke:#92400e,color:#fff
|
||||
classDef sh fill:#7c3aed,stroke:#6d28d9,color:#fff
|
||||
|
||||
subgraph NS["namespace « kadans »"]
|
||||
SA["ServiceAccount kadans<br>(chart du PRIMAIRE)"]:::sh
|
||||
VA["VaultAuth<br>role kadans · SA kadans"]:::sh
|
||||
PRIM["Deployment kadans<br>(front, sans Vault)"]:::prim
|
||||
COMP["Deployment kadans-api<br>(compagnon, lit la base)"]:::comp
|
||||
end
|
||||
VA -->|"vaultAuthRef"| VDS["VaultDynamicSecret<br>postgres/creds/kadans"]:::sh
|
||||
COMP --> VA
|
||||
VDS --> COMP
|
||||
COMP --> PGB["pgbouncer.tools → base kadans"]:::sh
|
||||
SA -.->|"identité empruntée"| VA
|
||||
```
|
||||
|
||||
## Précédents vivants
|
||||
|
||||
| Compagnon | Primaire | Partage | CRD Vault dans son chart |
|
||||
|---|---|---|---|
|
||||
| [`kadans-jobs`](https://gitea.arcodange.lab/arcodange/kadans-jobs) | `kadans` | namespace seul (sans état) | aucun |
|
||||
| [`kadans-api`](https://gitea.arcodange.lab/arcodange/kadans-api) | `kadans` | namespace + base + Vault | `vaultauth` (le primaire n'a pas de Vault) + `vaultdynamicsecret` |
|
||||
|
||||
## Delta de checklist
|
||||
|
||||
Par rapport à la [checklist standard](08-checklist.md), un compagnon **saute** :
|
||||
|
||||
- ❌ [Étape 2](02-database.md) — pas de nouvelle base ni de rôle propriétaire.
|
||||
- ❌ [Étape 5](05-app-terraform.md) — pas d'`iac/`, pas d'`app_roles`, rien à ajouter aux listes `applications`.
|
||||
|
||||
…et **ajuste** :
|
||||
|
||||
- ✅ [Étape 4](04-helm-chart.md) — `VaultDynamicSecret` pointe `creds/<primaire>` ; poser un `vaultauth.yaml` **seulement** si le primaire ne consomme pas déjà Vault (rôle + SA = ceux du primaire).
|
||||
- ✅ [Étape 7](07-argocd-register.md) — ajouter `namespace: <primaire>` à l'entrée `gitea_applications`.
|
||||
- ✅ Ordre de merge : le fix/chart du compagnon **avant** son enregistrement ArgoCD, pour que la 1ʳᵉ synchro parte d'un chart correct.
|
||||
|
||||
## Related
|
||||
|
||||
- [7. Enregistrement ArgoCD](07-argocd-register.md) — le champ `namespace:` qui place le compagnon dans le namespace du primaire.
|
||||
- [4. Chart Helm](04-helm-chart.md) — la forme des CRD VSO et la connexion via `pgbouncer.tools`.
|
||||
- [Conventions de nommage](conventions.md) — la règle « tout est `<app>` » que ce chapitre nuance pour un compagnon.
|
||||
- [Référence VSO faisant autorité](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/hashicorp-vault/iac/modules/README.md) — VaultConnection/VaultAuth/VaultDynamicSecret côté `tools`.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# Mettre en service une nouvelle application web
|
||||
|
||||
> **Last Updated:** 2026-07-12
|
||||
> **Last Updated:** 2026-07-24
|
||||
> **Status:** ✅ Procédure courante
|
||||
> **Related:** [Conventions de nommage](conventions.md) · [Checklist](08-checklist.md) · [ADR CI/CD](../../adr/03_cicd_gitea_action_argocd.md) · [ADR Vault](../../adr/04_tool_hashicorp_vault.md)
|
||||
|
||||
@@ -92,6 +92,7 @@ Ces fondations existent et ne sont **pas** à refaire pour chaque app :
|
||||
| 06b | [CI des apps Bun/Nuxt](06b-bun-nuxt-ci.md) | `.gitea/workflows/ci.yml` : gates lint/test/`nuxt build` + piège Node 18→20 | ✅ |
|
||||
| 07 | [Enregistrement ArgoCD](07-argocd-register.md) | `factory/argocd/values.yaml` → Application + déploiement | ✅ |
|
||||
| 08 | [Checklist](08-checklist.md) | Récapitulatif ordonné + definition of done | ✅ |
|
||||
| 09 | [Service compagnon](09-service-compagnon.md) | Un service qui partage le namespace + stack Vault/DB d'une app existante (ex. API cœur, façade) | ✅ |
|
||||
|
||||
## Légende de statut
|
||||
|
||||
|
||||
@@ -44,6 +44,9 @@ Les briques se « branchent » entre elles **par convention de nom**, pas par co
|
||||
✅ **Utilise un nom court, stable, kebab-case** dès le départ.
|
||||
❌ **N'introduis pas** de variantes (`my_app` vs `my-app`, `MyApp`, pluriels) : rien ne te préviendra, l'app échouera silencieusement à se connecter ou à se déployer.
|
||||
|
||||
> [!NOTE]
|
||||
> **Exception : les services compagnons.** Un service qui partage le namespace et le stack d'une app existante (ex. une API cœur à côté de son front) **emprunte l'identité du primaire** — sa base, son rôle Vault et son ServiceAccount portent le nom du **primaire**, pas le sien. La règle « tout est `<app>` » ne vaut alors que pour son dépôt, son chart et son image. Voir [9. Service compagnon](09-service-compagnon.md).
|
||||
|
||||
## Plusieurs environnements pour une même app
|
||||
|
||||
Une application peut être déployée plusieurs fois (prod, sandbox, …) **sans devenir une app distincte** : même dépôt, même chart, même version. On ajoute une seconde coordonnée `<env>` au nom, régie par une **règle d'élision** ([ADR-0002](../../../vibe/ADR/0002-per-application-environments.md)) :
|
||||
@@ -76,3 +79,4 @@ Déclaration : `postgres/iac/terraform.tfvars` et la liste `applications` côté
|
||||
- [05 · Terraform de l'app](05-app-terraform.md) — appelle `app_roles` avec `name=<app>`.
|
||||
- [06 · Workflows CI](06-ci-workflows.md) — s'authentifie avec `gitea_cicd_<app>`.
|
||||
- [07 · Enregistrement ArgoCD](07-argocd-register.md) — déclare `<app>` dans `gitea_applications`.
|
||||
- [09 · Service compagnon](09-service-compagnon.md) — l'exception : un compagnon emprunte l'identité de l'app primaire.
|
||||
|
||||
Reference in New Issue
Block a user