Files
factory/doc/runbooks/new-web-app/04-helm-chart.md
Gabriel Radureau 8330d82225 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>
2026-05-31 17:22:30 +02:00

184 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[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).