docs(runbooks): add "new web app" setup runbook under doc/runbooks/

Document, as a tree-docs tree, the end-to-end procedure to stand up a new
web application on the Arcodange platform — a mechanic spread across the
factory, tools and app repos with non-trivial ordering dependencies.

Covers: Gitea repo creation (org-secret inheritance), Postgres DB + owner
role (factory/postgres/iac), platform Vault declaration (gitea_cicd_<app>
+ policies, tools/hashicorp-vault/iac), the app Helm chart (VSO dynamic
secrets via pgbouncer), the app Terraform (app_roles module), the CI
workflows (tofu apply + image build, incl. the copy-pasted role pitfall),
and ArgoCD registration (factory/argocd/values.yaml). Adds a naming-
conventions concept page and an ordered checklist.

Wires the legacy doc/adr "setup hello world web app" item and the factory
README to the runbook. New docs live under doc/ (singular) per the PR #8
convention.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-31 17:22:30 +02:00
parent 54b3092305
commit 8330d82225
14 changed files with 1048 additions and 3 deletions

View File

@@ -0,0 +1,183 @@
[Factory](../../../README.md) > [Doc](../../README.md) > [Runbooks](../README.md) > [Nouvelle application web](README.md) > **4. Chart Helm**
# 4. Le chart Helm de l'application
> **Status:** ✅ Active
> **Upstream:** [5. Terraform de l'app](05-app-terraform.md) (crée les rôles Vault que le chart consomme)
> **Downstream:** [7. Enregistrement ArgoCD](07-argocd-register.md) (déploie ce chart)
> **Related:** [2. Base de données](02-database.md) · [3. Vault plateforme](03-vault-platform.md) · [6. Workflows CI](06-ci-workflows.md) · [Conventions de nommage](conventions.md)
---
## Summary
Le dossier `chart/` est l'**unité de déploiement** : c'est lui qu'ArgoCD applique (`path: chart`). Il décrit le Deployment, le Service, l'Ingress, et surtout le câblage des secrets via le **Vault Secrets Operator (VSO)** : pas un mot de passe en clair, des identifiants Postgres **dynamiques** injectés à l'exécution. On part d'un `helm create <app>` puis on ajoute les CRD Vault, la ConfigMap, et on ajuste l'ingress.
## Structure du chart
```
chart/
├── Chart.yaml # name: <app>, version, appVersion (= tag image par défaut)
├── values.yaml # image, service, ingress, serviceAccount, autoscaling…
└── templates/
├── deployment.yaml # consomme vso-db-credentials + secretkv
├── service.yaml # ClusterIP
├── ingress.yaml # Traefik (voir patterns ci-dessous)
├── serviceaccount.yaml # SA <app> (serviceAccount.create: true)
├── config.yaml # ConfigMap : env non-secrets (host DB = pgbouncer)
├── vaultauth.yaml # VaultAuth : SA <app> ↔ rôle Vault <app>
├── vaultdynamicsecret.yaml # creds Postgres dynamiques (postgres/creds/<app>)
├── vaultsecret.yaml # config statique (kvv2/<app>/config)
├── hpa.yaml # désactivé par défaut
├── _helpers.tpl # name/fullname/labels
└── NOTES.txt
```
> [!TIP]
> Bootstrap : `helm create <app>` génère deployment/service/ingress/serviceaccount/hpa/_helpers/NOTES. Il reste à **ajouter** `config.yaml` (ConfigMap), les 3 CRD Vault (`vaultauth`, `vaultdynamicsecret`, `vaultsecret`), et à **ajuster** `values.yaml` (image, ingress) + `deployment.yaml` (envFrom). Copier ceux d'[`erp/chart`](https://gitea.arcodange.lab/arcodange-org/erp/src/branch/main/chart) ou [`webapp/chart`](https://gitea.arcodange.lab/arcodange-org/webapp/src/branch/main/chart) est le plus rapide.
## Connexion à la base : via pgbouncer, jamais en direct
La ConfigMap pointe **`pgbouncer.tools`** (le pooler dans le namespace `tools`), port 5432, base `<app>`. Pas de host Postgres en dur, pas d'utilisateur statique.
```yaml
# chart/templates/config.yaml — exemple erp
data:
DOLI_DB_TYPE: pgsql
DOLI_DB_HOST: pgbouncer.tools # ← le pooler, pas postgres directement
DOLI_DB_HOST_PORT: !!str 5432
DOLI_DB_NAME: erp # = <app>
```
```yaml
# chart/templates/config.yaml — exemple webapp (chaîne de connexion)
data:
DATABASE_URL: postgres://pgbouncer_auth:pgbouncer_auth@pgbouncer.tools/postgres?sslmode=disable
```
Le **vrai** user/mot de passe vient du Secret K8s `vso-db-credentials` (voir ci-dessous), pas de la ConfigMap.
## Les secrets via VSO (le cœur)
Trois CRD du Vault Secrets Operator (extraits du chart `erp`) :
```yaml
# vaultauth.yaml — authentifie le pod auprès de Vault
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: erp # = rôle K8s Vault <app> (créé à l'étape 5)
serviceAccount: {{ include "erp.serviceAccountName" . }}
audiences: [vault]
```
```yaml
# vaultdynamicsecret.yaml — identifiants Postgres dynamiques
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultDynamicSecret
spec:
mount: postgres
path: creds/erp # = postgres/creds/<app>
destination:
create: true
name: vso-db-credentials # Secret K8s consommé par le Deployment
rolloutRestartTargets:
- kind: Deployment
name: {{ include "erp.fullname" . }} # redémarre le pod à chaque rotation
vaultAuthRef: auth
```
```yaml
# vaultsecret.yaml — config statique (mots de passe admin initiaux, etc.)
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
spec:
type: kv-v2
mount: kvv2
path: erp/config # = kvv2/<app>/config (rempli à l'étape 5)
destination:
name: secretkv
create: true
refreshAfter: 24h
vaultAuthRef: auth
```
Le Deployment consomme ensuite `vso-db-credentials` (clés `username`/`password`) et `secretkv` (via `envFrom`/`secretKeyRef`).
### Flux runtime
```mermaid
%%{init: {'theme': 'base'}}%%
flowchart LR
classDef pod fill:#b45309,stroke:#92400e,color:#fff
classDef vault fill:#7c3aed,stroke:#6d28d9,color:#fff
classDef db fill:#2563eb,stroke:#1e40af,color:#fff
SA["Pod · SA app"]:::pod
VA["VaultAuth<br>role app (k8s)"]:::vault
VDS["VaultDynamicSecret<br>postgres/creds/app"]:::vault
VSS["VaultStaticSecret<br>kvv2/app/config"]:::vault
SEC["Secret K8s<br>vso-db-credentials + secretkv"]:::pod
PGB["pgbouncer.tools:5432"]:::db
DB["base app<br>(user dynamique → app_role)"]:::db
SA --> VA
VA --> VDS
VA --> VSS
VDS --> SEC
VSS --> SEC
SEC --> SA
SA --> PGB --> DB
```
> [!IMPORTANT]
> `serviceAccount.create` doit valoir `true` dans `values.yaml` : c'est ce SA `<app>` que `VaultAuth` lie au rôle Vault `<app>`. Sans lui, pas d'authentification, donc pas de creds DB.
## Ingress : interne `.lab` vs public `.fr`
Deux patterns selon l'exposition voulue (annotations Traefik dans `values.yaml`) :
| | Interne (`.lab`) — ex. `erp` | Public (`.fr`) — ex. `webapp` |
|---|---|---|
| `entrypoints` | `websecure` | `web` |
| TLS | `router.tls: "true"` + `certresolver: letsencrypt` | (terminé en amont) |
| Middleware | `localIp@file` (restreint au LAN) | `kube-system-crowdsec@kubernetescrd` (WAF) |
| `nodeSelector` | — | `kubernetes.io/hostname: pi1` (garde l'IP source, point d'entrée réseau) |
| Hôte | `<app>.arcodange.lab` | `<app>.arcodange.fr` |
```yaml
# values.yaml — ingress interne .lab (extrait erp)
ingress:
enabled: true
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
traefik.ingress.kubernetes.io/router.middlewares: localIp@file
hosts:
- host: erp.arcodange.lab
paths: [{ path: /, pathType: Prefix }]
```
## Image
| Cas | `image.repository` | Exemple |
|---|---|---|
| Image **maison** (build dans le dépôt) | `gitea.arcodange.lab/arcodange-org/<app>` | `webapp` (cf. [étape 6](06-ci-workflows.md)) |
| Image **publique** | l'image upstream | `erp``dolibarr/dolibarr` |
## Notes / contraintes
- Si l'app stocke des fichiers, ajouter un `pvc.yaml` (`storageClassName: longhorn`, `accessModes: [ReadWriteMany]`, annotation `helm.sh/resource-policy: keep` pour survivre à un `helm uninstall`) — cf. `erp/chart/templates/pvc.yaml`.
- Les CRD VSO supposent que le VSO tourne dans le cluster (déployé par `tools`) et que le rôle K8s `<app>` + le rôle `creds/<app>` existent (étape 5).
## Related
- [5. Terraform de l'app](05-app-terraform.md) — crée `postgres/creds/<app>`, le rôle K8s `<app>` et remplit `kvv2/<app>/config` que ces CRD consomment.
- [2. Base de données](02-database.md) — la base `<app>` et `pgbouncer.tools` ciblés ici.
- [3. Vault plateforme](03-vault-platform.md) — la policy runtime `<app>` qu'utilise `VaultAuth`.
- [6. Workflows CI](06-ci-workflows.md) — construit l'image référencée par `image.repository`.
- [Référence VSO/secrets faisant autorité](https://gitea.arcodange.lab/arcodange-org/tools/src/branch/main/hashicorp-vault/iac/modules/README.md) — explication détaillée VaultConnection/VaultAuth/VaultDynamicSecret côté `tools` (à ne pas dupliquer ici).