使用 Istio CNI 支持強安全 TKE Stack 集群的服務網格流量捕獲
作者
陳計節,企業應用雲原生架構師,在騰訊企業 IT 負責雲原生應用治理產品的設計與研發工作,主要研究利用容器集群和服務網格等雲原生實踐模式降低微服務開發與治理門檻並提升運營效率。
摘要
給需要快速解決問題的集群管理員:
在 TKE Stack 中正確安裝 Istio CNI 有兩種方式:如果你的 TKE Stack 集群所使用 Galaxy 版本可以支持 cniVersion 0.3.1,請以默認的方式安裝 Istio CNI;否則請使用以「網卡插件」的方式安裝 Istio CNI,並在創建 Pod 時指定使用集群默認網絡名稱。
如果你發現你的 TKE Stack 集群安裝完 Istio CNI 之後,無法創建新的 Pod,請立即卸載已安裝的 Istio CNI,並手動恢復各個節點上寫入的 Galaxy 配置文件:將 /etc/cni/net.d/00-galaxy.conflist 文件內的 plugins 數組字段的第一個元素提取出來,並保存為單獨的 conf 文件: /etc/cni/net.d/00-galaxy.conf。刪除正在創建中、但無法成功的 Pod,等待其重建,Pod 的創建功能應該能自動恢復。
Istio 是流行的服務網格軟件,它通過向業務 Pod 注入可捕獲出入口流量的代理軟件 Envoy 作為 Sidecar 來完成對流量的觀測與治理。
Istio 為了讓 Envoy 代理能夠捕獲來去業務容器的流量,需要向 Pod 所在網絡下發如下 IPTABLES 規則:
*nat
-N ISTIO_INBOUND
-N ISTIO_REDIRECT
-N ISTIO_IN_REDIRECT
-N ISTIO_OUTPUT
-A ISTIO_INBOUND -p tcp --dport 15008 -j RETURN
-A ISTIO_REDIRECT -p tcp -j REDIRECT --to-ports 15001
-A ISTIO_IN_REDIRECT -p tcp -j REDIRECT --to-ports 15006
-A PREROUTING -p tcp -j ISTIO_INBOUND
-A ISTIO_INBOUND -p tcp --dport 22 -j RETURN
-A ISTIO_INBOUND -p tcp --dport 15090 -j RETURN
-A ISTIO_INBOUND -p tcp --dport 15021 -j RETURN
-A ISTIO_INBOUND -p tcp --dport 15020 -j RETURN
-A ISTIO_INBOUND -p tcp -j ISTIO_IN_REDIRECT
-A OUTPUT -p tcp -j ISTIO_OUTPUT
-A ISTIO_OUTPUT -o lo -s 127.0.0.6/32 -j RETURN
-A ISTIO_OUTPUT -o lo ! -d 127.0.0.1/32 -m owner --uid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --uid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -o lo ! -d 127.0.0.1/32 -m owner --gid-owner 1337 -j ISTIO_IN_REDIRECT
-A ISTIO_OUTPUT -o lo -m owner ! --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -m owner --gid-owner 1337 -j RETURN
-A ISTIO_OUTPUT -d 127.0.0.1/32 -j RETURN
-A ISTIO_OUTPUT -j ISTIO_REDIRECT
COMMIT
在常規安裝模式下,下發 IPTABLES 規則的操作,是通過與 Envoy 代理容器一同注入的初始化容器 istio-init 完成的。向 Pod 網絡下發 IPTABLES 規則要求 Pod 可以使用 NET_ADMIN 和 NET_RAW 兩個高權限功能(Capabilities)。Linux 將傳統與超級用戶 root 關聯的特權劃分為不同的單元,稱為 Capabilites。Capabilites 每個單元都可以獨立啟用和禁用。這樣當系統在做權限檢查的時候就檢查特定的 Capabilites,並決定當前用戶其進程是否可以進行相應特權操作。比如如果要設置系統時間,就得具有 CAP_SYS_TIME 這個 Capabilites。
Istio 流量捕獲功能面臨的安全挑戰
容器本質上是是宿主機上運行的進程,雖然容器運行時默認只向容器提供必要 Capabilities,但如果使用 --privileded
模式運行容器,或者向容器追加更多 Capabilities 時,容器就可以像其他進程一樣擁有很高權限的操作能力。這樣,能使用 NET_ADMIN 和 NET_RAW 權限的 Pod 理論上不光可以操作自己這個 Pod 的網絡,如果處理不當,還可能影響到同一工作節點的其他 Pod 甚至是宿主機的網絡配置。通常,這在一些對容器應用的權限嚴格限制的環境中,是不推薦使用的。
從 Kubernetes 1.11 版本開始,我們可以在集群中使用 PodSecurityPolicy 資源(PSP)來限制集群中的 Pod 可以使用的默認權限或能力。通過編寫如下 PSP 即可限制集群內的 Pod 均不得使用任何特權 Capabilities:
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: pod-limited
spec:
allowPrivilegeEscalation: false
# 不允許使用 Capabilities
# allowedCapabilities:
# - '*'
fsGroup:
rule: RunAsAny
runAsUser:
rule: RunAsAny
supplementalGroups:
rule: RunAsAny
volumes:
- configMap
- downwardAPI
- emptyDir
- persistentVolumeClaim
- secret
- nfs
可想而知,在一個添加了上述限制的集群上,istio-init 容器由於無法獲得相應特權,將無法完成預定工作:無法讓 Envoy 代理軟件捕獲 Pod 中的流量。這樣整個 Istio 軟件的功能也就無從談起了。此外,從 Kubernetes 1.21 版本開始, PSP 功能將逐步被棄用。新版集群上可能使用其他替代機制限制 Pod 權限。
使用 Istio CNI 解決權限擴散問題
上面談到的安全風險來自於在所有需要注入 Sidecar 的業務 Pod 均需要同步注入高權限 istio-init 容器,而業務 Pod 可以由使用集群的任何人來創建和使用。這對於集群來說,就構成了攻擊面的無限蔓延。這類問題的解決思路,通常是將攻擊面集中化管理。也就是說,由少量可控的高權限 Pod 來偵聽 Pod 創建的過程,在 Pod 啟動前,為它們完成 IPTABLES 下發過程。
這正是 Istio CNI 所解決的問題。
通常,如果要偵聽 Pod 創建、刪除的事件,使用 Imformer 機制即可很輕鬆地獲取到集群內各類資源的創建與回收事件。這也是在 Kubernetes 中開發各類 Controller 所常見的做法。但為 Pod 下發 IPTABLES 規則的任務與普通 Controller 有所不同:它需要在 Pod 事件創建或刪除時,在 Pod 所在工作節點上,進入相應容器的 Linux Namespace,並下發 IPTABLES 規則。從執行位置來說,這更像是一種 Daemonset。另一方面,對 Pod 的創建和刪除事件的處理恰好與 CNI 定義的一些命令吻合,CNI 是 Kubernetes 定義的用於為 Pod、Service 提供網絡的機制。正好下發 IPTABLES 也是一種網絡相關的操作,所以 Istio 團隊也就索性直接以 CNI 插件的方式提供這一功能。
Istio CNI 的工作流程如下圖:
Istio CNI DaemonSet 負責將 Istio CNI 插件的可執行程序安裝到工作節點上。這些程序稍後在新的業務 Pod 創建或銷毀時會收到來自 k8s 的調用,接着它們完成 IPTABLES 規則的配置。由於這些程序是運行在工作節點上,因此具有較高的權限:但它們可以被集中管理,因此權限是受控的,而業務 Pod 此時不再需要高權限來配置這些 IPTABLES,只需要運行一個簡單的檢查程序,確保業務容器運行之前,這些規則已就緒即可。
上圖是 Istio 自注入模板的代碼片斷,從中可以看出,當啟用 Istio CNI 時,如果啟用了 Istio CNI 功能,Istio 向 Pod 注入的容器不再需要高權限。
在 TKE Stack 中安裝 Istio CNI 的問題
與普通 CNI 插件不同,Istio CNI 並不提供常規的 IP 地址管理(IPAM)和聯網(SDN)功能。它只在 Pod 的網絡已建立之後,負責下發上述規則。因此,Istio CNI 並不會、也不能替換集群現有的 CNI 插件的功能。也就是說,在配置 Istio CNI 之外,k8s 集群還需要配置其他負責 IPAM 和 SDN 的軟件,比如我們熟悉的 Flannel 或 Calico 等。
為了配合不同種類的現有 CNI 插件,Istio CNI 既能以「網卡插件」(Interface Plugins)的方式運行,也能以「插件鏈」(Chained Plugins)的方式附加到現有網卡插件運行。根據 CNI 標準的描述,網卡插件是用於創建虛擬網卡的 CNI 插件,而插件鏈則允許多個插件為已創建的網卡提供附加功能。插件鏈模式很符合 Istio CNI 的定位,也是 Istio CNI 的默認運行方式。在這種運行方式下,Istio CNI 先會檢查集群當前 CNI 插件的配置:如果它已經是一個插件鏈,則將自身添加到它的尾部,成為新的功能;如果當前插件是一個「網卡插件」,則先將其轉換為插件鏈,再將自身添加到鏈的尾部。
TKE Stack 是由騰訊主導的開源 k8s 發行版,與社區版 k8s 相比,TKE Stack 主要提供了更強的網絡接入能力、多集群管理能力,以及將容器資源與業務和用戶等因素集成管理等豐富的功能。TKE Stack 也是騰訊雲提供的容器服務的開源版本,在騰訊內部部署了超過數十萬核的超大規模集群,穩定運行了數年。
TKE Stack 的默認 CNI 插件是 Galaxy,它是一個能讓集群接入各類網絡插件的「元 CNI」框架:基於它,我們可以讓集群中的 Pod 基於 Flannel 之類的插件獲得普通 Overlay 網絡的同時,還可以基於其他插件獲得諸如 Underlay 網絡等強大的能力。比如,典型的 Underlay 網絡可以提供的能力有,可以讓 Pod 獲取到另一個子網(比如工作節點所在子網)的 IP 地址、獲取固定 IP 地址等。
經過測試發現,在一些集群上,Istio CNI 插件默認的插件鏈運行模式與 Galaxy 不能兼容。原因是,Istio CNI 的配置轉換處理過程存在瑕疵:這些集群上的原有 Galaxy CNI 的配置是網卡插件(即 00-galaxy.conf)模式, 經過 Istio CNI 的處理之後,相關配置無法被 Galaxy CNI 識別和處理。
具體原因是,Istio CNI 在將原有配置複製為插件鏈模式的過程中,會刪除原配置中的 cniVersion 版本號(如果有),在新生成的插件鏈配置文件 00-galaxy.conflist 時,將此版本號強制改為 Galaxy CNI 尚未支持的 0.3.1。進入 Galaxy CNI 相關 daemonset 容器,並模擬執行 CNI 版本檢查命令,可以發現此集群上 Galaxy CNI 支持的 cniVersion 最高為 0.2.0。相關源碼可點擊此處。
CNI_COMMAND=VERSION /opt/cni/bin/galaxy-sdn </etc/cni/net.d/00-galaxy.conf
在這樣的 TKE Stack 集群中以插件鏈模式運行 Istio CNI 之後,將出現新 Pod 無法創建的問題。具體錯誤為:plugin galaxy-sdn does not support config version “0.3.1”。從 Pod 創建日誌及 kubelet 上都可以找到這一錯誤信息。
更糟糕的是,即使此時卸載 Istio CNI,仍然不能恢復 Galaxy CNI 的功能。這是因為雖然 Istio CNI 卸載過程會嘗試回退它做的修改,但是回退過程只是將 Istio CNI 相關內容從新創建的 conflist 格式配置中移除,而並未將 CNI 配置文件恢復為原始的 conf 格式,無法識別的版本號 0.3.1 被保留了下來。
此時,需要管理員登錄每台集群工作節點,手工將 /etc/cni/net.d/00-galaxy.conflist 文件內的 plugins 數組字段的第一個元素提取出來,並保存為單獨的 conf 文件: /etc/cni/net.d/00-galaxy.conf。刪除正在創建中、但無法成功的 Pod,等待其重建,Pod 的創建功能應該能自動恢復。
Istio CNI 安裝問題的解決思路
明確了問題的緣由,要解決這些問題就很直接了。在 TKE Stack 集群中安裝 Istio CNI 的兩個思路是:
- 使用網卡插件的方式運行 Istio CNI
- 升級 TKE Stack 集群中的 Galaxy CNI 版本
使用網卡插件的方式運行 Istio CNI
既然 Istio CNI 提供了網卡插件的運行方式,那啟用它是一種比較輕鬆的處置方法。安裝 Istio CNI 時,關閉 chained 參數即可以網卡插件的方式運行 Istio CNI。如果是為已有 Istio 集群補充安裝 Istio CNI,則可能需要手工修改位於 istio-system 命名空間的 Istio 注入模板配置 configmap/istio-sidecar-injector 資源中的 values 數據。其中的 istio_cni 配置節:
"istio_cni": {
"enabled": true,
"chained": false
}
需要注意的是,以網卡模式運行的 Istio CNI,會在 Pod 創建為其時添加 k8s.v1.cni.cncf.io/networks 註解(Annotation),以便通知集群上可以支持這個註解的 CNI 插件調用 Istio CNI 完成功能。Galaxy CNI 作為一個元 CNI 插件,是可以支持這個註解的。當 Galaxy 遇到這個註解時,將會跳過默認的 Galaxy 網絡插件,而啟用註解中配置的 CNI 插件。Istio CNI 並不實際提供聯網功能,因此如果只運行 Istio CNI 會導致 Pod 無法獲得正確的 IP,因此還是無法正確創建。以下代碼片斷來自 Istio 注入模板,從中可以看出其中的邏輯:
{{- if and (.Values.istio_cni.enabled) (not .Values.istio_cni.chained) }}
{{ if isset .ObjectMeta.Annotations `k8s.v1.cni.cncf.io/networks` }}
k8s.v1.cni.cncf.io/networks: "{{ index .ObjectMeta.Annotations `k8s.v1.cni.cncf.io/networks`}}, istio-cni"
{{- else }}
k8s.v1.cni.cncf.io/networks: "istio-cni"
{{- end }}
{{- end }}
從上面的代碼中可以看出,Istio 模板嘗試讀取 Pod 上已有的註解值,並將 istio-cni 追加到末尾。這樣,我們只需要在創建 Pod 時將 Galaxy 默認的網絡配置名稱以註解的方式提前列出,即可正確創建 Pod。從 kube-system 命名空間中的 configmap/galaxy-etc 配置的 DefaultNetworks 可以找到當前 Galaxy CNI 的默認網絡名稱。
kind: Pod
metadata:
annotations:
k8s.v1.cni.cncf.io/networks: galaxy-flannel
name: my-pod
實際測試結果表明,以網卡插件模式運行 Istio CNI,並在 Pod 上標記原有網絡模式,即可在 TKE Stack 上成功運行 Pod 並正常使用 Istio 的各項功能。
升級 TKE Stack 集群中的 Galaxy CNI
雖然以獨立網卡插件模式運行 Istio CNI 是可以解決 Pod 無法創建的問題的,但是由於需要向 Pod 上添加額外的註解,所以給應用開發者或者部署流水線增加了額外的複雜度,甚至有可能影響 Pod 使用 Galaxy CNI 提供的其他網絡功能。比較理想的效果是,能像 Istio CNI 原生提供的那樣,能透明地支持相關功能。
幸運的是,在最新的 1.0.8 版本 的 Galaxy CNI 的代碼中,已經支持了 0.4.0 及之前版本的各個 cniVersion。因此將 Galaxy CNI 的版本升級到最新版,就能以默認插件鏈模式運行 Istio CNI 了。如果你的集群中的組件經過了自己團隊的定製,則需要聯繫這些定製組件的開發團隊核實他們所使用的上游版本,並提醒他們升級 Galaxy 組件的版本。
升級到最新版本的 Galaxy 組件之後,再運行相應的驗證腳本,可以發現新版本的 Galaxy 已支持包括 0.3.1 在內的多個 cniVersion。
總結
作為流行的服務網格軟件,Istio 可以為微服務提供接近無侵入的強大流量治理能力和豐富的觀測能力。而 Istio 這些能力都來源於它對來往業務容器的網絡流量的完全捕獲能力。
雖然 Istio 本身提供了多種在指定命名空間安裝的特性,但將 Istio 作為一個集群級基礎平台能力是眾多團隊的首選。而在一些公開的多租戶集群、有特殊安全策略要求等複雜的集群環境,安裝和運營 Istio 會面臨一些獨特的挑戰。
本文簡要介紹了在安全限制嚴格的集群中,要使用 Istio 流量治理功能所依賴的 IPTABLES 網絡策略需要的 Istio CNI 插件的運行原理,以及要在 TKE Stack 集群中運行 Istio CNI 會遇到的問題和解決方法。運用這些方法,可以較好地使用較低的權限運行業務應用的同時,以兼容集群現有網絡功能的方式,提供 Istio 的完整功能。
參考資料
- Istio CNI 的安裝方法
- Kubernetes CNI 插件標準
- 在 OpenShift 上消除 Istio Pod 高權限並提高安全性
- [Linux 上的 Capabilities](//www.qikqiak.com/post/capabilities-on-k8s/ //istio.io/latest/docs/setup/additional-setup/cni/ “Linux 上的 Capabilities
關於我們
更多關於雲原生的案例和知識,可關注同名【騰訊雲原生】公眾號~
福利:
①公眾號後台回復【手冊】,可獲得《騰訊雲原生路線圖手冊》&《騰訊雲原生最佳實踐》~
②公眾號後台回復【系列】,可獲得《15個系列100+篇超實用雲原生原創乾貨合集》,包含Kubernetes 降本增效、K8s 性能優化實踐、最佳實踐等系列。
③公眾號後台回復【白皮書】,可獲得《騰訊雲容器安全白皮書》&《降本之源-雲原生成本管理白皮書v1.0》
④公眾號後台回復【光速入門】,可獲得騰訊雲專家5萬字精華教程,光速入門Prometheus和Grafana。
⑤公眾號後台回復【精選集】,可獲得騰訊24位騰訊雲專家精彩演講——4萬字《騰訊雲技術實踐精選集 2021》。
【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多乾貨!!