本文共 6062 字,大约阅读时间需要 20 分钟。
补充好基础知识后,上手实践才是最快的学习方法,所以先写一个validateadmission练练手。模拟一个生产需求,假设生产环境所有的deployment不允许单点,即副本数量不能少于2,如果少于2则不允许创建,且多个Kubernetes集群都有类似需求,那么这个场景用webhook实现就非常简单。
先上一下自己写的demo地址,可以对照查看
思路
之前有提过webhook的调用逻辑,结合当前场景,大致上是以下几个步骤:
- 创建一个tls 的webserver, 做好路由,路由绑定handler
- handler 用于接收api-server发来的admission review
- 创建一个ValidatingWebhookConfiguration,用于注册web server的url 以及 绑定证书。
- web server 解析admission review ,根据信息做判断
- 对结果进行处理,然后回调api-server
- 创建完成
大致的流程就是以上几个步骤,其实非常简单,主要搞清楚几个结构体就可以了,下面开始操作:
// 启动HTTP服务 mux := http.New() handle.InitHandle(mux) srv := &go_http.Server{ Handler: mux.GetRouter(), Addr: fmt.Sprintf(":%d", port), TLSConfig: &tls.Config{ Certificates: []tls.Certificate{cert}, }, WriteTimeout: 15 * time.Hour, ReadTimeout: 15 * time.Hour, } go srv.ListenAndServeTLS("", "")
入口就是一个简单的webserver, 指定好证书路径,以及加载mux路由即可。
func valiDateAdmission(res http.ResponseWriter, req *http.Request) { // 解析收到的报文 reqContent, err := ioutil.ReadAll(req.Body) defer req.Body.Close() if err != nil { klog.Errorf("[webhook] 请求报文解析失败") common.ReqBodyInvalid(res) return } // 定义resp的报文 var admissionResponse *admissionv1.AdmissionResponse requestedAdmissionReview := admissionv1.AdmissionReview{} // 对api-server传过来的报文做解析 _, _, err = deserializer.Decode(reqContent, nil, &requestedAdmissionReview) if err != nil { klog.Errorf("[webhook] 请求报文解析失败: %v", err) admissionResponse = &admissionv1.AdmissionResponse{ Result: &metav1.Status{ Code: http.StatusInternalServerError, Message: err.Error(), }, } // 解析成功,进行具体的拦截 } else { admissionResponse = controller.Validate(&requestedAdmissionReview) } // 构造返回的 AdmissionReview 这个结构体 responseAdmissionReview := admissionv1.AdmissionReview{} // admission/v1 responseAdmissionReview.APIVersion = requestedAdmissionReview.APIVersion responseAdmissionReview.Kind = requestedAdmissionReview.Kind if admissionResponse != nil { responseAdmissionReview.Response = admissionResponse if requestedAdmissionReview.Request != nil { // 返回相同的 UID responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID } } klog.Infof("[webhook] 回调api-server, 发送报文: %v",responseAdmissionReview.Response) // send response respBytes, err := json.Marshal(responseAdmissionReview) if err != nil { klog.Errorf("[webhook] 无法解析回调报文: %v", err) http.Error(res, fmt.Sprintf("Can't write reponse: %v", err), http.StatusBadRequest) return } klog.Info("[webhook] 准备发送回包") if _, err := res.Write(respBytes); err != nil { klog.Errorf("[webhook] 无法发送回调报文: %v", err) http.Error(res, fmt.Sprintf("Can't write reponse: %v", err), http.StatusBadRequest) }}
其中**admissionResponse=controller.Validate(&requestedAdmissionReview)**才是具体的拦截逻辑,前面的处理可以独立出来作为一个单独的装饰函数,因为都是用来解析admission review的
func Validate(ar *admissionv1.AdmissionReview) *admissionv1.AdmissionResponse { req := ar.Request var ( allowed = true code = http.StatusOK message = "" )
admission的具体结构可以看之前的文章,之前也有提到,拦截是否允许主要就是看allowed这个字段,之前的需求是判断副本数量,所以需要对admission进行解析
var dep appsv1.Deployment if err := json.Unmarshal(req.Object.Raw, &dep); err != nil { klog.Errorf("[webhook] 无法解析AdmissionReview object raw: %v", err) allowed = false code = http.StatusBadRequest return &admissionv1.AdmissionResponse{ Allowed: allowed, Result: &metav1.Status{ Code: int32(code), Message: err.Error(), }, } }
因为处理的是deployment, 所以需要对以appsv1.Deployment对结构体进行解析,然后进行判断
// 处理真正的业务逻辑 replicas := *dep.Spec.Replicas if replicas < 3 { klog.Infof("[webhook] deployment不满足最低副本数量,无法创建") allowed = false code = http.StatusForbidden message = "need 3 replicas at least, create deployment failed" }
然后打包成docker镜像,以deployment 部署在kubernetes 集群内,然后进行部署一个service, 用于api-server的调用,之后部署ValidatingWebhookConfiguration
apiVersion: admissionregistration.k8s.io/v1kind: ValidatingWebhookConfigurationmetadata: name: admission-registrywebhooks:- name: zyx.test.admission-registry rules: - apiGroups: ["apps"] apiVersions: ["v1"] operations: ["CREATE"] #resources: ["pods","deployments"] resources: ["deployments"] clientConfig: service: namespace: default name: admission-registry path: "/v1/api/validate" caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURuakNDQW9hZ0F3SUJBZ0lVT2RVeWVCenVVcFdIenFvMGczOE5XeE1SNm9jd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1p6RUxNQWtHQTFVRUJoTUNRMDR4RVRBUEJnTlZCQWdUQ0ZOb1lXNW5hR0ZwTVJFd0R3WURWUVFIRXdoVAphR0Z1WjJoaGFURU1NQW9HQTFVRUNoTURhemh6TVE4d0RRWURWUVFMRXdaVGVYTjBaVzB4RXpBUkJnTlZCQU1UCkNtdDFZbVZ5Ym1WMFpYTXdIaGNOTWpFd05USTBNRGMwT1RBd1doY05Nall3TlRJek1EYzBPVEF3V2pCbk1Rc3cKQ1FZRFZRUUdFd0pEVGpFUk1BOEdBMVVFQ0JNSVUyaGhibWRvWVdreEVUQVBCZ05WQkFjVENGTm9ZVzVuYUdGcApNUXd3Q2dZRFZRUUtFd05yT0hNeER6QU5CZ05WQkFzVEJsTjVjM1JsYlRFVE1CRUdBMVVFQXhNS2EzVmlaWEp1ClpYUmxjekNDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFLTWJLT0Q5WGMzMDhlWlQKK0J2MGYzR3QxTGtzWk1jUXNUUG5rQXpWeVRQSDdFSkpORkVoVzI0UE8yNzgvaTNWcEdPNU12MVZCcW1rcGwrSgorZ1dwZW5TSDNIbTdsOTZmckt4TmRGWG94Y3VLaWNPOHhvVUVxWCt0cVIvR0toUGYrcjBCWXFUR1o1aVVCZGdVCjU0STJIQjREc1Exc0lCY1JBUms5WVJ5ZjRLYXRZVVEzdGRuNFRyNmFhUXg5OE9Gd1hUNXpiNktINVQ2MTNKdk8KUExpV0JNWUxUZG5WZ256ZzVXeDUreDkvV0FtaTJSK0JjTllLUjY0RWgzeWJZam5NeGtGL1JEMTZPSTlFOTJNRApEaVB6U09oemplQ3RIY1NRcStpTllIQzhzSUJlRFlxL256bTFDR0hTL1NpMDdZVFBjZTBXeE5RelRpMlM2dlZXCkdrWGVPaWNDQXdFQUFhTkNNRUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHcKSFFZRFZSME9CQllFRlB0S2U2TXFoTjlLaEJ5S3Ntdy9sR3pZd25iL01BMEdDU3FHU0liM0RRRUJDd1VBQTRJQgpBUUFqNnVyVTVhS0I5MWZuQTF2dE1DVjZRVHFmenMrd1dFcHZVbElXOHdZYXVydDZPWUc1YkxFSHoyRUJJRXplCjMvcGM4ZUkvamJSU0JNZDJGbk9Sa0pVTmh5MDBBZE05MTRFUFo5Y0FIOFpGUVI1UTZCUmw1Qnl6Mk5jOHN3dGMKMmtraW0vWUJ4TkFFYnFLQjBhTmw0TW5LbTc3TzgzSXpwREN2SzBWdlRCVXJVYnlzd1l6d0lCaEZxSTl5ZUo2Zgo1U1JpemdrUHVYRG1qSlZiN092dzNTNkxRT1o4VmdJRS9wZGJvRTBsTHpmZ2pROWVDVFM0ZmRXSE9oNGp1TXJMCklqeG1pMHhOVVVkTXdPeFNvQlJKa3J3eU1Fa1FoN2swYmZROFdsRkFNenVST29RVmVraU1BNkhLeDdQOFd1UmUKYzhKRkVOVmk5VEVMTXkwWHpJNWhoOVlwCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K admissionReviewVersions: ["v1"] sideEffects: None
其中rules 就是对资源的限制条件,path是之前webserver的url, caBundle 是客户端的ca证书,cat **.pem | bare64即可。
个人公众号, 分享一些日常开发,运维工作中的日常以及一些学习感悟,欢迎大家互相学习,交流
转载地址:https://blog.csdn.net/zyxpaomian/article/details/117443963 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!