Kubernetes Operators — De l’enfer… au paradis

Kubernetes n’est pas compliqué.
Mais mal compris… c’est un enfer.

Kubernetes : délire d’Ops ?

On nous l’a surtout vendu comme un truc d’infra.
Moi aussi, je suis passé par ce réflexe.

  • « Trop complexe »
  • Réservé aux spécialistes plateforme
  • Magie noire / pas « mon » métier

Kubernetes c'est un délire de dev

Kubernetes = API + état + boucle

  • API REST
  • Base d’état (etcd)
  • Boucle de réconciliation → events
  • Pas de mystère → des mécanismes explicites (controllers, kubelet, containerd, etc.)

De vos outils au noyau Linux


    flowchart LR
      B[kube-api]
      subgraph Clients
      A01[kubectl]
      A02@{ img: "img/helmsh-icon.svg", pos: "b", h: 44, constraint: "on" }
      A03@{ img: "img/logo-kustomize.B5HO6GxI_1tyDev.webp", pos: "b", h: 36, constraint: "on" }
      end
      A01 --> B
      A02 --> B
      A03 --> B
      D --> B
      B --> C[etcd]
      B --> D[Controllers]
      D --> E[kubelet]
      E --> F[containerd]
      F --> G[Kernel Linux]
      classDef logoBare fill:transparent,stroke:transparent,stroke-width:0px;
      class A02,A03 logoBare;
  • kube-api — point d’entrée
  • etcd — source de vérité
  • kubelet — agent sur chaque nœud
  • container runtime (ex. containerd) — exécution des conteneurs

Kubernetes ne fait pas tourner vos apps : la boucle de contrôle

  • Il orchestre · il délègue · il observe
flowchart LR
    DS[Spec · désir] --> AP[kube-apiserver]
    AP <-->|objets cluster| ET[(etcd)]
    AP --> EV[Watch · événements]
    EV --> CTRL[Controllers]
    CTRL -->|réconciliation · requêtes API| AP
    subgraph NODE[Nœud]
      K[kubelet] -->|CRI| CD[containerd]
    end
    AP -->|spec Pods · synchro| K
    K -->|status| AP

Les changements d’objets (Pods, etc.) vivent dans l’API ; le kubelet les synchronise depuis l’API, puis traduit en appels CRI vers containerd. Le status remonte par le même kubelet vers l’API.

Sous containerd : cgroups et namespaces

flowchart TD
    F[containerd] --> K[Linux Kernel]
  • cgroups
  • namespaces

Kubernetes vous évite de vivre dans le noyau

  • Manipuler les cgroups à la main
  • Gérer isolation, CPU, mémoire au niveau des noyaux de vos differents noeuds

À côté de ça, un cluster qui râle, c'est presque du repos.

CNI, CSI, CRI : de l’infra branchable

  • CNI → réseau (ex : Istio, Cilium ,Calico, Flannel, etc.)
  • CSI → stockage (ex : Ceph, Longhorn , Amazon,etc.)
  • CRI → runtime (ex : containerd, CRI-O,etc.)
  • Ici : brancher l’infra — pas encoder la logique métier ( opérateur)

Brancher l’infra ou coder le métier ?

Type Rôle
CNI / CSI / CRI Infrastructure
Opérateur Logique métier

Qui écrit la logique du cluster ?

La logique vit dans les controllers

Ils ferment la boucle : observation, décision, action.

Les opérateurs : des controllers spécialisés

Sans opérateur : scripts, crons, humains dans la boucle

  • Scripts bash
  • Cron jobs
  • Interventions humaines
  • État incohérent

Automatisation éclatée : la logique reste dans les têtes

  • Runbooks Confluence
  • Scripts bash « magiques »
  • Cron jobs oubliés
  • Interventions humaines

Drift, pas de reproductibilité, 3 h du matin

  • Drift de configuration
  • Actions non reproductibles
  • Dépendance aux humains
  • Incidents à 3 h du matin

Vous déclarez ; le système réconcilie

kind: Database
spec:
  size: 3

Spec, opérateur, cluster : ça tourne en boucle

flowchart LR
    A[Spec] --> B[Operator]
    B --> C[Cluster]
    C --> B

Dès que le réel diverge, la réconciliation repart.

Reflector & Reloader : la puissance des annotations

Outil Idée Déclencheur typique
Reflector (EmberStack) Copie / réplique des Secrets et ConfigMaps (autres namespaces, miroirs) Annotations sur la ressource source ou la copie
Reloader (Stakater) Redémarre les Pods quand un Secret / ConfigMap référencé change Annotation sur le Deployment / StatefulSet / etc.
  • Reflector : annotations du type reflector.v1.k8s.emberstack.com/* pour autoriser la réflexion et cibler où copier.
  • Reloader : reloader.stakater.com/auto: "true" (ou annotations plus fines par config) pour lier redéploiement ↔ mise à jour de config.

Exemple Reflector : secret DB → namespace applicatif

Le secret « source de vérité » vit avec la base ; Reflector recrée un miroir là où l’app tourne.

# Namespace database — secret maître (ex. mot de passe géré par l’opérateur / la DBA)
apiVersion: v1
kind: Secret
metadata:
  name: db-credentials
  namespace: database
  annotations:
    reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
    reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"
    reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "app-shop"
stringData:
  DATABASE_URL: "postgres://…"
  • Reflector maintient un Secret du même nom dans app-shop, à jour quand la source change.
  • Alternative : miroir manuel avec reflector.v1.k8s.emberstack.com/reflects: "database/db-credentials".

Exemple Reloader : rollout quand le secret miroir change

Le Deployment monte le secret copié ; Reloader redémarre les Pods si le Secret (ou ConfigMap) référencé est mis à jour.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: shop-api
  namespace: app-shop
  annotations:
    reloader.stakater.com/auto: "true"
spec:
  template:
    spec:
      containers:
        - name: api
          envFrom:
            - secretRef:
                name: db-credentials   # copie Reflector depuis database/
  • Rotation du mot de passe dans database/db-credentials → Reflector met à jour app-shop/db-credentials → Reloader déclenche un nouveau rollout.
  • Sans Reloader, les Pods gardent l’ancienne valeur en mémoire jusqu’à un redémarrage manuel.

Cert-manager, Postgres, Keycloak : la spec devient une entité

Opérateur CRD (exemples) Ce que ça matérialise
cert-manager Certificate, Issuer, ClusterIssuer Secrets TLS, chaînes ACME / PKI
Postgres Operator (Zalando) postgresql (acid.zalan.do) Cluster PostgreSQL (Patroni, volumes, users…)
Keycloak Operator Keycloak, KeycloakRealm, KeycloakClient, … Instances Keycloak, royaumes, clients

La spec décrit l’état métier (cert, base, realm) ; le contrôleur crée / met à jour Deployments, Services, Secrets, etc.

Un certificat déclaratif, tout le cycle TLS en dessous

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: api-example
spec:
  dnsNames:
    - api.example.com

La suite est dérivée automatiquement :

  • CertificateRequest
  • Challenge ACME
  • Secret TLS

Certificats : du Certbot au renouvellement automatique

Avant — sans opérateur

  • Certbot manuel
  • Scripts cron
  • Cert expiré = prod down

Avec cert-manager

  • Déclaratif
  • Auto-renouvellement
  • Observabilité native (Certificate, conditions, events)

Postgres répliqué sans bricoler le failover à la main

apiVersion: acid.zalan.do/v1
kind: postgresql
metadata:
  name: my-cluster
spec:
  numberOfInstances: 3

Derrière cette spec, le contrôleur s’occupe de :

  • Réplication
  • Failover
  • Backup
  • Users / rôles

Quelle techno pour développer votre opérateur ?

Piste Outils typiques À retenir
Go (référence) Kubebuilder, controller-runtime, client-go Même patterns que l’upstream (reconcile, owner refs). Sous pic d’événements : goroutines + files + back-off → peu de RAM en plus — le Pod opérateur ne part pas en limite quand le cluster s’emballe.
Operator SDK CLI Red Hat · Go (souvent comme Kubebuilder) · Ansible / Helm Go = solide ; Ansible / Helm = wrapper playbooks / charts — court si la logique métier épaissit.
Autres langages Python Kopf, Rust kube-rs, Java (Fabric8, Quarkus)… Si équipe ou libs métier ; moins d’exemples tout faits côté core k8s.

Go + controller-runtime pour un opérateur maison sérieux ; sinon : celui que l’équipe livre et maintient le mieux.

Charge ↑ : un runtime qui gonfle la RAM par événement mange votre quota ; Go + controller-runtime, non.

Quand un opérateur est légitime

  • Système complexe
  • Règles claires et stables
  • État critique pour l’activité
  • Opérations répétitives

Keycloak Credential Manager — le même schéma, chez moi

  • Objectif : réconcilier des credentials Keycloak (clients, secrets, rotation…) avec ce qui est déclaré dans le cluster (CRD, Secrets annotés, ou les deux — selon ton design).
  • Intérêt conf : montrer le même schéma que les opérateurs « connus » : observe → compare → corrige sur ton domaine métier.
  • Incident ou compromission de secrets clients : un reload Keycloak émet des événements côté IdP ; l’opérateur les interroge régulièrement (ex. toutes les minutes), repère l’écart et propage la correction vers l’état attendu dans le cluster.

Exemple concret de CRD à la slide suivante.

Exemple — KeycloakClientCredential

CRD secrets.carbogame.com/v1alpha1spec.source (realm, client) → spec.target (Secret, rafraîchissement, mapping des clés).

apiVersion: secrets.carbogame.com/v1alpha1
kind: KeycloakClientCredential
metadata:
  name: gitea-creds
  namespace: keycloak
spec:
  source:
    keycloakServer: prod-keycloak
    realm: operator
    clientId: gitea
  target:
    secretName: gitea-secret-creds
    namespace:
      - gitea
    refreshPeriod: 10m
    fields:
      clientId: key
      clientSecret: secret

Extrait type ; en chart Helm, namespace et keycloakServer sont injectés via include "common.namespace" …, {{ .Values.global.env }}-keycloak, etc.

Développer son opérateur : réutiliser les autres

  • Ne pas tout recoder : un opérateur maison peut s’appuyer sur des opérateurs / contrôleurs existants pour ce qu’ils font déjà bien.
  • Ex. Secrets / ConfigMaps vers d’autres namespaces : plutôt que dupliquer la logique et une deuxième config de ce qui est autorisé, brancher Reflector — une politique de réflexion, un seul RBAC à raisonner (source vs cibles), moins de surface d’erreur.
  • Votre code se concentre sur le métier ; la diffusion transverse reste le problème de l’outil prévu pour ça.

Ce qu’un opérateur bien choisi vous apporte

  • Automatiser du répétitif
  • Encapsuler de la complexité
  • Stabiliser un système

Quand ne pas écrire d’opérateur

  • Logique floue ou en perpétuelle négociation métier
  • Processus instables (la règle change toutes les semaines)
  • Dépendances externes imprévisibles
  • One-shot : automatisation ponctuelle sans cycle de vie

Cinq signaux qu’un opérateur part en vrille

  • Spec incompréhensible sans lire le code
  • Effets implicites (créations ailleurs sans que ce soit visible)
  • Couplage externe fort (APIs fragiles, ordre d’appels magique)
  • Debug impossible sans les mainteneurs à côté de toi
  • État non observable (pas de conditions / status utiles)

Débugger un opérateur : describe, events, status, logs

  • kubectl describe (resource + events)
  • Events du namespace
  • Status / conditions sur la CRD
  • Logs du contrôleur

Un opérateur, c’est un produit logiciel — pas un script

  • Code et revues
  • Tests (unitaires, intégration, e2e si possible)
  • Maintenance dans la durée
  • Compatibilité avec les versions Kubernetes
  • Migrations de CRD / spec

Quand l’opérateur devient l’enfer

  • Logique implicite
  • Effets de bord
  • Debugging difficile
  • Couplage fort

En pratique, ça donne souvent :

  • Tout mettre en opérateur
  • Logique métier floue
  • Effets cachés

Un opérateur mal conçu, c’est un microservice dont plus personne ne tient les règles du jeu.

Kubernetes, plateforme de programmation

  • Kubernetes vous évite l’enfer de l’infrastructure bas niveau.
  • Les bons opérateurs vous évitent l’enfer du manuel — les mauvais vous y renvoient autrement.

Avant d’écrire un opérateur : la checklist

  • Répétitif
  • Règles claires
  • Déclaratif possible
  • Idempotent
  • Observable

Si une case importante manque, reculez : pas d’opérateur (ou pas tout de suite).

Votre logique, exécutée par le cluster au lieu d’un humain

Kubernetes simplifie l’infra — les opérateurs, ça peut simplifier ou empirer

Merci

Merci pour votre attention.

Des questions ?

Dépôt des sources et page d’accueil des slides — à scanner pour repartir avec les liens.

Dépôt
Slides — accueil