Construire son propre controller Kubernetes — Part 3 : Comprendre les mécanismes internes
Troisième partie d’une série pratique sur les controllers et operators Kubernetes Si vous n’avez pas lu la Part 1 et la Part 2, commencez…
Construire son propre controller Kubernetes — Part 3 : Comprendre les mécanismes internes
Troisième partie d’une série pratique sur les controllers et operators Kubernetes Si vous n’avez pas lu la Part 1 et la Part 2, commencez par là — cette partie s’appuie sur les concepts introduits précédemment.

Comprendre le mécanisme avant l’outil. L’outil changera. Le mécanisme, lui, restera.
Le pattern universel : le control loop
Avant de parler de Kubernetes, parlons d’un objet que vous avez chez vous.
Votre thermostat.
Vous réglez la température désirée — disons 21°C. Le thermostat mesure en permanence la température de la pièce. Si elle chute en dessous de 21°C, il déclenche le chauffage. Quand elle atteint 21°C, il l’arrête. Il recommence en permanence.
Vous n’avez pas dit au thermostat comment chauffer. Vous avez juste déclaré ce que vous vouliez. Le thermostat a fait le reste.
C’est un control loop — ou boucle de contrôle. Le pattern le plus fondamental de l’automatisation. La documentation officielle Kubernetes utilise d’ailleurs exactement cette analogie du thermostat pour expliquer les controllers. On le retrouve partout :
- Un thermostat qui régule la température
- Un pilote automatique qui maintient l’altitude d’un avion
- Terraform qui réconcilie l’infrastructure avec son state
- Kubernetes qui réconcilie l’état du cluster avec les manifests déclarés
Le pattern est toujours le même :
Observer → Comparer → Agir → Répéter

📷 IMAGE : 13-control-loop-pattern.png
Maintenant une nuance importante. Votre thermostat a deux composantes distinctes — un capteur qui mesure la température en continu, et un relais qui allume ou éteint le chauffage. Le capteur fait du polling — il mesure toutes les quelques secondes. C’est acceptable pour un thermostat.
Kubernetes fait mieux. Son mécanisme de détection n’est pas du polling — c’est de la notification push. On y reviendra.
Rappel : l’architecture Kubernetes
Avant d’entrer dans les mécanismes, un rappel rapide sur qui fait quoi dans Kubernetes. La documentation officielle décrit cette architecture en détail.

📷 IMAGE : k8s-architecture.png
Kubernetes est divisé en deux parties.
Le Control Plane — le cerveau. Il contient quatre composants principaux :
- **API Server** — le point d’entrée unique. Tout passe par lui. kubectl, les controllers, le kubelet, les webhooks — tous appellent l’API server. C’est une API REST stateless.
- **etcd** — la base de données distribuée. Stocke tout l’état du cluster. Seul l’API server y accède directement. Personne d’autre.
- **Scheduler** — décide sur quel nœud un pod doit tourner selon les ressources disponibles et les contraintes déclarées.
- **Controller Manager** — regroupe tous les controllers natifs en un seul processus. Deployment controller, ReplicaSet controller, Node controller — chacun dans sa propre goroutine.
Les Worker Nodes — les muscles. Chaque nœud contient un kubelet qui fait tourner les pods, un kube-proxy qui gère le réseau, et un container runtime comme containerd.
Ce qui est important pour la suite : votre controller custom ne vit pas dans le Control Plane. Il tourne dans un Pod ordinaire sur un Worker Node — exactement comme votre application métier. Mais il appelle l’API server exactement comme le Controller Manager natif le fait. Citoyen de première classe.
Le point d’entrée unique : l’API Server
Tout commence et finit par l’API Server.
Quand vous faites kubectl apply, vous appelez l'API server. Quand votre controller lit l'état d'une ressource, il appelle l'API server. Quand le kubelet démarre un pod, il appelle l'API server. Quand Flux CD synchronise Git avec le cluster, il appelle l'API server.
etcd n’est jamais appelé directement. Les controllers ne se parlent pas entre eux directement. Tout transite par l’API server — c’est le seul interlocuteur.
C’est pourquoi sa disponibilité est critique. Si l’API server tombe, le cluster ne s’arrête pas immédiatement — les pods continuent de tourner — mais plus rien ne peut être modifié ou réconcilié.

