本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
非 root 容器和裝置
當用戶希望在 Linux 上部署使用加速器裝置(透過 Kubernetes 裝置外掛)的容器時,Pod 的 securityContext
中與使用者/組 ID 相關的安全設定會引發問題。在這篇博文中,我將討論這個問題並描述迄今為止為解決它所做的工作。這不是一個關於如何修復 k/k 問題的長篇故事。
相反,本文旨在提高對這個問題的認識,並強調重要的裝置用例。這是必要的,因為 Kubernetes 正在開發新的相關功能,例如對使用者名稱空間的支援。
為什麼非 root 容器不能使用裝置,以及為什麼這很重要
在 Kubernetes 中執行容器的關鍵安全原則之一是最小許可權原則。Pod/容器的 securityContext
指定了要設定的配置選項,例如 Linux 能力、MAC 策略以及使用者/組 ID 值來實現此目的。
此外,叢集管理員可以使用 PodSecurityPolicy(已棄用)或 Pod Security Admission(Alpha)等工具來強制執行部署在叢集中的 Pod 所需的安全設定。這些設定可能要求容器必須 runAsNonRoot
,或者禁止它們以 root 的組 ID 執行在 runAsGroup
或 supplementalGroups
中。
在 Kubernetes 中,kubelet 構建要提供給容器的 Device
資源列表(基於裝置外掛的輸入),並將該列表包含在傳送到 CRI 容器執行時的 CreateContainer CRI 訊息中。每個 Device
包含少量資訊:主機/容器裝置路徑和所需的裝置 cgroup 許可權。
OCI 執行時 Linux 容器配置規範 期望除了裝置 cgroup 欄位外,還必須提供有關裝置的更詳細資訊。
{
"type": "<string>",
"path": "<string>",
"major": <int64>,
"minor": <int64>,
"fileMode": <uint32>,
"uid": <uint32>,
"gid": <uint32>
},
CRI 容器執行時(containerd、CRI-O)負責從主機獲取每個 Device
的此資訊。預設情況下,執行時會複製主機裝置的`uid`和`gid`。
uid
(uint32, 可選) - 容器名稱空間中裝置所有者的 ID。gid
(uint32, 可選) - 容器名稱空間中裝置組的 ID。
同樣,執行時會根據 CRI 欄位(包括 securityContext
中定義的欄位:runAsUser
/runAsGroup
)準備其他強制性的 config.json
部分,這些欄位透過以下方式成為 POSIX 平臺使用者結構的一部分:
uid
(int, 必填) 指定容器名稱空間中的使用者 ID。gid
(int, 必填) 指定容器名稱空間中的組 ID。additionalGids
(int 陣列, 可選) 指定容器名稱空間中要新增到程序的其他組 ID。
然而,當嘗試運行同時添加了裝置並透過 runAsUser
/runAsGroup
設定了非 root uid/gid 的容器時,生成的 config.json
會引發問題:即使容器的組 ID(gid,從主機複製)對非 root 組具有許可許可權,容器使用者程序也無權使用該裝置。這是因為容器使用者不屬於該主機組(例如,透過 additionalGids
)。
能夠以非 root 使用者身份執行使用裝置的應用程式是正常且預期可行的,這樣才能滿足安全原則。因此,我們考慮了幾種替代方案來填補 PodSec/CRI/OCI 目前支援的功能中的空白。
為解決該問題做了哪些工作?
您可能已經從問題定義中注意到,至少可以透過手動將裝置 gid 新增到 supplementalGroups
來解決問題,或者在只有一個裝置的情況下,將 runAsGroup
設定為裝置的組 ID。然而,這存在問題,因為裝置 gid 的值可能因叢集中節點的發行版/版本而異。例如,對於 GPU,不同發行版和版本的以下命令會返回不同的 gid:
Fedora 33
$ ls -l /dev/dri/
total 0
drwxr-xr-x. 2 root root 80 19.10. 10:21 by-path
crw-rw----+ 1 root video 226, 0 19.10. 10:42 card0
crw-rw-rw-. 1 root render 226, 128 19.10. 10:21 renderD128
$ grep -e video -e render /etc/group
video:x:39:
render:x:997:
Ubuntu 20.04
$ ls -l /dev/dri/
total 0
drwxr-xr-x 2 root root 80 19.10. 17:36 by-path
crw-rw---- 1 root video 226, 0 19.10. 17:36 card0
crw-rw---- 1 root render 226, 128 19.10. 17:36 renderD128
$ grep -e video -e render /etc/group
video:x:44:
render:x:133:
在您的 securityContext
中選擇哪個數字?此外,如果 runAsGroup
/runAsUser
值不能硬編碼,因為它們在 Pod 准入期間透過外部安全策略自動分配怎麼辦?
與帶有 fsGroup
的卷不同,裝置沒有 deviceGroup
/deviceUser
的官方概念,CRI 執行時(或 kubelet)無法使用。我們考慮使用裝置外掛設定的容器註解(例如,io.kubernetes.cri.hostDeviceSupplementalGroup/
)來獲取自定義 OCI config.json
的 uid/gid 值。這將需要更改所有現有的裝置外掛,這並不理想。
相反,我們更傾向於一種對終端使用者**無縫**的解決方案,無需裝置外掛供應商參與。所選擇的方法是重用 config.json
中用於裝置的 runAsUser
和 runAsGroup
值。
{
"type": "c",
"path": "/dev/foo",
"major": 123,
"minor": 4,
"fileMode": 438,
"uid": <runAsUser>,
"gid": <runAsGroup>
},
使用 runc
OCI 執行時(在非 rootless 模式下),裝置在容器名稱空間中建立(mknod(2)
),並使用 chmod(2)
將所有權更改為 runAsUser
/runAsGroup
。
說明
無根模式和裝置不受支援。runAsUser
/runAsGroup
,例如,容器中的 USER
設定目前被忽略。雖然“有問題的”部署(即非 root securityContext
+ 裝置)可能不存在,但為了確保沒有任何部署中斷,我們在 containerd 和 CRI-O 中都添加了一個選擇性配置條目來啟用新行為。以下配置
device_ownership_from_security_context (布林值)
預設為 false
,必須啟用才能使用該功能。
修復後檢視使用裝置的非 root 容器
為了演示新行為,我們以使用硬體加速器、Kubernetes CPU 管理器和 HugePages 的資料平面開發工具包 (DPDK) 應用程式為例。叢集執行 containerd,配置如下:
[plugins]
[plugins."io.containerd.grpc.v1.cri"]
device_ownership_from_security_context = true
或 CRI-O 配置如下:
[crio.runtime]
device_ownership_from_security_context = true
並使用以下 YAML 執行 DPDK 的 crypto-perf 測試工具的 Guaranteed
QoS Class Pod:
...
metadata:
name: qat-dpdk
spec:
securityContext:
runAsUser: 1000
runAsGroup: 2000
fsGroup: 3000
containers:
- name: crypto-perf
image: intel/crypto-perf:devel
...
resources:
requests:
cpu: "3"
memory: "128Mi"
qat.intel.com/generic: '4'
hugepages-2Mi: "128Mi"
limits:
cpu: "3"
memory: "128Mi"
qat.intel.com/generic: '4'
hugepages-2Mi: "128Mi"
...
要驗證結果,請檢查容器執行的使用者和組 ID:
$ kubectl exec -it qat-dpdk -c crypto-perf -- id
它們被設定為非零值,符合預期
uid=1000 gid=2000 groups=2000,3000
接下來,檢查裝置節點許可權 (qat.intel.com/generic
暴露 /dev/vfio/
裝置) 是否可供 runAsUser
/runAsGroup
訪問。
$ kubectl exec -it qat-dpdk -c crypto-perf -- ls -la /dev/vfio
total 0
drwxr-xr-x 2 root root 140 Sep 7 10:55 .
drwxr-xr-x 7 root root 380 Sep 7 10:55 ..
crw------- 1 1000 2000 241, 0 Sep 7 10:55 58
crw------- 1 1000 2000 241, 2 Sep 7 10:55 60
crw------- 1 1000 2000 241, 10 Sep 7 10:55 68
crw------- 1 1000 2000 241, 11 Sep 7 10:55 69
crw-rw-rw- 1 1000 2000 10, 196 Sep 7 10:55 vfio
最後,檢查非 root 容器是否也允許建立 HugePages。
$ kubectl exec -it qat-dpdk -c crypto-perf -- ls -la /dev/hugepages/
fsGroup
為 runAsUser
提供可寫 HugePages 的 emptyDir 掛載點。
total 0
drwxrwsr-x 2 root 3000 0 Sep 7 10:55 .
drwxr-xr-x 7 root root 380 Sep 7 10:55 ..
幫助我們測試並提供反饋!
這裡描述的功能有望幫助提高叢集安全性和裝置許可權的可配置性。為了允許非 root 容器使用裝置,叢集管理員需要透過設定 device_ownership_from_security_context = true
來選擇啟用該功能。要使其成為預設設定,請測試並提供您的反饋(透過 SIG-Node 會議或問題)!該標誌已在 CRI-O v1.22 版本中提供,並已排隊等待 containerd v1.6。
還需要更多工作才能**正確**支援它。已知它與 runc
配合使用,但它也需要與其他 OCI 執行時(如果適用)一起使用。例如,Kata Containers 支援裝置直通,並允許將裝置提供給虛擬機器沙箱中的容器。
此外,支援使用者名稱空間和裝置帶來了額外的挑戰。這個問題仍然懸而未決,需要更多的集思廣益。
最後,需要了解 runAsUser
/runAsGroup
是否足夠,或者 PodSpec/CRI v2 中是否需要類似於 fsGroups
的裝置特定設定。
感謝
我感謝 Mike Brown(IBM,containerd)、Peter Hunt(Redhat,CRI-O)和 Alexander Kanevskiy(Intel)提供的所有反饋和愉快的交流。