在雲原生架構中,Kubernetes 被設計用來整合多個工作負載到單一叢集中,但在實際企業環境中,我們經常需要管理多個叢集。這種需求源於多種因素,包括:

影響範圍控制

當我討論多叢集架構時,「影響範圍」(blast radius) 應該是首要考量因素。這是我觀察到的企業設計多叢集架構的主要原因之一。如同微服務架構中我們使用斷路器、重試機制和流量控制來限制系統損害範圍,基礎設施層也應該採取相同的設計思維。

舉例來說,如果一個叢集服務了 500 個應用程式,而發生平台層問題,將會影響 100% 的應用程式。但若將相同的應用程式分散在五個叢集上,任何單一平台問題最多隻會影響 20% 的應用程式。當然,這意味著你需要管理五個叢集,與資源整合率不會像單一叢集那麼高。

合規性需求

多叢集設計的另一個關鍵考量是合規性要求,特別是處理 PCI、HIPAA 等特殊工作負載時。雖然 Kubernetes 提供了一些多租戶功能,但這些合規性工作負載可能更容易在與一般用途工作負載分離的環境中管理。這些工作負載可能有特定的安全強化要求、非分享元件或專用資源需求,將它們分離通常比在單一叢集中特殊處理更為簡單。

安全性考量

在大型 Kubernetes 叢集中,安全性管理可能變得極為複雜。隨著更多團隊加入叢集,每個團隊可能有不同的安全需求,在單一多租戶叢集中滿足這些需求變得越來越困難。即使只是管理 RBAC、網路策略和 Pod 安全策略,在大規模環境中也會變得極為複雜。網路策略的微小變更可能無意中為叢集中的其他使用者帶來安全風險。

透過多叢集架構,你可以限制安全設定錯誤的影響範圍。如果你決定使用較大的 Kubernetes 叢集,請確保你有完善的操作流程來管理安全變更,並瞭解 RBAC、網路策略和 Pod 安全策略變更的影響範圍。

區域性工作負載

當執行需要從區域端點提供流量的工作負載時,你的設計將包括根據每個區域的多個叢集。對於全球分散式應用程式,這幾乎是必然的需求。當你有需要區域分散的工作負載時,這是叢集聯邦的絕佳使用案例。

專業化工作負載

高效能計算 (HPC)、機器學習 (ML) 和網格計算等專業工作負載也需要在多叢集架構中考慮。這些型別的專業工作負載可能需要特定型別的硬體、獨特的效能設定檔案和專業化的叢集使用者。在設計決策中,這種使用案例較少見,因為使用多個 Kubernetes 節點池可以幫助解決專用硬體和效能設定檔案的問題。但當你需要一個非常大的叢集用於 HPC 或機器學習工作負載時,應考慮為這些工作負載專門設定叢集。

多叢集設計的挑戰

選擇多叢集設計時,你會面臨一些挑戰。這些挑戰可能會讓你重新考慮是否要採用多叢集設計,因為它可能會過度複雜化你的架構。以下是我發現使用者常遇到的挑戰:

資料複製與一致性

跨地理區域和多個叢集佈署工作負載時,資料複製和一致性一直是核心挑戰。當執行這些服務時,你需要決定什麼在哪裡執行,並制定複製策略。大多數資料函式庫都有內建工具來執行複製,但你需要設計應用程式以處理複製策略。對於 NoSQL 型別的資料函式庫服務,這可能更容易,因為它們可以處理跨多個例項的擴充套件,但你仍然需要確保你的應用程式可以處理跨地理區域的最終一致性或至少處理區域間的延遲。

服務發現

每個 Kubernetes 叢集都佈署自己的服務發現登入檔,而這些登入檔不會在多個叢集之間同步。這使得應用程式難以輕鬆識別和發現彼此。像 HashiCorp 的 Consul 這樣的工具可以透明地同步來自多個叢集的服務,甚至是位於 Kubernetes 外部的服務。其他工具如 Istio、Linkerd 和 Cilium 也在構建多叢集架構,以擴充套件叢集間的服務發現。

網路由

Kubernetes 使叢集內的網路變得非常簡單,它是一個平面網路,避免使用網路地址轉換 (NAT)。但如果你需要將流量路由進出叢集,這會變得更加複雜。叢集的入口是作為入口到叢集的 1:1 對映實作的,因為它不支援具有 Ingress 資源的多叢集拓撲。你還需要考慮叢集之間的出口流量以及如何路由該流量。

當你的應用程式位於單一叢集內時,這很簡單,但引入多叢集時,你需要考慮對於在另一個叢集中有應用程式依賴關係的服務的額外跳轉延遲。對於具有緊密耦合依賴關係的應用程式,你應該考慮在同一叢集中執行這些服務,以減少延遲和額外的複雜性。

操作管理

管理多叢集的最大開銷之一是操作管理。你不再只需要管理一兩個叢集,而可能需要管理環境中的許多叢集。管理多叢集的最重要方面之一是確保你有良好的自動化實踐,因為這將有助於減少操作負擔。在自動化叢集時,你需要考慮基礎設施佈署和管理叢集的附加功能。對於管理基礎設施,使用像 HashiCorp 的 Terraform 這樣的工具可以幫助佈署和管理跨叢集的一致狀態。

