
Les Admission Controllers sont des plugins qui interceptent les requêtes vers l'API Kubernetes après authentification et autorisation, mais avant la persistance dans etcd. Ils peuvent valider (accepter ou refuser) ou muter (modifier) les ressources. Ce mécanisme est la base du policy-as-code sur Kubernetes.
Comment fonctionnent les Admission Controllers ?
Section intitulée « Comment fonctionnent les Admission Controllers ? »Quand vous créez une ressource avec kubectl apply, la requête traverse plusieurs étapes :
Les 2 types d'admission controllers
Section intitulée « Les 2 types d'admission controllers »Les admission controllers se divisent en deux catégories qui interviennent à des moments différents du cycle de vie de la requête. Comprendre cette distinction est essentiel pour savoir quel type utiliser selon votre besoin.
| Type | Rôle | Exemple |
|---|---|---|
| Mutating | Modifie la ressource avant persistance | Injecter un sidecar, ajouter des labels |
| Validating | Accepte ou refuse la ressource | Bloquer les pods privileged, exiger des labels |
Exemple concret : sur la création d'un pod, un Mutating Admission Controller ajoute le label team: backend à tous les pods d'un namespace donné. Un Validating Admission Controller vérifie ensuite que ce label est présent. Si le développeur l'avait oublié dans son manifeste, la mutation l'a déjà posé et la validation passe : c'est l'ordre d'exécution qui rend cette combinaison utilisable.
Les Admission Controllers intégrés
Section intitulée « Les Admission Controllers intégrés »Kubernetes inclut de nombreux admission controllers activés par défaut. Vous n'avez pas besoin de les installer : ils font partie de l'API Server. Leur activation se contrôle via le flag --enable-admission-plugins au démarrage de l'API Server.
Pour la certification CKS et la sécurité en production, certains contrôleurs sont particulièrement importants. Voyons lesquels et pourquoi.
Contrôleurs de sécurité
Section intitulée « Contrôleurs de sécurité »Ces contrôleurs protègent votre cluster contre les configurations dangereuses et les escalades de privilèges.
| Contrôleur | Type | Rôle | Activé par défaut |
|---|---|---|---|
| PodSecurity | Validating | Applique les Pod Security Standards | ✅ |
| NodeRestriction | Validating | Limite ce que les kubelets peuvent modifier | ✅ |
| ServiceAccount | Mutating | Injecte le token ServiceAccount | ✅ |
| AlwaysPullImages | Mutating | Force imagePullPolicy: Always | ❌ |
Pourquoi PodSecurity est-il si important ? Il refuse les pods dont le securityContext dépasse le niveau autorisé sur le namespace. Sans lui, n'importe quel compte disposant du droit de créer un pod peut demander le mode privileged, qui donne au conteneur un accès complet aux périphériques et au noyau du nœud hôte, donc à tous les autres pods qui y tournent.
Pourquoi NodeRestriction ? Ce contrôleur empêche un nœud compromis de modifier des ressources qui ne lui appartiennent pas. Par exemple, un kubelet malveillant ne peut pas modifier les pods d'un autre nœud.
Contrôleurs de ressources
Section intitulée « Contrôleurs de ressources »Ces contrôleurs gèrent les quotas et les limites pour éviter qu'un namespace ne consomme toutes les ressources du cluster.
| Contrôleur | Type | Rôle | Activé par défaut |
|---|---|---|---|
| LimitRanger | Mutating + Validating | Applique les limites par défaut | ✅ |
| ResourceQuota | Validating | Vérifie les quotas namespace | ✅ |
| DefaultStorageClass | Mutating | Ajoute la StorageClass par défaut | ✅ |
LimitRanger en action : Si vous créez un pod sans spécifier de resources.limits, LimitRanger peut automatiquement ajouter des limites par défaut définies dans un objet LimitRange. C'est une mutation qui évite les pods sans limites qui pourraient consommer toutes les ressources d'un nœud.
ResourceQuota : Ce contrôleur valide que la création d'une ressource ne dépasse pas les quotas du namespace. Si votre namespace a une limite de 10 pods et que vous essayez d'en créer un 11ème, ResourceQuota le refuse.
Voir les contrôleurs actifs
Section intitulée « Voir les contrôleurs actifs »La liste effective vit dans les arguments de démarrage de l'API Server, pas dans une ressource Kubernetes interrogeable. Deux chemins pour la lire selon votre accès. Depuis kubectl, ciblez le pod par son label, pas par un motif de nom : kube-apiserver-* serait développé par votre shell contre les fichiers du répertoire courant et ne parviendrait jamais à kubectl. Depuis un nœud de contrôle kubeadm, le manifeste statique donne la même information sans passer par l'API.
# Contrôleurs activés, vus depuis l'API (cluster kubeadm)kubectl get pods -n kube-system -l component=kube-apiserver \ -o jsonpath='{.items[*].spec.containers[*].command}' | tr ' ' '\n' | \ grep admission-plugins
# Ou via le manifeste statique, sur un nœud du plan de contrôlegrep admission-plugins /etc/kubernetes/manifests/kube-apiserver.yamlSur une distribution qui n'expose pas l'API Server comme un pod statique (k3s, Talos, offres managées), aucune des deux commandes ne renvoie de résultat : la configuration est portée par le service systemd ou par le fournisseur, et n'est pas toujours modifiable.
Pod Security Admission (PSA)
Section intitulée « Pod Security Admission (PSA) »Le Pod Security Admission est l'admission controller qui applique les Pod Security Standards. Il est activé par défaut depuis Kubernetes 1.25.
Configuration
Section intitulée « Configuration »PSA se configure via des labels sur les namespaces, ce qui évite tout objet de policy supplémentaire. Trois modes coexistent et se cumulent : enforce refuse le pod, audit l'accepte en consignant une entrée dans le journal d'audit, warn l'accepte en renvoyant un avertissement à kubectl. La méthode de déploiement sans casse consiste à poser d'abord warn et audit au niveau visé, observer ce qui remonte, puis seulement ajouter enforce.
Un point à connaître sur enforce-version : la valeur latest suit les évolutions du standard, donc un pod accepté aujourd'hui peut être refusé après une mise à jour du cluster. Épinglez une version (v1.31 par exemple) sur les namespaces de production si vous voulez un comportement stable dans le temps.
apiVersion: v1kind: Namespacemetadata: name: production labels: # Niveau à appliquer pod-security.kubernetes.io/enforce: restricted # Version du standard (latest ou v1.xx) pod-security.kubernetes.io/enforce-version: latest # Mode audit (logs uniquement) pod-security.kubernetes.io/audit: restricted # Mode warn (affiche un warning) pod-security.kubernetes.io/warn: restrictedExemptions
Section intitulée « Exemptions »Certains workloads système nécessitent des privilèges (CNI, monitoring). Exemptez-les dans la configuration :
# /etc/kubernetes/psa.yaml (fichier de config API Server)apiVersion: apiserver.config.k8s.io/v1kind: AdmissionConfigurationplugins:- name: PodSecurity configuration: apiVersion: pod-security.admission.config.k8s.io/v1 kind: PodSecurityConfiguration defaults: enforce: "baseline" enforce-version: "latest" exemptions: usernames: [] runtimeClasses: [] namespaces: - kube-system - kube-node-leaseWebhooks personnalisés
Section intitulée « Webhooks personnalisés »Au-delà des contrôleurs intégrés, vous pouvez créer vos propres admission controllers via des webhooks.
ValidatingWebhookConfiguration
Section intitulée « ValidatingWebhookConfiguration »Cet objet ne contient aucune logique : il décrit où joindre votre service et quand l'appeler. Le code de validation vit dans le service référencé par clientConfig, qui doit répondre en HTTPS avec un certificat validé par le caBundle. Trois champs déterminent le comportement en production. rules restreint le déclenchement aux ressources et opérations concernées, sans quoi vous ajoutez un appel réseau à chaque requête du cluster. sideEffects: None déclare que le webhook ne modifie rien en dehors de la réponse, ce que l'API Server exige pour les requêtes en --dry-run. Et failurePolicy décide de ce qui se passe quand votre service ne répond pas.
apiVersion: admissionregistration.k8s.io/v1kind: ValidatingWebhookConfigurationmetadata: name: require-labelswebhooks:- name: require-labels.example.com clientConfig: service: name: label-validator namespace: admission path: /validate caBundle: ${CA_BUNDLE} rules: - apiGroups: [""] apiVersions: ["v1"] operations: ["CREATE", "UPDATE"] resources: ["pods"] admissionReviewVersions: ["v1"] sideEffects: None failurePolicy: Fail # Refuse si le webhook est indisponibleMutatingWebhookConfiguration
Section intitulée « MutatingWebhookConfiguration »La structure est identique, mais le service renvoie cette fois un patch JSON appliqué à la ressource avant persistance. C'est le mécanisme derrière l'injection de sidecar des maillages de services. Deux différences avec l'exemple précédent méritent d'être relevées. Les operations se limitent à CREATE : muter sur UPDATE conduit à réinjecter indéfiniment le même contenu. Et le failurePolicy vaut ici Ignore, un choix cohérent pour une injection de confort, à ne pas reproduire pour une mutation portant sur la sécurité.
apiVersion: admissionregistration.k8s.io/v1kind: MutatingWebhookConfigurationmetadata: name: inject-sidecarwebhooks:- name: inject-sidecar.example.com clientConfig: service: name: sidecar-injector namespace: admission path: /mutate caBundle: ${CA_BUNDLE} rules: - apiGroups: [""] apiVersions: ["v1"] operations: ["CREATE"] resources: ["pods"] admissionReviewVersions: ["v1"] sideEffects: None failurePolicy: Ignore # Accepte si le webhook est indisponibleValidatingAdmissionPolicy (VAP) et MutatingAdmissionPolicy (MAP)
Section intitulée « ValidatingAdmissionPolicy (VAP) et MutatingAdmissionPolicy (MAP) »Kubernetes propose désormais un duo natif de policies déclaratives en CEL, sans webhook à maintenir :
ValidatingAdmissionPolicy(GA depuis 1.30), accepte ou refuse une ressource selon une expression CEL.MutatingAdmissionPolicy(GA depuis 1.36), modifie la ressource entrante (ajout de label, desecurityContextpar défaut, etc.) en CEL.
Avant 1.36, il fallait passer par un webhook externe (Kyverno, Gatekeeper, code maison) pour toute mutation. Désormais, la majorité des cas simples se traitent en quelques lignes de YAML.
Avantages vs Webhooks
Section intitulée « Avantages vs Webhooks »Pourquoi utiliser VAP plutôt que des webhooks classiques ? La réponse tient en deux mots : simplicité et fiabilité.
Avec un webhook, vous devez déployer un service, gérer ses certificats TLS, assurer sa haute disponibilité, et maintenir le code. Si le webhook est down, vos déploiements peuvent être bloqués (avec failurePolicy: Fail) ou passer sans validation (avec Ignore).
VAP élimine ces problèmes : la validation s'exécute directement dans l'API Server, sans appel réseau externe.
| Critère | Webhooks | VAP + MAP |
|---|---|---|
| Dépendance externe | Oui (service à maintenir) | Non (natif) |
| Latence | Variable (appel réseau) | Minimale |
| Langage | Go, Python, etc. | CEL |
| Complexité | Élevée | Faible à moyenne |
| Validation depuis K8s | 1.9+ | 1.30+ (stable) |
| Mutation depuis K8s | 1.9+ | 1.36+ (stable) |
La contrepartie ? CEL est moins flexible que du code Go ou Python. Pour des validations simples ("ce champ doit exister", "cette valeur est interdite") et des mutations simples (ajout d'un label, valeur par défaut), VAP et MAP sont parfaits. Pour de la génération de ressources (NetworkPolicy auto-créée), de la vérification de signature d'image (Cosign) ou de la logique métier complexe, les webhooks restent nécessaires.
Exemple VAP : interdire les pods privileged
Section intitulée « Exemple VAP : interdire les pods privileged »Une policy VAP se déploie toujours en deux objets. La ValidatingAdmissionPolicy porte la règle, le ValidatingAdmissionPolicyBinding décide où elle s'applique. Cette séparation permet d'écrire la règle une fois et de l'activer progressivement, namespace par namespace, en changeant seulement le binding.
Le piège de cet exemple est dans l'expression CEL. En CEL, lire un champ absent lève une erreur au lieu de renvoyer une valeur vide. Or la grande majorité des conteneurs n'ont pas de securityContext. Une expression écrite naïvement c.securityContext.privileged == true échoue donc sur un pod parfaitement normal avec no such key: securityContext, et comme failurePolicy vaut Fail, la policy bloque toute création de pod dans le namespace ciblé. La parade est l'opérateur has(), qui teste la présence du champ avant de le lire.
L'expression ci-dessous couvre aussi les initContainers : un conteneur d'initialisation en mode privileged obtient exactement les mêmes accès au nœud qu'un conteneur applicatif.
apiVersion: admissionregistration.k8s.io/v1kind: ValidatingAdmissionPolicymetadata: name: deny-privilegedspec: failurePolicy: Fail matchConstraints: resourceRules: - apiGroups: [""] apiVersions: ["v1"] operations: ["CREATE", "UPDATE"] resources: ["pods"] validations: - expression: >- !(object.spec.containers + (has(object.spec.initContainers) ? object.spec.initContainers : [])) .exists(c, has(c.securityContext) && has(c.securityContext.privileged) && c.securityContext.privileged) message: "Les conteneurs privileged sont interdits"---apiVersion: admissionregistration.k8s.io/v1kind: ValidatingAdmissionPolicyBindingmetadata: name: deny-privileged-bindingspec: policyName: deny-privileged validationActions: - Deny matchResources: namespaceSelector: matchExpressions: - key: environment operator: In values: ["production"]Exemple MAP : injecter un label par défaut
Section intitulée « Exemple MAP : injecter un label par défaut »Cette policy ajoute automatiquement le label team: platform sur tout pod créé dans le namespace ciblé. Le mécanisme diffère de celui d'un webhook mutant : au lieu de renvoyer un patch, vous décrivez en CEL un objet partiel que l'API Server fusionne avec la ressource entrante, selon la même sémantique que kubectl apply. Les champs non mentionnés restent intacts, vous ne risquez donc pas d'écraser les labels déjà posés par le manifeste.
Deux champs conditionnent le résultat. reinvocationPolicy: Never évite que la policy soit réévaluée après les mutations d'autres contrôleurs, ce qui suffit ici puisque l'opération est idempotente. Et le binding cible le namespace par le label kubernetes.io/metadata.name, posé automatiquement par Kubernetes sur chaque namespace : inutile de labelliser vous-même le namespace pour ce cas précis.
apiVersion: admissionregistration.k8s.io/v1kind: MutatingAdmissionPolicymetadata: name: add-team-labelspec: failurePolicy: Fail reinvocationPolicy: Never matchConstraints: resourceRules: - apiGroups: [""] apiVersions: ["v1"] operations: ["CREATE"] resources: ["pods"] mutations: - patchType: ApplyConfiguration applyConfiguration: expression: | Object{ metadata: Object.metadata{ labels: {"team": "platform"} } }---apiVersion: admissionregistration.k8s.io/v1kind: MutatingAdmissionPolicyBindingmetadata: name: add-team-label-bindingspec: policyName: add-team-label matchResources: namespaceSelector: matchLabels: kubernetes.io/metadata.name: map-testLe guide ValidatingAdmissionPolicy détaille les deux types patchType (ApplyConfiguration et JSONPatch) ainsi que la stratégie de déploiement progressif.
Comparatif des solutions Policy-as-Code
Section intitulée « Comparatif des solutions Policy-as-Code »Plusieurs outils permettent d'implémenter du policy-as-code sur Kubernetes. Comment choisir ? Cela dépend de votre contexte : préparation CKS, expertise de l'équipe, besoins fonctionnels.
| Solution | Type | Langage | Courbe d'apprentissage | CKS |
|---|---|---|---|---|
| Pod Security Admission | Intégré | Labels | Facile | ✅ |
| ValidatingAdmissionPolicy | Intégré | CEL | Moyenne | ✅ |
| MutatingAdmissionPolicy (1.36+) | Intégré | CEL | Moyenne | ✅ |
| Kyverno | Add-on | YAML | Facile | ⚠️ |
| OPA Gatekeeper | Add-on | Rego | Difficile | ⚠️ |
Comment lire ce tableau ?
- PSA est le point de départ : activez-le sur tous vos namespaces avec au minimum le niveau
baseline. C'est rapide et efficace. - VAP + MAP complètent PSA pour des règles personnalisées que PSA ne couvre pas (interdire certaines images, exiger des annotations, injecter un label, fixer un
securityContextpar défaut). Depuis 1.36 le duo couvre validation et mutation sans webhook. - Kyverno et Gatekeeper apportent ce que VAP+MAP ne couvrent pas : génération automatique de ressources (NetworkPolicy, Secret, ConfigMap), vérification de signatures d'images Cosign, rapports de conformité détaillés. Ils ajoutent en contrepartie de la complexité opérationnelle.
Diagnostic et dépannage
Section intitulée « Diagnostic et dépannage »Voir les webhooks configurés
Section intitulée « Voir les webhooks configurés »Devant un cluster où les créations de ressources échouent sans raison évidente, commencez par inventorier les webhooks : ce sont des objets cluster-scoped souvent installés par un chart Helm, donc invisibles dans le namespace où le problème se manifeste. Un maillage de services, un opérateur de certificats ou un moteur de policy en pose chacun au moins un. Dans la sortie de describe, regardez en priorité le failurePolicy et le champ Service : un webhook en Fail pointant vers un service sans endpoint bloque tout ce que couvrent ses rules.
# Webhooks de validationkubectl get validatingwebhookconfigurations
# Webhooks de mutationkubectl get mutatingwebhookconfigurations
# Détail d'un webhookkubectl describe validatingwebhookconfiguration <name>Erreurs courantes
Section intitulée « Erreurs courantes »"connection refused" ou timeout
Le webhook n'est pas accessible. Vérifiez :
- Le service existe et a des endpoints
- Le certificat CA est correct
- Le NetworkPolicy autorise le trafic depuis l'API Server
kubectl get endpoints -n <namespace> <service-name>kubectl describe service -n <namespace> <service-name>"failed to call webhook"
Le webhook retourne une erreur. Vérifiez les logs :
kubectl logs -n <namespace> -l app=<webhook-app>À retenir
Section intitulée « À retenir »- Admission Controllers : plugins qui interceptent les requêtes API avant persistance
- Mutating (modifie) → Validating (accepte/refuse) → etcd
- Pod Security Admission : contrôleur intégré pour les Pod Security Standards
- ValidatingAdmissionPolicy (K8s 1.30+) et MutatingAdmissionPolicy (K8s 1.36+) : duo natif validation + mutation en CEL, sans webhook
- Webhooks : pour la génération de ressources, la vérification de signatures et la logique métier complexe
- CKS : focus sur PSA, VAP et MAP (natives), pas sur Kyverno/Gatekeeper
Testez vos connaissances
Section intitulée « Testez vos connaissances »Ces questions reprennent uniquement ce qui vient d'être expliqué. Les points sur l'ordre d'exécution et sur le failurePolicy reviennent régulièrement à la CKS, ils méritent une seconde lecture si vous hésitez.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
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
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications