## Introduction

Les définitions de ressources personnalisées (Custom Resource Definitions, CRD) Kubernetes vous permettent d'étendre l'API Kubernetes avec vos propres types de ressources. Cet article présente une configuration complète de laboratoire local pour les CRD, destinée aux développeurs, aux consultants DevOps et aux équipes techniques de startups. Il relie la configuration, les tests, les exemples et le développement des CRD Kubernetes à des commandes spécifiques, des sorties attendues, des signaux d'échec et des décisions de récupération.

L'objectif est la sécurité opérationnelle : observer avant de modifier, limiter le rayon d'impact, utiliser des espaces réservés au lieu de secrets, vérifier le résultat et documenter comment récupérer si l'état attendu n'est pas atteint. À la fin, vous disposerez d'un environnement local reproductible, d'exemples de CRD fonctionnels et de modèles de dépannage que vous pourrez appliquer à vos propres opérateurs.

## Inventaire des versions et de l'environnement

Avant d'appliquer toute CRD, vous devez connaître votre environnement. Cette section couvre les plages de versions prises en charge, les prérequis, l'observation en lecture seule, le plus petit changement justifié et les commandes de vérification.

### Prérequis et vérifications de version

Pour le développement local de CRD, vous avez besoin d'un cluster Kubernetes. Le moyen le plus simple est d'utiliser `kind` (Kubernetes in Docker), `minikube` ou le Kubernetes de Docker Desktop. Nous utiliserons `kind` dans cet article car il est léger et scriptable. Assurez-vous d'avoir installé :

- `kubectl` (v1.25 ou version ultérieure recommandée)
- `kind` (v0.17.0 ou version ultérieure)
- Docker (pour les nœuds `kind`)
- `jq` (facultatif, pour filtrer les sorties JSON)

Vérifiez les versions :

```bash
kubectl version --client
# Exemple de sortie : Client Version: v1.27.3

kind version
# Exemple de sortie : kind v0.20.0 go1.20.4 linux/amd64

docker version --format '{{.Server.Version}}'
# Exemple de sortie : 24.0.2
```

### Observation en lecture seule

Avant d'apporter des modifications, observez l'état actuel du cluster. Si vous n'avez pas encore de cluster, créez-en un, mais vérifiez d'abord si un cluster est déjà configuré :

```bash
kubectl config current-context
# Exemple de sortie : kind-kind (s'il existe déjà) ou une erreur s'il n'y en a pas
```

Si vous voyez un contexte, inspectez ses nœuds et la version du serveur API :

```bash
kubectl get nodes
# Exemple de sortie : NAME                 STATUS   ROLES           AGE   VERSION
#                  kind-control-plane   Ready    control-plane   10m   v1.27.3

kubectl version --short
# Exemple de sortie : Client Version: v1.27.3
#                 Server Version: v1.27.3
```

S'il n'existe pas de cluster, créez-en un pour ce laboratoire :

```bash
kind create cluster --name crd-lab --image kindest/node:v1.27.3
# Exemple de sortie :
# Creating cluster "crd-lab" ...
#  ✓ Ensuring node image (kindest/node:v1.27.3) 🖼
#  ✓ Preparing nodes 📦
#  ✓ Writing configuration 📜
#  ✓ Starting control-plane 🕹️
#  ✓ Installing CNI 🔌
#  ✓ Installing StorageClass 💾
# Set kubectl context to "kind-crd-lab"
```

Maintenant, définissez le contexte sur le nouveau cluster :

```bash
kubectl config use-context kind-crd-lab
# Exemple de sortie : Switched to context "kind-crd-lab".
```

### Plus petit changement justifié

Maintenant que nous avons un cluster, nous allons ajouter une CustomResourceDefinition. Une CRD définit un nouveau type et son schéma. Nous commencerons par une CRD minimale pour une ressource fictive `Widget`.

Créez un fichier `widget-crd.yaml` :

```yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: widgets.example.com
spec:
  group: example.com
  names:
    kind: Widget
    listKind: WidgetList
    plural: widgets
    singular: widget
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                size:
                  type: integer
                  minimum: 1
                  maximum: 10
                color:
                  type: string
                  enum: [red, green, blue]
              required: [size, color]
```