使用基礎架構即程式碼 (IaC) 工具如 Terraform 將使你能夠以可重複的方式佈署叢集。另一方面,你還需要能夠一致地管理叢集的附加元件,如監控、日誌記錄、入口、安全性和其他工具。安全性是操作管理的另一個重要方面,你必須能夠維護跨叢集的安全策略、RBAC 和網路策略。

准入控制:確保叢集安全與合規

准入控制器位於 Kubernetes API 伺服器請求流程中,在身份驗證和授權階段之後接收請求。它們用於在將請求物件儲存到儲存之前驗證或修改(或兩者兼有)請求物件。驗證和修改准入控制器的區別在於,修改准入控制器可以修改它們接受的請求物件,而驗證准入控制器則不能。

准入控制器的重要性

由於准入控制器位於所有 API 伺服器請求的路徑中,你可以多種不同方式使用它們。最常見的是,准入控制器的使用可以分為以下三類別:

政策與治理

准入控制器允許執行政策以滿足業務需求,例如:

  • 在開發名稱空間中只能使用內部雲負載平衡器
  • Pod 中的所有容器必須有資源限制
  • 為所有資源增加預定義的標準標籤或註解,使它們可被現有工具發現
  • 所有 Ingress 資源只使用 HTTPS

安全性

你可以使用准入控制器在叢集中執行一致的安全態勢。一個典型的例子是 Pod 安全准入控制器,它根據 Pod 規範中定義的安全敏感欄位的設定來決定是否應該接受 Pod。例如,它可以拒絕特權容器或使用主機檔案系統的特定路徑。你可以使用准入 webhook 執行更細粒度或自定義的安全規則。

資源管理

准入控制器允許你驗證並為叢集使用者提供最佳實踐,例如:

  • 確保所有入口完全限定網域名稱 (FQDN) 都在特定字尾內
  • 確保入口 FQDN 不重疊
  • Pod 中的所有容器必須有資源限制

准入控制器型別

准入控制器有兩類別:標準和動態。標準准入控制器被編譯到 API 伺服器中,並作為外掛與每個 Kubernetes 版本一起提供;它們需要在 API 伺服器啟動時設定。另一方面,動態控制器可以在執行時設定,並在核心 Kubernetes 程式碼函式庫之外開發。動態准入控制的唯一型別是准入 webhook,它透過 HTTP 回呼接收准入請求。

授權機制:控制 API 存取

Kubernetes 中的授權機制決定了使用者可以對 API 資源執行哪些操作。這是確保叢集安全的關鍵部分,特別是在多租戶環境中。

RBAC:根據角色的存取控制

RBAC 是 Kubernetes 中最常用的授權機制,它允許你定義角色和角色繫結來控制對 API 資源的存取。

在 RBAC 中,有兩種型別的角色:

  1. Role:名稱空間範圍的角色,只能授予對特定名稱空間中資源的存取許可權
  2. ClusterRole:叢集範圍的角色,可以授予對叢集範圍資源的存取許可權

同樣,有兩種型別的角色繫結:

  1. RoleBinding:將角色繫結到特定名稱空間中的使用者
  2. ClusterRoleBinding:將叢集角色繫結到整個叢集中的使用者

以下是一個 RBAC 角色的例子,它允許使用者在 default 名稱空間中讀取 Pod:

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: default
  name: pod-viewer
rules:
- apiGroups: [""] # "" 表示核心 API 組
  resources: ["pods"]
  verbs: ["get", "watch", "list"]

然後,你可以使用 RoleBinding 將此角色繫結到特定使用者:

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: noc-helpdesk-view
  namespace: default
subjects:
- kind: User
  name: helpdeskuser@example.com
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role #這必須是 Role 或 ClusterRole
  name: pod-viewer # 這必須比對要繫結的 Role 或 ClusterRole 的名稱
  apiGroup: rbac.authorization.k8s.io

多叢集管理最佳實踐

考慮以下管理多個 Kubernetes 叢集的最佳實踐:

  1. 限制叢集的影響範圍:確保級聯故障不會對應用程式產生更大影響。

  2. 合規性隔離:如果你有 PCI、HIPAA 或 HiTrust 等監管問題,考慮使用多叢集來簡化將這些工作負載與一般工作負載混合的複雜性。

  3. 硬多租戶需求:如果硬多租戶是業務需求,工作負載應佈署到專用叢集。

  4. 區域流量管理:如果應用程式需要多個區域,使用全球負載平衡器來管理叢集間的流量。

  5. 專業工作負載隔離:可以將高效能計算等專業工作負載分解到自己的獨立叢集中,以確保滿足工作負載的專業需求。

  6. 資料複製策略:如果你佈署的工作負載將分佈在多個區域資料中心,首先確保工作負載有資料複製策略。跨區域的多個叢集可能很容易,但跨區域複製資料可能很複雜,因此確保有一個健全的策略來處理非同步和同步工作負載。

  7. 利用 Kubernetes 運算元:使用像 prometheus-operator 或 Elasticsearch 運算元這樣的 Kubernetes 運算元來處理自動化操作任務。

  8. 服務發現與網路:設計多叢集策略時,還要考慮如何實作叢集間的服務發現和網路。像 HashiCorp 的 Consul 或 Istio 這樣的服務網格工具可以幫助跨叢集網路。

  9. 持續佈署策略:確保你的 CD 策略可以處理區域間或多個叢集之間的多次佈署。

  10. GitOps 方法:考慮使用 GitOps 方法來管理多個叢集操作元件,以確保艦隊中所有叢集之間的一致性。GitOps 方法可能不適合每個人的環境,但你至少應該調查它以減輕多叢集環境的操作負擔。

准入控制與授權最佳實踐

以下是有關 Kubernetes 准入控制和授權的最佳實踐:

  1. 最小許可權原則:只授予使用者完成其工作所需的最小許可權集。

  2. 使用名稱空間隔離:利用名稱空間來隔離不同團隊或應用程式的資源和許可權。

  3. 實施 Pod 安全標準:使用 Pod 安全准入控制器來執行 Pod 安全標準,防止特權升級和其他安全問題。

  4. 自定義准入控制:使用准入 webhook 來實施組織特定的政策和最佳實踐。

  5. 稽核日誌:啟用稽核日誌以跟蹤誰在何時對叢集進行了哪些更改。

  6. 服務帳戶管理:謹慎管理服務帳戶,並使用 RBAC 限制它們的許可權。

  7. 定期審查許可權:定期審查和更新 RBAC 角色和繫結,以確保它們仍然適當。

  8. 使用外部身份提供者:與企業身份管理系統整合,如 LDAP 或 OIDC 提供者。

  9. 網路策略:實施網路策略來控制 Pod 之間的通訊。

  10. 加密敏感資料:使用 Kubernetes 金鑰來儲存和管理敏感資料,並考慮啟用靜態加密。

在本文中,我們探討了 Kubernetes 多叢集管理、准入控制和授權機制的最佳實踐。隨著組織擴充套件其 Kubernetes 佈署,這些考慮因素變得越來越重要。

多叢集架構提供了更好的隔離和彈性,但也帶來了複雜性和管理開銷。透過遵循本文中概述的最佳實踐,你可以設計一個既滿足你的業務需求又易於管理的架構。

同樣,准入控制和授權是確保 Kubernetes 環境安全的關鍵元件。透過實施適當的政策和控制,你可以防止未經授權的存取並確保遵守組織標準。

最終,成功的 Kubernetes 佈署需要在靈活性、安全性和可管理性之間取得平衡。透過採用這些最佳實踐,你可以建立一個既能滿足當前需求又能隨著組織成長而擴充套件的環境。

全球佈署與叢集管理:實作應用程式的跨區域擴充套件

全球化佈署策略

在現代雲端架構中,將應用程式佈署到全球不同區域已成為提高用性和降低延遲的關鍵策略。全球化佈署不僅能讓使用者獲得更好的體驗,還能提供災難復原的能力。實施全球化佈署時,需要考慮幾個關鍵因素:

地理位置選擇

選擇佈署區域時,應考慮以下因素:

regions:
  - name: asia-east1
    location: "台灣"
    purpose: "服務亞洲東部使用者"
  - name: europe-west3
    location: "德國"
    purpose: "服務歐洲使用者"
  - name: us-central1
    location: "美國中部"
    purpose: "服務北美使用者與備援"

這個設定展示了多區域佈署的基本結構,每個區域都有特定的地理位置和服務目的。選擇台灣、德國和美國作為佈署點,可以有效覆寫亞洲、歐洲和北美的使用者群,同時提供跨洲際的備援能力。在實際佈署中,這種設定通常會整合到基礎架構即程式碼(IaC)工具中,如Terraform或Pulumi。

資源管理的藝術

在容器協調平台中,有效的資源管理是確保應用程式穩定執行的基礎。資源管理涉及CPU、記憶體、儲存空間等計算資源的分配與限制,以及如何在叢集中平衡這些資源。

資源請求與限制

apiVersion: v1
kind: Pod
metadata:
  name: resource-demo
spec:
  containers:
  - name: resource-demo-ctr
    image: nginx
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "128Mi"
        cpu: "500m"

這個Pod定義展示了資源管理的兩個關鍵概念:requests(請求)和limits(限制)。requests指定了容器啟動所需的最小資源,這幫助排程器決定將Pod放在哪個節點上;limits則設定了容器可以使用的最大資源量,防止單一容器消耗過多資源影響其他工作負載。

在這個例子中,容器請求250毫核CPU(相當於1/4核心)和64MB記憶體,最大可使用500毫核CPU和128MB記憶體。當容器嘗試使用超過限制的資源時,對於CPU會被限流,而對於記憶體則可能導致容器被終止(OOMKilled)。

網路架構與策略

網路是連線應用程式各個元件的關鍵基礎設施。在現代容器平台中,網路不僅提供基本的連線功能,還負責負載平衡、服務發現和安全隔離。

網路策略實作

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-allow
spec:
  podSelector:
    matchLabels:
      app: api-service
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: database
    ports:
    - protocol: TCP
      port: 5432

這個網路策略定義了精確的流量控制規則,實作了微服務之間的安全通訊。具體來說:

  1. 策略適用於標記為app: api-service的所有Pod
  2. 入站(Ingress)規則:只允許來自標記為app: frontend的Pod存取8080連線埠
  3. 出站(Egress)規則:只允許api-service向標記為app: database的Pod的5432連線埠(PostgreSQL標準連線埠)傳送流量

這種精細的網路控制實作了"最小許可權原則",每個服務只能與其直接相關的服務通訊,大減少了潛在的攻擊面。在實際生產環境中,這種網路隔離是實作零信任安全架構的重要組成部分。

Pod安全性強化

容器安全是一個多層次的挑戰,從容器映像的安全掃描到執行時的行為監控。Pod安全上下文(Security Context)提供了一種宣告式的方法來控制容器的許可權和能力。

安全上下文設定

apiVersion: v1
kind: Pod
metadata:
  name: security-enhanced-pod
spec:
  securityContext:
    runAsNonRoot: true
    fsGroup: 2000
  containers:
  - name: secure-app
    image: my-secure-app:1.0.2
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
        - ALL
        add:
        - NET_BIND_SERVICE

這個Pod定義實作了多層安全防護:

  1. Pod級別的安全設定:

    • runAsNonRoot: true:確保容器以非root使用者執行,這是容器安全的基本要求
    • fsGroup: 2000:設定檔案系統組ID,確保掛載的卷有正確的許可權
  2. 容器級別的安全設定:

    • allowPrivilegeEscalation: false:防止程式取得比父程式更多的許可權
    • readOnlyRootFilesystem: true:將根檔案系統設為只讀,防止執行時修改
    • 能力管理:移除所有預設Linux能力,只增加NET_BIND_SERVICE(允許繫結1024以下的連線埠)

這種設定遵循"最小許可權"原則,只授予應用程式正常執行所需的最小許可權集,大減少了潛在的攻擊面。在實際佈署中,這些安全設定應該與其他安全措施(如網路策略、准入控制器)結合使用,形成深度防禦策略。

政策與治理

隨著組織規模的擴大,確保所有團隊遵循一致的安全和合規標準變得至關重要。政策和治理工具提供了一種自動化的方式來強制執行這些標準。

使用OPA Gatekeeper實作政策控制

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: require-team-label
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Namespace"]
  parameters:
    labels: ["team", "environment"]
---
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredLabels
      validation:
        openAPIV3Schema:
          properties:
            labels:
              type: array
              items:
                type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package k8srequiredlabels
        
        violation[{"msg": msg}] {
          provided := {label | input.review.object.metadata.labels[label]}
          required := {label | label := input.parameters.labels[_]}
          missing := required - provided
          count(missing) > 0
          msg := sprintf("missing required labels: %v", [missing])
        }

這個例子展示瞭如何使用OPA Gatekeeper(一個流行的政策引擎)來強制執行標籤要求。具體來說:

  1. ConstraintTemplate定義了一個可重用的政策範本,使用Rego語言(OPA的政策語言)編寫
  2. K8sRequiredLabels是一個根據該範本的具體約束,要求所有名稱空間必須有"team"和"environment"標籤

這種政策控制機制可以確保所有資源都符合組織的標準,例如:

  • 確保所有資源都有適當的標籤用於計費和所有權追蹤
  • 防止使用已知不安全的容器映像
  • 強制執行網路隔離政策
  • 限制特權容器的使用

在大型組織中,這種自動化的政策執行是實作"安全即程式碼"和"合規即程式碼"的關鍵。

多叢集管理策略

隨著應用規模的擴大,單一叢集可能無法滿足所有需求。多叢集架構提供了更好的隔離性、可用性和地理分佈能力,但也帶來了管理複雜性。

多叢集管理架構

  graph TD
    A[管理叢集] --> B[生產叢集-亞洲]
    A --> C[生產叢集-歐洲]
    A --> D[生產叢集-美洲]
    A --> E[開發/測試叢集]
    
    subgraph "Fleet管理"
    A
    end
    
    subgraph "工作負載叢集"
    B
    C
    D
    E
    end

多叢集管理的關鍵挑戰包括:

  1. 設定同步:如何確保所有叢集的設定保持一致
  2. 工作負載分發:如何決定將工作負載佈署到哪個叢集
  3. 身份與存取管理:如何在多個叢集間管理使用者身份和許可權
  4. 可觀測性:如何獲得跨叢集的統一監控檢視

使用Fleet進行多叢集管理

kind: GitRepo
apiVersion: fleet.cattle.io/v1alpha1
metadata:
  name: global-config
  namespace: fleet-default
spec:
  repo: https://github.com/organization/global-configs
  branch: main
  paths:
  - /base
  targets:
  - name: production
    clusterSelector:
      matchLabels:
        environment: production
  - name: development
    clusterSelector:
      matchLabels:
        environment: development
    overlays:
    - /overlays/development

這個設定展示了使用Fleet(一個多叢集GitOps工具)進行組態管理的方法。它定義了:

  1. 一個Git倉函式庫作為設定源
  2. 根據叢集標籤的目標分組(production和development)
  3. 針對不同環境的設定覆寫(overlays)

這種GitOps方法將叢集設定作為程式碼管理,提供了版本控制、稽核跟蹤和自動化佈署的能力。在實際使用中,這種方法可以與其他工具(如Argo CD、Flux CD)結合,構建完整的多叢集管理解決方案。

多叢集管理不僅是技術挑戰,也是組織挑戰。成功的多叢集策略需要明確的責任分工、標準化的流程和自動化的工具支援。隨著雲原生技術的成熟,像Fleet、Karmada和Cluster API這樣的工具正在使多叢集管理變得更加可行。

在設計多叢集架構時,玄貓建議從業務需求出發,明確每個叢集的用途和責任邊界,避免不必要的複雜性。對於大多陣列織來說,從單一管理平面管理多個工作負載叢集的模式是一個好的起點。

全球佈署和叢集管理是現代雲原生架構的重要組成部分。透過合理的資源管理、網路策略、安全控制和治理機制,可以構建既靈活又安全的多叢集環境,為全球使用者提供高用性、低延遲的服務體驗。隨著技術的不斷發展,這些領域的最佳實踐也在不斷演進,持續學習和適應變化是保持競爭力的關鍵。

Kubernetes 准入控制器實作:保護叢集安全與合規

准入控制器的核心概念與實作方式

Kubernetes 准入控制器是保護叢集安全與合規的重要機制,它們在 API 伺服器處理請求的過程中扮演著關鍵角色。當使用者或服務帳號嘗試建立、修改或刪除資源時,准入控制器可以攔截這些請求,並根據預定義的規則進行驗證或修改。

准入控制器分為兩種型別:驗證性准入控制器(Validating Admission Controller)和變更性准入控制器(Mutating Admission Controller)。前者只負責驗證請求是否符合規則,後者則可以在請求被接受前修改資源定義。

准入控制器的工作流程

  1. 使用者傳送 API 請求
  2. 請求透過認證與授權檢查
  3. 變更性准入控制器處理請求(可能修改資源定義)
  4. 驗證性准入控制器檢查請求合規性
  5. 如果透過所有檢查,請求被接受並持久化到 etcd

現在讓我們看如何實作一個拒絕特定請求的准入控制器:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: deny-specific-requests
webhooks:
- name: deny.example.com
  clientConfig:
    service:
      namespace: admission-webhook
      name: admission-webhook-service
      path: "/validate"
    caBundle: <base64-encoded-ca-cert>
  rules:
  - apiGroups: ["apps"]
    apiVersions: ["v1"]
    operations: ["CREATE", "UPDATE"]
    resources: ["deployments"]
    scope: "Namespaced"
  admissionReviewVersions: ["v1"]
  sideEffects: None
  timeoutSeconds: 5
  failurePolicy: Fail

這個 YAML 定義了一個驗證性 Webhook 設定,它會攔截所有建立或更新 Deployment 的請求。關鍵設定包括:

  • clientConfig:指定處理准入請求的服務位置,包括名稱空間、服務名稱和路徑
  • rules:定義哪些 API 請求會被攔截,這裡設定為攔截 apps/v1 群組中的 Deployment 資源的建立和更新操作
  • failurePolicy: Fail:如果 Webhook 服務無法存取,則拒絕所有請求,這是一種安全優先的設定
  • sideEffects: None:表明 Webhook 不會有任何副作用,這是 v1 版本中的必要設定

實作準入控制器服務

准入控制器的核心是一個 Webhook 服務,它接收來自 Kubernetes API 伺服器的請求,並回傳允許或拒絕的決定。以下是使用 Go 語言實作的一個簡單准入控制器:

package main

import (
    "encoding/json"
    "fmt"
    "io/ioutil"
    "net/http"
    
    admissionv1 "k8s.io/api/admission/v1"
    appsv1 "k8s.io/api/apps/v1"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "k8s.io/klog"
)

// 處理驗證請求
func validateDeployments(w http.ResponseWriter, r *http.Request) {
    var body []byte
    if r.Body != nil {
        if data, err := ioutil.ReadAll(r.Body); err == nil {
            body = data
        }
    }
    
    // 解析 AdmissionReview 請求
    requestedAdmissionReview := admissionv1.AdmissionReview{}
    if err := json.Unmarshal(body, &requestedAdmissionReview); err != nil {
        klog.Error(err)
        w.WriteHeader(http.StatusBadRequest)
        return
    }
    
    // 提取佈署資源
    var deployment appsv1.Deployment
    err := json.Unmarshal(requestedAdmissionReview.Request.Object.Raw, &deployment)
    if err != nil {
        klog.Error(err)
        w.WriteHeader(http.StatusBadRequest)
        return
    }
    
    // 檢查是否符合規則
    allowed := true
    var result *metav1.Status
    
    // 範例規則:拒絕沒有設定資源限制的佈署
    if deployment.Spec.Template.Spec.Containers[0].Resources.Limits == nil {
        allowed = false
        result = &metav1.Status{
            Reason: "必須設定資源限制",
        }
    }
    
    // 建立回應
    responseAdmissionReview := admissionv1.AdmissionReview{
        TypeMeta: requestedAdmissionReview.TypeMeta,
        Response: &admissionv1.AdmissionResponse{
            UID:     requestedAdmissionReview.Request.UID,
            Allowed: allowed,
            Result:  result,
        },
    }
    
    // 回傳 JSON 回應
    resp, err := json.Marshal(responseAdmissionReview)
    if err != nil {
        klog.Error(err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    w.Write(resp)
}

func main() {
    http.HandleFunc("/validate", validateDeployments)
    klog.Info("准入控制器服務啟動於 :8443")
    klog.Fatal(http.ListenAndServeTLS(":8443", "/certs/tls.crt", "/certs/tls.key", nil))
}

這段 Go 程式碼實作了一個簡單的驗證性准入控制器:

  1. 它建立了一個 HTTP 伺服器,監聽 /validate 路徑上的請求
  2. 當收到請求時,它解析 AdmissionReview 物件,提取出 Deployment 資源
  3. 它檢查 Deployment 是否設定了資源限制,如果沒有,則拒絕請求
  4. 最後,它建立一個 AdmissionResponse 物件,指明請求是否被允許,以及拒絕的原因
  5. 伺服器使用 TLS 加密通訊,這是 Kubernetes 准入控制器的必要條件

佈署准入控制器

准入控制器需要在 Kubernetes 叢集中佈署為一個服務,並且需要 TLS 證書以確保安全通訊。以下是佈署准入控制器的 YAML 範例:

apiVersion: v1
kind: Namespace
metadata:
  name: admission-webhook
apiVersion: v1
kind: Service
metadata:
  name: admission-webhook-service
  namespace: admission-webhook
spec:
  selector:
    app: admission-webhook
  ports:
  - port: 443
    targetPort: 8443

這個 YAML 檔案定義了佈署准入控制器所需的資源:

  1. 首先建立一個專用的名稱空間 admission-webhook
  2. 然後定義一個 Deployment,佈署准入控制器的容器
  3. 容器掛載了一個包含 TLS 證書的 Secret
  4. 最後定義一個 Service,將准入控制器暴露給 API 伺服器

生成和管理 TLS 證書

准入控制器需要 TLS 證書才能與 API 伺服器安全通訊。以下是生成證書的指令碼範例:

#!/bin/bash

# 設定變數
SERVICE=admission-webhook-service
NAMESPACE=admission-webhook
SECRET_NAME=webhook-certs

# 建立臨時目錄
mkdir -p certs
cd certs

# 生成 CA 私鑰和證書
openssl genrsa -out ca.key 2048
openssl req -new -x509 -key ca.key -out ca.crt -subj "/CN=admission-webhook-ca"

# 生成伺服器私鑰
openssl genrsa -out server.key 2048

# 生成伺服器證書籤名請求
cat > server.conf << EOF
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name

[req_distinguished_name]

[v3_req]
basicConstraints = CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = ${SERVICE}
DNS.2 = ${SERVICE}.${NAMESPACE}
DNS.3 = ${SERVICE}.${NAMESPACE}.svc
EOF

openssl req -new -key server.key -out server.csr -subj "/CN=${SERVICE}.${NAMESPACE}.svc" -config server.conf

# 簽署伺服器證書
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -extensions v3_req -extfile server.conf

# 建立 Kubernetes Secret
kubectl create secret generic ${SECRET_NAME} \
    --from-file=tls.key=server.key \
    --from-file=tls.crt=server.crt \
    --dry-run=client -o yaml | kubectl -n ${NAMESPACE} apply -f -

# 取得 CA 證書的 base64 編碼
CA_BUNDLE=$(cat ca.crt | base64 | tr -d '\n')

# 輸出 CA 證書的 base64 編碼
echo "CA Bundle for webhook configuration: ${CA_BUNDLE}"

# 清理
cd ..

這個 Bash 指令碼執行以下操作:

  1. 生成一個自簽名的 CA 證書
  2. 生成伺服器私鑰和證書籤名請求(CSR)
  3. 使用 CA 證書籤署伺服器證書
  4. 將證書和私鑰儲存在 Kubernetes Secret 中
  5. 輸出 CA 證書的 base64 編碼,用於 ValidatingWebhookConfiguration 中的 caBundle 欄位

這些證書確保了 API 伺服器和准入控制器之間的安全通訊。

變更性准入控制器實作

除了驗證請求外,准入控制器還可以修改資源定義。以下是一個變更性准入控制器的範例,它會自動為 Pod 增加標籤:

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: pod-label-mutator
webhooks:
- name: mutate.pods.example.com
  clientConfig:
    service:
      namespace: admission-webhook
      name: admission-webhook-service
      path: "/mutate"
    caBundle: <base64-encoded-ca-cert>
  rules:
  - apiGroups: [""]
    apiVersions: ["v1"]
    operations: ["CREATE"]
    resources: ["pods"]
    scope: "Namespaced"
  admissionReviewVersions: ["v1"]
  sideEffects: None
  timeoutSeconds: 5
  failurePolicy: Ignore

以下是對應的 Go 程式碼實作:

func mutatePods(w http.ResponseWriter, r *http.Request) {
    var body []byte
    if r.Body != nil {
        if data, err := ioutil.ReadAll(r.Body); err == nil {
            body = data
        }
    }
    
    // 解析 AdmissionReview 請求
    requestedAdmissionReview := admissionv1.AdmissionReview{}
    if err := json.Unmarshal(body, &requestedAdmissionReview); err != nil {
        klog.Error(err)
        w.WriteHeader(http.StatusBadRequest)
        return
    }
    
    // 提取 Pod 資源
    var pod corev1.Pod
    err := json.Unmarshal(requestedAdmissionReview.Request.Object.Raw, &pod)
    if err != nil {
        klog.Error(err)
        w.WriteHeader(http.StatusBadRequest)
        return
    }
    
    // 建立修改操作的 JSON patch
    var patches []map[string]string
    
    // 如果 Pod 沒有標籤,先建立標籤欄位
    if pod.Labels == nil {
        patches = append(patches, map[string]string{
            "op":    "add",
            "path":  "/metadata/labels",
            "value": "{}",
        })
    }
    
    // 增加自訂標籤
    patches = append(patches, map[string]string{
        "op":    "add",
        "path":  "/metadata/labels/injected-by",
        "value": "admission-webhook",
    })
    
    // 序列化 patch
    patchBytes, err := json.Marshal(patches)
    if err != nil {
        klog.Error(err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }
    
    // 建立回應
    responseAdmissionReview := admissionv1.AdmissionReview{
        TypeMeta: requestedAdmissionReview.TypeMeta,
        Response: &admissionv1.AdmissionResponse{
            UID:     requestedAdmissionReview.Request.UID,
            Allowed: true,
            Patch:   patchBytes,
            PatchType: func() *admissionv1.PatchType {
                pt := admissionv1.PatchTypeJSONPatch
                return &pt
            }(),
        },
    }
    
    // 回傳 JSON 回應
    resp, err := json.Marshal(responseAdmissionReview)
    if err != nil {
        klog.Error(err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    w.Write(resp)
}

這段程式碼實作了一個變更性准入控制器:

  1. 它解析請求中的 Pod 資源
  2. 建立一個 JSON patch 操作,用於增加標籤
  3. 如果 Pod 沒有標籤欄位,先增加一個空的標籤欄位
  4. 然後增加一個 injected-by: admission-webhook 的標籤
  5. 將 patch 操作包含在 AdmissionResponse 中,並設定 PatchType 為 JSONPatch
  6. API 伺服器會在將 Pod 持久化到 etcd 前應用這些修改

進階准入控制策略

准入控制器可以實作各種複雜的策略,以下是一些常見的使用案例:

強制執行資源配額和限制

// 檢查容器是否設定了資源請求和限制
func validateResourceRequirements(container corev1.Container) (bool, string) {
    if container.Resources.Requests == nil || container.Resources.Limits == nil {
        return false, "所有容器必須設定資源請求和限制"
    }
    
    cpuRequest := container.Resources.Requests.Cpu()
    memoryRequest := container.Resources.Requests.Memory()
    cpuLimit := container.Resources.Limits.Cpu()
    memoryLimit := container.Resources.Limits.Memory()
    
    if cpuRequest.IsZero() || memoryRequest.IsZero() {
        return false, "CPU 和記憶體請求不能為零"
    }
    
    if cpuLimit.IsZero() || memoryLimit.IsZero() {
        return false, "CPU 和記憶體限制不能為零"
    }
    
    // 確保限制大於請求
    if cpuLimit.Cmp(*cpuRequest) < 0 {
        return false, "CPU 限制必須大於或等於請求"
    }
    
    if memoryLimit.Cmp(*memoryRequest) < 0 {
        return false, "記憶體限制必須大於或等於請求"
    }
    
    return true, ""
}

強制使用特定的映像倉函式庫

// 檢查容器映像是否來自允許的倉函式庫
func validateImageSource(container corev1.Container) (bool, string) {
    allowedRegistries := []string{
        "registry.company.com/",
        "gcr.io/company-project/",
        "docker.io/company/",
    }
    
    for _, registry := range allowedRegistries {
        if strings.HasPrefix(container.Image, registry) {
            return true, ""
        }
    }
    
    return false, fmt.Sprintf("容器映像必須來自允許的倉函式庫: %v", allowedRegistries)
}

強制使用網路策略

// 檢查名稱空間是否有網路策略
func validateNetworkPolicy(namespace string, client kubernetes.Interface) (bool, string) {
    policies, err := client.NetworkingV1().NetworkPolicies(namespace).List(context.TODO(), metav1.ListOptions{})
    if err != nil {
        return false, fmt.Sprintf("無法檢查網路策略: %v", err)
    }
    
    if len(policies.Items) == 0 {
        return false, "名稱空間必須至少有一個網路策略"
    }
    
    return true, ""
}

准入控制器的最佳實踐

在實作和佈署准入控制器時,以下是一些最佳實踐:

  1. 漸進式佈署:先使用 failurePolicy: Ignore 和日誌記錄來測試准入控制器,確保它不會意外阻止合法請求。

  2. 設定超時:使用 timeoutSeconds 設定合理的超時間,避免准入控制器成為系統瓶頸。

  3. 名稱空間選擇器:使用 namespaceSelector 限制准入控制器的範圍,例如排除系統名稱空間。

  4. 高用性:佈署多個准入控制器副本,並使用 Pod 反親和性確保它們分佈在不同節點上。

  5. 監控和警示:設定監控和警示,及時發現准入控制器的問題。

  6. 定期審查:定期審查准入控制策略,確保它們仍然符合組織的需求。

  7. 檔案化:詳細記錄准入控制策略,確保團隊成員瞭解為什麼某些操作會被拒絕。

准入控制器的限制與挑戰

雖然准入控制器是強大的工具,但它們也有一些限制和挑戰:

  1. 效能影響:每個 API 請求都需要經過准入控制器,可能會增加延遲。

  2. 複雜性:複雜的准入控制邏輯可能難以維護和除錯。

  3. 錯誤處理:如果准入控制器出現錯誤,可能會阻止合法請求或允許不合規的請求。

  4. 升級風險:升級准入控制器時,需要小心避免中斷正常操作。

  5. 繞過風險:某些特權操作可能會繞過准入控制器,需要額外的安全措施。

實際案例:防止特權容器

以下是一個實際案例,使用准入控制器防止佈署特權容器:

func validateSecurityContext(pod corev1.Pod) (bool, string) {
    // 檢查 Pod 安全上下文
    if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.RunAsNonRoot != nil && *pod.Spec.SecurityContext.RunAsNonRoot == false {
        return false, "Pod 必須以非 root 使用者執行"
    }
    
    // 檢查每個容器的安全上下文
    for _, container := range pod.Spec.Containers {
        if container.SecurityContext != nil {
            // 檢查特權模式
            if container.SecurityContext.Privileged != nil && *container.SecurityContext.Privileged {
                return false, "不允許特權容器"
            }
            
            // 檢查 capabilities
            if container.SecurityContext.Capabilities != nil && len(container.SecurityContext.Capabilities.Add) > 0 {
                for _, cap := range container.SecurityContext.Capabilities.Add {
                    if cap == "SYS_ADMIN" || cap == "NET_ADMIN" {
                        return false, fmt.Sprintf("不允許增加敏感 capability: %s", cap)
                    }
                }
            }
            
            // 檢查是否以 root 執行
            if container.SecurityContext.RunAsNonRoot != nil && *container.SecurityContext.RunAsNonRoot == false {
                return false, "容器必須以非 root 使用者執行"
            }
        }
    }
    
    return true, ""
}

這段程式碼實作了一個安全性檢查函式,用於防止佈署特權容器:

  1. 它檢查 Pod 的安全上下文,確保 Pod 以非 root 使用者執行
  2. 它檢查每個容器的安全上下文,禁止特權容器
  3. 它檢查容器是否增加了敏感的 capabilities,如 SYS_ADMIN 或 NET_ADMIN
  4. 它確保每個容器都以非 root 使用者執行

這種檢查可以防止攻擊者利用特權容器逃逸到主機系統。

准入控制器與 OPA 整合

Open Policy Agent (OPA) 是一個通用的策略引擎,可以與 Kubernetes 准入控制器整合,提供更靈活的策略定義方式。以下是使用 OPA 的准入控制器範例:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: opa-validating-webhook
webhooks:
- name: validating.opa.example.com
  clientConfig:
    service:
      namespace: opa
      name: opa
      path: "/v1/data/kubernetes/admission"
    caBundle: <base64-encoded-ca-cert>
  rules:
  - apiGroups: ["*"]
    apiVersions: ["*"]
    operations: ["CREATE", "UPDATE"]
    resources: ["*"]
    scope: "*"
  admissionReviewVersions: ["v1"]
  sideEffects: None
  timeoutSeconds: 5
  failurePolicy: Fail

OPA 策略使用 Rego 語言定義,以下是一個簡單的策略範例:

package kubernetes.admission

deny[msg] {
    input.request.kind.kind == "Pod"
    container := input.request.object.spec.containers[_]
    container.securityContext.privileged
    msg := sprintf("特權容器不允許: %v", [container.name])
}

deny[msg] {
    input.request.kind.kind == "Pod"
    not input.request.object.metadata.labels.app
    msg := "Pod 必須有 app 標籤"
}

這個 Rego 策略定義了兩個拒絕規則:

  1. 第一個規則拒絕特權容器
  2. 第二個規則要求所有 Pod 必須有 app 標籤

OPA 的優勢在於它提供了一個宣告式的策略語言,使策略定義更加靈活和可維護。

准入控制器的故障排除

當准入控制器出現問題時,以下是一些故障排除的步驟:

  1. 檢查 Webhook 日誌:檢視準入控制器的日誌,尋找錯誤訊息。

  2. 檢查 API 伺服器日誌:API 伺服器的日誌可能包含有關 Webhook 呼叫失敗的訊息。

  3. 檢查 Webhook 設定:確保 Webhook 設定正確,特別是 caBundle 和服務參考。

  4. 測試 Webhook 連線:使用 kubectl 或 curl 直接測試 Webhook 服務的連線性。

  5. 檢查 TLS 證書:確保 TLS 證書有效與正確設定。

  6. 使用 failurePolicy: Ignore:暫時將 failurePolicy 設為 Ignore,以便在修復問題時不阻止正常操作。

  7. 檢查資源限制:確保準入控制器有足夠的資源處理請求。

Kubernetes 提供了一個實用的命令來檢查 Webhook 設定:

kubectl get validatingwebhookconfiguration -o yaml
kubectl get mutatingwebhookconfiguration -o yaml

這些命令可以顯示當前的 Webhook 設定,幫助識別設定問題。

准入控制器是 Kubernetes 安全和合規策略的重要組成部分。透過實作自定義的准入控制器,組織可以強制執行各種策略,如資源限制、安全要求和最佳實踐。

在實作準入控制器時,需要考慮效能、可靠性和使用者經驗。漸進式佈署、充分測試和良好的監控是成功實作的關鍵。

隨著 Kubernetes 的不斷發展,准入控制器的功能和靈活性也在不斷提升,使其成為保護 Kubernetes 環境的強大工具。透過結合 OPA 等策略引擎,組織可以建立更加靈活和可維護的策略框架,確保 Kubernetes 叢集的安全和合規。