Pourquoi des environnements temporaires ?
Chaque dev teste sur sa machine. docker compose up, un script maison, ou pire, lancer les services à la main. Du temps perdu à maintenir un setup local au lieu de bosser sur le produit. Et configurer les bonnes variables d’environnement, connecter l’app aux services dont elle dépend — ça peut faire beaucoup.
L’application tourne toute seule dans son coin. En production, elle parle à d’autres services, une base partagée, un cache, une API. Les bugs d’intégration ne se révèlent pas en local. Ils se révèlent quand on teste avec de vraies données et les autres apps qui tournent à côté.
Le problème, c’est que le staging est souvent unique et partagé. On ne peut pas y déployer sa branche sans écraser celle du collègue. Pire : si quelqu’un déploie une version cassée d’un service, les autres apps qui en dépendent se mettent à échouer. On perd du temps à debugger un bug qui n’est pas le sien, ou on valide des tests faussés sans le savoir. On attend que ce soit fixé, ou on ne teste pas du tout en conditions réelles avant de merger.
1 PR = 1 environnement
L’idée : quand quelqu’un ouvre une Pull Request, un environnement complet se crée automatiquement. App, base de données, URL avec HTTPS. Quand la PR est fermée, tout est supprimé. Personne n’intervient.
Concrètement, la CI build l’image et la pousse dans le registry avec un tag pr-42. ArgoCD poll le dépôt toutes les 60 secondes, détecte la PR, crée un namespace dédié, déploie l’app avec sa propre base PostgreSQL et son propre ingress. Deux minutes plus tard, https://pr-42.myapp.example.com est accessible avec un certificat valide.
L’environnement éphémère déploie la version de la PR, mais les services dont il dépend (autres APIs, bases partagées) pointent vers le staging stable. On teste sa branche en conditions réelles, contre les mêmes services que la prod, sans casser l’environnement des autres.
À chaque push sur la branche, la CI rebuild l’image, le pod redémarre. Le nouveau code est live en quelques secondes. PR mergée ? ArgoCD supprime tout. Namespace, base, ingress, certificats. Rien ne traîne.
Comment ça marche
Le cœur du mécanisme, c’est l’ApplicationSet ArgoCD avec un PR generator. Un seul fichier YAML de 30 lignes qui fait tout le travail :
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: myapp
namespace: argocd
spec:
goTemplate: true
generators:
- pullRequest:
github:
owner: my-org
repo: my-app
tokenRef:
secretName: github-token
key: token
requeueAfterSeconds: 60
template:
metadata:
name: "myapp-pr-{{ .number }}"
spec:
project: default
source:
repoURL: https://github.com/my-org/infra.git
targetRevision: main
path: charts/generic-app
helm:
valuesObject:
image:
repository: registry.example.com/my-app
tag: "pr-{{ .number }}"
ingress:
enabled: true
host: "pr-{{ .number }}.myapp.example.com"
cnpg:
enabled: true
database: myapp
destination:
server: https://kubernetes.default.svc
namespace: "preview-myapp-pr-{{ .number }}"
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
goTemplate: true permet d’injecter le numéro de PR partout. CreateNamespace=true crée le namespace à la volée. prune: true fait le ménage quand la PR disparaît. C’est tout. Pour chaque nouvelle app, on duplique ce fichier et on change 3 valeurs.
Un chart Helm générique
Plutôt que de maintenir un chart par application, j’utilise un chart unique qui couvre 90% des cas. Deployment, Service, Ingress — c’est la base. Ensuite, chaque app active ce dont elle a besoin : un Redis, une base PostgreSQL via CloudNativePG, du MySQL via un autre opérateur. Tout est conditionnel, un flag dans les values et le service est provisionné avec l’environnement.
Le détail qui compte : imagePullPolicy: Always. Les tags pr-42 sont mutables, chaque commit écrase l’image. Sans cette option, Kubernetes utilise le cache et ne tire pas la nouvelle version. C’est le genre de bug qui fait perdre 2 heures à chercher pourquoi “ça marche pas alors que j’ai pushé”.
Côté réseau, Traefik, external-dns et cert-manager travaillent ensemble. On ouvre une PR, on a une URL avec un certificat Let’s Encrypt valide. On ferme la PR, l’URL disparaît. Aucune configuration DNS manuelle.
Une base de données par PR
C’est là que ça devient intéressant. Chaque environnement éphémère a sa propre base PostgreSQL, grâce à CloudNativePG.
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
name: {{ .Release.Name }}-db
spec:
instances: 1
storage:
size: 1Gi
bootstrap:
initdb:
database: {{ .Values.cnpg.database }}
owner: {{ .Values.cnpg.owner }}
Une instance, 1 Go de stockage, bootstrap automatique. Quand ArgoCD supprime le namespace, la base part avec. Coût marginal : quasi nul, ça tourne sur les nœuds du cluster.
Pourquoi pas une base managée ? Parce qu’à 10 PRs ouvertes en parallèle, ça fait 10 instances Managed DB à payer. Avec CNPG, c’est gratuit. Sur un projet avec du PostGIS, j’ai configuré les postInitSQL pour créer les extensions automatiquement. La base est prête à l’emploi dès le déploiement.
Ce que ça change au quotidien
Sur un projet avec 3 apps (backend, frontend, mobile) et une équipe de 8 devs, il y a en permanence 5 à 15 environnements éphémères actifs. Un nouvel environnement se déploie en ~2 minutes. Coût supplémentaire : quasi nul, ça tourne sur les nœuds existants. Interventions ops : zéro depuis la mise en place.
L’équipe prend l’habitude de demander des reviews avec un lien vers un environnement live. Le reviewer clique, teste, valide. Les bugs sont catchés avant le merge. Et plus personne ne se bat pour le staging.
Ça demande un cluster Kubernetes avec ArgoCD, cert-manager, external-dns et CloudNativePG. Une fois en place, c’est autonome. Chaque nouvelle app, c’est un fichier de 30 lignes.
C’est un des premiers éléments que je mets en place quand j’accompagne une équipe sur Kubernetes. Contactez-moi si vous voulez en discuter.