Cette CRD définit une ressource à espace de noms `widgets` dans le groupe `example.com`, avec un schéma qui exige `size` (entier entre 1 et 10) et `color` (un parmi red, green, blue). Appliquez-la :

```bash
kubectl apply -f widget-crd.yaml
# Exemple de sortie : customresourcedefinition.apiextensions.k8s.io/widgets.example.com created
```

Vérifiez que la CRD est établie :

```bash
kubectl get crd widgets.example.com
# Exemple de sortie :
# NAME                    CREATED AT
# widgets.example.com     2023-07-01T12:00:00Z
```

Vérifiez ses conditions :

```bash
kubectl get crd widgets.example.com -o jsonpath='{.status.conditions[?(@.type=="Established")].status}'
# Exemple de sortie : True
```

Désormais, le cluster dispose d'un nouveau point de terminaison API. Vous pouvez lister les ressources personnalisées (actuellement aucune) :

```bash
kubectl get widgets
# Exemple de sortie : No resources found in default namespace.
```

## Chemin de configuration sûr

Dans cette section, nous étendons la CRD précédente à un scénario plus réaliste : une ressource personnalisée à espace de noms avec des valeurs par défaut de validation, des colonnes d'impression supplémentaires et un modèle de contrôleur simple simulé via un script shell. Nous couvrons également comment apporter des modifications sûres et revenir en arrière si nécessaire.

### CRD à espace de noms avec sous-ressource de statut

Ajoutez une sous-ressource de statut et des colonnes d'impression pour rendre `kubectl get` plus informatif. Modifiez `widget-crd.yaml` (ou créez un nouveau fichier `widget-crd-v2.yaml`) :

```yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: widgets.example.com
spec:
  group: example.com
  names:
    kind: Widget
    listKind: WidgetList
    plural: widgets
    singular: widget
    shortNames:
      - wd
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                size:
                  type: integer
                  minimum: 1
                  maximum: 10
                color:
                  type: string
                  enum: [red, green, blue]
              required: [size, color]
            status:
              type: object
              properties:
                phase:
                  type: string
                  enum: [Pending, Running, Failed]
                message:
                  type: string
      subresources:
        status: {}
      additionalPrinterColumns:
        - name: Size
          type: integer
          jsonPath: .spec.size
        - name: Color
          type: string
          jsonPath: .spec.color
        - name: Phase
          type: string
          jsonPath: .status.phase
```

Appliquez la CRD mise à jour :

```bash
kubectl apply -f widget-crd-v2.yaml
# Exemple de sortie : customresourcedefinition.apiextensions.k8s.io/widgets.example.com configured
```

Remarque : la mise à jour d'une CRD est un changement sûr si vous ajoutez uniquement des champs, des sous-ressources ou des colonnes d'impression. La suppression de champs ou la modification de la validation peut affecter les ressources personnalisées existantes.

Créez maintenant un exemple de Widget :

```bash
cat <<EOF | kubectl apply -f -
apiVersion: example.com/v1
kind: Widget
metadata:
  name: my-widget
  namespace: default
spec:
  size: 5
  color: blue
EOF
# Exemple de sortie : widget.example.com/my-widget created
```

Vérifiez-le avec les colonnes personnalisées :

```bash
kubectl get widgets
# Exemple de sortie :
# NAME        SIZE   COLOR   PHASE
# my-widget   5      blue
```

### Simulation d'un contrôleur avec une boucle de surveillance Bash

Un vrai contrôleur surveille les changements et met à jour le statut. Pour la sécurité, nous pouvons simuler un contrôleur minimal à l'aide d'une boucle shell qui liste tous les widgets et définit un statut si aucun n'existe. Ce n'est pas de qualité production, mais cela démontre le modèle.

Tout d'abord, créez un petit script `simple-controller.sh` :

```bash
#!/usr/bin/env bash
set -euo pipefail

while true; do
  for widget in $(kubectl get widgets -o jsonpath='{.items[*].metadata.name}'); do
    phase=$(kubectl get widget "$widget" -o jsonpath='{.status.phase}')
    if [ -z "$phase" ]; then
      echo "Setting status for $widget to Pending"
      kubectl patch widget "$widget" --type merge -p '{"status":{"phase":"Pending","message":"Processing"}}'
    fi
  done
  sleep 10
done
```

