Tutorial · Java → K8s translator

Kubernetes pour développeur Java

Tout ce vocabulaire K8s qui floute (Pod, Deployment, Helm, ArgoCD, Ingress, PVC, ConfigMap...) traduit en concepts familiers à un dev Spring Boot. Écrit en bossant moi-même la migration AetherWX vers k3s en mai 2026 — 21 ans de Java derrière, mais novice K8s. Si t'as l'expérience JEE/Spring/Maven, t'as déjà 80% du modèle mental, juste un vocabulaire différent.

Pourquoi cette page existe

Je suis dev Java/Web depuis 2005. J'ai migré mon application maritime (AetherWX, ~20 microservices Spring-Boot + Python + nginx) d'un setup docker-compose mono-serveur vers un cluster Kubernetes en 14 heures. Le code applicatif lui-même n'a quasi pas changé. Ce qui a changé c'est comment les services sont déclarés, déployés, scalés, networkés, monitorés.

Pendant ce sprint j'ai pris des notes mentales du genre "ah ok ce truc c'est en fait juste l'équivalent Spring de X". Je partage cette carte de traduction ici — peut-être qu'elle aidera un autre dev Java qui débute K8s.

1. Le mental model d'ensemble

En Java/Spring, ton runtime c'est une JVM (souvent dans un .jar / .war) qui tourne sur un serveur (Tomcat, bare metal, container Docker). En K8s, ton runtime c'est un Pod (1 ou plusieurs containers Docker) qui tourne sur un Node (machine physique ou VM).

En Java, t'as un conteneur de servlets (Tomcat, Jetty) qui gère le cycle de vie, et un conteneur d'IOC (Spring) qui gère les beans et leurs dépendances. En K8s, c'est kubelet qui gère le cycle de vie des pods, et kube-controller-manager qui gère les ressources et leurs relations (Deployments, Services, Replicas, etc.).

En Java, t'as un application.yml qui configure le runtime via profils Spring (dev / preprod / prod). En K8s, t'as des ConfigMaps et Secrets qui sont injectés dans le Pod via variables d'environnement ou fichiers montés.

Et surtout : en Java/Spring tout est impératif ("démarre cette appli avec ces paramètres"). En K8s tout est déclaratif ("voici l'état désiré, démerde-toi pour y arriver et y rester"). C'est un changement de paradigme plus fort que le passage à Spring depuis JEE manuel.

2. Le tableau de traduction

Concept K8s ≈ Java/Spring Ce qu'il fait vraiment
Pod JVM process Unité d'exécution minimale (1+ containers Docker partageant network & storage)
Container .jar exécutable L'image Docker = ton binaire packagé avec ses deps
Deployment @Service + replication Déclare "je veux N pods de ce container", K8s les maintient
ReplicaSet JEE cluster manager Géré auto par Deployment, garantit N pods identiques running
Service DNS + load-balancer Spring Cloud Adresse stable interne (ClusterIP) qui distribue les requêtes sur les N pods
Ingress nginx / Apache reverse-proxy Reverse-proxy déclaratif : "host X.example.com → Service Y, path /api → Service Z"
ConfigMap application-prod.yml Config non-sensible injectée dans les pods en env vars ou fichiers
Secret .env / Vault / @PropertySource encrypted Pareil que ConfigMap mais base64-encoded + RBAC restreint
PersistentVolumeClaim (PVC) DataSource JDBC Demande de stockage persistant ; K8s alloue un PV qui survit aux restarts pod
StorageClass JDBC URL pool + driver "Comment provisionner le stockage" (local-path RWO, Longhorn RWX, NFS, AWS EBS, etc.)
Namespace Java package + profile Isolation logique : on met "maritime", "preprod", "argocd" séparés pour gérer accès et quotas
CRD (Custom Resource) @Entity + Spring repository Tu déclares ton propre type de ressource (ex: RabbitmqCluster) avec son operator
Operator @PostConstruct + scheduled reconciler Daemon qui watch les CRD et fait du "réconciliation continue" : "l'état réel correspond-il au YAML ?"
Job / CronJob Quartz Scheduler Une exécution one-shot (Job) ou récurrente (CronJob) — utile pour batches, migrations DB
StatefulSet Cluster JEE stateful (replica DB) Comme Deployment mais avec identité stable (pod-0, pod-1) et stockage par pod (pour DB, Kafka, etc.)
ServiceAccount + RBAC Spring Security + @PreAuthorize Identité d'un pod + ce qu'il a le droit de faire dans le cluster (lire pods, écrire configs, etc.)

