Kubernetes 准入控制介绍


通过 Adminssion Webhook 扩展准入控制

Kubernetes 准入控制介绍

通过 Adminssion Webhook 扩展准入控制

  • Kubernetes 知识图谱
  • 云原生导航
  • 谷歌搜索
  • Raft 协议动画演示
  • 搬瓦工

    1. 什么是准入控制


    准入控制(Admission Controller)是 Kubernetes API Server 用于拦截请求的一种手段。Admission 可以做到对请求的资源对象进行校验,修改。service mesh 最近很火的项目 Istio 天生支持 Kubernetes,利用的就是 Admission 对服务实例自动注入 sidecar。

    假如对 Kubernetes 有一定的了解的话,应该会知道在 Kubernetes 中还有 authn/authz,为什么还会引入 admission 这种机制?

    • authn/authz 是 Kubernetes 的认证和鉴权,运行在 filter 中,只能获取 http 请求 header 以及证书,并不能获取请求的 body。所以 authn/authz 只能对客户端进行认证和鉴权,不可以对请求的对象进行任何操作,因为这里根本还获取不到对象。

    • Admission 运行在 API Server 的增删改查 handler 中,可以自然地操作 API resource。它是在经过授权之后,资源持久化之前的一个处理 API server 请求的步骤。准入过程能获取到和认证过程一致的信息(用户、URL 等),以及绝大多数 API 请求的完整报文。

    准入阶段由不同的插件组成,每个插件都能 “各司其职”,并明确知道自己要检查的对象结构。例如:PodNodeSelector(影响调度决策),PodSecurityPolicy(防止升级的容器)和 ResourceQuota(为每个 Namespace 限制资源配额)。

    https://hugo-picture.oss-cn-beijing.aliyuncs.com/images/5VRe5t.jpg

    准入分为两个阶段:

    • 修改 (Mutation) 阶段 : 在对象持久化之前修改对象的主体内容以及拒绝 API 请求。

    • 验证 (Validation) 阶段 :在对象持久化之前进行校验以及拒绝 API 请求。

    一个准入插件可以在这两个阶段应用,但是所有的修改阶段都发生在验证阶段之前。

    修改 (Mutation)阶段

    Admission 的 Mutation 阶段允许在资源内容生成前进行修改。因为同一个字段在 Admission 链上可以被多次修改,因此 Admission 插件的执行顺序很重要。

    准入修改插件(Mutating Admission Plugin)中的一个例子就是 PodNodeSelector,它使用 Namespace 的一个 annotation:namespace.annotations[“scheduler.alpha.kubernetes.io/node-selector”] 来查找标签选择器并将其添加到 pod.spec.nodeselector 字段。这一功能正向限制了特定 Namespace 中的 pod 能够落在哪个节点上,这与提供反向限制的 taints 正相反(也是通过 Admission 插件来实现的)。

    验证 (Validating)阶段

    我们可以在 Admisson 的验证阶段来检查特定 API 资源以保证其不变。验证阶段在所有的 mutators 完成之后运行,以确保资源在做完验证之后不会被再次改变。

    准入验证插件(Validation Admission Plugin)的一个例子也是 PodNodeSelector 插件,它可以确保所有 pod 的 spec.nodeSelector 字段都能符合 Namespace 上节点选择器的约束。即使在 Mutating 链中运行 PodNodeSelector 之后,有其他修改插件试图更改 spec.nodeSelector 字段,验证链中的 PodNodeSelector 插件也会因验证失败而阻止 API 资源的创建。

    下面将对准入控制工作流做一番详解。

    https://hugo-picture.oss-cn-beijing.aliyuncs.com/images/E2ykx1.jpg

    API Server 接收到客户端请求后首先进行认证鉴权,认证鉴权通过后才会进行后续的 endpoint handler 处理。

    • 当 API Server 接收到对象后首先根据 http 的路径可以知道对象的版本号,然后将 request body 反序列化成 versioned object
    • versioned object 转化为 internal object,即没有版本的内部类型,这种资源类型是所有 versioned 类型的超集。只有转化为 internal 后才能适配所有的客户端 versioned object 的校验。
    • Admission Controller 具体的 admit 操作,可以通过这里修改资源对象,例如为 Pod 挂载一个默认的 Service Account 等。
    • API Server internal object validation,校验某个资源对象数据和格式是否合法,例如:Service Name 的字符个数不能超过 63 等。
    • Admission Controller validate,可以自定义任何的对象校验规则。
    • internal object 转化为 versioned object,并且持久化存储到 etcd。

    2. 如何使用准入控制


    Kubernetes 1.10 之前的版本可以使用 --admission-control 打开准入控制。同时 --admission-control 的顺序决定 Admission 运行的先后。其实这种方式对于用户来讲其实是挺复杂的,因为这要求用户对所有的准入控制器需要完全了解。

    如果使用 Kubernetes 1.10 之后的版本,--admission-control 已经废弃,建议使用 --enable-admission-plugins--disable-admission-plugins 指定需要打开或者关闭的准入控制器。 同时用户指定的顺序并不影响实际准入控制器的执行顺序,对用户来讲非常友好。

    值得一提的是,有些准入控制器可能会使用 Alpha 版本的 API,这时必须首先使能其使用的 API 版本。否则准入控制器不能工作,可能会影响系统功能。

    3. Admission Webhook


    目前 Kubernetes 中已经有非常多的 Admission 插件, 但是并不能保证满足所有开发者的需求。 众所周知,Kbernetes 之所以受到推崇,它的可扩展能力功不可没。Admission 也提供了一种 webhook 的扩展机制。

    • MutatingAdmissionWebhook :在对象持久化之前进行修改
    • ValidatingAdmissionWebhook :在对象持久化之前进行校验

    Admission Webhook 允许 Kubernetes 安装人员或集群管理员,不需要进行重新编译,就可以直接添加修改(Mutation)和验证(Validation)这两种插件到 kube-apiserver 和任何基于 k8s.io/apiserver 1.9 扩展的 apiserver (如 metrics, service-catalog, kube-projects 等) 准入链中。这两种 Admission Webhook 插件分别会在修改和验证链的最后执行,与编译的准入插件具有相同的功能。

    可能有读者接触过另外一种动态可扩展的机制 Initializers,不过至今还是 Apha 特性,社区讨论有可能会把它移除。所以选择动态 Admission 首选 webhook。

    Webhook Admission 插件的优势

    Webhook Admission 插件允许对任何 API server 的任何资源进行修改和验证,所以应用场景非常广泛,比较常见的用例包括:

    • 修改如 pod 这样的资源:Istio 通过修改 pod 资源,把 sidecar 容器注入到 pod 中。你也可以编写一个能够强制将镜像 tag 解析成 SHA 的插件。
    • 命名限制:在多租户系统上,保留 Namespace 已经成为一种用例。
    • 复杂的 CustomResource 验证:因为整个对象是可见的,所以插件可以对字段间依赖(A 需要 B)甚至外部资源(对比 LimitRanges)进行复杂的验证。
    • 安全响应:如果你把镜像 tag 改成了 SHA,你可以通过写一个插件来阻止对应某些 SHA 的镜像运行。

    注册

    这两种类型的 Webhook Admission 插件都需要在 API 中注册,所有 API servers(kube-apiserver 和所有扩展 API servers )都共享一个通用配置。在注册过程中,一个 Webhook Admission 插件描述了以下信息:

    • 如何连接到 Webhook Admission Server
    • 如何验证 Webhook Admission Server(是否是我们期望的 server)
    • 数据应该发送到 Server 的哪个 URL 路径
    • 它将处理哪些资源和哪些 HTTP 动词
    • API server 在连接失败后应该做什么(例如如果 Webhook Admission Server 停止服务了)

      apiVersion: admissionregistration.k8s.io/v1beta1
      kind: ValidatingWebhookConfiguration
      metadata:
      name: namespacereservations.admission.online.openshift.io
      webhooks:
      - name: namespacereservations.admission.online.openshift.io ①
      clientConfig:  ②
      service:
        namespace: default
        name: kubernetes
        path: /apis/admission.online.openshift.io/v1alpha1/namespacereservations
      caBundle: KUBE\_CA\_HERE ⑤
      rules:  ③
      - apiGroups:
      - ""
      apiVersions:
      - ""
      operations:
      - CREATE
      resources:
      - namespaces
      failurePolicy: Fail  ④
      
    • name : Webhook 的名称。mutating Webhooks 会根据名称进行排序。

    • clientConfig : 提供关于如何连接、信任以及发送数据给 Webhook Admission Server 的信息。

    • rules : 用来描述 API server 应该在什么时候调用 Admission 插件。在这个例子中,只有创建 Namespace 的时候才触发。你可以指定任何资源,例如 serviceinstances.servicecatalog.k8s.io 的 create 操作也是可行的。

    • failurePolicy : 如果 Webhook Admission Server 无法连接时如何处理。有两个选项分别是 “Ignore”(故障时开放) 和 “Fail”(故障时关闭)。“故障时开放”可能会导致无法预测的行为。

    • caBundle : 注意 API server 调用 Webhook 时一定是通过 TLS 认证的,所以 MutatingWebhookConfiguration 中一定要配置 caBundle。

    注意

    对比 initializerConfiguration,ValidatingWebhookConfiguration 和 MutatingWebhookConfiguration 在 rule 的定义时,增加了 operations field,在 resources 定义时候可以指定 subresource,格式为 resource/subresource。

    认证和信任

    由于 Webhook Admission 插件具有强大的功能(他们可以查看 API 资源内容中任何发给他们的请求,并可以通过插件进行修改),所以在使用时需要考虑的重点是:

    • 各个 API servers 如何验证其与 Webhook Admission Server 的连接。
    • Webhook Admission Server 如何准确地认证哪个 API server 正在与它连接。
    • 该特定的 API server 是否有权进行请求。

    连接可以分为以下三大类:

    • kube-apiserverextension-apiservers 到运行在集群外部的 Admission Webhooks
    • kube-apiserver 到运行在集群内部的 Admission Webhooks。
    • extension-apiservers 到运行在集群内部的 Admission Webhooks。

    为了支持这三大类连接,Webhook Admission 插件可以支持从 kubeconfig 文件中读取连接各个 server 的信息。由于认证/授权和访问路径是由用户所连接的服务器所决定的,因此为了与运行在集群外部的 Admission Webhooks 进行交互,除了手动配置这个文件之外,实际上没有其他选择。

    对于在集群内运行的 Admission Webhook 来说,一个巧妙构建的 Webhook Admission Server 和拓扑结构,就是能够利用 Admission 插件中内置的安全默认值,并具有可从任何 API server 运行的安全、可移植和零配置的拓扑结构。

    4. 简单安全,可移植的拓扑结构


    如果你建立的 Webhook Admission Server 也是一个 extension API server,就有可能把它作为一个普通的 API server 来聚合。这具有许多优点:

    • 你的 Webhook 在默认 kube-apiserver 服务 kubernetes.default.svc 下变得可用 (例如,https://kubernetes.default.svc/apis/admission.example.com/v1/mymutatingadmissionreviews)。另一个好处是,你可以使用 kubectl 进行测试。

    • 你的 Webhook 会自动(无需任何配置)使用 kube-apiserver 提供的集群内认证和授权。你可以使用正常的 RBAC 规则限制对 Webhook 的访问。

    • 你的 extension API servers 和 kube-apiserver (无需任何配置)可以自动利用其集群内的凭证与 Webhook 进行通信。

    • 因为中间会经过 kube-apiserver 这个安全的前端代理,所以 extension API servers 不会将其 service account token 泄漏给 Webhook。

    https://hugo-picture.oss-cn-beijing.aliyuncs.com/images/dte9NC.jpg

    简而言之:一个安全的拓扑结构可以使用 API server 聚合 (API server aggregation) 的所有安全机制,不需要额外的配置。其他的拓扑结构也是可行的,但是需要额外的手动配置以及创建安全设置工作。尤其是像 service catalog 这种 extension API servers,上面的拓扑结构就是零配置,并且可移植到任何 Kubernetes 集群中。

    5. 如何使用 Admission Webhook


    Webhook Admission 属于同步调用,需要用户部署自己的 webhook server,创建自定义的配置资源对象: ValidatingWebhookConfigurationMutatingWebhookConfiguration

    开发 Webhook Server

    这里我推荐参考社区 e2e 测试用的 server,对细节源代码感兴趣的读者可以自行参考 github.com/kubernetes/…,这里面利用 golang 标准库实现的一个基本的 http server,并注册多个路由,同时服务于多种 resource 的准入控制。重点关注一下资源对象的 decode 过程,这是 k8s apimachinery 的高级功能。利用了 apimachinery 的 scheme 的能力,使用之前必须要将 api 注册到 scheme 中,代码详见: github.com/kubernetes/…。一个典型的 webhook 修改资源对象(Pod)的样例代码如下所示。

     func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
       glog.V(2).Info("mutating pods")
       podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
       if ar.Request.Resource != podResource {
          glog.Errorf("expect resource to be %s", podResource)
          return nil
       }
    
       raw := ar.Request.Object.Raw
       pod := corev1.Pod{}
       deserializer := codecs.UniversalDeserializer()
    // pod的解码,利用apimachinery
       if _, _, err := deserializer.Decode(raw, nil, &pod); err != nil {
          glog.Error(err)
          return toAdmissionResponse(err)
       }
       reviewResponse := v1beta1.AdmissionResponse{}
       reviewResponse.Allowed = true
       if pod.Name == "webhook-to-be-mutated" {
          reviewResponse.Patch = []byte(addInitContainerPatch)
          pt := v1beta1.PatchTypeJSONPatch
          reviewResponse.PatchType = &pt
       }
       return &reviewResponse
       }
    

    部署 Webhook Server

    $ kubectl create –f webhook-server.yaml
    
    apiVersion: v1
    kind: Namespace
    metadata:
      name: e2e-tests-webhook-gbgt6
    spec:
      finalizers:
      - kubernetes
    ---
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      labels:
        app: sample-webhook
        webhook: "true"
      name: sample-webhook-deployment
      namespace: e2e-tests-webhook-gbgt6
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: sample-webhook
          webhook: "true"
      template:
        metadata:
          labels:
            app: sample-webhook
            webhook: "true"
        spec:
          containers:
          - args:
            - --tls-cert-file=/webhook.local.config/certificates/tls.crt
            - --tls-private-key-file=/webhook.local.config/certificates/tls.key
            - --alsologtostderr
            - -v=4
            - 2>&1
            image: gcr.io/kubernetes-e2e-test-images/k8s-sample-admission-webhook-amd64:1.10v2
            imagePullPolicy: IfNotPresent
            name: sample-webhook
            volumeMounts:
            - mountPath: /webhook.local.config/certificates
              name: webhook-certs
              readOnly: true
          volumes:
          - name: webhook-certs
            secret:
              defaultMode: 420
              secretName: sample-webhook-secret
    ---
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        test: webhook
      name: e2e-test-webhook
      namespace: e2e-tests-webhook-gbgt6
    spec:
      ports:
      - port: 443
        protocol: TCP
        targetPort: 443
      selector:
        webhook: "true"
      sessionAffinity: None
      type: ClusterIP
    

    创建 webhook server Deployment 以及 Service,供 API Server 调用。

    创建 MutatingWebhookConfiguration

    $ kubectl create –f webhook-config.yaml
    
    
    apiVersion: admissionregistration.k8s.io/v1beta1
    kind: MutatingWebhookConfiguration
    metadata:
      name: e2e-test-mutating-webhook-pod
    webhooks:
    - clientConfig:
        caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMyRENDQWNDZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFkTVJzd0dRWURWUVFERXhKbE1tVXQKYzJWeWRtVnlMV05sY25RdFkyRXdIaGNOTVRnd056RTVNRGMwT1RJeFdoY05Namd3TnpFMk1EYzBPVEl4V2pBZApNUnN3R1FZRFZRUURFeEpsTW1VdGMyVnlkbVZ5TFdObGNuUXRZMkV3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBCkE0SUJEd0F3Z2dFS0FvSUJBUURFVVFEWVN6SGl3SUFHU1dHSWRBSmVBbnMrNFhaYjlZc3VuQlBVTkJPdHZqeFoKV3NSbUxydE0zVU9lcEszeGsvMzZCSS96RkdXdUNpMlJ0TWUxSWtEa2tVMzNEZE83K0ExVyt2NVZNVnFqL0lDTApsc29USml3TFhTcGowTHNwSUNVdGtqT1dlRjVhK3lJVHgyR01TMG9ZbWtuaHB0RXMrc2tKQjFMWm1uVTBaWFpzClRKak9Lb05ueHdVaTl4QnRUTXBQRWw2cVhmb3dCWlpvYjlkUzNtNzFLbjJCdU5Ec0s3YnVRcGJvdk9XdUQyNDAKdzNLQVJnT04xcjA4Vm4zd1I1MHVXS09tSkVsLzRUZ2JnSTRkaG85WHNIWUhUdnk4R3JRMXhYZE43ZEhSTlpHNQo5aDhmOUUzdjg1VWxwSEVWQThqUHB4RE5SSm9qRXVGQk9raFJEZEY1QWdNQkFBR2pJekFoTUE0R0ExVWREd0VCCi93UUVBd0lDcERBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUEwR0NTcUdTSWIzRFFFQkN3VUFBNElCQVFDWWl4VUsKYkhsRUpCK2t4THdqdktySDQ1OVVsNUJjb0VXZE1BNnArUC8yWXVZa2NuWC9GRVNjUFRxUS9vdkF3ejU1ZG1FUwpJTjVZOWd2ZlJxdWhZcEdWOHVFSWpzVkczTjdKQm1wM0NyclEyd3FYeHV3cndkVXV1dDltQSt2RkQ4Q2FQSE8xCmVad1J6NEkzTktFQ0xHMHJXQWxseEVvUm9tQ2UvaWZIUnRNRklTRk5sSnZVNlhIbzFDVWNFQ2FwOG9hYXN2cFcKT2JBQjVqQzc5WWJXN2lWVm54cjZGMnRvOG9oSEdNSEpXR1pwSTNKbVpNbGVOK01kVm5ySFdXSXBkOG9iS2E3TgpqSlZTczgzRmlDMzd4d2dqMUQyaTNHUnh5bHNKZEdJWTl4WVpQVmNNUTh6Z2FMMUpJUk1BdVZYbHczUkRzSDR0Cms5WmFybGY1NG9BOUN0Nk8KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
        service:
          name: e2e-test-webhook
          namespace: e2e-tests-webhook-gbgt6
          path: /mutating-pods
      failurePolicy: Ignore
      name: adding-init-container.k8s.io
      namespaceSelector: {}
      rules:
      - apiGroups:
        - ""
        apiVersions:
        - v1
        operations:
        - CREATE
        resources:
        - pods
        - 
    

    rules 表示对于 core/v1/pods 资源对象创建的时候调用 mutating webhook。server 的地址及路径通过 clientConfig 指明。

    /mutating-pods 是指调用 webhook server 执行 mutatePods,为 pod 增加 init initContainers。

    func mutatePods(ar v1beta1.AdmissionReview) *v1beta1.AdmissionResponse {
       glog.V(2).Info("mutating pods")
       podResource := metav1.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}
       if ar.Request.Resource != podResource {
          glog.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 {
          glog.Error(err)
          return toAdmissionResponse(err)
       }
       reviewResponse := v1beta1.AdmissionResponse{}
       reviewResponse.Allowed = true
       if pod.Name == "webhook-to-be-mutated" {
          reviewResponse.Patch = []byte(addInitContainerPatch)
          pt := v1beta1.PatchTypeJSONPatch
          reviewResponse.PatchType = &pt
       }
       return &reviewResponse
    }
    

    创建 Pod

    $ kubectl create –f pod.yaml
    
    apiVersion: v1
    kind: Pod
    metadata:
      name: webhook-to-be-mutated
      namespace: e2e-tests-webhook-gbgt6
    spec:
      containers:
      - image: k8s.gcr.io/pause:3.1
        name: example
    

    查询 Pod

    $ kubectl get pod webhook-to-be-mutated –n e2e-tests-webhook-gbgt6 -oyaml
    
    apiVersion: v1
    kind: Pod
    metadata:
      creationTimestamp: 2018-07-19T07:49:37Z
      name: webhook-to-be-mutated
      namespace: e2e-tests-webhook-gbgt6
      resourceVersion: "806"
      selfLink: /api/v1/namespaces/e2e-tests-webhook-gbgt6/pods/webhook-to-be-mutated
      uid: 48d2e91d-8b28-11e8-b16d-286ed488dc10
    spec:
      containers:
      - image: k8s.gcr.io/pause:3.1
        imagePullPolicy: IfNotPresent
        name: example
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
        volumeMounts:
        - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
          name: default-token-jhqlb
          readOnly: true
      dnsPolicy: ClusterFirst
      initContainers:
      - image: webhook-added-image
        imagePullPolicy: Always
        name: webhook-added-init-container
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      nodeName: 127.0.0.1
      priority: 0
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      serviceAccount: default
      serviceAccountName: default
      terminationGracePeriodSeconds: 30
      tolerations:
      - effect: NoExecute
        key: node.kubernetes.io/not-ready
        operator: Exists
        tolerationSeconds: 300
      - effect: NoExecute
        key: node.kubernetes.io/unreachable
        operator: Exists
        tolerationSeconds: 300
      volumes:
      - name: default-token-jhqlb
        secret:
          defaultMode: 420
          secretName: default-token-jhqlb
    

    可以看出,创建成功的pod已经多了一个名字为 webhook-added-init-container 的 initContainers。

    Istio 就是使用 ValidatingAdmissionWebhooks 验证 Istio 配置,使用 MutatingAdmissionWebhooks 自动将 sidecar 代理注入至用户 pod。可以参考:动态准入 Webhooks 概述

    6. 总结


    最后我们来总结下 webhook Admission 的优势:

    • webhook 可动态扩展 Admission 能力,满足自定义客户的需求。
    • 不需要重启 API Server,可通过创建 webhook configuration 热加载 webhook admission。

    7. 参考



    https://hugo-picture.oss-cn-beijing.aliyuncs.com/images/wechat.gif
    扫一扫关注微信公众号


    -------他日江湖相逢 再当杯酒言欢-------

    「真诚赞赏,手留余香」

    米开朗基杨

    真诚赞赏,手留余香

    使用微信扫描二维码完成支付


    相关推荐