Kubernetes
8 min de lecture

Un chart Helm générique pour 100+ apps

100+ apps, 1 chart, ~30 lignes de values par app. Séparation plateforme/dev, conditionnels par runtime, migrations API sans friction.

Arthur Zinck
Arthur Zinck
Expert DevOps Kubernetes & Cloud

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/v2beta1 dé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 :

  • Deployment avec probes configurables
  • Service avec port par défaut selon le runtime
  • Ingress conditionnel
  • HPA, PDB activables par flag
  • ConfigMap, Secret, truststore (pour les apps Java)
  • ServiceMonitor ou é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_OPTIONS sur Node, PYTHONUNBUFFERED sur Python, OPcache sur PHP
  • Exporter de métriques adapté : sidecar JMX pour Java, /metrics natif en Go ou Node, prometheus_client pour Python, php-fpm_exporter pour PHP
  • Probes par défaut : /actuator/health pour Spring, /health pour Node/Python, /ping pour 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 de runAsUser, 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 :

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. Automatise les conventions avec des helpers. Plutôt que de demander à chaque dev d’écrire containerPort: 8080, un helper base-chart.containerPort déduit la valeur du label language. 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
kubernetes helm chart platform-engineering gitops devops

Partager cet article

Twitter LinkedIn