Kubernetes Admission Controller 准入控制

Kubernetes Admission Controller Configuration

Admission Controller 概述

Kubernetes Admission Controller 是 API Server 中的一個重要元件,負責在物件持久化到 etcd 之前攔截請求並進行處理。當一個 API 請求通過身份驗證(Authentication)和授權(Authorization)之後,Admission Controller 會對請求進行額外的驗證或修改。

Admission Controller 的工作流程如下:

1
API 請求 → 身份驗證 → 授權 → Admission Controller → etcd 持久化

主要用途包括:

  • 強制執行安全策略
  • 設定資源預設值
  • 限制資源配額
  • 注入 sidecar 容器

驗證型與變更型

Admission Controller 分為兩種類型:

驗證型(Validating)

驗證型 Admission Controller 只會檢查請求是否符合特定條件,不會修改請求內容。如果驗證失敗,請求會被拒絕。

常見用途:

  • 檢查 Pod 是否有設定資源限制
  • 驗證映像檔是否來自可信任的 Registry
  • 確保 Label 符合命名規範

變更型(Mutating)

變更型 Admission Controller 可以修改請求的內容,通常用於設定預設值或注入額外配置。

常見用途:

  • 自動注入 sidecar 容器(如 Istio)
  • 設定預設的資源請求和限制
  • 添加 Label 或 Annotation

執行順序:Mutating Admission Controller 會先於 Validating Admission Controller 執行。

內建 Admission Controller

Kubernetes 提供多個內建的 Admission Controller,以下是常用的幾個:

名稱類型說明
NamespaceLifecycleValidating防止在終止中或不存在的 Namespace 建立資源
LimitRangerMutating為 Pod 設定預設的資源限制
ResourceQuotaValidating確保資源使用不超過配額
PodSecurityValidating強制執行 Pod 安全標準
DefaultStorageClassMutating為 PVC 設定預設的 StorageClass

查看目前啟用的 Admission Controller:

1
2
kubectl exec -it kube-apiserver-<node> -n kube-system -- \
  kube-apiserver -h | grep enable-admission-plugins

動態 Admission Webhook

除了內建的 Admission Controller,Kubernetes 還支援動態 Admission Webhook,讓您可以自定義准入邏輯。

Webhook 的運作方式:

  1. API Server 收到請求
  2. 將請求內容以 AdmissionReview 格式發送到 Webhook 服務
  3. Webhook 服務處理請求並回傳結果
  4. API Server 根據結果決定是否允許請求

ValidatingWebhookConfiguration

ValidatingWebhookConfiguration 用於定義驗證型 Webhook。以下是完整的配置範例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: pod-policy-validator
webhooks:
  - name: pod-policy.example.com
    admissionReviewVersions:
      - v1
      - v1beta1
    clientConfig:
      service:
        name: webhook-service
        namespace: webhook-system
        path: /validate
        port: 443
      caBundle: LS0tLS1CRUdJTi... # Base64 編碼的 CA 憑證
    rules:
      - apiGroups:
          - ""
        apiVersions:
          - v1
        operations:
          - CREATE
          - UPDATE
        resources:
          - pods
        scope: Namespaced
    failurePolicy: Fail
    matchPolicy: Equivalent
    sideEffects: None
    timeoutSeconds: 10
    namespaceSelector:
      matchLabels:
        environment: production
    objectSelector:
      matchExpressions:
        - key: skip-validation
          operator: NotIn
          values:
            - "true"

重要欄位說明:

  • failurePolicy:Webhook 無法連線時的處理方式(Fail 或 Ignore)
  • sideEffects:是否有副作用(None、Some、Unknown)
  • timeoutSeconds:請求逾時時間(預設 10 秒,最大 30 秒)

MutatingWebhookConfiguration

MutatingWebhookConfiguration 用於定義變更型 Webhook。以下範例展示如何自動注入 Label:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: pod-label-injector
webhooks:
  - name: label-injector.example.com
    admissionReviewVersions:
      - v1
    clientConfig:
      service:
        name: label-injector-service
        namespace: webhook-system
        path: /mutate
        port: 443
      caBundle: LS0tLS1CRUdJTi...
    rules:
      - apiGroups:
          - ""
        apiVersions:
          - v1
        operations:
          - CREATE
        resources:
          - pods
        scope: Namespaced
    failurePolicy: Ignore
    sideEffects: None
    timeoutSeconds: 5
    reinvocationPolicy: IfNeeded

reinvocationPolicy 說明:

  • Never:每個 Webhook 只會被呼叫一次
  • IfNeeded:如果其他 Webhook 修改了物件,可能會再次呼叫

Webhook 實作範例

以下是使用 Python Flask 實作的簡易 Webhook 服務:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
from flask import Flask, request, jsonify
import base64
import json

app = Flask(__name__)

@app.route('/validate', methods=['POST'])
def validate():
    admission_review = request.get_json()
    pod = admission_review['request']['object']

    # 檢查 Pod 是否有設定資源限制
    allowed = True
    message = "Validation passed"

    containers = pod.get('spec', {}).get('containers', [])
    for container in containers:
        resources = container.get('resources', {})
        if not resources.get('limits'):
            allowed = False
            message = f"Container {container['name']} 必須設定資源限制"
            break

    response = {
        "apiVersion": "admission.k8s.io/v1",
        "kind": "AdmissionReview",
        "response": {
            "uid": admission_review['request']['uid'],
            "allowed": allowed,
            "status": {
                "message": message
            }
        }
    }
    return jsonify(response)

@app.route('/mutate', methods=['POST'])
def mutate():
    admission_review = request.get_json()

    # 建立 JSON Patch 來添加 Label
    patch = [
        {
            "op": "add",
            "path": "/metadata/labels/injected-by",
            "value": "admission-webhook"
        }
    ]

    response = {
        "apiVersion": "admission.k8s.io/v1",
        "kind": "AdmissionReview",
        "response": {
            "uid": admission_review['request']['uid'],
            "allowed": True,
            "patchType": "JSONPatch",
            "patch": base64.b64encode(
                json.dumps(patch).encode()
            ).decode()
        }
    }
    return jsonify(response)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=443, ssl_context=('cert.pem', 'key.pem'))

故障排除

常見問題與解決方案

1. Webhook 連線失敗

1
2
3
4
5
6
# 檢查 Webhook 服務是否正常運作
kubectl get svc -n webhook-system
kubectl get pods -n webhook-system

# 檢查憑證是否正確
kubectl get secret webhook-tls -n webhook-system -o yaml

2. 請求被意外拒絕

1
2
3
4
5
6
# 查看 API Server 日誌
kubectl logs kube-apiserver-<node> -n kube-system | grep admission

# 暫時將 failurePolicy 改為 Ignore 進行測試
kubectl patch validatingwebhookconfiguration pod-policy-validator \
  --type='json' -p='[{"op": "replace", "path": "/webhooks/0/failurePolicy", "value": "Ignore"}]'

3. 憑證問題

確保 caBundle 包含正確的 CA 憑證:

1
2
3
4
5
6
# 取得 CA 憑證並編碼
CA_BUNDLE=$(kubectl config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')

# 更新 WebhookConfiguration
kubectl patch validatingwebhookconfiguration pod-policy-validator \
  --type='json' -p="[{\"op\": \"replace\", \"path\": \"/webhooks/0/clientConfig/caBundle\", \"value\": \"${CA_BUNDLE}\"}]"

最佳實踐

安全性

  1. 使用 TLS:Webhook 必須使用 HTTPS,確保通訊安全
  2. 限制 Namespace:使用 namespaceSelector 限制 Webhook 的作用範圍
  3. 排除系統 Namespace:避免影響 kube-system 等關鍵 Namespace
1
2
3
4
5
6
7
namespaceSelector:
  matchExpressions:
    - key: kubernetes.io/metadata.name
      operator: NotIn
      values:
        - kube-system
        - kube-public

可靠性

  1. 設定合理的 timeout:建議 5-10 秒
  2. 使用 failurePolicy: Ignore:在非關鍵場景下避免阻塞整個叢集
  3. 實作健康檢查:確保 Webhook 服務高可用

效能

  1. 使用 objectSelector:精確過濾需要處理的物件
  2. 快取頻繁存取的資料:減少 Webhook 處理時間
  3. 水平擴展:部署多個 Webhook Pod 副本
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webhook-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: webhook-server
  template:
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                topologyKey: kubernetes.io/hostname

參考資料

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy