Introduction
Les webhooks d'admission Kubernetes sont des points d'extension puissants qui permettent de valider ou de muter les ressources avant leur persistance. Cependant, les tester directement sur un cluster partagé est risqué : un webhook mal configuré peut bloquer les déploiements, provoquer des défaillances en cascade, voire rendre le cluster partiellement inopérant. Un environnement de laboratoire local offre un bac à sable sûr et reproductible pour expérimenter la logique des webhooks, comprendre les modes de défaillance et gagner en confiance avant une mise en production.
Dans ce guide, nous allons configurer un laboratoire local à l'aide d'outils légers comme kind ou minikube, créer un webhook d'admission de validation et un webhook de mutation avec des exemples pratiques, vérifier leur comportement et couvrir les étapes de récupération. En suivant ce guide, vous disposerez d'un environnement pleinement fonctionnel pour développer et tester des webhooks d'admission sans risquer l'infrastructure partagée.
Une configuration claire réduit les retouches en séparant les étapes : préparation de l'environnement, gestion des certificats, déploiement du webhook et tests. Nous commencerons par un exemple restreint et mesurable pour que chaque étape soit observable.
Inventaire des versions et de l'environnement
Avant de commencer, confirmez les versions et les outils que vous utiliserez. Pour ce guide, nous supposons ce qui suit :
| Composant | Version | Rôle |
|---|---|---|
| Kubernetes | v1.27+ | Serveur d'API du cluster local |
| kind | v0.20.0+ | Cluster local léger |
| ou minikube | v1.31.0+ | Cluster local alternatif |
| OpenSSL | 3.0+ | Génération de certificats TLS |
| Go | 1.20+ | Écriture du serveur webhook |
| curl | toute version récente | Tester les points de terminaison du webhook |
Prérequis :
- Une machine avec au moins 2 CPU et 4 Go de RAM disponibles.
- Docker ou un conteneur runtime pris en charge par kind/minikube.
- Une familiarité de base avec les manifestes Kubernetes et les outils en ligne de commande.
Topologie : Nous allons créer un cluster à nœud unique où le serveur d'API s'exécute dans un conteneur (kind) ou une machine virtuelle (minikube). Le serveur webhook sera déployé sous forme de pod dans le même cluster et exposé via un service. Le serveur d'API appellera le webhook en HTTPS à l'aide d'un certificat auto-signé.
Assurez-vous que votre cluster local est opérationnel :
kind create cluster --name webhook-lab
Sortie attendue :
Creating cluster "webhook-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-webhook-lab"
Si vous utilisez minikube :
minikube start --driver=docker --kubernetes-version=v1.27.3
Vérifiez l'accès au cluster :
kubectl cluster-info
Chemin de configuration sécurisé
Nous allons maintenant implémenter deux webhooks : un webhook de mutation qui ajoute une étiquette à chaque pod, et un webhook de validation qui rejette les pods avec une valeur d'étiquette spécifique. Cette approche ciblée nous permet de vérifier chaque élément indépendamment.
Étape 1 : Générer les certificats TLS
Le serveur d'API doit faire confiance au serveur webhook. Comme nous sommes dans un laboratoire, nous pouvons utiliser une autorité de certification auto-signée et émettre un certificat de serveur. La configuration du webhook spécifiera le bundle de l'autorité de certification. Exécutez ces commandes pour générer les certificats (en utilisant un répertoire temporaire) :
mkdir -p /tmp/webhook-certs && cd /tmp/webhook-certs
# Générer la clé et le certificat de l'autorité de certification
openssl genrsa -out ca.key 2048
openssl req -x509 -new -nodes -key ca.key -subj "/CN=webhook-ca" -days 365 -out ca.crt
# Générer la clé du serveur et la CSR (demande de signature de certificat)
openssl genrsa -out server.key 2048
openssl req -new -key server.key -subj "/CN=webhook-server.default.svc" -out server.csr
# Signer le certificat du serveur avec notre autorité de certification
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365
Le CN du certificat doit correspondre au nom DNS du service : webhook-server.default.svc.
Créez un secret Kubernetes avec le certificat et la clé du serveur :
kubectl create secret tls webhook-server-cert --cert=server.crt --key=server.key
Vous aurez également besoin du certificat de l'autorité de certification encodé en base64 pour la configuration du webhook. Nous l'utiliserons lors de la création de la configuration du webhook.
Étape 2 : Déployer le serveur webhook
Créez un serveur HTTP simple qui répond aux demandes d'examen d'admission. Voici un exemple en Go. Enregistrez-le sous le nom main.go :
package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"crypto/tls"
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func mutatePods(w http.ResponseWriter, r *http.Request) {
var admissionReview admissionv1.AdmissionReview
body, _ := ioutil.ReadAll(r.Body)
if err := json.Unmarshal(body, &admissionReview); err != nil {
http.Error(w, "could not parse admission review", http.StatusBadRequest)
return
}
// Construire le patch pour ajouter une étiquette
patchType := admissionv1.PatchTypeJSONPatch
pod := corev1.Pod{}
if err := json.Unmarshal(admissionReview.Request.Object.Raw, &pod); err != nil {
http.Error(w, "could not unmarshal pod", http.StatusBadRequest)
return
}
// Si les étiquettes sont nil, créer une carte vide
if pod.Labels == nil {
pod.Labels = map[string]string{}
}
pod.Labels["webhook-lab"] = "mutated"
// Créer le patch
patch := []map[string]interface{}{
{"op": "add", "path": "/metadata/labels/webhook-lab", "value": "mutated"},
}
patchBytes, _ := json.Marshal(patch)
admissionResponse := &admissionv1.AdmissionResponse{
UID: admissionReview.Request.UID,
Allowed: true,
PatchType: &patchType,
Patch: patchBytes,
}
response := admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{
APIVersion: "admission.k8s.io/v1",
Kind: "AdmissionReview",
},
Response: admissionResponse,
}
respBytes, _ := json.Marshal(response)
w.Header().Set("Content-Type", "application/json")
w.Write(respBytes)
}
func main() {
http.HandleFunc("/mutate", mutatePods)
server := &http.Server{
Addr: ":8443",
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{loadX509KeyPair("/etc/webhook/certs/tls.crt", "/etc/webhook/certs/tls.key")},
},
}
fmt.Println("Starting webhook server on :8443")
server.ListenAndServeTLS("", "")
}
func loadX509KeyPair(certFile, keyFile string) tls.Certificate {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
panic(err)
}
return cert
}
Construisez l'image Docker et chargez-la dans kind :
docker build -t webhook-server:latest .
kind load docker-image webhook-server:latest --name webhook-lab
Créez un déploiement et un service :
# webhook-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: webhook-server
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: webhook-server
template:
metadata:
labels:
app: webhook-server
spec:
containers:
- name: server
image: webhook-server:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8443
volumeMounts:
- name: certs
mountPath: /etc/webhook/certs
readOnly: true
volumes:
- name: certs
secret:
secretName: webhook-server-cert
# webhook-service.yaml
apiVersion: v1
kind: Service
metadata:
name: webhook-server
namespace: default
spec:
selector:
app: webhook-server
ports:
- port: 443
targetPort: 8443
Appliquez :
kubectl apply -f webhook-deployment.yaml
kubectl apply -f webhook-service.yaml
Vérifiez que le pod est en cours d'exécution :
kubectl get pods -l app=webhook-server
Sortie attendue :
NAME READY STATUS RESTARTS AGE
webhook-server-79c4f4c9b7-9x2kq 1/1 Running 0 5s
Étape 3 : Créer la MutatingWebhookConfiguration
Créez une configuration qui indique au serveur d'API d'appeler notre webhook lors de la création de pods. Remplacez CA_BUNDLE par le base64 de ca.crt :
CA_BUNDLE=$(cat /tmp/webhook-certs/ca.crt | base64 | tr -d '\n')
# mutating-webhook.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: pod-mutator
webhooks:
- name: pod-mutator.default.svc
clientConfig:
service:
name: webhook-server
namespace: default
path: "/mutate"
port: 443
caBundle: ${CA_BUNDLE}
rules:
- operations: ["CREATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
admissionReviewVersions: ["v1"]
sideEffects: None
failurePolicy: Fail
Appliquez :
envsubst < mutating-webhook.yaml | kubectl apply -f -
Étape 4 : Tester le webhook de mutation
Créez un pod simple :
kubectl run test-pod --image=nginx
Vérifiez les étiquettes :
kubectl get pod test-pod --show-labels
La sortie attendue inclut webhook-lab=mutated.
Étape 5 : Ajouter un webhook de validation
Ajoutez maintenant un webhook de validation qui rejette les pods si l'étiquette webhook-lab est définie sur mutated (pour démontrer le refus). Vous pouvez étendre le serveur ou en créer un séparé. Par souci de concision, supposez un serveur similaire avec un point de terminaison /validate. La configuration :
# validating-webhook.yaml
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: pod-validator
webhooks:
- name: pod-validator.default.svc
clientConfig:
service:
name: webhook-server
namespace: default
path: "/validate"
port: 443
caBundle: ${CA_BUNDLE}
rules:
- operations: ["CREATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
admissionReviewVersions: ["v1"]
sideEffects: None
failurePolicy: Fail
Appliquez et testez : la création du pod doit être refusée avec un message.
Vérification et diagnostics
Pour vérifier que les webhooks fonctionnent correctement, effectuez ces contrôles :
- Vérifiez que les configurations de webhook existent :
kubectl get mutatingwebhookconfigurations, validatingwebhookconfigurations
- Inspectez les journaux du webhook :
kubectl logs deployment/webhook-server
Vous devriez voir les demandes d'examen d'admission entrantes.
- Utilisez
kubectl apply --dry-run=serverpour voir si le webhook est appelé sans persister l'objet.
- Testez avec un dry-run :
kubectl run test-pod --image=nginx --dry-run=server
- Utilisez
kubectl describesur la configuration du webhook pour voir le bundle CA et les règles.
Si le webhook n'est pas appelé, vérifiez les journaux du serveur d'API (pour kind, voir les journaux du conteneur) pour des messages d'erreur comme x509: certificate signed by unknown authority ou connection refused.
Modes de défaillance et récupération
Les webhooks d'admission peuvent causer des problèmes. Modes de défaillance courants :
- Serveur webhook indisponible : Si
failurePolicyestFail, toutes les requêtes correspondantes seront rejetées. Pour récupérer, vous pouvez soit supprimer la configuration du webhook, soit mettre à l'échelle le déploiement pour restaurer la disponibilité. Si le serveur d'API ne peut pas être joint pour supprimer le webhook (peu probable mais possible), vous devrez peut-être modifier le manifeste du serveur d'API pour désactiver les plugins d'admission. - Bundle CA incorrect : Le serveur d'API rejettera l'appel du webhook, et avec
failurePolicy: Fail, la création de ressources échouera. Corrigez le bundle CA dans la configuration du webhook. - Délai d'attente : Si le serveur webhook ne répond pas dans le délai imparti (10 secondes par défaut), le serveur d'API appliquera la politique d'échec.
Étapes de récupération :
# Désactiver temporairement le webhook en supprimant sa configuration
kubectl delete mutatingwebhookconfiguration pod-mutator
kubectl delete validatingwebhookconfiguration pod-validator
Ensuite, corrigez le problème sous-jacent et réappliquez.
Exemple de retour en arrière : si un nouveau webhook cause des problèmes, revenez à une version précédente en réappliquant l'ancienne configuration ou en utilisant kubectl apply avec le manifeste connu comme bon.
Pièges courants et comment les éviter
Au-delà des modes de défaillance déjà couverts, plusieurs autres pièges peuvent vous faire trébucher lorsque vous travaillez avec des webhooks d'admission. Être conscient de ceux-ci vous fera gagner du temps de débogage.
- Versions
admissionReviewVersionsincompatibles : Le serveur d'API envoie les demandes d'admission dans les versions listées dans la configuration du webhook. Si votre serveur webhook ne comprend que v1beta1 mais que la configuration liste v1, la demande échouera. Déclarez toujours au moins v1 et assurez-vous que votre serveur la gère. Testez avec un point de terminaison d'écho simple pour voir quelle version est envoyée.
- Oublier
sideEffects: None: Si votre webhook n'a pas d'effets secondaires (il ne modifie pas l'état externe), définissezsideEffects: None. Cela permet au serveur d'API de l'appeler pendant les demandes de dry-run et l'empêche d'être ignoré lorsque la fonctionnalité dry-run est utilisée. Si votre webhook a des effets secondaires, documentez-les et envisagezsideEffects: NoneOnDryRun.
- Règles trop larges : Faire correspondre toutes les ressources avec
/peut amener le webhook à interférer avec les composants système. Limitez vos règles au minimum nécessaire, comme des ressources, des groupes et des opérations spécifiques. Par exemple, au lieu de faire correspondre tous les pods, faites correspondre uniquement les pods avec une étiquette spécifique ou dans un espace de noms particulier.
- Espace de noms manquant dans clientConfig : Si vous omettez
namespacedansclientConfig.service, le serveur d'API suppose le même espace de noms que la configuration du webhook, ce qui n'est souvent pas là où vit votre service. Spécifiez toujours l'espace de noms explicitement.
- Renouvellement du certificat : Les certificats auto-signés ont une date d'expiration. Si le certificat expire, le webhook échouera. En production, utilisez une solution de gestion des certificats comme cert-manager avec renouvellement automatique. Dans le laboratoire, notez la date d'expiration et régénérez si nécessaire.
- Ignorer les délais d'attente des webhooks : Le délai par défaut est de 10 secondes, mais certaines opérations peuvent prendre plus de temps. Définissez un
timeoutSecondsapproprié dans la configuration du webhook et assurez-vous que votre webhook répond dans ce délai. Si votre webhook a besoin de plus de temps, ajustez le délai, mais soyez prudent quant au ralentissement de toutes les requêtes correspondantes.
Liste de contrôle des opérations
Utilisez cette liste de contrôle pour votre laboratoire local et votre future utilisation en production. Chaque tâche a un seul responsable redevable et une fréquence de révision. Dans un cadre de production, attribuez un responsable tel que le responsable de l'ingénierie de plateforme.
| Tâche | Commande / Action | Résultat attendu | Responsable | Fréquence de révision |
|---|---|---|---|---|
| Vérifier que les pods du webhook s'exécutent | kubectl get pods -l app=webhook-server | 1/1 En cours d'exécution | Ingénieur DevOps de garde | À chaque déploiement, ou hebdomadaire |
| Vérifier les configurations de webhook | kubectl get mutatingwebhookconfigurations, validatingwebhookconfigurations | La liste montre les configurations | Responsable de plateforme | Mensuel |
| Tester le webhook de mutation | Créer un pod et inspecter les étiquettes | Étiquette ajoutée | Développeur | À chaque modification |
| Tester le webhook de validation | Créer un pod avec une étiquette interdite | Demande rejetée | Développeur | À chaque modification |
| Surveiller les journaux du webhook | kubectl logs deployment/webhook-server | Demandes d'admission journalisées | SRE | Quotidien pendant un incident, hebdomadaire sinon |
| Vérifier les journaux du serveur d'API pour les erreurs | Variable selon le cluster | Aucune erreur TLS ou de connexion | Administrateur de cluster | Hebdomadaire |
| Tester la politique d'échec | Mettre à l'échelle le déploiement du webhook à 0, créer un pod | La création du pod échoue avec une erreur | Ingénieur QA | Avant chaque version |
| Restaurer le webhook | Remettre à l'échelle à 1 | La création du pod réussit | Ingénieur QA | Après le test d'échec |
Conclusion
Vous disposez maintenant d'un laboratoire local fonctionnel pour les webhooks d'admission Kubernetes. Vous avez appris à générer des certificats, déployer un serveur webhook, créer des configurations de webhooks de mutation et de validation, vérifier le comportement et récupérer après des défaillances. Utilisez cet environnement pour développer et tester en toute sécurité une logique d'admission plus complexe avant de la déployer en production. Commencez par de petits changements mesurables, et testez toujours les scénarios de défaillance pour comprendre l'impact. Les compétences que vous avez pratiquées ici—configuration TLS, enregistrement de webhook et récupération après défaillance—sont directement transférables aux environnements de production, où des tests minutieux et un plan de retour en arrière solide sont essentiels.