搞懂 Kubernetes 准入控制(Admission Controller)

張晉濤發表於2021-11-30

大家好,我是張晉濤。

在我之前釋出的文章 《雲原生時代下的容器映象安全》(系列)中,我提到過 Kubernetes 叢集的核心元件 -- kube-apiserver,它允許來自終端使用者或叢集的各元件與之進行通訊(例如,查詢、建立、修改或刪除 Kubernetes 資源)。

本篇我們將聚焦於 kube-apiserver 請求處理過程中一個很重要的部分 -- 准入控制器(Admission Controller)

K8s 的准入控制器是什麼

K8s 中的請求處理流程

在聊 K8s 准入控制器是什麼之前,讓我們先來回顧一下 Kubernetes API 的處理具體請求的過程。

img

圖 1 ,Kubernetes API 處理請求的過程 (從 API Handler 到 etcd 持久化的過程)

如上圖所示,每個 API 的請求從開始被 kube-apiserver 接收到最終持久化到 ETCD 的過程,即為 Kubernetes API 的請求處理流程。

它主要包含了以下幾個部分:

  • API Handler -- 主要負責提供服務,接收請求。

對於其內部實現而言,請求會先到 FullHandlerChain (它是由 DefaultBuildHandlerChain 構建出來的)是一個 director 物件

type director struct {
    name               string
    goRestfulContainer *restful.Container
    nonGoRestfulMux    *mux.PathRecorderMux
}

director根據配置進行初始化,如果 goRestfulContainer的 WebServices 的 RootPath 是 /apis,或者請求字首與 RootPath 匹配,則進入 Restful 處理鏈路。

  • Authentication -- 認證的流程。

在TLS 連線建立後,會進行認證處理,如果請求認證失敗,會拒絕該請求並返回 401 錯誤碼;如果認證成功,將進行到鑑權的部分。目前支援的客戶端認證方式有很多,例如:x509 客戶端證書、Bearer Token、基於使用者名稱密碼的認證、OpenID 認證等。由於這些內容不是本篇的重點我們暫且跳過,感興趣的小夥伴可以在評論區留言討論。

  • Authorization -- 鑑權的流程。

對於 Kubernetes 而言,支援多種的鑑權模式,例如,ABAC 模式,RBAC 模式和 Webhook 模式等。我們在建立叢集時,可以直接為 kube-apiserver 傳遞引數進行配置,這裡也不贅述了。

  • Mutating Admission -- 指執行可用於變更操作的准入控制器,下文中會詳細介紹。
  • Object Schema Validation -- 對資源物件的 schema 校驗。
  • Validating Admission -- 指執行可用於驗證操作的准入控制器,下文中會詳細介紹。
  • ETCD -- ETCD 實現資源的持久化儲存。

上面便是一個請求的處理流程,其中 Mutating Admission 和 Validating Admission 便是我們今天的主角。我們來詳細的看一看。

什麼是准入控制器(Admission Controller)

准入控制器是指在請求通過認證和授權之後,可用於對其進行變更操作或驗證操作的一些程式碼或功能。

准入控制的過程分為兩個階段:

  • 第一階段,執行變更准入控制器(Mutating Admission)。它可以修改被它接受的物件,這就引出了它的另一個作用,將相關資源作為請求處理的一部分進行變更;
  • 第二階段,執行驗證准入控制器(Validating Admission)。它只能進行驗證,不能進行任何資源資料的修改操作;

需要注意的是,某些控制器可以既是變更准入控制器又是驗證准入控制器。如果任一個階段的准入控制器拒絕了該請求,則整個請求將立即被拒絕,並向終端使用者返回錯誤。

為什麼需要准入控制器 Admission Controller

我們主要從兩個角度來理解為什麼我們需要准入控制器:

  • 從安全的角度
    • 我們需要明確在 Kubernetes 叢集中部署的映象來源是否可信,以免遭受攻擊;
    • 一般情況下,在 Pod 內儘量不使用 root 使用者,或者儘量不開啟特權容器等;
  • 從治理的角度
    • 比如通過 label 對業務/服務進行區分,那麼可以通過 admission controller 校驗服務是否已經有對應的 label 存在之類的;
    • 比如新增資源配額限制 ,以免出現資源超賣之類的情況;

准入控制器

考慮到這些需求比較有用 & 確實也比較需要,所以 Kubernetes 目前已經實現了很多內建的 Admission Controller 。 可以參考官方文件來獲取詳細列表: https://kubernetes.io/docs/re...

這些內建的 Admission Controller 都是以外掛的方式與 kube-apiserver 構建到一起的,你可以對其進行啟用和關停。比如用如下引數進行控制:

➜  bin ./kube-apiserver --help |grep admission-plugins    
      --admission-control strings              Admission is divided into two phases. In the first phase, only mutating admission plugins run. In the second phase, only validating admission plugins run. The names in the below list may represent a validating plugin, a mutating plugin, or both. The order of plugins in which they are passed to this flag does not matter. Comma-delimited list of: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass, DefaultStorageClass, DefaultTolerationSeconds, DenyServiceExternalIPs, EventRateLimit, ExtendedResourceToleration, ImagePolicyWebhook, LimitPodHardAntiAffinityTopology, LimitRanger, MutatingAdmissionWebhook, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, NodeRestriction, OwnerReferencesPermissionEnforcement, PersistentVolumeClaimResize, PersistentVolumeLabel, PodNodeSelector, PodSecurity, PodSecurityPolicy, PodTolerationRestriction, Priority, ResourceQuota, RuntimeClass, SecurityContextDeny, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionWebhook. (DEPRECATED: Use --enable-admission-plugins or --disable-admission-plugins instead. Will be removed in a future version.)
      --disable-admission-plugins strings      admission plugins that should be disabled although they are in the default enabled plugins list (NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, PodSecurity, Priority, DefaultTolerationSeconds, DefaultStorageClass, StorageObjectInUseProtection, PersistentVolumeClaimResize, RuntimeClass, CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass, MutatingAdmissionWebhook, ValidatingAdmissionWebhook, ResourceQuota). Comma-delimited list of admission plugins: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass, DefaultStorageClass, DefaultTolerationSeconds, DenyServiceExternalIPs, EventRateLimit, ExtendedResourceToleration, ImagePolicyWebhook, LimitPodHardAntiAffinityTopology, LimitRanger, MutatingAdmissionWebhook, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, NodeRestriction, OwnerReferencesPermissionEnforcement, PersistentVolumeClaimResize, PersistentVolumeLabel, PodNodeSelector, PodSecurity, PodSecurityPolicy, PodTolerationRestriction, Priority, ResourceQuota, RuntimeClass, SecurityContextDeny, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionWebhook. The order of plugins in this flag does not matter.
      --enable-admission-plugins strings       admission plugins that should be enabled in addition to default enabled ones (NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, PodSecurity, Priority, DefaultTolerationSeconds, DefaultStorageClass, StorageObjectInUseProtection, PersistentVolumeClaimResize, RuntimeClass, CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass, MutatingAdmissionWebhook, ValidatingAdmissionWebhook, ResourceQuota). Comma-delimited list of admission plugins: AlwaysAdmit, AlwaysDeny, AlwaysPullImages, CertificateApproval, CertificateSigning, CertificateSubjectRestriction, DefaultIngressClass, DefaultStorageClass, DefaultTolerationSeconds, DenyServiceExternalIPs, EventRateLimit, ExtendedResourceToleration, ImagePolicyWebhook, LimitPodHardAntiAffinityTopology, LimitRanger, MutatingAdmissionWebhook, NamespaceAutoProvision, NamespaceExists, NamespaceLifecycle, NodeRestriction, OwnerReferencesPermissionEnforcement, PersistentVolumeClaimResize, PersistentVolumeLabel, PodNodeSelector, PodSecurity, PodSecurityPolicy, PodTolerationRestriction, Priority, ResourceQuota, RuntimeClass, SecurityContextDeny, ServiceAccount, StorageObjectInUseProtection, TaintNodesByCondition, ValidatingAdmissionWebhook. The order of plugins in this flag does not matter.

這麼多的准入控制器中有兩個比較特別,分別是 MutatingAdmissionWebhookValidatingAdmissionWebhook。它們並沒有真正執行相應的策略,而是為 kube-apiserver 提供了一種可擴充套件的方式, 使用者可以通過配置 MutatingAdmissionWebhookValidatingAdmissionWebhook 來使用自構建的服務, 而且這種方式是無需對 kube-spiserver 執行編譯或者重啟,完全是動態的,非常的方便。

我們來具體看一下。

動態准入控制器

利用上文中提到的 MutatingAdmissionWebhookValidatingAdmissionWebhook 進行執行時配置的,以 Webhook 形式呼叫的 Admission Controller 即為動態准入控制器。

它是一種用於接收准入請求並對其進行處理的 HTTP 回撥機制,就是一個 Web 服務。當前的兩種型別的准入 webhook:

  • validating admission webhook
  • mutating admission webhook

mutating admission webhook 會優先被呼叫,這個過程中可以對資源進行修改。

如果我們需要確保物件的最終狀態以執行某些操作應該考慮使用 validating admission webhook 因為到達這個階段的請求不會再被修改。

使用條件

  • 確保 Kubernetes 叢集版本至少為 v1.16(以便使用 admissionregistration.k8s.io/v1 API) 或者 v1.9 (以便使用 admissionregistration.k8s.io/v1beta1 API);
  • 確保已經啟用 MutatingAdmissionWebhookValidatingAdmissionWebhook 准入控制器;
  • 確保啟用了 admissionregistration.k8s.io/v1beta1 或者 admissionregistration.k8s.io/v1 API;

Admission Webhook 是什麼

它其實就是一個普通的 HTTP Server,需要處理的就是 AdmissionReview 型別的資源,我們來看個示例,比如說要對 Ingress資源進行准入校驗:

func (ia *IngressAdmission) HandleAdmission(obj runtime.Object) (runtime.Object, error) {

    review, isV1 := obj.(*admissionv1.AdmissionReview)
    if !isV1 {
        return nil, fmt.Errorf("request is not of type AdmissionReview v1")
    }

    if !apiequality.Semantic.DeepEqual(review.Request.Kind, ingressResource) {
        return nil, fmt.Errorf("rejecting admission review because the request does not contain an Ingress resource but %s with name %s in namespace %s",
            review.Request.Kind.String(), review.Request.Name, review.Request.Namespace)
    }

    status := &admissionv1.AdmissionResponse{}
    status.UID = review.Request.UID

    ingress := networking.Ingress{}

    codec := json.NewSerializerWithOptions(json.DefaultMetaFactory, scheme, scheme, json.SerializerOptions{
        Pretty: true,
    })
    codec.Decode(review.Request.Object.Raw, nil, nil)
    _, _, err := codec.Decode(review.Request.Object.Raw, nil, &ingress)
    if err != nil {
        klog.ErrorS(err, "failed to decode ingress")
        status.Allowed = false
        status.Result = &metav1.Status{
            Status: metav1.StatusFailure, Code: http.StatusBadRequest, Reason: metav1.StatusReasonBadRequest,
            Message: err.Error(),
        }

        review.Response = status
        return review, nil
    }

    if err := ia.Checker.CheckIngress(&ingress); err != nil {
        klog.ErrorS(err, "invalid ingress configuration", "ingress", fmt.Sprintf("%v/%v", review.Request.Name, review.Request.Namespace))
        status.Allowed = false
        status.Result = &metav1.Status{
            Status: metav1.StatusFailure, Code: http.StatusBadRequest, Reason: metav1.StatusReasonBadRequest,
            Message: err.Error(),
        }

        review.Response = status
        return review, nil
    }

    klog.InfoS("successfully validated configuration, accepting", "ingress", fmt.Sprintf("%v/%v", review.Request.Name, review.Request.Namespace))
    status.Allowed = true
    review.Response = status

    return review, nil
}

核心處理邏輯其實就是處理請求 Webhook 時候傳送的 AdmissionReview ,它會包含著我們待校驗的資源物件。然後我們就按照實際的需要對資源物件進行校驗或者修改了。

這裡需要注意幾個點:

  • Mutating Webhook 的處理是序列的,而 Validating Webhook 是並行處理的;
  • Mutating Webhook 雖然處理是序列的,但是並不保證順序;
  • 注意對 Mutating Webhook 的處理做到冪等性,以免結果不符合預期;
  • 請求處理時,注意要處理資源物件的所有 API 版本;

如何部署 Admission Webhook

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  labels:
    app.kubernetes.io/name: ingress-nginx
  name: ingress-nginx-admission
webhooks:
  - name: validate.nginx.ingress.kubernetes.io
    matchPolicy: Equivalent
    rules:
      - apiGroups:
          - networking.k8s.io
        apiVersions:
          - v1
        operations:
          - CREATE
          - UPDATE
        resources:
          - ingresses
    failurePolicy: Fail
    sideEffects: None
    admissionReviewVersions:
      - v1
    clientConfig:
      service:
        namespace: ingress-nginx
        name: ingress-nginx-controller-admission
        path: /networking/v1/ingresses

webhooks 中配置 webhook 的具體連線資訊,以及觸發規則。rules中可指定對哪些資源的具體行為生效。

總結

本篇主要介紹了 Kubernetes 中的 Admission Controller ,預設情況下有一些已經以外掛形式與 kube-apiserver 編譯到了一起,另外我們也可以通過自己編寫動態准入控制器來完成相關的需求。

當然,目前在 K8s 生態中已經有很多現成的工具可以幫我們完成對應的這些事情了,很多情況下不需要再自行開發對應的服務了。後續我來為大家分享當前一些比較主流的,可用於進行 Mutating 和 Validating 准入控制的工具, 歡迎關注。


歡迎訂閱我的文章公眾號【MoeLove】

相關文章