100+ apps. 1 seul chart Helm. ~30 lignes de values par application.
C’est la configuration que je mets en place chez chacun de mes clients. Chaque app se décline ensuite en plusieurs environnements. En pratique, c’est surtout une équipe plateforme qui bosse tranquille pendant que des dizaines de devs shippent sans jamais toucher à du YAML Kubernetes.
Chaque équipe choisit sa méthode
Sans cadre, chaque équipe de dev déploie à sa sauce. Une équipe pousse du YAML brut. Une autre fait du Helm. Une troisième monte un playbook Ansible. Une quatrième fait du Kustomize. C’est la jungle.
Résultat six mois plus tard :
- Aucune manière commune de gérer les secrets
- Des apps encore sur
autoscaling/v2beta1déprécié - Personne ne sait quelle version de l’ingress est censée être utilisée
- À chaque migration Kubernetes, l’équipe plateforme ouvre 60 PR, chacune dans un format différent
En tant qu’équipe plateforme, on veut garder la main. Cadrer ce qui est fait, et surtout ce qui est faisable. Les devs écrivent du YAML parce qu’ils doivent bien déployer, mais ce n’est ni leur métier ni leur envie.
Un chart unique, des values courtes
Le chart générique couvre les briques dont 90% des apps ont besoin :
Deploymentavec probes configurablesServiceavec port par défaut selon le runtimeIngressconditionnelHPA,PDBactivables par flagConfigMap,Secret, truststore (pour les apps Java)ServiceMonitorou équivalent observabilité- SecurityContext durci par défaut
Côté app, un fichier values.yaml minimaliste suffit. Exemple réel :
image:
repository: myorg/authentication
tag: 1.2.0
labels:
language: java
team: tech
resources:
limits:
memory: 1Gi
cpu: 700m
requests:
memory: 1Gi
cpu: 700m
secrets:
enabled: true
truststore:
enabled: true
configMap:
enabled: true
content:
PUBLIC_KEY_PATH: /etc/truststore/authentication_pub.pem
JAVA_OPTS: -Xms128m -Xmx128m
31 lignes. C’est tout ce qu’un dev a besoin d’écrire pour déployer une nouvelle app.
Conditionnels par runtime
Le label language pilote des comportements par défaut. Un helper choisit le port du container selon la stack :
{{- define "base-chart.containerPort" -}}
{{- if .Values.service.containerPort }}
{{- .Values.service.containerPort }}
{{- else if eq .Values.labels.language "java" }}
{{- printf "8080" }}
{{- else if eq .Values.labels.language "scala" }}
{{- printf "9000" }}
{{- else if eq .Values.labels.language "node" }}
{{- printf "3000" }}
{{- else if eq .Values.labels.language "python" }}
{{- printf "8000" }}
{{- else if eq .Values.labels.language "php" }}
{{- printf "9100" }}
{{- end }}
{{- end }}
Même logique pour plein d’autres trucs :
- Truststore monté uniquement sur Java
- JVM options sur Java,
NODE_OPTIONSsur Node,PYTHONUNBUFFEREDsur Python, OPcache sur PHP - Exporter de métriques adapté : sidecar JMX pour Java,
/metricsnatif en Go ou Node,prometheus_clientpour Python,php-fpm_exporterpour PHP - Probes par défaut :
/actuator/healthpour Spring,/healthpour Node/Python,/pingpour PHP, etc. - Resources requests/limits par défaut calibrés sur le runtime (Java a besoin de plus de mémoire baseline que Go)
- Annotations Prometheus / ServiceMonitor avec les bons paths et ports
Le dev renseigne language: node et hérite de conventions saines sans lire la doc du chart.
Qui fait quoi
C’est là que le pattern paye. La responsabilité est claire :
L’équipe plateforme maintient le chart et l’IaC. Elle fait les migrations d’API Kubernetes (autoscaling/v2beta1 → v2, extensions → networking.k8s.io/v1), durcit la sécu, ajoute du support pour les nouvelles briques d’observabilité, impose des standards. Une PR sur le chart, toutes les apps migrent. Pas besoin d’aller convaincre chaque squad de prioriser un changement d’infra dans leur sprint — la plateforme l’applique elle-même.
Les devs maintiennent 30 lignes de values. Ils ne savent même pas qu’une migration k8s est en cours. Ils n’ont pas à savoir. Leur métier, c’est le code applicatif, pas la conformité k8s.
Impact business
Concrètement :
- Ratio plateforme/dev tenable : j’ai vu une équipe de 5-6 platform engineers supporter 250+ devs avec ce pattern — et le chart n’était qu’une petite partie de leur job, une fois posé c’est une modif par mois, pas un full-time. Sans chart générique, le chart mangerait tout leur temps. Et de toute façon, maintenir 60 charts à la main c’est ingérable pour une équipe infra — un chart Helm est fait pour être un template, l’utiliser comme tel est juste la bonne manière de s’en servir.
- Migrations k8s invisibles pour les devs : une migration d’API, c’est plusieurs heures de travail par chart. Avec 60 charts séparés, c’est un trimestre. Avec 1 chart générique, c’est une après-midi. L’équipe infra update, teste, roll out sur tout le parc.
- Onboarding dev accéléré : 10 minutes entre “je commence” et “mon app tourne dans un environnement”. Sans apprendre à faire du Go template.
- Sécurité propagée : un durcissement ajouté au chart (par exemple
readOnlyRootFilesystem: true, changement derunAsUser, ou drop de capabilities Linux) s’applique partout au prochain déploiement. - Cohérence : labels, observabilité, conventions de nommage — tout est uniforme par construction.
Quand ça casse
Le chart générique n’est pas une solution universelle. Les cas où je maintiens un chart dédié :
- StatefulSets complexes : base de données, Kafka, Zookeeper. Trop spécifiques pour un moule commun.
- Batch jobs avec logique custom : CronJobs avec dépendances, argo workflows, jobs de migration one-shot.
- Apps legacy avec contraintes de packaging particulières (sidecars propriétaires, init containers complexes).
La règle : le chart générique couvre les services stateless. Tout le reste, on traite au cas par cas.
Les contreparties
Le pattern n’est pas gratuit :
- Blast radius : un bug dans le chart casse toutes les apps au prochain déploiement. Un changement mal testé = incident multi-équipes. Il faut une CI sérieuse sur le chart lui-même (tests Helm, déploiement sur un env de référence avant promotion) et une discipline de release (bumper le chart dans une PR, pas en douce).
- Sortir du moule fait mal : une app avec un besoin exotique (un port custom, une annotation spécifique) doit soit convaincre l’équipe plateforme d’ajouter un flag au chart, soit quitter le chart générique. Les deux options ont un coût. D’où l’importance de rester strict sur le périmètre : dès qu’une app dévie vraiment, on la sort plutôt que de polluer le chart générique.
- Couplage implicite : les devs dépendent de l’équipe plateforme pour évoluer. Si la plateforme est lente à livrer un flag demandé, la friction monte. Le contrat doit être clair et les délais tenus.
Comment y arriver
Que tu partes de zéro ou que t’aies déjà 20 charts qui traînent, la démarche est la même :
- Cartographie. Si tu pars de zéro, liste tes apps stateless. Si t’as déjà un parc, regroupe les charts existants par pattern (Java stateless, Node API, worker async). Dans les deux cas, 80% des apps partagent la même structure.
- Commence par le groupe le plus homogène. Écris un chart qui couvre leur cas commun — Deployment + Service + Ingress et basta. Le reste arrivera par flags, désactivés par défaut : HPA, PDB, ServiceMonitor, truststore, etc. Une app n’active que ce dont elle a besoin.
- Migre 2-3 apps pilotes, puis les autres. Chaque app absorbée révèle un cas manquant, tu l’ajoutes au chart. Pas de big bang : tu peux très bien avoir 40 charts dédiés et 20 apps sur le générique pendant des mois. L’objectif, c’est d’inverser le ratio, pas de tout casser en une semaine.
- Fixe le contrat d’ownership dès le départ : la plateforme owne le chart, les devs ownent les values. Sans ce cadre, les devs continuent à patcher le chart à la demande et tu perds le bénéfice.
- Automatise les conventions avec des helpers. Plutôt que de demander à chaque dev d’écrire
containerPort: 8080, un helperbase-chart.containerPortdéduit la valeur du labellanguage. Même logique pour les labels communs, les probes par défaut, les annotations d’observabilité. Les devs renseignent l’intention (language: node), le chart fait le reste.
Le chart générique est l’un des premiers patterns que je mets en place quand j’accompagne une équipe sur Kubernetes. C’est le cas chez Rakuten France sur leur plateforme e-commerce et chez ValueXchange pour leur infra événementielle. Il démultiplie l’effet d’un platform engineer et rend les migrations k8s indolores. C’est aussi la fondation pour aller plus loin : des environnements éphémères par PR deviennent triviaux une fois que toutes les apps partagent le même chart.
FAQ
Chart générique vs library chart Helm, c’est quoi la différence ?
Un library chart expose des templates réutilisables par d’autres charts — chaque app maintient son propre chart qui importe la library. Un chart générique est un chart complet et unique : les apps n’ont que des values, pas de chart à elles. Library chart = plus de flexibilité, plus d’effort côté dev. Chart générique = plus de cadre, moins d’effort. Pour mutualiser des dizaines d’apps stateless dans un contexte plateforme, le chart générique est plus direct et plus contraignant — c’est ce qu’on veut.
Comment gérer une app qui sort du moule ?
Deux réflexes : vérifier si le besoin est généralisable (auquel cas on ajoute un flag conditionnel au chart, désactivé par défaut), ou accepter qu’une app sorte du chart générique si son besoin est vraiment spécifique (StatefulSet complexe, sidecar custom, logique de déploiement particulière). Règle d’or : ne jamais polluer le chart générique pour un cas unique. Mieux vaut un chart dédié pour 2-3 apps exotiques qu’un chart générique de 3000 lignes truffé de branches.
Quel ratio platform engineer / dev viser ?
Avec un chart générique en place, j’ai vu une équipe de 5-6 platform engineers supporter 250+ devs — et le chart n’était qu’une petite partie de leur job. Un bon repère : 1 platform engineer pour ~50 devs, à condition que le pattern soit bien posé et que la plateforme owne le chart et l’IaC. Sans chart générique, le ratio s’effondre : chaque migration k8s devient un goulot d’étranglement.
Quelle CI mettre en place pour un chart partagé ?
Le chart partagé a un blast radius énorme — il faut une CI solide. helm lint pour la base. Pour la régression, j’ai vu deux approches qui marchent bien :
helm unittest: on décrit les assertions attendues (présence d’un label, valeur d’une env var, activation conditionnelle d’un HPA) dans des fichiers YAML.- Golden testing avec Terratest : on commit le rendu complet du chart pour un jeu de values représentatif (
*.golden.yaml), et chaque PR compare le nouveau rendu au golden. Toute diff fait échouer la CI. Le dev relit le diff dans la PR pour valider si c’est intentionnel, puis régénère avec-update. C’est le pattern que j’ai mis en place sur des charts complexes, il attrape absolument tout — plus robuste que du testing par assertions quand les conditionnels deviennent nombreux.
Ensuite, chaque version du chart est taguée, les apps épinglent une version, et le rollout se fait progressivement par environnement (dev → staging → prod).
Tu as 20 apps et chacune a son chart ? On peut en parler. Audit GitOps gratuit 30 min — je regarde ton catalogue de charts et je t’indique quoi factoriser.
Points clés à retenir
- ✓ Un chart unique couvre 90% des cas : Deployment, Service, Ingress, HPA, PDB, ServiceMonitor
- ✓ Les conditionnels par runtime (Java, Node, Scala, Python, PHP, etc.) évitent la duplication sans sacrifier la flexibilité
- ✓ La plateforme garde la main sur les migrations k8s — les devs ne sont jamais interrompus
- ✓ Un platform engineer supporte facilement 50+ devs avec ce pattern
- ✓ À éviter pour StatefulSets complexes, batch jobs, apps legacy