Rendez-le exécutable et lancez-le dans un terminal séparé (ou en arrière-plan) :

```bash
chmod +x simple-controller.sh
./simple-controller.sh
```

Après quelques secondes, vérifiez le statut du widget :

```bash
kubectl get widget my-widget -o yaml
# Extrait de sortie attendu :
# status:
#   message: Processing
#   phase: Pending
```

Cela démontre comment un contrôleur peut gérer les mises à jour de statut en toute sécurité à l'aide de `kubectl patch` avec stratégie de fusion.

### Retour en arrière et récupération

Si vous appliquez une modification de CRD qui casse les ressources existantes, vous pouvez parfois revenir en arrière en appliquant un manifeste précédent. Conservez les manifestes versionnés dans un référentiel Git. Par exemple, pour revenir à la v1 :

```bash
kubectl apply -f widget-crd.yaml
# Exemple de sortie : customresourcedefinition.apiextensions.k8s.io/widgets.example.com configured
```

Si une mise à jour de CRD invalide des ressources personnalisées existantes, vous devrez peut-être modifier ou supprimer ces ressources d'abord. Testez toujours dans un laboratoire local avant de toucher à la production.

## Vérification et diagnostics

La vérification ne se limite pas à `kubectl apply` ; il s'agit de confirmer que la ressource se comporte comme prévu. Cette section fournit des contrôles concrets, des signaux d'échec courants et des commandes de diagnostic.

### Vérification fonctionnelle du schéma de la CRD

Le schéma que nous avons défini applique des contraintes. Essayez de créer un widget avec une couleur invalide :

```bash
cat <<EOF | kubectl apply -f -
apiVersion: example.com/v1
kind: Widget
metadata:
  name: bad-widget
spec:
  size: 7
  color: purple
EOF
# Erreur attendue :
# The Widget "bad-widget" is invalid: spec.color: Unsupported value: "purple": supported values: "red", "green", "blue"
```

Le serveur API rejette la demande avant de la stocker. Cela confirme que la validation du schéma fonctionne.

Testez les limites de taille :

```bash
cat <<EOF | kubectl apply -f -
apiVersion: example.com/v1
kind: Widget
metadata:
  name: big-widget
spec:
  size: 11
  color: red
EOF
# Erreur attendue :
# The Widget "big-widget" is invalid: spec.size: Invalid value: 11: spec.size in body should be less than or equal to 10
```

### Commandes de diagnostic pour les problèmes de CRD

Si votre CRD ne s'établit pas, vérifiez ses conditions de statut et ses événements.

```bash
kubectl describe crd widgets.example.com
# Recherchez les événements en bas, par exemple :
# Events:
#   Type     Reason                 Age   From                            Message
#   Warning  NonStructuralSchema    2m    customresourcedefinition-api   spec.validation.openAPIV3Schema.type: Required value: must not be empty at the root
```

Signaux d'échec courants :
- `NonStructuralSchema` : Le schéma n'est pas structurel (par exemple, il manque `type: object`).
- `NoServedVersions` : La liste `versions` est vide ou toutes les versions ont `served: false`.
- `Terminating` : La CRD est bloquée en suppression en raison de finaliseurs.

Si vous voyez `Terminating`, inspectez les finaliseurs :

```bash
kubectl get crd widgets.example.com -o jsonpath='{.metadata.finalizers}'
# Exemple de sortie : ["customresourcecleanup.apiextensions.k8s.io"]
```

Si vous devez forcer la suppression (uniquement en laboratoire, pas en production), supprimez le finaliseur :

```bash
kubectl patch crd widgets.example.com -p '{"metadata":{"finalizers":[]}}' --type=merge
# Exemple de sortie : customresourcedefinition.apiextensions.k8s.io/widgets.example.com patched
```

Vérifiez ensuite s'il disparaît.

### Vérification du comportement du contrôleur

Si votre contrôleur personnalisé ne détecte pas les changements, vérifiez ses journaux. Comme notre contrôleur simple s'exécute dans un shell local, sa sortie va au terminal. Dans un vrai pod, vous utiliseriez :

```bash
kubectl logs <controller-pod-name>
```

