
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 contrôleurs d'admission se divisent en deux catégories, qui interviennent à des moments différents du cycle de vie de la requête. Cette distinction commande tout le reste : c'est elle qui dit lequel des deux répond à 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 embarque de nombreux contrôleurs d'admission activés par défaut. Rien à installer : ils font partie de l'API Server. Leur activation se règle par le drapeau --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 ou une offre managée, 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.
Ce que cette commande ne dit pas
Section intitulée « Ce que cette commande ne dit pas »Sur un cluster kubeadm, la sortie tient en une ligne, et elle induit en erreur :
--enable-admission-plugins=NodeRestrictionIl serait faux d'en conclure qu'un seul contrôleur est actif. Le drapeau s'appelle « enable », mais sa documentation dit précisément ceci : admission plugins that should be enabled in addition to default enabled ones. Il ajoute à une liste par défaut, il ne la remplace pas.
Cette liste par défaut n'est écrite dans aucun fichier du cluster : seul le binaire la connaît. Elle se lit dans son aide, et l'image étant distroless, le binaire s'appelle directement :
kubectl -n kube-system exec kube-apiserver-<nœud> -- \ kube-apiserver --help | grep -A2 'enable-admission-plugins'En Kubernetes 1.37, elle compte vingt-cinq contrôleurs :
NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition,PodSecurity, Priority, DefaultTolerationSeconds, DefaultStorageClass,StorageObjectInUseProtection, PodGroupProtection, PersistentVolumeClaimResize,RuntimeClass, CertificateApproval, CertificateSigning, ClusterTrustBundleAttest,CertificateSubjectRestriction, DefaultIngressClass, PodTopologyLabels,NodeDeclaredFeatureValidator, PodResizeValidator, MutatingAdmissionPolicy,MutatingAdmissionWebhook, ValidatingAdmissionPolicy, ValidatingAdmissionWebhook,ResourceQuotaDeux enseignements. PodSecurity y figure, ce qui explique que les Pod
Security Standards fonctionnent sans rien installer. Et MutatingAdmissionPolicy
y figure aussi, aux côtés de ValidatingAdmissionPolicy : les deux moteurs de
policy natifs sont actifs par défaut.
Pour retirer un contrôleur de cette liste, c'est --disable-admission-plugins
qu'il faut employer, jamais une omission dans --enable-admission-plugins.
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 la 1.36, toute mutation passait par un webhook externe : Kyverno, Gatekeeper, ou du code maison. Désormais, la majorité des cas simples se traite 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é.
Un webhook, c'est un service à déployer, des certificats TLS à gérer, une haute disponibilité à assurer et du code à maintenir. S'il tombe, vos déploiements sont soit bloqués, avec failurePolicy: Fail, soit acceptés sans aucune 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 règle 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, que Kubernetes pose lui-même sur chaque namespace : inutile de l'étiqueter vous-même 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 »Un contrôleur d'admission qui dysfonctionne produit des symptômes trompeurs :
une création refusée sans message clair, un Pod qui n'apparaît jamais, ou
un kubectl apply qui met trente secondes avant d'échouer sur un délai
dépassé. Rien n'indique qu'un webhook est en cause, parce que l'erreur
remonte au client sans dire quel composant l'a produite.
Les trois temps ci-dessous répondent chacun à une question : quels webhooks existent sur ce cluster, lequel a refusé cette requête précise, et pourquoi il refuse. Menez-les dans cet ordre, un webhook oublié étant la cause la plus fréquente et la moins visible.
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 joignable. Les trois causes se vérifient dans cet ordre, de la plus fréquente à la plus rare :
- Le Service existe et pointe vers des Pods, sa liste d'
EndpointSlicen'est pas vide - Le certificat de l'autorité est le bon, celui que le webhook présente
- Aucune NetworkPolicy ne bloque le trafic venant de l'API Server
kubectl get endpointslice -n <namespace> -l kubernetes.io/service-name=<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
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Kyverno : Un moteur de policies en YAML, capable de valider, muter et générer.
- Gatekeeper : L'approche OPA et Rego pour les règles d'admission les plus complexes.
- VAP vs Kyverno vs Gatekeeper : Le comparatif qui tranche entre les trois moteurs selon le contexte.