📷 IMAGE : 14-api-server-central.png
Ce qui se passe avant etcd : les Admission Controllers
Quand une requête arrive à l’API server, elle ne va pas directement dans etcd. Elle passe d’abord par un pipeline de validation — les Admission Controllers.
kubectl apply
→ Authentification
→ Autorisation (RBAC)
→ Mutating Admission Webhooks
→ Validation du schéma
→ Validating Admission Webhooks
→ etcd
→ Watch → Controller

📷 IMAGE : 04-full-pipeline.png
Deux types de webhooks optionnels vous permettent d’intervenir dans ce pipeline :
**Mutating Admission Webhooks** — modifient la ressource avant qu’elle soit stockée. Injecter des valeurs par défaut, normaliser des champs, ajouter des labels automatiquement. C’est l’API server qui appelle votre webhook — pas l’inverse. Votre webhook est un service HTTP dans le cluster.
**Validating Admission Webhooks** — valident et peuvent rejeter. Si la vaultPolicy demandée n'est pas autorisée pour ce namespace — rejet immédiat avec un message clair au kubectl apply.
Ces webhooks sont optionnels. Sans eux, la validation arrive dans le Reconcile() — asynchrone, moins immédiat, mais parfaitement fonctionnel pour un premier controller.
Une note sur l’implémentation : le webhook n’est pas nécessairement un service séparé. Avec Kubebuilder, il s’exécute dans le même binaire que le controller — un seul Pod, un seul Deployment, une seule image. Le controller gère la réconciliation, le webhook expose un endpoint HTTPS dans une goroutine supplémentaire du même processus (source : Kubebuilder Book — Admission Webhook).
Une contrainte importante : les webhooks nécessitent un certificat TLS. L’API server refuse d’appeler un endpoint non sécurisé.
Ce certificat doit être signé par une CA que l’API server reconnaît. Quand vous enregistrez un webhook via un objet MutatingWebhookConfiguration ou ValidatingWebhookConfiguration, vous fournissez le caBundle — le certificat CA encodé en base64 que l'API server utilisera pour valider votre webhook :
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
webhooks:
- name: vaultapp.vault.exemple.com
clientConfig:
service:
name: vaultapp-controller-webhook
namespace: vault-system
path: /validate
caBundle: LS0tLS1CRUdJTi... # CA encodé en base64
En pratique, Kubebuilder s’intègre avec **cert-manager** pour générer et renouveler automatiquement les certificats, et injecter le caBundle dans la configuration du webhook (source : Kubebuilder Book — Deploying cert-manager). Sans cert-manager, vous pouvez aussi utiliser l'API [CertificateSigningRequest](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/) de Kubernetes pour faire signer votre certificat par le CA interne du cluster.
Si votre webhook est indisponible au moment d’un kubectl apply, la requête échoue — c'est pourquoi ils ajoutent de la complexité opérationnelle.
Voir les webhooks en action dans votre cluster
Lister tous les Mutating Webhooks actifs :
kubectl get mutatingwebhookconfigurations
Lister tous les Validating Webhooks actifs :
kubectl get validatingwebhookconfigurations
Inspecter un webhook en détail :
kubectl describe mutatingwebhookconfiguration <nom-du-webhook>
Voici une sortie réaliste et annotée — chaque champ a son importance :
Name: vaultapp-controller-mutating-webhook-configuration
Namespace:
Labels: app.kubernetes.io/managed-by=controller-manager
Annotations: cert-manager.io/inject-ca-from: vault-system/vaultapp-controller-certificate
# ↑ pointe vers un objet Certificate cert-manager
# Voir aussi : inject-ca-from-secret (pointe vers un Secret directement)
# Source : https://cert-manager.io/docs/concepts/ca-injector/
Webhooks:
Name: mvaultapp.kb.io # ← nom unique du webhook
Admission Review Versions: [v1]
Client Config:
CA Bundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0t...
# ↑ certificat CA encodé en base64
# l'API Server s'en sert pour valider
# le certificat TLS de votre webhook
Service:
Name: vaultapp-controller-webhook-service
Namespace: vault-system
Path: /mutate-vault-exemple-com-v1alpha1-vaultapp
Port: 443
# ↑ endpoint HTTPS que l'API Server appelle
# c'est votre Pod qui répond ici
Failure Policy: Fail
# ↑ si le webhook est indisponible →
# la requête kubectl apply échoue
# (alternative : Ignore → laisse passer)
Match Policy: Equivalent
Namespace Selector: # ← filtre par namespace (vide = tous)
Object Selector: # ← filtre par labels sur la ressource
Rules: # ← quand ce webhook est déclenché
API Groups: vault.exemple.com # groupe de l'API
API Versions: v1alpha1 # version de la CRD
Operations:
CREATE # ← déclenché à la création
UPDATE # ← déclenché à la modification
Resources:
vaultapps # ← sur cette ressource uniquement
Scope: * # ← namespaced ou cluster-scoped
Side Effects: None
Timeout Seconds: 10
# ↑ l'API Server attend 10s max
# passé ce délai → Failure Policy appliquée
Sur un cluster Kubernetes réel avec plusieurs outils installés, kubectl get mutatingwebhookconfigurations révèle tout ce qui intercepte tes requêtes avant etcd. Voici un exemple réel anonymisé — chaque ligne est un outil qui s'intercale dans le pipeline de validation :
kubectl get mutatingwebhookconfigurations
# NAME WEBHOOKS AGE
...
# opentelemetry-operator-mutation 3 2y
...
Chaque webhook a son rôle — cert-manager injecte le CA, vault-agent-injector injecte les sidecars Vault, kyverno applique les policies, opentelemetry-operator instrumente les pods automatiquement. Tous s’exécutent en série avant que ta ressource atteigne etcd (source : Dynamic Admission Control).
Et voici ce que contient concrètement le webhook de l’OpenTelemetry Operator — un exemple réel annoté :
Name: opentelemetry-operator-mutation
Labels:
app.kubernetes.io/managed-by: Helm
helm.toolkit.fluxcd.io/name: opentelemetry-operator # ← géré par Flux CD
Annotations:
cert-manager.io/inject-ca-from: opentelemetry/otel-serving-cert
# ↑ cert-manager injecte automatiquement le caBundle
# dans cette configuration — zéro gestion manuelle du certificat
Webhooks:
Name: minstrumentation.kb.io
Client Config:
Ca Bundle: LS0tLS1CRUdJT... # ← certificat CA (tronqué)
Service:
Name: opentelemetry-operator-webhook
Namespace: opentelemetry
Path: /mutate-opentelemetry-io-v1alpha1-instrumentation
Port: 443
# ↑ l'API Server appelle ce endpoint HTTPS
# sur le Pod du webhook dans le namespace opentelemetry
Failure Policy: Fail
# ↑ si ce webhook est down → kubectl apply échoue
# choisir Ignore pour les webhooks non critiques
Rules:
API Groups: opentelemetry.io # ← groupe de l'API
API Versions: v1alpha1 # ← version ciblée
Operations:
CREATE # ← déclenché à la création
UPDATE # ← et à la modification
Resources: instrumentations # ← uniquement cette ressource
Scope: Namespaced # ← pas cluster-scoped
Timeout Seconds: 10
# ↑ l'API Server attend 10s max — au-delà → Failure Policy
# Deuxième webhook dans la même configuration
Name: mpod.kb.io
Rules:
API Groups: (vide = core API)
API Versions: v1
Operations: CREATE, UPDATE
Resources: pods # ← intercepte TOUS les pods
Scope: Namespaced
Failure Policy: Ignore
# ↑ si down → les pods passent quand même
# logique : l'instrumentation auto est optionnelle
Ce dernier point est important : un même webhook peut surveiller plusieurs ressources différentes. L’OpenTelemetry Operator intercepte à la fois les Instrumentation (pour la configuration) et les Pod (pour injecter l'agent automatiquement au démarrage).
Le Watch : la notification push
Une fois la ressource stockée dans etcd, comment le controller sait-il qu’il doit agir?
La réponse naive serait le polling — le controller interroge l’API server toutes les X secondes. C’est non scalable. Sur un cluster avec des milliers de ressources et des dizaines de controllers, l’API server serait saturé en permanence.
Kubernetes utilise le mécanisme de Watch — une connexion HTTP longue durée entre le controller et l’API server. Le controller s’abonne aux ressources qui l’intéressent. L’API server pousse une notification dès qu’une ressource change — Create, Update, Delete.
C’est du push, pas du pull. Exactement comme SNS sur AWS, comme ActiveMQ, comme les Server-Sent Events.

📷 IMAGE : 05-watch-vs-polling.png
Le controller ne se réveille que quand quelque chose change. Entre deux événements, il ne consomme rien.
La queue interne : isolation et déduplication
Quand une notification arrive via le Watch, elle n’appelle pas directement Reconcile(). Elle passe d'abord par une queue interne — propre à chaque controller.

📷 IMAGE : 07-queue-per-controller.png
Deux propriétés importantes de cette queue :
Isolation — chaque controller a sa propre queue en mémoire, dans son Pod. Un controller lent ou en erreur n’impacte jamais les autres. L’API server pousse les événements et oublie — c’est le controller qui gère son propre rythme.
Déduplication — si le même objet reçoit 5 événements rapides pendant que le controller est occupé, controller-runtime n’insère qu’une seule entrée dans la queue. Le controller traite l’état courant, pas l’historique des changements. C’est cohérent avec le modèle déclaratif — ce qui compte c’est ce qui est, pas ce qui s’est passé.
API Server → Watch → 📬 Queue controller A → Reconcile()
→ 📬 Queue controller B → Reconcile()
→ 📬 Queue controller C → Reconcile()
La boucle de réconciliation : idempotence obligatoire
Le cœur du controller est sa fonction Reconcile(). Elle implémente le control loop vu plus haut — Observer, Comparer, Agir.
fonction Reconcile(ressource):
// 1. Observer — lire l'état désiré depuis l'API Server
etatDesire = lireDepuisKubernetes(ressource)
// 2. Comparer — lire l'état actuel dans la destination
etatActuel = lireDepuisDestination(etatDesire.nom)
// 3. Agir — corriger l'écart si nécessaire
si etatActuel est différent de etatDesire:
appliquerEtatDesire(etatDesire)
// 4. Répéter — le prochain événement déclenchera un nouvel appel
retourner succès
Une règle absolue : l’idempotence. Le même événement peut arriver plusieurs fois — réseau instable, redémarrage du controller, retry automatique. Chaque opération doit être un upsert, jamais un create aveugle.
// ❌ Non idempotent — plante si la policy existe déjà
vault.CreatePolicy(policy)
// ✅ Idempotent — create ou update, peu importe
vault.WritePolicy(policy)

📷 IMAGE : 06-reconcile-loop.png
La documentation officielle sur les controllers insiste sur ce point — un controller doit toujours être capable de traiter le même événement plusieurs fois sans effet de bord. Le Kubebuilder Book documente également les bonnes pratiques d’idempotence dans la section Good Practices.
Le self-healing : la convergence garantie
Ces mécanismes combinés — Watch, queue, réconciliation idempotente — produisent une propriété fondamentale : le self-healing.
Quelqu’un modifie manuellement une policy Vault directement. L’API Server notifie le controller via le stream Watch (source). Une notification arrive dans la queue. Reconcile() est appelé. L'écart est détecté. La correction est appliquée.
Le système converge toujours vers l’état désiré. Pas juste au moment du déploiement — en permanence.
C’est la différence fondamentale avec Terraform ou Ansible. Ces outils réconcilent quand on les déclenche. Un controller Kubernetes réconcilie sur événement — et peut être configuré pour requeuer périodiquement via RequeueAfter (source).
Les goroutines du controller : coopération interne
C’est la partie la plus importante de cette série. Comprendre ce qui se passe à l’intérieur du processus du controller explique pourquoi il est fiable, résilient, et scalable.
Un controller Kubebuilder est un seul binaire qui démarre plusieurs goroutines coopérantes. Chacune a un rôle précis.
Watch goroutine — maintient une connexion HTTP longue durée avec l’API Server. Quand une ressource change, l’API Server pousse l’événement sur ce stream. La Watch goroutine reçoit l’événement et place uniquement le namespace/name de la ressource dans la workqueue. Pas l'objet complet — juste l'identifiant.
Workqueue — la pièce centrale. Elle garantit deux propriétés critiques documentées dans client-go :
- Déduplication — si le même objet reçoit 5 événements pendant que la queue est pleine, une seule entrée est conservée
- Protection contre la concurrence — une ressource en cours de traitement ne peut pas être prise par un autre worker simultanément
Worker goroutines — une ou plusieurs (configurable via [MaxConcurrentReconciles](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/internal/controller#Controller), défaut : 1). Chaque worker dépile un namespace/name, appelle l'API Server pour lire l'état désiré, puis appelle Reconcile().
Avec MaxConcurrentReconciles > 1, plusieurs ressources différentes peuvent être réconciliées en parallèle. Mais la workqueue garantit qu'une même ressource n'est jamais traitée par deux workers en même temps — c'est la propriété "stingy" de la workqueue, démontrée et testée ici.
Webhook goroutine — si tu as des webhooks, une goroutine séparée expose l’endpoint HTTPS dans le même binaire. Elle tourne indépendamment du controller.
Metrics goroutine — expose les métriques Prometheus sur un port dédié.

📷 IMAGE : 15-goroutines.png
Pour aller plus loin sur ce sujet : Diving into controller-runtime est une des meilleures analyses de l’architecture interne disponibles. Learning Concurrent Reconciling (OpenKruise) explique en détail pourquoi augmenter MaxConcurrentReconciles améliore le throughput sans risque de race condition.
Quand est-ce que Reconcile() est appelé après le premier apply?
Une précision importante. On a dit que “le controller réconcilie en continu” — c’est une simplification. La réalité est plus nuancée.
Reconcile() est déclenché sur événement. Entre deux événements, le controller ne consomme rien. Il n’y a pas de boucle infinie qui tourne en permanence.
Ce qui peut déclencher un Reconcile après le premier kubectl apply :
1. Un changement sur la ressource — quelqu’un modifie le ConfigMap via kubectl edit. L'API Server pousse un événement Update sur le stream Watch. Reconcile est appelé.
2. Un changement sur une ressource surveillée — si le controller surveille des ressources secondaires (Pods, Secrets, etc.), un changement sur celles-ci peut déclencher un Reconcile sur la ressource principale.
3. Le RequeueAfter explicite — dans Reconcile(), tu peux retourner une durée pour forcer un prochain appel :
retourner RequeueAfter: 1 heure
C’est optionnel et intentionnel. Tu le codes quand tu en as besoin. La documentation controller-runtime précise que Requeue: true (sans durée) est déprécié — RequeueAfter est la bonne approche.
4. Le SyncPeriod du Manager — controller-runtime resynchronise son cache périodiquement avec l’API Server. Par défaut toutes les 10 heures (configurable via ctrl.Options{SyncPeriod}), ce resync génère des événements synthétiques sur tous les objets surveillés. C'est un filet de sécurité contre les événements Watch potentiellement manqués (source : controller-runtime Manager Options).
Les events Kubernetes vs les événements Watch
Tu as peut-être remarqué des events en boucle dans kubectl get events. Ces deux choses ne sont pas la même chose.
**kubectl get events affiche des objets de type Event — des ressources Kubernetes créées par* les controllers pour journaliser ce qui se passe. C'est de l'observabilité, pas du déclenchement. Un controller crée un Event pour dire "j'ai créé ce pod" ou "j'ai échoué à contacter Vault"*.
Les événements Watch sont des notifications internes sur la connexion HTTP longue durée entre le controller et l’API Server. Ils sont invisibles pour l’utilisateur — ils ne s’affichent pas dans kubectl get events.
Si tu vois des events en boucle dans kubectl get events, c'est souvent le signe que :
- Le controller requeue en boucle suite à une erreur répétée
- Un
RequeueAftercourt est configuré - Une ressource dépendante change fréquemment
Un exemple concret de requeue : cert-manager
cert-manager est l’exemple parfait de requeue intentionnel. Son controller Certificate calcule exactement quand le certificat doit être renouvelé et retourne RequeueAfter pour se réveiller au bon moment.
Par défaut, un certificat dure 90 jours et est renouvelé aux 2/3 de sa durée — soit à 60 jours. Le controller se requeue donc 60 jours après l’émission, vérifie que le certificat est toujours valide, et renouvelle si nécessaire.
C’est le même pattern que le CronJob controller natif de Kubernetes — il calcule le prochain schedule et retourne RequeueAfter pour se réveiller exactement au bon moment (source : Kubebuilder Book — CronJob tutorial).
CRD sans controller : est-ce possible?
Oui. Kubernetes accepte et stocke une CRD sans qu’aucun controller ne la gère. La ressource est inerte — bien structurée, stockée dans etcd, lisible via l’API — mais personne ne réagit à son contenu.
C’est comme une table de base de données sans application qui la gère. Les données existent, elles sont bien structurées, on peut les lire et écrire — mais rien ne se passe automatiquement.
Sans controller, c’est toi qui maintiens l’état à jour via kubectl apply. Il n'y a pas de self-healing, pas de réconciliation automatique.

📷 IMAGE : 16-crd-comparison.png
Cas d’usage valides pour une CRD sans controller :
- Schéma de validation — un CRD avec un schéma OpenAPI strict. Un webhook valide les manifests contre ce schéma. Pas besoin de controller.
- Registre de configuration — un CRD
TeamConfigqui stocke les paramètres d'une équipe. Des pipelines ou scripts le lisent via l'API Kubernetes. Kubernetes devient un registre structuré. - Marqueur déclaratif — un CRD
MaintenanceWindowque d'autres controllers lisent dans leur propre logique de réconciliation.
Pour aller plus loin
La documentation officielle est la référence la plus fiable, mais ces articles complémentaires offrent des angles différents qui peuvent aider à la compréhension :
Sur le control loop et la réconciliation
- Controllers — Kubernetes.io — la référence officielle avec l’analogie du thermostat
- Kubernetes and Reconciliation Patterns — une analyse approfondie des patterns de réconciliation avec des exemples concrets
- Kubernetes Controllers 101 — Watch, Reconcile, Repeat — une introduction accessible sur Medium
Sur l’architecture Kubernetes
- Kubernetes Components — documentation officielle de l’architecture
- Kubernetes Operators 101, Part 2 — Red Hat Developer — explication claire de la relation entre operators et control plane
Sur les Admission Webhooks
- Dynamic Admission Control — Kubernetes.io — documentation officielle des webhooks
- Admission Webhook — Kubebuilder Book — guide pratique d’implémentation avec Kubebuilder
- What is an Admission Controller & Admission Webhook — démystification accessible sur Medium
- A Simple Kubernetes Admission Webhook — Slack Engineering — retour d’expérience en production chez Slack
Sur la queue et la concurrence
- How do Kubernetes Operators Handle Concurrency? — explication détaillée du mécanisme de déduplication de la workqueue
Ce qu’on a vu
- Le control loop est un pattern universel — Observer, Comparer, Agir, Répéter
- L’API Server est le point d’entrée unique — tout passe par lui, jamais par etcd directement
- Les Admission Webhooks permettent d’intervenir avant etcd — optionnels mais puissants
- Le Watch est un mécanisme push — pas de polling, notification uniquement sur changement
- La queue interne isole les controllers et déduplique les événements
- La réconciliation doit être idempotente — upsert, jamais create aveugle
- Le self-healing est la conséquence naturelle de tous ces mécanismes combinés
La suite
Dans la Part 4, on passe à la pratique. On installe Kubebuilder, on génère le scaffolding d’un controller from scratch, et on décortique chaque fichier généré — ce qu’il fait, pourquoi il est là, ce que le framework fait pour vous en coulisses.
À propos de l’auteur
J’écris pour valider ma propre compréhension. Pas d’expertise revendiquée ici — juste un esprit curieux qui démystifie les concepts complexes pour les esprits simples comme le mien. Si après avoir lu cet article vous comprenez mieux qu’avant, on a tous les deux gagné quelque chose.
Cette série est construite autour d’un cas réel : un operator Kubernetes pour gérer la configuration HashiCorp Vault de façon déclarative et self-service.
메타데이터
- post_id
- e63b2ed173c4
- slug
- construire-son-propre-controller-kubernetes-part-3-comprendre-les-mécanismes-internes-e63b2ed173c4
- url
- https://medium.com/@amour.martial/construire-son-propre-controller-kubernetes-part-3-comprendre-les-m%C3%A9canismes-internes-e63b2ed173c4
- canonical_url
- https://medium.com/@amour.martial/construire-son-propre-controller-kubernetes-part-3-comprendre-les-m%C3%A9canismes-internes-e63b2ed173c4
- author_url
- https://medium.com/@amour.martial
- status
- ok
- fetched_at
- 2026-06-29 02:33:43