Files
arcodangeandClaude Opus 4.8 51d01f47c2 docs(runbook) — corrige 09 : NE PAS écrire vaultConnectionRef dans un ns applicatif
Le chapitre « service compagnon » montrait `vaultConnectionRef: default` dans
l'exemple VaultAuth — c'est faux hors du namespace `tools` et ça a réellement bloqué
le déploiement de kadans-api (pods en CreateContainerConfigError, VaultDynamicSecret
sur « VaultConnection default not found »).

VSO résout vaultConnectionRef dans le namespace DU CR ; la VaultConnection `default`
ne vit que dans `tools`. Les apps hors `tools` (erp, webapp) OMETTENT le champ et
laissent VSO retomber sur sa defaultVaultConnection. On retire donc la ligne de
l'exemple + on ajoute un encart WARNING dédié au piège.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Claude-Session: https://claude.ai/code/session_013ws8L74dVZmp97Wu36fm8j
2026-07-24 11:29:17 +02:00

8.7 KiB

Factory > Doc > Runbooks > Nouvelle application web > 9. Service compagnon

9. Service compagnon (namespace partagé)

Status: Active Upstream: 7. Enregistrement ArgoCD (le champ namespace: utilisé ici) Related: Conventions de nommage · 4. Chart Helm · 2. Base de données · Checklist


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) 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 : la clé namespace:.

# 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 (pas de nouvelle base), PAS l'étape 5 (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 :

# 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 « 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). 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 :
# 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:
  # PAS de vaultConnectionRef → VSO retombe sur sa connexion globale (comme erp/webapp).
  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.

Warning

N'écris PAS vaultConnectionRef: default dans un namespace applicatif. VSO résout vaultConnectionRef dans le namespace du CR — or la VaultConnection default n'existe que dans le namespace tools. La nommer explicitement ailleurs fait chercher <ns>/default (inexistant) : le VaultDynamicSecret reste bloqué sur VaultConnection "default" not found, le Secret n'est jamais matérialisé, et le pod tourne en CreateContainerConfigError. Les apps hors tools (erp, webapp) omettent ce champ et laissent VSO utiliser sa defaultVaultConnection. (crowdsec/plausible peuvent l'écrire car ils vivent dans tools.)

Carte

%%{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 kadans namespace seul (sans état) aucun
kadans-api kadans namespace + base + Vault vaultauth (le primaire n'a pas de Vault) + vaultdynamicsecret

Delta de checklist

Par rapport à la checklist standard, un compagnon saute :

  • Étape 2 — pas de nouvelle base ni de rôle propriétaire.
  • Étape 5 — pas d'iac/, pas d'app_roles, rien à ajouter aux listes applications.

…et ajuste :

  • Étape 4VaultDynamicSecret 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 — 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.