美文网首页
kubernetes中的AOP - 准入控制

kubernetes中的AOP - 准入控制

作者: doublegao | 来源:发表于2019-11-21 10:43 被阅读0次

    1.Admission Controllers

    在整体的流程中可以理解为过滤器,拦截器甚至是AOP中的切面。准入控制是通过一个个插件实现,每个插件实现特定功能。例如ServiceAccount插件就是为了完成验证工作,MutatingAdmissionWebhook 和ValidatingAdmissionWebhook是为了实现动态注入控制。

    2.kubernetes本身有哪些webhook

    image.png

    其中
    NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, Priority, DefaultTolerationSeconds, DefaultStorageClass, StorageObjectInUseProtection, PersistentVolumeClaimResize, MutatingAdmissionWebhook, ValidatingAdmissionWebhook, ResourceQuota是默认开启的

    • AlwaysAdmit
      使用这个插件自行通过所有的请求。

    • AlwaysDeny
      拒绝所有的请求。用于测试

    • AlwaysPullImages
      不管镜像的拉去策略是那种,都将会被改成Always。在多租户的场景下,即便镜像被拉去到本地,其他租户的pod也使用不了

    • ServiceAccount
      做ServiceAccount的验证

    • NamespaceLifecycle
      这个插件强制不能在一个正在被终止的 Namespace 中创建新对象,和确保使用不存在 Namespace 的请求被拒绝。

    • PodNodeSelector
      通过读取命名空间注释和全局配置,这个插件默认并限制了在一个命名空间中使用什么节点选择器。

    • other
      其他

    3.动态准入控制 Dynamic Admission Control

    上面所说的准入控制都是kubernetes自身的,并且是编译时织如。对于面向定制化、插件化的kubernetes提供了运行时的动态实现,可以让开发人员实现相关插件,动态织入并生效。

    4.如何实现自己的准入控制插件

    首先了解下Admission webhooks,它是接收许可请求并对其执行操作的HTTP回调,Admission webhooks有两个两种类型validating admission Webhookmutating admission webhook

    • mutating admission webhook
      mutating 是最先被注入的,并且他可以拦截并修改修改发送给API server的请求。
    • validating admission Webhook
      在mutating 修改完请求并被apiserver验证之后,validating 将被注入。根据校验规则可以拒绝强制执行自定义策略的请求。

    注意:为了保证对象的最终状态,都应该有一个validating admission Webhook去验证,因为准入控制产检有后多个,在后续进过其他插件式对象数据可能会被修改。

    Admission webhooks属于kubernetes控制面的一部分。

    4.1、使用Admission webhooks的条件
    • kubernetes版本>1.9
    • MutatingAdmissionWebhook 和ValidatingAdmissionWebhook被启用,默认启用
    • admissionregistration.k8s.io/v1或者admissionregistration.k8s.io/v1beta1在APiserver中被启用
    4.2、kubernetes有哪些API

    既然webhook是http的回调,那么首先得了解kubernetes中有哪些API接口

    [root@node4 pki]# kubectl get --raw=/api/v1 |jq . |grep name\":
          "name": "bindings",
          "name": "componentstatuses",
          "name": "configmaps",
          "name": "endpoints",
          "name": "events",
          "name": "limitranges",
          "name": "namespaces",
          "name": "namespaces/finalize",
          "name": "namespaces/status",
          "name": "nodes",
          "name": "nodes/proxy",
          "name": "nodes/status",
          "name": "persistentvolumeclaims",
          "name": "persistentvolumeclaims/status",
          "name": "persistentvolumes",
          "name": "persistentvolumes/status",
          "name": "pods",
          "name": "pods/attach",
          "name": "pods/binding",
          "name": "pods/eviction",
          "name": "pods/exec",
          "name": "pods/log",
          "name": "pods/portforward",
          "name": "pods/proxy",
          "name": "pods/status",
          "name": "podtemplates",
          "name": "replicationcontrollers",
          "name": "replicationcontrollers/scale",
          "name": "replicationcontrollers/status",
          "name": "resourcequotas",
          "name": "resourcequotas/status",
          "name": "secrets",
          "name": "serviceaccounts",
          "name": "services",
          "name": "services/proxy",
          "name": "services/status",
    

    kubernetes中资源是有类型和版本的,不同的资源类型和版本API也不同,其他可自行查看,常用的如下

    [root@node4 pki]# kubectl get --raw=/apis/extensions/v1beta1 |jq . |grep name\":
    [root@node4 pki]# kubectl get --raw=/apis/batch/v1 |jq . |grep name\":
    [root@node4 pki]# kubectl get --raw=/apis/apps/v1 |jq . |grep name\":
    
    4.3、针对特定的接口添加回调函数

    以下两个场景来举例说明。创建namespaces时通过webhook添加label,创建pod是为其注入新的容器(istio中的sidecar实现类似)。

    注册回调事件

    http.HandleFunc("/namespaces", serveNamespaces)
    http.HandleFunc("/pods", servePods)
    server := &http.Server{
        Addr:      ":443",
        TLSConfig: configTLS(config),
    }
    

    将请求报文体转换为AdmissionReview

    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)
        }
    

    给pod注入label和sidecar 容器

    func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
        data,_ := json.Marshal(ar.Request.Object)
        fmt.Println(string(data))
        klog.V(2).Info("mutating pods")
        podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
        if ar.Request.Resource != podResource {
            klog.Errorf("expect resource to be %s", podResource)
            return nil
        }
    
        raw := ar.Request.Object.Raw
        pod := corev1.Pod{}
        deserializer := codecs.UniversalDeserializer()
        if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
            klog.Error(err)
            return toAdmissionResponse(err)
        }
        reviewResponse := v1beta1.AdmissionResponse{}
        reviewResponse.Allowed = true
        fmt.Println("为了测试,此处指定只有pod name=demo时才注入")
        if pod.Name == "demo" {
            saMountName,saMount := getSAMount(pod.Spec.Containers)
            fmt.Println("原pod的secretMount的挂载名称:"+saMountName)
    
            needAddedContainers := injectPod(saMount);
            patch := injectLabels()
            for _, container := range needAddedContainers{
                patch = append(patch, PatchOperation{
                    Op:    "add",
                    Path:  "/spec/containers/-",//此处虚注意,如果是数组类型,非第一个需要加上“/-”
                    Value: container,
                })
            }
            patchBytes ,_ := json.Marshal(patch)
            reviewResponse.Patch = patchBytes
            fmt.Println("==========" + string(reviewResponse.Patch))
            pt := v1beta1.PatchTypeJSONPatch
            reviewResponse.PatchType = &pt
        }
        return &reviewResponse
    }
    
    func injectPod(secretMount corev1.VolumeMount) []corev1.Container{
        return []corev1.Container{
            {
                Name: "agent",
                Image: "prima/filebeat:6",
                ImagePullPolicy: corev1.PullIfNotPresent,
                VolumeMounts: []corev1.VolumeMount{secretMount},
            },
        }
    }
    
    
    //获取原容器中serviceaccount的挂载目录
    func getSAMount(originContainer []corev1.Container)(string,corev1.VolumeMount){
        mountName := ""
        var mount corev1.VolumeMount
        // find service account secret volume mount(/var/run/secrets/kubernetes.io/serviceaccount,
        // https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/#service-account-automation) from app container
        for _, add := range originContainer {
            for _, vmount := range add.VolumeMounts {
                if vmount.MountPath == "/var/run/secrets/kubernetes.io/serviceaccount" {
                    mountName = vmount.Name
                    mount = vmount
                }
            }
        }
        return mountName,mount
    }
    
    func injectLabels() []PatchOperation{
        return []PatchOperation{
            {
                Op: "add",
                Path: "/metadata/labels",
                Value: map[string]string{
                    "webhook-label": "HelloWebhook",
                },
            },
        }
    }
    
    4.4、如何部署admission webhook服务

    部署admission webhook需要创建MutatingWebhookConfigurationValidatingWebhookConfiguration两个配置文件。

    一个配置文件中可以包含多个webhook的配置,多个配置通过名称区分。Configuration的版本目前有v1和v1beta1,相比v1,v1beta1功能更加健壮,同时审核日志和度量更易于与激活的配置匹配。

    ①为webhook生成证书,此处使用cfssl

    准备生成证书的配置文件
    ca-csr.json

    {
        "CN": "etcd",
        "key": {
            "algo": "rsa",
            "size": 2048
        },
        "names": [{
            "C": "CN",
            "ST": "NanJing",
            "L": "NanJing",
            "O": "Kubernetes",
            "OU": "Kubernetes-manual"
        }]
    }
    

    webhook-csr.json

    {
        "CN": "webhook",
            "hosts": [
          "webhook.default.svc",
          "10.48.17.175",
              "localhost",
              "kubernetes",
              "kubernetes.default",
              "kubernetes.default.svc",
              "kubernetes.default.svc.cluster",
              "kubernetes.default.svc.cluster.local"
            ],
    
        "key": {
            "algo": "rsa",
            "size": 2048
        },
        "names": [{
            "C": "CN",
            "ST": "NanJing",
            "L": "NanJing",
            "O": "Kubernetes",
            "OU": "Kubernetes-manual"
        }]
    }
    
    

    生成配置证书文件

    # 产生根证书
    [root@node4 pki]# cfssl gencert -initca ca-csr.json | cfssljson -bare ca
    [root@node4 pki]# cfssl gencert \
      -ca=ca.pem \
      -ca-key=ca-key.pem \
      -config=ca-config.json \
      -profile=kubernetes \
      webhook-csr.json | cfssljson -bare webhook
    [root@node4 pki]# ll
    total 36
    -rw-r--r-- 1 root root  342 Nov 20 11:31 ca-config.json
    -rw-r--r-- 1 root root 1017 Nov 20 11:27 ca.csr
    -rw-r--r-- 1 root root  250 Nov 20 11:31 ca-csr.json
    -rw------- 1 root root 1675 Nov 20 11:31 ca-key.pem
    -rw-r--r-- 1 root root 1391 Nov 20 11:27 ca.pem
    -rw-r--r-- 1 root root 1289 Nov 20 11:31 webhook.csr
    -rw-r--r-- 1 root root  550 Nov 20 11:31 webhook-csr.json
    -rw------- 1 root root 1675 Nov 20 11:31 webhook-key.pem
    -rw-r--r-- 1 root root 1675 Nov 20 11:31 webhook.pem
    

    ②部署为webhook server

    webhook server的部署可以通过deployment部署在集群内也可以部署在集群外,此处为了调试,直接在外部启动(注意证书生成时要指定外部的IP),可以通过tls-cert-file和tls-private-key-file指定证书所在位置。

        var config Config
        config.CertFile ="F:/pki/webhook/webhook.pem"
        config.KeyFile="F:/pki/webhook/webhook-key.pem"
        config.addFlags()
    
        flag.Parse()
        http.HandleFunc("/namespaces", serveNamespaces)
        http.HandleFunc("/pods", servePods)
        http.HandleFunc("/ping", pong)
        server := &http.Server{
            Addr:      ":443",
            TLSConfig: configTLS(config),
        }
        fmt.Println("start ......")
        server.ListenAndServeTLS("", "")
    

    ③配置ValidatingWebhookConfiguration和MutatingWebhookConfiguration规则

    MutatingWebhookConfiguration.yaml

    kind: MutatingWebhookConfiguration
    metadata:
      name: pod-webhook
    webhooks:
      - name: pod.cloud.org
        failurePolicy: Fail
        clientConfig:
          url: "https://10.48.17.175:443/pods"
          caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle containing the CA that signed the webhook's serving certificate>...tLS0K"
        rules:
          - operations: [ "CREATE" ]
            apiGroups: [""]
            apiVersions: ["v1"]
            resources: ["pods"]
            scope: "*"
    

    ValidatingWebhookConfiguration.yaml

    kind: ValidatingWebhookConfiguration
    metadata:
      name: pod-webhook
    webhooks:
      - name: pod.cloud.org
        failurePolicy: Fail
        clientConfig:
          url: "https://10.48.17.175:443/pods"
          caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle containing the CA that signed the webhook's serving certificate>...tLS0K"
        rules:
          - operations: [ "CREATE" ]
            apiGroups: [""]
            apiVersions: ["v1"]
            resources: ["pods"]
            scope: "*"
    
    • clientConfig
      求达到API server之后规则匹配后,就需要知道发给哪些webhook进行处理。clientConfig字段就是来指定的webhook,主要有urlservice两种方式。

      URL方式直接指定webhook服务地址,URL方式必须是https协议。如果有DNS可以可以通过域名访问,另外不建议使用localhost或127.0.0.1

       clientConfig:
        url: "https://10.48.17.175:443/pods"
        caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle containing the CA that signed the webhook's serving certificate>...tLS0K"
      

      Service方式主要借助于内部域名的方式

      clientConfig:
        caBundle: "Ci0tLS0tQk...<base64-encoded PEM bundle containing the CA that signed the webhook's serving certificate>...tLS0K"
        service:
          namespace: my-service-namespace
          name: my-service-name
          path: /my-path
          port: 1234
      

      注意: caBundle是对证书进行base64处理的结果,其中ca.pem是生成证书是产生的文件

       cat  ca.pem | base64 | tr -d '\n'
      
    • operations
      指一系列的方法(操作)类型,可选值CREATE, UPDATE, DELETE, CONNECT, *

    • apiGroups
      资源所属组,kubernetes中资源组分为核心组(core)和其他类型。核心组路径为/api/v1,组为空。其他组一般是指扩展组,路劲/apis/$GROUP_NAME,如extensions,apps,batch,autoscaling,policy等。*表示匹配所有资源组。

    • apiVersions
      指资源的版本,如v1, v1beta1,*表示匹配所有版本

    • resources
      指具体的资源类型,支持简单的表达式配置,如下:
      * 匹配所有资源,但不包括子资源
      */* 匹配所有资源,并且包括子资源
      pods/* 匹配pods下的所有子资源.
      */status 匹配pods下的所有资源下的子资源status

    • scope
      webhook的作用域,可选值如下:
      Cluster:集群级别的资源都匹配这个规则
      Namespaced:Namespace级别匹配
      *: 匹配所有,无限制

    除此之外,在1.15+版本中,可以规则还可以增加对象标签匹配、命名空间匹配,策略匹配(matchPolicy)。

    apiVersion: admissionregistration.k8s.io/v1beta1
    kind: MutatingWebhookConfiguration
    webhooks:
    - name: my-webhook.example.com
      objectSelector:
        matchLabels:
          foo: bar
      namespaceSelector:
         matchExpressions:
         - key: runlevel
           operator: NotIn
           values: ["0","1"]
      rules:
      - operations: ["CREATE"]
        apiGroups: ["*"]
        apiVersions: ["*"]
        resources: ["*"]
        scope: "*"
    

    ④部署配置

    [root@node4 webhook]# kubectl apply -f MutatingWebhookConfiguration.yaml
    [root@node4 webhook]# kubectl apply -f ValidatingWebhookConfiguration.yaml
    

    只有匹配上述规则的请求才会被发送到对应的webhook

    5.webhook测试

    5.1测试名称为demo的pod

    编写pod编排文件

    apiVersion: v1
    kind: Pod
    metadata:
      name: demo
      namespace: webhook
    spec:
      containers:
      - name: demo
        image: nginx:1.11.7
        imagePullPolicy: IfNotPresent
    

    部署pod测试

    [root@node4 webhook]# kubectl create ns webhook
    namespace/webhook created
    [root@node4 webhook]# kubectl apply -f nginx-pod.yaml 
    pod/demo created
    

    查看webhook打印的日志

    image.png

    查看标签和容器是否被注入

    标签已被注入

    image.png

    容器已被注入

    image.png

    5.1测试名称为demo-webhook的pod

    标签未注入


    image.png

    demo源码地址

    相关文章

      网友评论

          本文标题:kubernetes中的AOP - 准入控制

          本文链接:https://www.haomeiwen.com/subject/xyqwictx.html