diff --git a/doc/runbooks/new-web-app/07-argocd-register.md b/doc/runbooks/new-web-app/07-argocd-register.md index 9f54536..27e118d 100644 --- a/doc/runbooks/new-web-app/07-argocd-register.md +++ b/doc/runbooks/new-web-app/07-argocd-register.md @@ -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: ` | **service compagnon** partageant le namespace d'une app existante | déploie hors du namespace `` (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: ` 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`. diff --git a/doc/runbooks/new-web-app/09-service-compagnon.md b/doc/runbooks/new-web-app/09-service-compagnon.md new file mode 100644 index 0000000..0092b6d --- /dev/null +++ b/doc/runbooks/new-web-app/09-service-compagnon.md @@ -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 ``. 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é `` » ([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
(chart du PRIMAIRE)"]:::sh + VA["VaultAuth
role kadans · SA kadans"]:::sh + PRIM["Deployment kadans
(front, sans Vault)"]:::prim + COMP["Deployment kadans-api
(compagnon, lit la base)"]:::comp + end + VA -->|"vaultAuthRef"| VDS["VaultDynamicSecret
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/` ; 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: ` à 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 `` » 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`. diff --git a/doc/runbooks/new-web-app/README.md b/doc/runbooks/new-web-app/README.md index 5ca9196..339d961 100644 --- a/doc/runbooks/new-web-app/README.md +++ b/doc/runbooks/new-web-app/README.md @@ -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 diff --git a/doc/runbooks/new-web-app/conventions.md b/doc/runbooks/new-web-app/conventions.md index 8788c2e..3911bf3 100644 --- a/doc/runbooks/new-web-app/conventions.md +++ b/doc/runbooks/new-web-app/conventions.md @@ -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 `` » 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 `` 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=`. - [06 · Workflows CI](06-ci-workflows.md) — s'authentifie avec `gitea_cicd_`. - [07 · Enregistrement ArgoCD](07-argocd-register.md) — déclare `` dans `gitea_applications`. +- [09 · Service compagnon](09-service-compagnon.md) — l'exception : un compagnon emprunte l'identité de l'app primaire.