Problèmes courants de contrôleur :
- Mauvaise configuration RBAC : Le contrôleur n'a pas la permission de lister/surveiller les widgets. Vérifiez avec `kubectl auth can-i list widgets --as=system:serviceaccount:default:controller-sa`
- CRD non servie : `kubectl get widgets` renvoie l'erreur "the server could not find the requested resource". Assurez-vous que la CRD est appliquée et que `served: true`.
- Dépréciation de l'API : Si vous utilisez une ancienne version d'API, assurez-vous d'utiliser `apiextensions.k8s.io/v1` (pas v1beta1).

## Modes de défaillance et récupération

Les opérateurs doivent prévoir les défaillances. Cette section détaille les modes de défaillance courants pour les CRD et les ressources personnalisées, ainsi que les étapes de récupération.

### Mode de défaillance : la suppression de la CRD laisse des ressources personnalisées orphelines

Lorsque vous supprimez une CRD, toutes les ressources personnalisées de ce type sont supprimées immédiatement (récupérées par le ramasse-miettes). Si vous supprimez accidentellement une CRD avec des données importantes, vous risquez de perdre des ressources. La récupération nécessite des sauvegardes.

Avant de supprimer une CRD, sauvegardez toutes les ressources personnalisées :

```bash
kubectl get widgets -o yaml > widgets-backup.yaml
```

Si vous supprimez la CRD et devez la restaurer, recréez d'abord la CRD, puis réappliquez la sauvegarde :

```bash
kubectl delete crd widgets.example.com
# Exemple de sortie : customresourcedefinition.apiextensions.k8s.io "widgets.example.com" deleted

# Maintenant, recréez la CRD (en supposant que votre manifeste est sauvegardé)
kubectl apply -f widget-crd.yaml
# Puis restaurez les ressources
kubectl apply -f widgets-backup.yaml
```

Mais notez : si les ressources personnalisées avaient des champs de statut, ils peuvent ne pas être conservés car les sauvegardes capturées avant la suppression peuvent manquer le statut. Meilleure pratique : utilisez un outil de sauvegarde comme Velero.

### Mode de défaillance : un schéma trop restrictif bloque les mises à jour

Si un schéma empêche des mises à jour légitimes en raison d'un nouveau champ, vous pouvez assouplir le schéma en ajoutant le champ comme facultatif. Exemple : ajoutez un champ `description` au schéma :

```yaml
spec:
  description:
    type: string
```

Appliquez la CRD mise à jour, et les ressources existantes ne sont pas affectées car le champ est facultatif.

Pour éviter les changements cassants, suivez ces règles :
- Ne supprimez jamais un champ du schéma.
- Ne modifiez jamais le type d'un champ.
- Ajoutez uniquement de nouveaux champs facultatifs.

Pour les changements majeurs, introduisez une nouvelle version d'API (par exemple, v2) et laissez les anciennes ressources migrer progressivement.

### Mode de défaillance : mauvaise configuration du webhook de conversion

Si vous utilisez des webhooks de conversion pour plusieurs versions, un webhook mal configuré peut casser tous les appels API pour la CRD. Pour récupérer, patchez la CRD pour supprimer la stratégie de conversion ou réparer le service webhook.

Exemple : vérifiez les paramètres de conversion :

```bash
kubectl get crd widgets.example.com -o jsonpath='{.spec.conversion}'
# Exemple de sortie : {"strategy":"Webhook","webhook":{"clientConfig":{"service":{"name":"conversion-webhook","namespace":"default","path":"/convert","port":443}},"conversionReviewVersions":["v1"]}}
```

Si le webhook est en panne, les demandes d'API peuvent échouer avec `conversion webhook not found`. Changez temporairement la stratégie en `None` (si une seule version est servie) pour rétablir l'accès :

```bash
kubectl patch crd widgets.example.com --type merge -p '{"spec":{"conversion":{"strategy":"None"}}}'
```

Remarque : cela exige que toutes les versions soient servies par le serveur API sans conversion, donc assurez-vous de n'avoir qu'une seule version ou que toutes les versions sont structurellement identiques.

### Vérification de la récupération

