← Back to list

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…

Amour Agbangla · 2026-03-09 03:10 · 0 claps · 14.7 min read
#devops #kubernetes #kubernetes-controller #golang
Open on Medium ↗
Wiki topics: ☁️ · DevOps & Cloud

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

📷 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

📷 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

📷 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

📷 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

📷 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

📷 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

📷 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

📷 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 RequeueAfter court 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

📷 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 TeamConfig qui 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 MaintenanceWindow que 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

Sur l’architecture Kubernetes

Sur les Admission Webhooks

Sur la queue et la concurrence

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