k8s与Admission--webhook admission
前言
Kubernetes 对 API 访问提供了三种安全访问控制措施:认证、授权和 Admission Control。认证解决用户是谁的问题,授权解决用户能做什么的问题,Admission Control 则是资源管理方面的作用。通过合理的权限管理,能够保证系统的安全可靠。
本文主要讲讲Admission中ValidatingAdmissionWebhook和MutatingAdmissionWebhook。
AdmissionWebhook
我们知道k8s在各个方面都具备可扩展性,比如通过cni实现多种网络模型,通过csi实现多种存储引擎,通过cri实现多种容器运行时等等。而AdmissionWebhook就是另外一种可扩展的手段。 除了已编译的Admission插件外,可以开发自己的Admission插件作为扩展,并在运行时配置为webhook。
Admission webhooks是HTTP回调,它接收Admission请求并对它们做一些事情。可以定义两种类型的Admission webhook,ValidatingAdmissionWebhook和MutatingAdmissionWebhook。
如果启用了MutatingAdmission,当开始创建一种k8s资源对象的时候,创建请求会发到你所编写的controller中,然后我们就可以做一系列的操作。比如我们的场景中,我们会统一做一些功能性增强,当业务开发创建了新的deployment,我们会执行一些注入的操作,比如敏感信息aksk,或是一些优化的init脚本。
而与此类似,只不过ValidatingAdmissionWebhook 是按照你自定义的逻辑是否允许资源的创建。比如,我们在实际生产k8s集群中,处于稳定性考虑,我们要求创建的deployment 必须设置request和limit。
如何实现自己的 AdmissionWebhook Server
前提条件
- k8s版本需至少v1.9
- 确保启用了 MutatingAdmissionWebhook and ValidatingAdmissionWebhook admission controllers
- 确定 启用了admissionregistration.k8s.io/v1beta1
写一个 admission webhook server
官方提供了有个demo。大家可以详细研究,核心思想就是:
webhook处理apiservers发送的AdmissionReview请求,并将其决定作为AdmissionReview对象发送回去。
package main import ( "encoding/json" "flag" "fmt" "io/ioutil" "net/http" "k8s.io/api/admission/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/klog" // TODO: try this library to see if it generates correct json patch // https://github.com/mattbaird/jsonpatch ) // toAdmissionResponse is a helper function to create an AdmissionResponse // with an embedded error func toAdmissionResponse(err error) *v1beta1.AdmissionResponse { return &v1beta1.AdmissionResponse{ Result: &metav1.Status{ Message: err.Error(), }, } } // admitFunc is the type we use for all of our validators and mutators type admitFunc func(v1beta1.AdmissionReview) *v1beta1.AdmissionResponse // serve handles the http portion of a request prior to handing to an admit // function func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) { 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)) // The AdmissionReview that was sent to the webhook requestedAdmissionReview := v1beta1.AdmissionReview{} // The AdmissionReview that will be returned responseAdmissionReview := v1beta1.AdmissionReview{} deserializer := codecs.UniversalDeserializer() if _, _, err := deserializer.Decode(body, nil, &requestedAdmissionReview); err != nil { klog.Error(err) responseAdmissionReview.Response = toAdmissionResponse(err) } else { // pass to admitFunc responseAdmissionReview.Response = admit(requestedAdmissionReview) } // Return the same UID responseAdmissionReview.Response.UID = requestedAdmissionReview.Request.UID klog.V(2).Info(fmt.Sprintf("sending response: %v", responseAdmissionReview.Response)) respBytes, err := json.Marshal(responseAdmissionReview) if err != nil { klog.Error(err) } if _, err := w.Write(respBytes); err != nil { klog.Error(err) } } func serveAlwaysDeny(w http.ResponseWriter, r *http.Request) { serve(w, r, alwaysDeny) } func serveAddLabel(w http.ResponseWriter, r *http.Request) { serve(w, r, addLabel) } func servePods(w http.ResponseWriter, r *http.Request) { serve(w, r, admitPods) } func serveAttachingPods(w http.ResponseWriter, r *http.Request) { serve(w, r, denySpecificAttachment) } func serveMutatePods(w http.ResponseWriter, r *http.Request) { serve(w, r, mutatePods) } func serveConfigmaps(w http.ResponseWriter, r *http.Request) { serve(w, r, admitConfigMaps) } func serveMutateConfigmaps(w http.ResponseWriter, r *http.Request) { serve(w, r, mutateConfigmaps) } func serveCustomResource(w http.ResponseWriter, r *http.Request) { serve(w, r, admitCustomResource) } func serveMutateCustomResource(w http.ResponseWriter, r *http.Request) { serve(w, r, mutateCustomResource) } func serveCRD(w http.ResponseWriter, r *http.Request) { serve(w, r, admitCRD) } func main() { var config Config config.addFlags() flag.Parse() 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("/configmaps", serveConfigmaps) http.HandleFunc("/mutating-configmaps", serveMutateConfigmaps) http.HandleFunc("/custom-resource", serveCustomResource) http.HandleFunc("/mutating-custom-resource", serveMutateCustomResource) http.HandleFunc("/crd", serveCRD) server := &http.Server{ Addr: ":443", TLSConfig: configTLS(config), } server.ListenAndServeTLS("", "") }
动态配置admission webhooks
您可以通过ValidatingWebhookConfiguration或MutatingWebhookConfiguration动态配置哪些资源受入口webhooks的限制。
具体示例如下:
apiVersion: admissionregistration.k8s.io/v1beta1 kind: ValidatingWebhookConfiguration metadata: name: <name of this configuration object> webhooks: - name: <webhook name, e.g., pod-policy.example.io> rules: - apiGroups: - "" apiVersions: - v1 operations: - CREATE resources: - pods scope: "Namespaced" clientConfig: service: namespace: <namespace of the front-end service> name: <name of the front-end service> caBundle: <pem encoded ca cert that signs the server cert used by the webhook> admissionReviewVersions: - v1beta1 timeoutSeconds: 1
总结
最后我们来总结下 webhook Admission 的优势:
- webhook 可动态扩展 Admission 能力,满足自定义客户的需求
- 不需要重启 API Server,可通过创建 webhook configuration 热加载 webhook admission
相关推荐
###host字段指定授权使用该证书的etcd节点IP或子网列表,需要将etcd集群的3个节点都添加其中。cp etcd-v3.3.13-linux-amd64/etcd* /opt/k8s/bin/