Aller au contenu
Conteneurs & Orchestration medium

Operators et CRDs pour développeurs

20 min de lecture

logo kubernetes

Les Custom Resource Definitions (CRDs) étendent Kubernetes avec de nouveaux types de ressources. En tant que développeur, vous n'avez pas besoin de créer des opérateurs, mais vous devez savoir utiliser les ressources custom que d'autres ont créées. Ce guide vous montre comment découvrir, manipuler et diagnostiquer les CRDs dans votre cluster.

Le parcours va de la découverte des CRDs enregistrées dans un cluster jusqu'au déblocage d'une ressource en Terminating. Les exemples s'appuient sur des opérateurs répandus, cert-manager et le Prometheus Operator en tête, mais la mécanique reste identique quelle que soit l'extension installée.

  • Comprendre le modèle CRD/Operator sans entrer dans le développement
  • Découvrir les CRDs disponibles dans votre cluster
  • Manipuler des ressources custom avec kubectl
  • Installer un opérateur depuis OperatorHub
  • Diagnostiquer les problèmes courants liés aux CRDs

CRD et Operator : l'essentiel pour un développeur

Section intitulée « CRD et Operator : l'essentiel pour un développeur »

Deux objets distincts se cachent derrière le sujet : la CRD déclare un nouveau type auprès de l'API server, l'opérateur est le programme qui donne un sens à ce type en créant les ressources sous-jacentes. Sans opérateur, une CRD ne produit rien, elle se contente de stocker vos manifests.

Une Custom Resource Definition (CRD) ajoute un nouveau type d'objet à Kubernetes. Au lieu de manipuler uniquement des Pods, Services ou Deployments, vous pouvez manipuler des objets métier comme PostgresCluster, Certificate ou GitRepository.

ConceptExemple
CRDkind: PostgresCluster
Custom Resource (CR)Un cluster PostgreSQL spécifique
OperatorCrée les Pods, Services, Secrets nécessaires

L'opérateur est un controller qui observe les ressources custom et agit pour atteindre l'état désiré :

┌─────────────────────────────────────────────────────────────┐
│ Boucle de réconciliation │
├─────────────────────────────────────────────────────────────┤
│ 1. OBSERVER : L'opérateur watch les CRs │
│ 2. COMPARER : État actuel vs état désiré │
│ 3. AGIR : Créer/modifier/supprimer des ressources │
│ 4. RÉPÉTER : En continu │
└─────────────────────────────────────────────────────────────┘

Les types disponibles changent d'un cluster à l'autre selon les opérateurs installés. Avant d'écrire un manifest, il faut savoir quelles CRDs sont enregistrées, dans quelle version d'API elles sont servies, et si la ressource est namespacée ou globale.

Le nom complet d'une CRD suit toujours la forme <pluriel>.<groupe d'API>, ce qui identifie d'un coup d'œil l'opérateur qui l'a posée. La colonne CREATED AT donne la date d'installation, pratique pour repérer une extension ajoutée récemment sur un cluster partagé.

Fenêtre de terminal
# Toutes les CRDs installées
kubectl get crds
# Exemple de sortie :
# NAME CREATED AT
# certificates.cert-manager.io 2026-03-20T10:00:00Z
# issuers.cert-manager.io 2026-03-20T10:00:00Z
# postgresclusters.postgres-operator.crunchydata.com 2026-03-15T08:30:00Z

describe affiche les versions servies, la portée de la ressource et les shortnames déclarés. La sortie YAML complète contient en plus le schéma OpenAPI qui valide vos manifests : c'est lui qui provoque le rejet quand un champ est mal orthographié.

Fenêtre de terminal
# Détails d'une CRD
kubectl describe crd certificates.cert-manager.io
# Structure YAML de la CRD
kubectl get crd certificates.cert-manager.io -o yaml

kubectl api-resources liste tout ce que l'API server sait servir, ressources natives et custom confondues. La colonne NAMESPACED indique s'il faut préciser un namespace, et APIVERSION donne exactement la valeur à recopier dans le champ apiVersion de votre manifest.

