Les admission controllers sont une fonctionnalité de kubernetes qui permet d’inclure des plug-in exécutés lors d’un ajout ou modification d’une ressource dans le but de la valider avant qu’elle ne soit enregistrée dans la base de données etcd.
C’est donc un mécanisme assez puissant car il étend les fonctionnalités de l’api serveur sans avoir à modifier le code source de ce dernier via l’inclusion des appels d’un ou de plusieurs plugins les uns après les autres a chaque ajout ou modification de ressources.
Grâce à ce mécanisme, vous pouvez vous assurez que les ressources du cluster avant qu’elles ne soient prises en compte par le scheduler kubernetes respectent toujours certaines règles et configurations métier – on peut prendre l’exemple l’admission controller AlwaysPullImages qui va s’assurer que le paramètre imagePullPolicy de tous les pods ai la valeur “always”, ainsi si à la création ce paramètre est absent, il sera injecté et s’il est présent avec une autre valeur que always, elle sera modifiée.
Le schéma ci-dessous détaille le cheminement d’une requête jusqu’à l’enregistrement (on parlera des webhooks par la suite).
On en distingue 2 types :
- Validating admission controller
Les admissions controller de type validating permettent d’approuver ou pas les action sur les ressources avant qu’elles ne soient prises. Ils vont donc uniquement accepter ou rejeter la demande, et pour que la ressource soit validée et persistée il faut que TOUS les validating admission controller l’approuvent.
(vous pouvez par exemple avoir un admission controller qui n’autorise pas la création des services de type loadbalancer)
- Mutating admission controller
Ces admission controllers vont modifier la ressource et peuvent aussi injecter carrément une nouvelle ressource qui devra être enregistrée en plus de celle initiale (exemple l’injection d’un sidecar oauth-proxy container dans un pod lors de sa création)
Activation/desactivation
Vous avez ici une liste d’admission controller natives. En fonction de l’implémentation de kubernetes que vous utilisez (openshift, rancher, ..) il pourrait en avoir d’autres.
L’activation de base se fait en passant à l’api serveur la liste des plugins à charger :
kube-apiserver –admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,ResourceQuota,…
Selon l’implémentation de kubernetes, vous avez de wrapper qui vous permettent de définir les admissions controller a utiliser dans des fichiers de configurations par exemple.
Les webhooks
Dans l’optique de donner aux utilisateurs la possibilite d’écrire leurs propres webhook et donc de bénéficier de cette superbe fonctionnalité, la communauté kubernetes a mis en place 2 plugins : MutatingAdmissionWebhook et ValidatingAdmissionWebhook qui vont introduire la possibilité de faire des appels webservice vers des endpoint externes pour valider ou muter les ressources. Du coup cela nous offre la possibilité d’écrire nos propre webhook et de les enregistrer auprès de l’api serveur. Cet enregistrement se fait simplement par la création d’une custom ressource de type mutatingWebhookConfiguration ou validatingWebhookConfiguration (Exemple ci-dessous)
La plupart des produits s’exécutant sur kubernetes s’en servent d’ailleur dans leur fonctionnement. On peut citer :
- Le webhook nginx – https://kubernetes.github.io/ingress-nginx/deploy/validating-webhook/ utilisé pour valider la configuration de l’ingress avant qu’elle ne soit prise en compte par le controlleur pour une modification de la conf nginx. Cela va éviter que l’utilisateur introduit des erreurs qui pourraient empêcher nginx de fonctionner.
- Le webhook multus – https://github.com/openshift/multus-admission-controller permet de verifier les configurations reseau avant leur enregistrement
- Le webhook d’istio permet d’injecter à vos pod un sidecar controller qui va mettre en place la fonctionnalité de service mesh d’Istio (Je ferai un article complet sur Istio plus tard)
Fonctionnement des webhook
L’api server lors de chaque action fait un appel post vers votre endpoint, elle contient en payload une requête de type admissionReview:
{
“apiVersion”: “admission.k8s.io/v1”,
“kind”: “AdmissionReview”,
“request”: {
# Random uid uniquely identifying this admission call
“uid”: “705ab4f5-6393-11e8-b7cc-42010a800002”,
# Fully-qualified group/version/kind of the incoming object
“kind”: {“group”:”autoscaling”,”version”:”v1″,”kind”:”Scale”},
# Fully-qualified group/version/kind of the resource being modified
“resource”: {“group”:”apps”,”version”:”v1″,”resource”:”deployments”},
# subresource, if the request is to a subresource
“subResource”: “scale”,
# Fully-qualified group/version/kind of the incoming object in the original request to the API server.
# This only differs from `kind` if the webhook specified `matchPolicy: Equivalent` and the
# original request to the API server was converted to a version the webhook registered for.
“requestKind”: {“group”:”autoscaling”,”version”:”v1″,”kind”:”Scale”},
# Fully-qualified group/version/kind of the resource being modified in the original request to the API server.
# This only differs from `resource` if the webhook specified `matchPolicy: Equivalent` and the
# original request to the API server was converted to a version the webhook registered for.
“requestResource”: {“group”:”apps”,”version”:”v1″,”resource”:”deployments”},
# subresource, if the request is to a subresource
# This only differs from `subResource` if the webhook specified `matchPolicy: Equivalent` and the
# original request to the API server was converted to a version the webhook registered for.
“requestSubResource”: “scale”,
# Name of the resource being modified
“name”: “my-deployment”,
# Namespace of the resource being modified, if the resource is namespaced (or is a Namespace object)
“namespace”: “my-namespace”,
# operation can be CREATE, UPDATE, DELETE, or CONNECT
“operation”: “UPDATE”,
“userInfo”: {
# Username of the authenticated user making the request to the API server
“username”: “admin”,
# UID of the authenticated user making the request to the API server
“uid”: “014fbff9a07c”,
# Group memberships of the authenticated user making the request to the API server
“groups”: [“system:authenticated”,”my-admin-group”],
# Arbitrary extra info associated with the user making the request to the API server.
# This is populated by the API server authentication layer and should be included
# if any SubjectAccessReview checks are performed by the webhook.
“extra”: {
“some-key”:[“some-value1”, “some-value2”]
}
},
# object is the new object being admitted.
# It is null for DELETE operations.
“object”: {“apiVersion”:”autoscaling/v1″,”kind”:”Scale”,…},
# oldObject is the existing object.
# It is null for CREATE and CONNECT operations.
“oldObject”: {“apiVersion”:”autoscaling/v1″,”kind”:”Scale”,…},
# options contains the options for the operation being admitted, like meta.k8s.io/v1 CreateOptions, UpdateOptions, or DeleteOptions.
# It is null for CONNECT operations.
“options”: {“apiVersion”:”meta.k8s.io/v1″,”kind”:”UpdateOptions”,…},
# dryRun indicates the API request is running in dry run mode and will not be persisted.
# Webhooks with side effects should avoid actuating those side effects when dryRun is true.
# See http://k8s.io/docs/reference/using-api/api-concepts/#make-a-dry-run-request for more details.
“dryRun”: false
}
}
La réponse doit se faire avec le code http 200, Content-Type: application/json et contenir le même objet de type admissionReview en y introduisant un attribut response qui va etre les détails de validation/mutation de la ressource.
{
“apiVersion”: “admission.k8s.io/v1”,
“kind”: “AdmissionReview”,
“response”: {
“uid”: “”,
“allowed”: true
}
}
Une fois le webhook serveur développer et déployer, il doit être déclaré auprès de l’api serveur kubernetes. Cette déclaration se fait en enregistrant une custom ressource de type mutatingwebhookconfiguration ou validatingwebhook configuration.
Ci-dessous un exemple de ressource de type validatingwebhookconfiguration
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: project-validation-webhook
webhooks:
– admissionReviewVersions:
– v1beta1
– v1
clientConfig:
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURTakNDQWpLZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREEyTVRRd01nWURWUVFEREN0dmNHVnUKYzJocFpuUXRjMlZ5ZG1salpTMXpaWEoyYVc1bkxYTnBaMjVsY2tBeE5UZzFOVFkwT1RneE1CNFhEVEl3TURNegpNREV3TkRNd01Wb1hEVEl4TURNek1ERXdORE13TWxvd05qRTBNRElHQTFVRUF3d3JiM0JsYm5Ob2FXWjBMWE5sCmNuWnBZMlV0YzJWeWRtbHVaeTF6YVdkdVpYSkFNVFU0TlRVMk5EazRNVENDQVNJd0RRWUpLb1pJaHZjTkFRRUIKQlFBRGdnRVBBRENDQVFvQ2dnRUJBSjE2OG5lOXQ2Zjd5Q2hneWkzb3RyVXVTdlZuaFZ1bXVKN2dSR00wMEtURwpGZHdTODlxTlN0R0w2MEwwWVNWYlUvcmg4ejJwcmxpSVgvZjVqVDhVRkxxZ3BZc0NOY0ZsWUkzVWRIZk5VTjRICjhvOHdRdTQ3SWt3SWE2ZjdvRi9rRjlGOE9pdTBHTlFRNGN0ZTNFQm80V21hUis0U1huZjVlYXYyWENEUVVQeTUKTHQwd1JPeFJJVSswUlc4N0pxMGZGMGFMK2l4ZTZ0a0ljNFA0U0lmMUF5TmdRY0I1K3NmNnBQTENDNVFrTXMzSgpWMUVKM1FyQmQxbXJPbUxDNUJyRFBDUkF0NmsyWmxWNXRHY1JWVkphbkhwbjFyREYxOFQyYnZDRDExckNsNjhOCnJMbFRURkI0aStpZWo3eGtERnpXZjU3bGt4NVlGQlFGZ1NuVWtERmJRbThDQXdFQUFhTmpNR0V3RGdZRFZSMFAKQVFIL0JBUURBZ0trTUE4R0ExVWRFd0VCL3dRRk1BTUJBZjh3SFFZRFZSME9CQllFRk9sYWg5Y3RMNEgzUENTSQpVYzl1M21DbEtsMEVNQjhHQTFVZEl3UVlNQmFBRk9sYWg5Y3RMNEgzUENTSVVjOXUzbUNsS2wwRU1BMEdDU3FHClNJYjNEUUVCQ3dVQUE0SUJBUUNYUE8vc1M4TDNoNkpBcDVXV2N6ekt0OElQZU5oNlR2YzllOHNHc0V5bFlzMmsKbUgrVDkvVGxsbFZnUkJNVVB4M0FreDJ0TG5EWlJGTHVxTFRwWnprSFk2ZFdNWnRTVk0ybG9OTlZZT0ZxVDh1WApkcXJKcC9ITS8zdFVpYytla2dYSlVBcUZyWXZHNFlYVnMzaEJDTTFhS2toQVd6STFOYnpQSXc2am1GVWl4TUVrCmxzU3dTM3U3RFB2UlpHdjFUaGlMemMxNExsNXA3N3F5dmV0bkN3TXBIcXlNUnJ4cGJvYldVeGRHajh1VXNzRmoKcFZmNFloSVB3RlJXSklrM1IyLzVQQUxSVnk2VHh1QkJwd1Q2Und3eTRxQ2s5T1RJMGdMSWJjemRzNmplRXBJOApYUmpoSzh1cnBDMktrUzVnMjlLSHRPcm1YT2F5N3RhMnpCYkNEZnlSCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
service:
name: project-admission-controller-svc
namespace: project-admission-validation
path: /validate-project
port: 443
failurePolicy: Ignore
matchPolicy: Equivalent
name: project-validation-webhook.cluster.local
namespaceSelector: {}
objectSelector: {}
rules:
– apiGroups:
– project.openshift.io
apiVersions:
– ‘*’
operations:
– CREATE
resources:
– projectrequests
scope: ‘*’
sideEffects: None
timeoutSeconds: 10
Après l’enregistrement de ce webhook, chaque création de ressource appartement au groupe project.openshift.io va generer une demande de validation vers l’url https://project-admission-controller-svc.project-admission-validation.svc.cluster.local/validate-project
Precautions et recommandations
- Comme vous vous en doutez, chaque appel webhook va impliquer une latence supplémentaire dans le processus de traitement de vos requêtes kubernetes. Donc il est très important d’utiliser les webhook à bon escient et se rassurer qu’ils soient robustes et rapides.
- Evitez de mettre la valeur * (tous) dans les règles de filtrage(apigroups, apiversions, operations, resources) mais mettez plutôt la donnée exacte afin de ne solliciter le serveur webhook que pour les ressources appropriées
- Utilisez le paramètre namespaceSelector pour que le webhook ne soit appeler que pour certains namespace projet, car il n’est pas nécessaire de l’utiliser pour les composants de l’infra.
PS : Cela peut s’avérer fatal si vous avez par exemple un webhook défaillant qui doit valider tous les pods. Aucun pod ne pourra plus être demarrer, meme pas les composant vitaux de kubernetes s’exécutant dans des pods en cas de redémarrage.
Exemple complet et source
Pour démarrer dans cette merveilleuse aventure de développement de webhook, vous pouvez trouver ici un exemple basique.