[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 ` puis on ajoute les CRD Vault, la ConfigMap, et on ajuste l'ingress. ## Structure du chart ``` chart/ ├── Chart.yaml # name: , 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 (serviceAccount.create: true) ├── config.yaml # ConfigMap : env non-secrets (host DB = pgbouncer) ├── vaultauth.yaml # VaultAuth : SA ↔ rôle Vault ├── vaultdynamicsecret.yaml # creds Postgres dynamiques (postgres/creds/) ├── vaultsecret.yaml # config statique (kvv2//config) ├── hpa.yaml # désactivé par défaut ├── _helpers.tpl # name/fullname/labels └── NOTES.txt ``` > [!TIP] > Bootstrap : `helm create ` 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 ``. 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 # = ``` ```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 (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/ 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//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
role ‹app› (k8s)"]:::vault VDS["VaultDynamicSecret
postgres/creds/‹app›"]:::vault VSS["VaultStaticSecret
kvv2/‹app›/config"]:::vault SEC["Secret K8s
vso-db-credentials + secretkv"]:::pod PGB["pgbouncer.tools:5432"]:::db DB["base ‹app›
(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 `` que `VaultAuth` lie au rôle Vault ``. 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 | `.arcodange.lab` | `.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/` | `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 `` + le rôle `creds/` existent (étape 5). ## Related - [5. Terraform de l'app](05-app-terraform.md) — crée `postgres/creds/`, le rôle K8s `` et remplit `kvv2//config` que ces CRD consomment. - [2. Base de données](02-database.md) — la base `` et `pgbouncer.tools` ciblés ici. - [3. Vault plateforme](03-vault-platform.md) — la policy runtime `` 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).