Fenêtre de terminal
# Tous les types de ressources disponibles (natifs + custom)
kubectl api-resources
# Filtrer par API group
kubectl api-resources --api-group=cert-manager.io
# Exemple de sortie :
# NAME SHORTNAMES APIVERSION NAMESPACED KIND
# certificates cert cert-manager.io/v1 true Certificate
# issuers cert-manager.io/v1 true Issuer

Une fois le type connu, les CRs se listent comme n'importe quel objet Kubernetes. L'option -A balaie tous les namespaces, réflexe utile quand vous ignorez où la ressource a été créée ; -o wide révèle les colonnes supplémentaires définies par la CRD elle-même, souvent l'état de la ressource.

Fenêtre de terminal
# Lister les instances d'une CR
kubectl get certificates -n mon-namespace
# Dans tous les namespaces
kubectl get certificates -A
# Avec plus de détails
kubectl get certificates -o wide

Une CR se manipule avec les verbes habituels : apply, get, describe, edit, patch, delete. La différence tient au partage du contenu : vous écrivez la spec, l'opérateur écrit le status, et toute modification de la spec relance sa boucle de réconciliation.

Le format est identique aux ressources natives. Exemple avec cert-manager :

certificate.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: mon-app-tls
namespace: production
spec:
secretName: mon-app-tls-secret
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- mon-app.example.com
- www.mon-app.example.com
Fenêtre de terminal
kubectl apply -f certificate.yaml

describe reste le point d'entrée : il regroupe la spec, le status et les événements émis par l'opérateur, qui portent le motif exact d'un échec. Le JSONPath sert quand vous cherchez un champ précis, par exemple dans un script qui attend qu'une condition passe à True.

Fenêtre de terminal
# Détails complets
kubectl describe certificate mon-app-tls -n production
# YAML brut
kubectl get certificate mon-app-tls -n production -o yaml
# JSONPath pour extraire un champ
kubectl get certificate mon-app-tls -n production \
-o jsonpath='{.status.conditions[0].status}'

Les CRs bien conçues exposent un champ .status mis à jour par l'opérateur :

status:
conditions:
- type: Ready
status: "True"
reason: CertificateIssued
message: Certificate is up to date and has been issued
lastTransitionTime: "2026-03-25T10:30:00Z"
notAfter: "2026-06-25T10:30:00Z"
notBefore: "2026-03-25T10:30:00Z"
renewalTime: "2026-05-25T10:30:00Z"

edit ouvre le manifest dans votre éditeur et applique le résultat à la fermeture. patch --type=merge fait la même chose sans interaction, donc en script ; attention, sur une liste comme dnsNames, la fusion remplace le tableau entier au lieu d'y ajouter une entrée.

Fenêtre de terminal
# Édition interactive
kubectl edit certificate mon-app-tls -n production
# Patch (modifier un champ)
kubectl patch certificate mon-app-tls -n production \
--type=merge \
-p '{"spec":{"dnsNames":["mon-app.example.com","api.example.com"]}}'

La commande rend la main aussitôt, mais l'objet peut rester en Terminating le temps que l'opérateur achève le nettoyage des ressources qu'il avait créées.

Fenêtre de terminal
kubectl delete certificate mon-app-tls -n production

Les quatre extensions ci-dessous couvrent des besoins que Kubernetes ne traite pas nativement : certificats TLS, collecte de métriques, déploiement continu et récupération de secrets externes. Toutes suivent la même grammaire, un bloc spec déclaratif que l'opérateur traduit en objets natifs.

L'objet Certificate décrit le certificat voulu, pas la façon de l'obtenir. issuerRef désigne l'autorité qui le délivrera, dnsNames les noms à couvrir, et secretName le Secret dans lequel cert-manager déposera la clé privée et le certificat une fois la validation aboutie.

certificate-letsencrypt.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: api-tls
namespace: production
spec:
secretName: api-tls-secret
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- api.example.com

Un ServiceMonitor ne collecte rien par lui-même : il indique au Prometheus Operator quels Services interroger. Le selector cible ces Services par label, port reprend le nom du port déclaré dans le Service et non son numéro, ce qui est la cause d'erreur la plus fréquente sur cette ressource.

servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: mon-app
namespace: monitoring
spec:
selector:
matchLabels:
app: mon-app
endpoints:
- port: metrics
interval: 30s

L'objet Application relie un dépôt Git à un namespace de destination. Les deux options de syncPolicy.automated sont les plus lourdes de conséquences : prune autorise Argo CD à supprimer les objets retirés du dépôt, selfHeal annule toute modification faite directement sur le cluster.

argo-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: mon-app
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/mon-org/mon-app
path: k8s
targetRevision: HEAD
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true

Un ExternalSecret décrit une correspondance entre des chemins d'un coffre externe et les clés d'un Secret Kubernetes. L'opérateur relit la source à chaque refreshInterval et met à jour le Secret cible : la valeur sensible ne figure jamais dans le manifest, seul son emplacement y apparaît.

external-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: db-credentials
namespace: production
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: ClusterSecretStore
target:
name: db-credentials
data:
- secretKey: username
remoteRef:
key: secret/data/database
property: username
- secretKey: password
remoteRef:
key: secret/data/database
property: password

Installer un opérateur revient à poser deux choses : ses CRDs et le Deployment du controller, avec les droits RBAC qui vont avec. Les trois méthodes ci-dessous se distinguent surtout sur la mise à jour et sur le sort des CRDs à la désinstallation, car supprimer une CRD détruit au passage toutes les CRs de ce type.

La plupart des opérateurs sont distribués via Helm :

Fenêtre de terminal
# Exemple : cert-manager
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true

Certains opérateurs fournissent un manifest unique :

Fenêtre de terminal
# Exemple : metrics-server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

Si votre cluster utilise Operator Lifecycle Manager :

Fenêtre de terminal
# Installer OLM (si pas déjà présent) : télécharger le script, l'inspecter, puis l'exécuter
curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.28.0/install.sh -o olm-install.sh
less olm-install.sh
bash olm-install.sh v0.28.0
# Installer un opérateur depuis OperatorHub
kubectl create -f https://operatorhub.io/install/prometheus.yaml

Quand une CR ne produit pas l'effet attendu, le manifest est rarement en cause : l'API server l'aurait rejeté à l'apply si sa structure était invalide. Trois pistes couvrent la majorité des cas, l'opérateur ne tourne pas, la CRD n'est pas installée, ou les permissions RBAC du controller sont insuffisantes.

Un objet accepté mais jamais traité signale que personne ne l'écoute. Regardez d'abord les événements de la CR, puis l'état du Pod de l'opérateur, enfin ses logs : c'est là qu'apparaissent les erreurs de validation métier ou d'accès à un service externe.

Fenêtre de terminal
# 1. Vérifier les événements de la CR
kubectl describe <kind> <name> -n <namespace>
# 2. Vérifier que l'opérateur tourne
kubectl get pods -n <operator-namespace>
# 3. Lire les logs de l'opérateur
kubectl logs -n <operator-namespace> deployment/<operator-name>

Erreur "no matches for kind" lors du kubectl apply

Section intitulée « Erreur "no matches for kind" lors du kubectl apply »

Ce message vient de l'API server : le kind demandé n'est enregistré nulle part. Soit la CRD n'est pas installée, soit vous visez une version d'API que le cluster ne sert plus, cas courant après le passage d'un groupe de v1beta1 à v1.

Fenêtre de terminal
# Le CRD n'est pas installé
kubectl get crd | grep <kind>
# Solution : installer l'opérateur d'abord

Un finalizer suspend la suppression tant que l'opérateur n'a pas confirmé son nettoyage. Si le controller a été désinstallé avant ses ressources, plus personne ne retire ce marqueur et l'objet reste indéfiniment en Terminating, y compris son namespace s'il en contient d'autres.

Fenêtre de terminal
# Vérifier les finalizers
kubectl get <kind> <name> -o jsonpath='{.metadata.finalizers}'
# Si l'opérateur ne tourne plus, retirer le finalizer manuellement
kubectl patch <kind> <name> \
--type=json \
-p='[{"op": "remove", "path": "/metadata/finalizers"}]'

L'opérateur n'a pas les droits nécessaires :

