Kubernetes开发(4)-webhook 实现拦截请求
发布日期:2021-06-29 11:37:48 浏览次数:2 分类:技术文章

本文共 8586 字,大约阅读时间需要 28 分钟。

什么是webhook

Kubernetes 通过rbac进行权限控制,实现了哪些account对哪些资源具有哪些权限的控制,但它并不是万能的, 因为rbac控制的操作权限类型是有限的,需要再进行一些细化的权限管控就无从下手了,比如需要限制一些controller只能从制定的harbor进行image的下载,比如需要限制一些controller只能使用指定范围的端口号等,所幸Kubernetes在各个方面都可以进行一些自定义的开发,而webhook就是用来实现类似需求的。

先看下官网的说明:

官方说明

官网写的很清楚,webhook本质上就是一个拦截器+回调器,它在拦截了用户的请求之后对通过以下2类webhook对请求做处理,然后再回调api-server。

  • MutatingWebhookConfiguration: 修改用户请求的配置
  • ValidatingWebhookConfiguration: 验证 用户请求的配置是否合法

这2类webhook的调用顺序可以参考下图:

拦截器

前置检查:

  • 先要看下api-server的启动参数里有没有开启准入配置
    –enable-admission-plugins=NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,由于开发环境是mac 上,用docker-desktop部署的,所以可以通过pod配置进行查看。
kubectl get pods kube-apiserver-docker-desktop -n kube-system -o yaml | grep enable-admiss

AdmissionReview

webhook本质上是一个http 服务,由api-server通过一个叫做AdmissionReview的对象来发送&接受请求的。

一个官网的AdmissionReview示例(admissionregistration.k8s.io/v1):

{
"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 }}

可以看到在结构体内的request里包含了一个完整的资源配置,而webhook 对这些资源配置进行各种的资源修改/准入的操作后,再回调api-server,同时在AdmissionReview结构体内加上一个response的结构体,类似如下:

{
"apiVersion": "admission.k8s.io/v1", "kind": "AdmissionReview", "response": {
"uid": "
", "allowed": false }}

其中response里的allowed 里的布尔值,就是webhook判断该资源是否准入的结果。

所以一个webhook的实现流程可以初步认为是api-server --(admissionreview) – webhook --(admissionreview) – api-server

webhook的代码逻辑

前面有提到webhook本质上是一个web server,详细点说的话就是一个带tls的web server,官网有对应的例子: ,根据官网的例子可以分析一下:

func main(cmd *cobra.Command, args []string) {   config := Config{   	CertFile: certFile,   	KeyFile:  keyFile,   }   http.HandleFunc("/always-allow-delay-5s", serveAlwaysAllowDelayFiveSeconds)   http.HandleFunc("/always-deny", serveAlwaysDeny)   http.HandleFunc("/add-label", serveAddLabel)   http.HandleFunc("/pods", servePods)   http.HandleFunc("/pods/attach", serveAttachingPods)   http.HandleFunc("/mutating-pods", serveMutatePods)   http.HandleFunc("/mutating-pods-sidecar", serveMutatePodsSidecar)   http.HandleFunc("/configmaps", serveConfigmaps)   http.HandleFunc("/mutating-configmaps", serveMutateConfigmaps)   http.HandleFunc("/custom-resource", serveCustomResource)   http.HandleFunc("/mutating-custom-resource", serveMutateCustomResource)   http.HandleFunc("/crd", serveCRD)   http.HandleFunc("/readyz", func(w http.ResponseWriter, req *http.Request) { w.Write([]byte("ok")) })   server := &http.Server{   	Addr:      fmt.Sprintf(":%d", port),   	TLSConfig: configTLS(config),   }   err := server.ListenAndServeTLS("", "")   if err != nil {   	panic(err)   }}

上面主函数可以看出就是个简单的 http server , 其中有很多api的 url,拿**http.HandleFunc("/mutating-pods", serveMutatePods)**举例好了

func serveMutatePods(w http.ResponseWriter, r *http.Request) {	serve(w, r, newDelegateToV1AdmitHandler(mutatePods))}func serve(w http.ResponseWriter, r *http.Request, admit admitHandler) {	var body []byte	if r.Body != nil {		if data, err := ioutil.ReadAll(r.Body); err == nil {			body = data		}	}	// verify the content type is accurate	contentType := r.Header.Get("Content-Type")	if contentType != "application/json" {		klog.Errorf("contentType=%s, expect application/json", contentType)		return	}	klog.V(2).Info(fmt.Sprintf("handling request: %s", body))	deserializer := codecs.UniversalDeserializer()	obj, gvk, err := deserializer.Decode(body, nil, nil)	if err != nil {		msg := fmt.Sprintf("Request could not be decoded: %v", err)		klog.Error(msg)		http.Error(w, msg, http.StatusBadRequest)		return	}	var responseObj runtime.Object	switch *gvk {	case v1beta1.SchemeGroupVersion.WithKind("AdmissionReview"):		requestedAdmissionReview, ok := obj.(*v1beta1.AdmissionReview)		if !ok {			klog.Errorf("Expected v1beta1.AdmissionReview but got: %T", obj)			return		}		responseAdmissionReview := &v1beta1.AdmissionReview{}		responseAdmissionReview.SetGroupVersionKind(*gvk)		responseAdmissionReview.Response = admit.v1beta1(*requestedAdmissionReview)		responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID		responseObj = responseAdmissionReview	case v1.SchemeGroupVersion.WithKind("AdmissionReview"):		requestedAdmissionReview, ok := obj.(*v1.AdmissionReview)		if !ok {			klog.Errorf("Expected v1.AdmissionReview but got: %T", obj)			return		}		responseAdmissionReview := &v1.AdmissionReview{}		responseAdmissionReview.SetGroupVersionKind(*gvk)		responseAdmissionReview.Response = admit.v1(*requestedAdmissionReview)		responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID		responseObj = responseAdmissionReview	default:		msg := fmt.Sprintf("Unsupported group version kind: %v", gvk)		klog.Error(msg)		http.Error(w, msg, http.StatusBadRequest)		return	}	klog.V(2).Info(fmt.Sprintf("sending response: %v", responseObj))	respBytes, err := json.Marshal(responseObj)	if err != nil {		klog.Error(err)		http.Error(w, err.Error(), http.StatusInternalServerError)		return	}	w.Header().Set("Content-Type", "application/json")	if _, err := w.Write(respBytes); err != nil {		klog.Error(err)	}}

这里的代码主要只是举例,所以serve方法写的比较简单,基本没做啥判断,也没有对资源配置做修改,但从基本的框架上看到其实一个简单的webhook是不难的,大致的代码逻辑就是写一个tls 的web server,然后渲染路由,路由的作用是后面注册到ValidatingWebhookConfiguration用的,不同路由有不同的准入规则,然后路由对应的处理函数就是实际对admissionreview资源配置的处理过程了。

个人公众号, 分享一些日常开发,运维工作中的日常以及一些学习感悟,欢迎大家互相学习,交流

在这里插入图片描述

转载地址:https://blog.csdn.net/zyxpaomian/article/details/117443864 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Kubernetes开发(5)-validateadmission练手
下一篇:Kubernetes开发(3)-如何感知资源的实时变化

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2024年04月07日 18时12分23秒