Après toute action de récupération, vérifiez en listant et en décrivant les ressources, en vérifiant les conditions de la CRD et en vous assurant que les contrôleurs sont en cours d'exécution.

```bash
kubectl get crd widgets.example.com -o jsonpath='{.status.conditions}' | jq .
```

## Liste de contrôle opérationnelle

Utilisez cette liste de contrôle avant et après tout changement de CRD dans votre laboratoire local (ou comme modèle pour la production). Chaque élément inclut un exemple concret.

| # | Élément de la liste | Exemple de commande / action | Résultat attendu |
|---|----------------------|------------------------------|-------------------|
| 1 | Enregistrez les versions actuelles du cluster et de la CRD | `kubectl version --short` et `kubectl get crd widgets.example.com -o yaml \| grep -A1 'name: v'` | Exemple : v1.27.3, version de CRD v1 uniquement |
| 2 | Sauvegardez les ressources personnalisées existantes | `kubectl get widgets -o yaml > widgets-backup-$(date +%Y%m%d).yaml` | Fichier enregistré (par exemple, widgets-backup-20230701.yaml) |
| 3 | Appliquez le nouveau manifeste de CRD | `kubectl apply -f widget-crd-v2.yaml` | Sortie : customresourcedefinition configured |
| 4 | Vérifiez que la CRD est établie | `kubectl get crd widgets.example.com -o jsonpath='{.status.conditions[?(@.type=="Established")].status}'` | True |
| 5 | Testez la validation du schéma avec un objet invalide | `echo '{"apiVersion":"example.com/v1","kind":"Widget","metadata":{"name":"test"},"spec":{"size":5,"color":"purple"}}' \| kubectl apply -f -` | Erreur : Unsupported value: "purple" |
| 6 | Créez une ressource personnalisée valide | Appliquez un manifeste avec size=3, color=green | Sortie : widget.example.com/test created |
| 7 | Vérifiez les champs de la ressource personnalisée via `kubectl get` | `kubectl get widgets test` | Affiche SIZE=3, COLOR=green, PHASE vide |
| 8 | Simulez la mise à jour du statut par le contrôleur | Exécutez `simple-controller.sh` | Patche le widget avec phase Pending |
| 9 | Vérifiez la mise à jour du statut | `kubectl get widget test -o jsonpath='{.status.phase}'` | Pending |
| 10 | Testez le retour en arrière | `kubectl apply -f widget-crd.yaml` (ancienne CRD) | CRD configurée sans sous-ressource de statut |
| 11 | Nettoyez les ressources | `kubectl delete crd widgets.example.com` après sauvegarde | CRD supprimée, toutes les ressources personnalisées supprimées |

## Conclusion

La configuration d'un laboratoire local de définitions de ressources personnalisées Kubernetes avec des exemples pratiques est essentielle pour le développement sûr d'opérateurs personnalisés. Cet article a fourni un guide étape par étape : inventaire de l'environnement, CRD minimale, configuration sûre avec sous-ressources et colonnes d'impression, commandes de vérification et scénarios de récupération en cas de défaillance.

Pour renforcer l'apprentissage, pratiquez ces tâches :
1. Étendez la CRD Widget avec un nouveau champ facultatif et un modèle de validation.
2. Implémentez une deuxième version d'API (v2) avec une stratégie de conversion (None ou Webhook) et testez la migration.
3. Déployez un vrai contrôleur à l'aide d'un déploiement Kubernetes et d'un compte de service avec RBAC approprié.
4. Utilisez `kubectl explain widgets` pour explorer la documentation API générée.

Rappelez-vous les principes de sécurité opérationnelle : observer avant de modifier, limiter le rayon d'impact, utiliser des espaces réservés, vérifier et documenter la récupération. Avec un laboratoire local solide, vous pouvez itérer plus rapidement et réduire les risques en production.

Prochaine étape : choisissez une vérification à faible risque de la liste de contrôle (par exemple, la validation du schéma), enregistrez l'état actuel, exécutez la vérification, comparez avec la sortie attendue et examinez les dépendances telles que les rôles RBAC pour votre contrôleur.

Un flux de travail technique fiable rend les défaillances visibles, protège les valeurs sensibles, limite les modifications à la ressource prévue et définit la vérification de récupération avant qu'un incident ne force la décision.