Kubernetes 准入控制 Admission Controller 介紹

華為雲發表於2018-09-20

1.什麼是Admission Controller

Admission Controller(准入控制)是 Kubernetes API Server 用於攔截請求的一種手段。Admission可以做到對請求的資源物件進行校驗,修改。service mesh最近很火的專案Istio天生支援Kubernetes,利用的就是admission對服務例項自動注入sidecar。

假如對 Kubernetes 有一定的瞭解的話,應該會知道在 Kubernetes 中還有 authn/authz,為什麼還會引入 admission 這種機制?

1)authn/authz 是 Kubernetes 的認證鑑權,執行在 filter 中,只能獲取 http 請求 header 以及證書,並不能獲取請求的 body。所以 authn/authz 只能對客戶端進行認證和鑑權,不可以對請求的物件進行任何操作,因為這裡根本還獲取不到物件。 2)Admission 執行在 API Server 的增刪改查 handler 中,可以自然地操作 API resource。

下面將對 Admission Controller 工作流做一番詳解。

Kubernetes 准入控制 Admission Controller 介紹
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 等。 4)API Server internal object validation,校驗某個資源物件資料和格式是否合法,例如:Service Name 的字元個數不能超過63等。 5)Admission Controller validate,可以自定義任何的物件校驗規則。 6)internal object 轉化為 versioned object,並且持久化儲存到 etcd。

注:以上 versioned object 和 internal object 直接的轉換關係會在《深度剖析Kubernetes API Server三部曲 - part 2》詳細解釋,歡迎持續關注。

2.如何使用 admission controller

Kubernetes 1.10之前的版本可以使用--admission-control 開啟 Admission Controller。同時--admission-control 的順序決定 Admission 執行的先後。其實這種方式對於使用者來講其實是挺複雜的,因為這要求使用者對所有的 Admission Controllers 需要完全瞭解。

如果使用Kubernetes 1.10之後的版本,--admission-control 已經廢棄,建議使用 --enable-admission-plugins --disable-admission-plugins 指定需要開啟或者關閉的 Admission Controller。 同時使用者指定的順序並不影響實際 Admission Controllers 的執行順序,對使用者來講非常友好。

值得一提的是,有些 Admission Controller 可能會使用 Alpha 版本的 API,這時必須首先使能其使用的 API 版本。否則 Admission Controller 不能工作,可能會影響系統功能。

2.1 webhook admission

目前 Kubernetes 中已經有非常多的 Admission 外掛, 但是並不能保證滿足所有開發者的需求。 眾所周知,Kbernetes 之所以受到推崇,它的可擴充套件能力功不可沒。Admission 也提供了一種 webhook 的擴充套件機制。

  • MutatingAdmissionWebhook:在物件持久化之前進行修改
  • ValidatingAdmissionWebhook:在物件持久化之前進行

可能有讀者接觸過另外一種動態可擴充套件的機制 Initializers,不過至今還是 Apha 特性,社群討論有可能會把它移除。所以選擇動態 Admission 首選 webhook。

2.2 如何使用webhook admission

Webhook Admission 屬於同步呼叫,需要使用者部署自己的 webhook server,建立自定義的配置資源物件: ValidatingWebhookConfiguration 或 MutatingWebhookConfiguration。

  • 開發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

3.總結

最後我們來總結下 webhook Admission 的優勢

  • webhook 可動態擴充套件 Admission 能力,滿足自定義客戶的需求

  • 不需要重啟 API Server,可通過建立 webhook configuration 熱載入 webhook admission。

相關文章