docs(runbook) — chapitre « service compagnon » (namespace + stack partagés) #42
@@ -39,6 +39,7 @@ Options supplémentaires :
|
|||||||
| Champ | Quand l'utiliser | Effet |
|
| Champ | Quand l'utiliser | Effet |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `org: arcodange` | dépôt hors `arcodange-org` | change le `repoURL` (défaut `arcodange-org`) |
|
| `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}`) |
|
| `syncPolicy: …` | contrôle manuel | surcharge la policy (défaut : `automated {prune, selfHeal}`) |
|
||||||
|
|
||||||
## Ce que ça génère
|
## Ce que ça génère
|
||||||
@@ -79,7 +80,7 @@ flowchart LR
|
|||||||
## Notes / contraintes
|
## Notes / contraintes
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!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.
|
- 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`.
|
- `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/`).
|
- [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.
|
- [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`.
|
- [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
|
# Mettre en service une nouvelle application web
|
||||||
|
|
||||||
> **Last Updated:** 2026-07-12
|
> **Last Updated:** 2026-07-24
|
||||||
> **Status:** ✅ Procédure courante
|
> **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)
|
> **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 | ✅ |
|
| 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 | ✅ |
|
| 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 | ✅ |
|
| 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
|
## 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.
|
✅ **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.
|
❌ **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
|
## 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)) :
|
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>`.
|
- [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>`.
|
- [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`.
|
- [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