作者
陳計節,企業應用雲原生架構師,在騰訊企業 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](https://www.qikqiak.com/post/capabilities-on-k8s/ https://istio.io/latest/docs/setup/additional-setup/cni/ "Linux 上的 Capabilities
關於我們
更多關於雲原生的案例和知識,可關注同名【騰訊雲原生】公眾號~
福利:
①公眾號後臺回覆【手冊】,可獲得《騰訊雲原生路線圖手冊》&《騰訊雲原生最佳實踐》~
②公眾號後臺回覆【系列】,可獲得《15個系列100+篇超實用雲原生原創乾貨合集》,包含Kubernetes 降本增效、K8s 效能優化實踐、最佳實踐等系列。
③公眾號後臺回覆【白皮書】,可獲得《騰訊雲容器安全白皮書》&《降本之源-雲原生成本管理白皮書v1.0》
④公眾號後臺回覆【光速入門】,可獲得騰訊雲專家5萬字精華教程,光速入門Prometheus和Grafana。
⑤公眾號後臺回覆【精選集】,可獲得騰訊24位騰訊雲專家精彩演講——4萬字《騰訊雲技術實踐精選集 2021》。
【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多幹貨!!