Fenêtre de terminal
# Vérifier les logs pour des erreurs RBAC
kubectl logs -n <operator-namespace> deployment/<operator-name> | grep -i forbidden
# Vérifier les Roles/ClusterRoles de l'opérateur
kubectl get clusterrole | grep <operator-name>
kubectl describe clusterrole <operator-role>

La commande kubectl explain fonctionne aussi avec les CRDs :

Fenêtre de terminal
# Structure générale
kubectl explain certificate
# Champ spécifique
kubectl explain certificate.spec.issuerRef
# Liste des champs disponibles
kubectl explain certificate.spec --recursive

Les trois listes suivantes évitent les allers-retours les plus coûteux : un manifest rejeté faute de CRD, une ressource introuvable faute de convention de nommage, et une CR en échec silencieux parce que personne ne surveille son status.

Ces quatre vérifications prennent moins d'une minute. Notez la limite de --dry-run=client : il contrôle la syntaxe YAML localement, sans jamais confronter le manifest au schéma OpenAPI de la CRD. Pour cette validation-là, il faut --dry-run=server, qui envoie l'objet à l'API server sans le persister.

  1. Vérifiez que la CRD existe : kubectl get crd <name>
  2. Lisez la documentation : Les CRDs ont souvent des exemples
  3. Utilisez kubectl explain : Pour comprendre les champs disponibles
  4. Validez avec --dry-run : kubectl apply -f cr.yaml --dry-run=client

Les CRs s'accumulent dans un namespace partagé sans le regroupement visuel qu'offre un Deployment avec ses Pods : rien n'indique quelle application a créé quel objet. Un préfixe applicatif et des labels cohérents rendent le filtrage possible, y compris pour les suppressions groupées.

  1. Préfixez par application : mon-app-certificate plutôt que certificate-1
  2. Utilisez des labels : Facilitez le filtrage et le monitoring
  3. Documentez : Ajoutez des annotations expliquant le but

Une CR peut rester en échec sans que rien ne le signale : elle ne redémarre pas comme un Pod et n'apparaît dans aucun kubectl get pods. Suivre status.conditions est le seul moyen fiable de détecter un certificat non renouvelé ou une synchronisation qui échoue depuis des semaines.

  1. Surveillez les status : Les CRs exposent leur état dans .status
  2. Alertez sur les échecs : Conditions Ready=False ou Failed=True
  3. Tracez les changements : Activez l'audit logging pour les CRs sensibles

Le tableau condense le vocabulaire du modèle. Un point revient dans presque tous les incidents : une CR acceptée par l'API server ne signifie pas qu'elle a été traitée. Seul le status écrit par l'opérateur en atteste, et c'est lui qu'il faut lire avant de conclure que la ressource fonctionne.

ConceptCe qu'il faut retenir
CRDNouveau type de ressource Kubernetes
CR (Custom Resource)Instance d'une CRD
OperatorController qui gère les CRs
kubectl api-resourcesDécouvrir les types disponibles
kubectl explainComprendre la structure d'une CR
.status.conditionsÉtat de la ressource
FinalizersRetardent la suppression pour nettoyage

Règle d'or : En tant que développeur, vous utilisez les CRs, vous n'avez pas besoin de comprendre le code de l'opérateur, seulement la spec que vous fournissez et le status qu'il retourne.

Dix questions tirées de la banque CKAD portent sur la découverte des types, la lecture du status et le déblocage d'une ressource en Terminating. Comptez huit minutes et 70 % de bonnes réponses pour valider.

Contrôle de connaissances

Validez vos connaissances avec ce quiz interactif

10 questions
8 min.
70% requis

Informations

  • Le chronomètre démarre au clic sur Démarrer
  • Questions à choix multiples, vrai/faux et réponses courtes
  • Vous pouvez naviguer entre les questions
  • Les résultats détaillés sont affichés à la fin

Lance le quiz et démarre le chronomètre

Si consommer des CRs ne suffit plus et que vous voulez produire les vôtres, le guide dédié aux opérateurs aborde la boucle de réconciliation côté code. Les deux autres liens complètent le sujet par l'installation via Helm et par les permissions des ServiceAccounts que réclament ces controllers.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn