本文已超過一年。較舊的文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

為容器網路介面 (CNI) 供應商定義網路策略一致性

特別感謝 Tim Hockin 和 Bowie Du (Google)、Dan Winship 和 Antonio Ojea (Red Hat)、Casey Davenport 和 Shaun Crampton (Tigera) 以及 Abhishek Raut 和 Antonin Bas (VMware) 對此工作的支持,並與我們合作解決不同容器網路介面 (CNI) 隨著時間推移出現的問題。

2020 年 4 月關於「節點本地」網路政策的簡短對話啟發了 SIG Network 建立 NetworkPolicy 子專案。顯而易見的是,作為一個社群,我們需要一個關於如何在 Kubernetes 上進行 Pod 網路安全的可靠方案,而這個方案需要一個社群圍繞它,以便在 K8s 中發展企業安全模式的文化採用。

在這篇文章中,我們將討論

  • 為什麼我們為網路政策建立子專案
  • 我們如何更改 Kubernetes e2e 框架以視覺化您的 CNI 供應商的 NetworkPolicy 實作
  • 我們綜合 NetworkPolicy 一致性驗證器 Cyclonus 的初步結果,該驗證器圍繞這些原則而建構
  • 子專案貢獻者對 NetworkPolicy 使用者體驗所做的改進

為什麼我們為 NetworkPolicies 建立子專案

在 2020 年 4 月,越來越明顯的是,許多 CNI 正在湧現,並且許多供應商以略有不同的方式實作這些 CNI。使用者開始對如何為不同情境實作政策感到有些困惑,並要求新功能。顯而易見的是,我們需要開始統一我們在 Kubernetes 中思考網路政策的方式,以避免 API 碎片化和不必要的複雜性。

例如

  • 為了對使用者的環境保持彈性,Calico 作為 CNI 供應商可以使用 IPIP 或 VXLAN 模式運行,或者在沒有封裝開銷的情況下運行。Antrea 和 Cilium 等 CNI 也提供類似的組態選項。
  • 一些 CNI 外掛程式為 NetworkPolicies 提供 iptables 以及其他選項,而其他 CNI 則使用完全不同的技術堆疊(例如,Antrea 專案使用 Open vSwitch 規則)。
  • 一些 CNI 外掛程式僅實作 Kubernetes NetworkPolicy API 的子集,而一些則實作超集。例如,某些外掛程式不支援鎖定具名端口的能力;其他外掛程式不適用於某些 IP 位址類型,並且類似政策類型存在不同的語義。
  • 一些 CNI 外掛程式與其他 CNI 外掛程式結合以實作 NetworkPolicies (canal),一些 CNI 可能混合實作 (multus),而一些雲端將路由與 NetworkPolicy 實作分開處理。

儘管這種複雜性在一定程度上是支援不同環境所必需的,但終端使用者發現他們需要遵循多步驟流程來實作網路政策以保護其應用程式

  • 確認他們的網路外掛程式支援 NetworkPolicies(有些不支援,例如 Flannel)
  • 確認他們的叢集網路外掛程式支援他們感興趣的特定 NetworkPolicy 功能(再次,具名端口或端口範圍範例浮現腦海)
  • 確認他們應用程式的網路政策定義正在執行正確的操作
  • 找出供應商政策實作的細微差別,並檢查該實作是否具有 CNI 中立實作(有時對於使用者來說已足夠)

上游 Kubernetes 中的 NetworkPolicy 專案旨在提供一個社群,人們可以在其中學習和貢獻 Kubernetes NetworkPolicy API 和周圍的生態系統。

第一步:一個直觀易用且易於理解的 NetworkPolicies 驗證框架

Kubernetes 端到端套件一直都有 NetworkPolicy 測試,但這些測試未在 CI 中運行,並且它們的實作方式並未提供關於政策在叢集中如何運作的完整、易於使用的資訊。這是因為原始測試未提供任何形式的叢集連線性視覺摘要。因此,我們最初著手透過使端到端測試(管理員或使用者經常使用這些測試來診斷叢集一致性)易於解釋,來簡化確認 CNI 對 NetworkPolicies 的支援。

為了解決確認 CNI 支援大多數使用者關心的基本政策功能的問題,我們在 Kubernetes e2e 框架中建構了一個新的 NetworkPolicy 驗證工具,該工具允許視覺檢查政策及其對叢集中標準 Pod 集合的影響。例如,以下面的測試輸出為例。我們在 OVN Kubernetes 中發現了一個錯誤。此錯誤現已解決。使用此工具,該錯誤很容易描述,其中某些政策導致狀態修改,稍後,導致流量被錯誤地封鎖(即使在從叢集中刪除所有網路政策之後)。

這是所討論測試的網路政策

metadata:
  creationTimestamp: null
  name: allow-ingress-port-80
spec:
  ingress:
  - ports:
    - port: serve-80-tcp
  podSelector: {}

這些是預期的連線性結果。測試設定是 9 個 Pod(3 個命名空間:x、y 和 z;以及每個命名空間中的 3 個 Pod:a、b 和 c);每個 Pod 在同一個端口和協定上運行伺服器,在沒有網路政策的情況下,可以透過 HTTP 呼叫訪問該伺服器。透過使用 agnhost 網路工具發出 HTTP 呼叫,在預期其他 Pod 正在服務的端口和協定上,來驗證連線性。測試情境首先運行連線性檢查,以確保每個 Pod 都可以訪問彼此,共 81 (= 9 x 9) 個資料點。這是「控制」。然後應用擾動,具體取決於測試情境:建立、更新和刪除政策;從 Pod 和命名空間新增和移除標籤等等。每次變更後,都會重新收集連線性矩陣並將其與預期的連線性進行比較。

這些結果以簡單的矩陣視覺化指示連線性。向下最左邊的列是「來源」Pod,或發出請求的 Pod;橫跨最上面的行是「目的地」Pod,或接收請求的 Pod。. 表示連線被允許;X 表示連線被封鎖。例如

Nov  4 16:58:43.449: INFO: expected:

-   x/a x/b x/c y/a y/b y/c z/a z/b z/c
x/a .   .   .   .   .   .   .   .   .
x/b .   .   .   .   .   .   .   .   .
x/c .   .   .   .   .   .   .   .   .
y/a .   .   .   .   .   .   .   .   .
y/b .   .   .   .   .   .   .   .   .
y/c .   .   .   .   .   .   .   .   .
z/a .   .   .   .   .   .   .   .   .
z/b .   .   .   .   .   .   .   .   .
z/c .   .   .   .   .   .   .   .   .

以下是在 OVN Kubernetes 錯誤情況下觀察到的連線性結果。請注意,前三行表示來自命名空間 x 的所有請求,無論 Pod 和目的地如何,都被封鎖了。由於這些實驗結果與預期結果不符,因此將報告失敗。請注意,失敗的特定模式如何提供對問題性質的清晰見解 -- 由於來自特定命名空間的所有請求都失敗,因此我們有了一個明確的線索來開始我們的調查。

Nov  4 16:58:43.449: INFO: observed:

-   x/a x/b x/c y/a y/b y/c z/a z/b z/c
x/a X   X   X   X   X   X   X   X   X
x/b X   X   X   X   X   X   X   X   X
x/c X   X   X   X   X   X   X   X   X
y/a .   .   .   .   .   .   .   .   .
y/b .   .   .   .   .   .   .   .   .
y/c .   .   .   .   .   .   .   .   .
z/a .   .   .   .   .   .   .   .   .
z/b .   .   .   .   .   .   .   .   .
z/c .   .   .   .   .   .   .   .   .

這是我們在網路政策小組中的最早勝利之一,因為我們能夠識別並與 OVN Kubernetes 小組合作,以修復出口政策處理中的錯誤。

然而,即使這個工具簡化了驗證大約 30 個常見情境,它也沒有驗證所有 Network Policy 情境 - 因為可能創建的排列組合數量非常龐大(從技術上講,我們可以說這個數字是無限的,因為可以創建的命名空間/Pod/端口/協定變化數量是無限的)。

一旦這些測試開始運行,我們就與上游 SIG Network 和 SIG Testing 社群(感謝 Antonio Ojea 和 Ben Elder)合作,建立了一個 testgrid Network Policy 任務。此任務針對GCE 與 Calico 作為 Network Policy 供應商持續運行整個 Network Policy 測試套件。

作為子專案的一部分,我們的職責是幫助確保在這些測試失敗時,我們可以有效地分類它們。

Cyclonus:朝向 Network Policy 一致性的下一步

在大約我們完成驗證工作的時候,社群清楚地表明,總體而言,我們需要解決測試所有可能的 Network Policy 實作的總體問題。例如,最近編寫了一個 KEP,其中引入了 Network Policies 的微版本控制概念,以適應 Dan Winship 在 API 層級描述這一點

為了回應對所有供應商全面評估 Network Policy 實作的日益明顯的需求,Matt Fenwick 決定再次發展我們的 Network Policy 驗證方法,創建了 Cyclonus。

Cyclonus 是一個綜合性的 Network Policy 模糊測試工具,它透過定義與端到端測試中示範的類似真值表/政策組合,同時還提供政策「類別」的階層式表示,來驗證 CNI 供應商是否符合數百種不同的 Network Policy 情境。我們在到目前為止測試的幾乎每個 CNI 中都發現了一些有趣的細微差別和問題,甚至回饋了一些修復程式。

要執行 Cyclonus 驗證運行,您可以創建一個類似於以下的 Job 清單

apiVersion: batch/v1
kind: Job
metadata:
  name: cyclonus
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
        - command:
            - ./cyclonus
            - generate
            - --perturbation-wait-seconds=15
            - --server-protocol=tcp,udp
          name: cyclonus
          imagePullPolicy: IfNotPresent
          image: mfenwick100/cyclonus:latest
      serviceAccount: cyclonus

Cyclonus 輸出它將運行的所有測試案例的報告

test cases to run by tag:
- target: 6
- peer-ipblock: 4
- udp: 16
- delete-pod: 1
- conflict: 16
- multi-port/protocol: 14
- ingress: 51
- all-pods: 14
- egress: 51
- all-namespaces: 10
- sctp: 10
- port: 56
- miscellaneous: 22
- direction: 100
- multi-peer: 0
- any-port-protocol: 2
- set-namespace-labels: 1
- upstream-e2e: 0
- allow-all: 6
- namespaces-by-label: 6
- deny-all: 10
- pathological: 6
- action: 6
- rule: 30
- policy-namespace: 4
- example: 0
- tcp: 16
- target-namespace: 3
- named-port: 24
- update-policy: 1
- any-peer: 2
- target-pod-selector: 3
- IP-block-with-except: 2
- pods-by-label: 6
- numbered-port: 28
- protocol: 42
- peer-pods: 20
- create-policy: 2
- policy-stack: 0
- any-port: 14
- delete-namespace: 1
- delete-policy: 1
- create-pod: 1
- IP-block-no-except: 2
- create-namespace: 1
- set-pod-labels: 1
testing 112 cases

請注意,Cyclonus 會根據正在創建的政策類型標記其測試,因為政策本身是自動產生的,因此沒有可識別的有意義的名稱。

對於每個測試,Cyclonus 輸出一個真值表,該真值表再次類似於 E2E 測試的真值表,以及正在驗證的政策

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  creationTimestamp: null
  name: base
  namespace: x
spec:
  egress:
  - ports:
    - port: 81
    to:
    - namespaceSelector:
        matchExpressions:
        - key: ns
          operator: In
          values:
          - "y"
          - z
      podSelector:
        matchExpressions:
        - key: pod
          operator: In
          values:
          - a
          - b
  - ports:
    - port: 53
      protocol: UDP
  ingress:
  - from:
    - namespaceSelector:
        matchExpressions:
        - key: ns
          operator: In
          values:
          - x
          - "y"
      podSelector:
        matchExpressions:
        - key: pod
          operator: In
          values:
          - b
          - c
    ports:
    - port: 80
      protocol: TCP
  podSelector:
    matchLabels:
      pod: a
  policyTypes:
  - Ingress
  - Egress

0 wrong, 0 ignored, 81 correct
+--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| TCP/80 | X/A | X/B | X/C | Y/A | Y/B | Y/C | Z/A | Z/B | Z/C |
| TCP/81 |     |     |     |     |     |     |     |     |     |
| UDP/80 |     |     |     |     |     |     |     |     |     |
| UDP/81 |     |     |     |     |     |     |     |     |     |
+--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| x/a    | X   | X   | X   | X   | X   | X   | X   | X   | X   |
|        | X   | X   | X   | .   | .   | X   | .   | .   | X   |
|        | X   | X   | X   | X   | X   | X   | X   | X   | X   |
|        | X   | X   | X   | X   | X   | X   | X   | X   | X   |
+--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| x/b    | .   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
+--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| x/c    | .   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
+--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| y/a    | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
+--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| y/b    | .   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
+--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| y/c    | .   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
+--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| z/a    | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
+--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| z/b    | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
+--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| z/c    | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
|        | X   | .   | .   | .   | .   | .   | .   | .   | .   |
+--------+-----+-----+-----+-----+-----+-----+-----+-----+-----+

Cyclonus 和 e2e 測試都使用相同的策略來驗證 Network Policy - 透過 TCP 或 UDP 探測 Pod,對於支援它的 CNI(例如 Calico),也提供 SCTP 支援。

作為我們如何使用 Cyclonus 來幫助從 Network Policy 角度改進 CNI 實作的範例,您可以看到以下問題

好消息是,Antrea 和 Calico 已經合併了針對已發現的所有問題的修復程式,而其他 CNI 供應商正在 SIG Network 和 Network Policy 子專案的支持下努力解決這個問題。

您是否有興趣驗證叢集上的 NetworkPolicy 功能?(如果您關心安全性或提供多租戶 SaaS,您應該關心)如果是這樣,您可以運行上游端到端測試或 Cyclonus,或兩者都運行。

  • 如果您只是剛開始使用 NetworkPolicies,並且想要簡單地驗證大多數 CNI 應該正確實作的「常見」NetworkPolicy 案例,並且診斷速度很快,那麼您最好只運行 e2e 測試。
  • 如果您對 CNI 供應商的 NetworkPolicy 實作感到非常好奇,並且想要驗證它:請使用 Cyclonus。
  • 如果您想測試數百個政策,並評估您的 CNI 外掛程式的綜合功能,以深入發現潛在的安全漏洞:請使用 Cyclonus,並考慮運行端到端叢集測試。
  • 如果您正在考慮參與上游 NetworkPolicy 工作:請使用 Cyclonus,並至少閱讀相關 e2e 測試的概述。

從哪裡開始進行 NetworkPolicy 測試?

  • Cyclonus 很容易在您的叢集上運行,請查看 github 上的說明,並確定您的特定 CNI 組態是否完全符合數百種不同的 Kubernetes Network Policy API 結構。
  • 或者,您可以使用像 sonobuoy 這樣的工具在 Kubernetes 中運行現有的 E2E 測試,並帶有 --ginkgo.focus=NetworkPolicy 標誌。請確保您使用 K8s 一致性映像,適用於 K8s 1.21 或更高版本(例如,透過使用 --kube-conformance-image-version v1.21.0 標誌),因為較舊的映像不會包含新的 Network Policy 測試。

NetworkPolicy API 和使用者體驗的改進

除了清理實作 NetworkPolicies 的 CNI 外掛程式的驗證方案外,子專案貢獻者還花了一些時間改進 Kubernetes NetworkPolicy API,以滿足一些常見請求的功能。經過數月的審議,我們最終確定了一些核心改進領域

  • 端口範圍政策:我們現在允許您為政策指定端口範圍。這允許對 FTP 或虛擬化等情境感興趣的使用者啟用進階政策。網路政策的端口範圍選項將在 Kubernetes 1.21 中提供使用。請閱讀鎖定端口範圍以了解更多資訊。

  • 命名空間作為名稱政策:允許 Kubernetes >= 1.21 中的使用者在使用 Network Policy 物件建構時,使用名稱鎖定命名空間。這是與 Jordan Liggitt 和 Tim Hockin 在 API Machinery 方面合作完成的。此變更使我們能夠在不實際變更 API 的情況下改進 Network Policy 使用者體驗!如需更多詳細資訊,您可以閱讀關於命名空間頁面中的自動標籤。重點是;對於 Kubernetes 1.21 及更高版本,預設情況下會新增以下標籤到所有命名空間

    kubernetes.io/metadata.name: <name-of-namespace>
    

這表示您可以針對此命名空間編寫命名空間政策,即使您無法編輯其標籤。例如,此政策將「正常運作」,而無需運行類似 kubectl edit namespace 的命令。事實上,即使您根本無法編輯或查看此命名空間的資料,它也將正常運作,因為 API 伺服器預設值的魔力。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: test-network-policy
  namespace: default
spec:
  podSelector:
    matchLabels:
      role: db
  policyTypes:
  - Ingress
  # Allow inbound traffic to Pods labelled role=db, in the namespace 'default'
  # provided that the source is a Pod in the namespace 'my-namespace'
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: my-namespace

結果

在我們的測試中,我們發現

  • Antrea 和 Calico 處於一個支援 Cyclonus 所有情境的階段,除了我們所做的一些非常小的調整之外。
  • Cilium 也符合大多數政策,除了已知的不完全支援的功能之外(例如,與 Cilium 處理 Pod CIDR 政策的方式相關)。

如果您是 CNI 供應商,並且有興趣幫助我們更好地策劃大型網路政策測試,請與我們聯繫!我們正在繼續策劃來自 Cyclonus 的 Network Policy 一致性結果此處,但我們無法單獨維護 NetworkPolicy 測試資料中的所有細微之處。目前,我們使用 github actions 和 Kind 在 CI 中進行測試。

未來

我們也在努力改進 Network Policies 的未來,包括

  • 完全合格網域名稱政策:Google Cloud 團隊創建了一個 FQDN 政策的原型(我們對此感到非常興奮)。此工具使用 Network Policy API 對 L7 URL 強制執行政策,方法是在發出請求時找到其 IP 並主動封鎖它們。
  • 叢集管理政策:我們正在努力為未來啟用管理叢集範圍的網路政策。這些政策正在迭代地提交給 NetworkPolicy 子專案。您可以在叢集範圍網路政策中閱讀有關它們的資訊。

Network Policy 子專案在美國東部時間星期一下午 4 點舉行會議。有關詳細資訊,請查看SIG Network 社群儲存庫。我們很樂意與您一起交流、研究東西,並盡可能幫助您為您的叢集採用 K8s 網路政策。

關於使用者回饋的快速說明

我們從使用者那裡獲得了許多關於 Network Policies 的想法和回饋。很多人對 Network Policies 有有趣的見解,但我們發現,作為一個子專案,很少有人對充分實作這些想法深感興趣。

幾乎每個對 NetworkPolicy API 的變更都包含數週或數月的討論,以涵蓋不同的情況,並確保不會引入 CVE。因此,長期所有權是隨著時間推移改進 NetworkPolicy 使用者體驗的最大障礙。

  • 我們記錄了許多 Network Policy 對話的歷史記錄此處
  • 我們還對使用者進行了調查,了解他們希望在 Network Policy API 中看到什麼此處

我們鼓勵任何人向我們提供回饋,但我們目前最迫切的問題是尋找長期所有者來幫助我們推動變革

這不需要大量的技術知識,而只需要長期承諾幫助我們保持組織性、處理文書工作以及迭代完成 K8s 功能流程的許多階段。如果您想幫助我們並參與其中,請在 SIG Network 郵件列表中或 k8s.io Slack 頻道中的 SIG Network 聊天室中與我們聯繫!

任何人都可以貢獻一份力量,幫助 NetworkPolicies 變得更好!