3. L'outillage : Helm, ArgoCD, kubectl

Si t'as fait du Maven / Gradle, tu sais que : pom.xml déclare ton application. mvn package build le .jar. Un repository (Nexus, JFrog, Maven Central) stocke les artifacts.

En K8s c'est strictement la même architecture, juste avec d'autres noms :

  • Helm chart = pom.xml. Un dossier templates/*.yaml avec des placeholders Go template, plus un values.yaml = ton application-prod.yml.
  • helm install / upgrade = mvn deploy. Rend les templates avec les values et applique le YAML résultat au cluster.
  • Helm repo = Maven Central. Tu peux pull des charts pré-fait (CNPG pour PostgreSQL, cert-manager, ingress-nginx, etc.).
  • kubectl = la CLI que tu utilises pour interroger ou modifier le cluster (lister pods, exec dans un pod, voir les logs, etc.). C'est jconsole + jstack + jvisualvm mais pour K8s.
  • ArgoCD = ton CI/CD continu. Tu commits ton chart dans Git, ArgoCD watch ton repo, dès qu'il détecte une modif il "synchronise" l'état du cluster avec ce qui est dans Git. C'est exactement le GitOps : "Git est la source de vérité, le cluster doit y converger". Equivalent Java : un Jenkins ou GitHub Actions qui ferait helm upgrade à chaque push.

4. Comment ça communique (Service mesh light)

En Spring/JEE pour qu'un service appelle un autre tu fais @Autowired ou RestTemplate.getForObject("http://internal-host/api", ...). L'URL "internal-host" est résolue par ton DNS d'entreprise.

En K8s, le DNS est natif au cluster. Chaque Service que tu déclares prend automatiquement un nom DNS : <service-name>.<namespace>.svc.cluster.local. Donc depuis un pod du namespace "maritime", appeler http://api:3010/health résout vers le Service "api" du même namespace, qui load-balance sur les pods derrière.

Pour exposer un service vers l'extérieur, t'as une Ingress : une règle déclarative qui dit "host aetherwx.sladoire.dev → Service maritime-frontend port 80". Le contrôleur Ingress (généralement nginx en backstage) applique la règle et fait le reverse-proxy. Conceptuellement identique à un nginx avec des location / blocks, mais géré par K8s.

5. Le piège du débutant : YAML hell vs nettoyage Spring

En Spring tu as @SpringBootApplication qui scanne ton classpath et configure auto. En K8s, il n'y a pas d'auto-config. Tu dois TOUT déclarer explicitement : le Deployment, le Service, l'Ingress, la PVC, les env vars, les liveness probes, les resources limits...

D'où Helm : tu écris la verbosité une fois dans un chart templated, puis tu réutilises avec des values différents. Quand t'as 14 services comme AetherWX, sans Helm tu te retrouves avec ~140 fichiers YAML quasi-identiques. Avec Helm c'est 9 templates + 1 values.yaml qui rend les 139 fichiers à l'install.

Apprends Helm vite. C'est ce qui transforme K8s de "verbeux et pénible" à "maintenable et productif".

6. Le storage : oublie le filesystem du serveur

En Java traditionnel, ton appli écrit dans /var/lib/myapp/data/ et tu pries pour que ce répertoire survive aux redémarrages. En Docker tu utilises des volumes nommés (myapp-data:/data). En K8s c'est plus formel : PVC + PV + StorageClass.

  • PVC (PersistentVolumeClaim) : ton pod demande "je veux 5 GiB en RWO" (ReadWriteOnce) ou "1 GiB en RWX" (ReadWriteMany).
  • PV (PersistentVolume) : K8s alloue un volume réel quelque part (sur le node, ou dans le cloud) qui satisfait la PVC.
  • StorageClass : la "recette" pour provisionner le PV. Ex : local-path = un répertoire sur le node (rapide, mais RWO seulement). longhorn = block storage distribué K8s-natif (RWX OK). nfs-client = un share NFS partagé.
  • RWO vs RWX : si plusieurs pods doivent lire le même volume EN MÊME TEMPS, il faut RWX. Sinon RWO suffit.

Sur AetherWX, weather-fetcher écrit des GeoTIFF dans /coverage/, GeoServer les lit pour faire du WMS. Donc 2 pods → 1 volume partagé → RWX obligatoire. k3s par défaut ne fait que RWO, d'où l'install Longhorn pour avoir le RWX.

7. Le workflow quotidien post-setup

Une fois le cluster bootstrappé (~1h) et le chart Helm écrit (~30 min par service complexe), le workflow devient extrêmement agréable :

  1. Tu modifies du code Java/TypeScript/Python comme d'habitude
  2. git commit + git push main
  3. GitHub Actions build l'image Docker, push sur GHCR (~2-5 min)
  4. Tu bumps le tag dans values.yaml + commit + push
  5. ArgoCD détecte le nouveau commit dans le repo gitops
  6. ArgoCD apply le chart → K8s tue les anciens pods + crée les nouveaux avec la nouvelle image
  7. Tes utilisateurs voient zéro downtime (rolling update)

Tu n'as plus jamais à ssh sur un serveur, copier un .jar, faire un systemctl restart. Tout vit dans Git.

8. Les 5 pièges que j'aurais voulu connaître

  1. Liveness probe trop agressive = pod en crashloop avant même d'avoir fini son boot. Sur AetherWX, GeoServer met 90s pour démarrer Tomcat ; sans startupProbe avec failureThreshold: 60, K8s le tue à 30s en plein boot.
  2. Resource limits trop bas = OOMKilled surprises. Une JVM Java sans -Xmx alignée avec le limit K8s OOMKill bizarrement. Toujours setter -Xmx=80% du limit.
  3. imagePullPolicy par défaut = IfNotPresent : si tu rebuilds une image avec le MÊME tag (genre :latest), K8s utilisera la version cached. Toujours bumper le tag ( :v1.2.3 ou :sha-abc1234) pour forcer le pull.
  4. ArgoCD selfHeal=true réverte tes kubectl set image manuels. C'est conçu pour ça : si Git est la source de vérité, les modifs out-of-band sont du drift à corriger. Toujours passer par Git.
  5. RWX volumes en home-lab : la storage class par défaut de k3s ne supporte que RWO. Si tu as besoin de partager un volume entre pods (souvent le cas), installe Longhorn ou un NFS provisioner. Sinon tu galères avec des hostPath en single-node (anti-pattern accepté seulement en home-lab).

Conclusion · Tu peux y arriver

Si tu as bossé Spring Boot pendant plusieurs années, tu connais déjà les bonnes pratiques : découplage, inversion de dépendance, configuration externalisée, health checks, graceful shutdown, retry/circuit breaker. K8s formalise toutes ces pratiques au niveau infrastructure. C'est une consécration plus qu'une rupture.

Le secret c'est d'apprendre dans cet ordre : 1) kubectl + pods + deployments → comprendre ce qu'est un cluster vivant. 2) Helm → industrialiser les manifests. 3) ArgoCD → automatiser le déploiement. 4) CNPG / Operators → comprendre le reconciliation pattern. À chaque étape une analogie Spring existe.

Pour AetherWX j'ai fait ce parcours en mode pair-programming avec Claude Code (un assistant IA chez Anthropic) qui m'a fait gagner des dizaines d'heures sur le YAML/glue plomberie, en me laissant prendre toutes les décisions d'archi. Si tu débutes K8s en 2026 et que tu as déjà l'expérience Java, je recommande chaudement la même approche.

Pour voir ces concepts appliqués sur un projet réel : l'encyclopédie AetherWX détaille l'archi dual-cluster, les 17 services, les 6 tokens, le flux GitOps complet, et les 5 leçons qui ont coûté 6 heures de debug. 4 diagrammes Mermaid. Lecture recommandée après cette page.