本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
CRI-O:從 OCI 映象庫應用 seccomp 配置檔案
Seccomp 是 secure computing mode(安全計算模式)的縮寫,自 Linux 核心 2.6.12 版本以來一直是其一項特性。它可用於對程序的許可權進行沙箱化,限制該程序能夠從使用者空間向核心發起的呼叫。Kubernetes 允許你將節點上載入的 seccomp 配置檔案自動應用到你的 Pod 和容器中。
但在 Kubernetes 中分發這些 seccomp 配置檔案是一個主要挑戰,因為 JSON 檔案必須在工作負載可能執行的所有節點上都可用。像 Security Profiles Operator 這樣的專案透過在叢集內作為守護程序執行來解決這個問題,這讓我思考分發的哪個部分可以由容器執行時來完成。
執行時通常從本地路徑應用配置檔案,例如
apiVersion: v1
kind: Pod
metadata:
name: pod
spec:
containers:
- name: container
image: nginx:1.25.3
securityContext:
seccompProfile:
type: Localhost
localhostProfile: nginx-1.25.3.json
配置檔案 nginx-1.25.3.json
必須在 kubelet 的根目錄中可用,並附加 seccomp
目錄。這意味著該配置檔案在磁碟上的預設位置是 /var/lib/kubelet/seccomp/nginx-1.25.3.json
。如果配置檔案不可用,則執行時在建立容器時會失敗,如下所示
kubectl get pods
NAME READY STATUS RESTARTS AGE
pod 0/1 CreateContainerError 0 38s
kubectl describe pod/pod | tail
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 117s default-scheduler Successfully assigned default/pod to 127.0.0.1
Normal Pulling 117s kubelet Pulling image "nginx:1.25.3"
Normal Pulled 111s kubelet Successfully pulled image "nginx:1.25.3" in 5.948s (5.948s including waiting)
Warning Failed 7s (x10 over 111s) kubelet Error: setup seccomp: unable to load local profile "/var/lib/kubelet/seccomp/nginx-1.25.3.json": open /var/lib/kubelet/seccomp/nginx-1.25.3.json: no such file or directory
Normal Pulled 7s (x9 over 111s) kubelet Container image "nginx:1.25.3" already present on machine
手動分發 Localhost
配置檔案的主要障礙將導致許多終端使用者退回到使用 RuntimeDefault
,甚至以 Unconfined
(停用 seccomp)方式執行其工作負載。
CRI-O 來拯救
Kubernetes 容器執行時 CRI-O 使用自定義註解提供了多種功能。v1.30 版本 添加了對一組新註解的支援,名為 seccomp-profile.kubernetes.cri-o.io/POD
和 seccomp-profile.kubernetes.cri-o.io/<CONTAINER>
。這些註解允許你指定
- 用於特定容器的 seccomp 配置檔案,使用方式為:
seccomp-profile.kubernetes.cri-o.io/<CONTAINER>
(例如:seccomp-profile.kubernetes.cri-o.io/webserver: 'registry.example/example/webserver:v1'
) - 用於 Pod 內每個容器的 seccomp 配置檔案,使用時沒有容器名稱字尾,而是使用保留名稱
POD
:seccomp-profile.kubernetes.cri-o.io/POD
- 用於整個容器映象的 seccomp 配置檔案,如果映象本身包含註解
seccomp-profile.kubernetes.cri-o.io/POD
或seccomp-profile.kubernetes.cri-o.io/<CONTAINER>
。
只有當執行時配置為允許該註解,並且工作負載以 Unconfined
模式執行時,CRI-O 才會遵守該註解。所有其他工作負載仍將使用 securityContext
中的值,且優先順序更高。
僅靠註解對分發配置檔案幫助不大,但引用它們的方式卻可以!例如,你現在可以透過使用 OCI 製品來像指定常規容器映象一樣指定 seccomp 配置檔案
apiVersion: v1
kind: Pod
metadata:
name: pod
annotations:
seccomp-profile.kubernetes.cri-o.io/POD: quay.io/crio/seccomp:v2
spec: …
映象 quay.io/crio/seccomp:v2
包含一個 seccomp.json
檔案,其中包含實際的配置檔案內容。可以使用 ORAS 或 Skopeo 等工具來檢查映象的內容
oras pull quay.io/crio/seccomp:v2
Downloading 92d8ebfa89aa seccomp.json
Downloaded 92d8ebfa89aa seccomp.json
Pulled [registry] quay.io/crio/seccomp:v2
Digest: sha256:f0205dac8a24394d9ddf4e48c7ac201ca7dcfea4c554f7ca27777a7f8c43ec1b
jq . seccomp.json | head
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 38,
"defaultErrno": "ENOSYS",
"archMap": [
{
"architecture": "SCMP_ARCH_X86_64",
"subArchitectures": [
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
# Inspect the plain manifest of the image
skopeo inspect --raw docker://quay.io/crio/seccomp:v2 | jq .
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config":
{
"mediaType": "application/vnd.cncf.seccomp-profile.config.v1+json",
"digest": "sha256:ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356",
"size": 3,
},
"layers":
[
{
"mediaType": "application/vnd.oci.image.layer.v1.tar",
"digest": "sha256:92d8ebfa89aa6dd752c6443c27e412df1b568d62b4af129494d7364802b2d476",
"size": 18853,
"annotations": { "org.opencontainers.image.title": "seccomp.json" },
},
],
"annotations": { "org.opencontainers.image.created": "2024-02-26T09:03:30Z" },
}
映象清單包含一個對特定必需的配置媒體型別(application/vnd.cncf.seccomp-profile.config.v1+json
)的引用,以及一個指向 seccomp.json
檔案的單一層(application/vnd.oci.image.layer.v1.tar
)。現在,讓我們來試試這個新功能!
為特定容器或整個 pod 使用註解
CRI-O 需要進行適當配置才能使用該註解。為此,將該註解新增到執行時的 allowed_annotations
陣列中。這可以透過使用一個 drop-in 配置 /etc/crio/crio.conf.d/10-crun.conf
來完成,如下所示
[crio.runtime]
default_runtime = "crun"
[crio.runtime.runtimes.crun]
allowed_annotations = [
"seccomp-profile.kubernetes.cri-o.io",
]
現在,讓我們從最新的 main
提交執行 CRI-O。這可以透過從原始碼構建、使用靜態二進位制包或預釋出包來完成。
為了演示,我透過 local-up-cluster.sh
在一個單節點 Kubernetes 叢集上從命令列運行了 crio
二進位制檔案。現在叢集已經啟動並執行,讓我們嘗試一個沒有註解、以 seccomp Unconfined
模式執行的 pod
cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod
spec:
containers:
- name: container
image: nginx:1.25.3
securityContext:
seccompProfile:
type: Unconfined
kubectl apply -f pod.yaml
工作負載已啟動並正在執行
kubectl get pods
NAME READY STATUS RESTARTS AGE
pod 1/1 Running 0 15s
如果我使用 crictl
檢查容器,會發現沒有應用 seccomp 配置檔案
export CONTAINER_ID=$(sudo crictl ps --name container -q)
sudo crictl inspect $CONTAINER_ID | jq .info.runtimeSpec.linux.seccomp
null
現在,讓我們修改 pod,將配置檔案 quay.io/crio/seccomp:v2
應用到容器中
apiVersion: v1
kind: Pod
metadata:
name: pod
annotations:
seccomp-profile.kubernetes.cri-o.io/container: quay.io/crio/seccomp:v2
spec:
containers:
- name: container
image: nginx:1.25.3
我必須刪除並重新建立 Pod,因為只有重新建立才會應用新的 seccomp 配置檔案
kubectl delete pod/pod
pod "pod" deleted
kubectl apply -f pod.yaml
pod/pod created
CRI-O 的日誌現在會顯示執行時已拉取了該製品
WARN[…] Allowed annotations are specified for workload [seccomp-profile.kubernetes.cri-o.io]
INFO[…] Found container specific seccomp profile annotation: seccomp-profile.kubernetes.cri-o.io/container=quay.io/crio/seccomp:v2 id=26ddcbe6-6efe-414a-88fd-b1ca91979e93 name=/runtime.v1.RuntimeService/CreateContainer
INFO[…] Pulling OCI artifact from ref: quay.io/crio/seccomp:v2 id=26ddcbe6-6efe-414a-88fd-b1ca91979e93 name=/runtime.v1.RuntimeService/CreateContainer
INFO[…] Retrieved OCI artifact seccomp profile of len: 18853 id=26ddcbe6-6efe-414a-88fd-b1ca91979e93 name=/runtime.v1.RuntimeService/CreateContainer
容器最終使用了該配置檔案
export CONTAINER_ID=$(sudo crictl ps --name container -q)
sudo crictl inspect $CONTAINER_ID | jq .info.runtimeSpec.linux.seccomp | head
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 38,
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
如果使用者將 /container
字尾替換為保留名稱 /POD
,那麼同樣的效果也適用於 pod 中的每個容器,例如
apiVersion: v1
kind: Pod
metadata:
name: pod
annotations:
seccomp-profile.kubernetes.cri-o.io/POD: quay.io/crio/seccomp:v2
spec:
containers:
- name: container
image: nginx:1.25.3
為容器映象使用註解
雖然將 seccomp 配置檔案作為 OCI 製品指定給某些工作負載是一項很酷的功能,但大多數終端使用者希望將 seccomp 配置檔案連結到已釋出的容器映象。這可以透過使用容器映象註解來完成;該註解不是應用於 Kubernetes Pod,而是在容器映象本身應用的元資料。例如,可以使用 Podman 在映象構建期間直接新增映象註解
podman build \
--annotation seccomp-profile.kubernetes.cri-o.io=quay.io/crio/seccomp:v2 \
-t quay.io/crio/nginx-seccomp:v2 .
推送的映象隨後會包含該註解
skopeo inspect --raw docker://quay.io/crio/nginx-seccomp:v2 |
jq '.annotations."seccomp-profile.kubernetes.cri-o.io"'
"quay.io/crio/seccomp:v2"
如果我現在在 CRI-O 測試 pod 定義中使用該映象
apiVersion: v1
kind: Pod
metadata:
name: pod
# no Pod annotations set
spec:
containers:
- name: container
image: quay.io/crio/nginx-seccomp:v2
那麼 CRI-O 的日誌將表明映象註解已被評估,並且配置檔案已應用
kubectl delete pod/pod
pod "pod" deleted
kubectl apply -f pod.yaml
pod/pod created
INFO[…] Found image specific seccomp profile annotation: seccomp-profile.kubernetes.cri-o.io=quay.io/crio/seccomp:v2 id=c1f22c59-e30e-4046-931d-a0c0fdc2c8b7 name=/runtime.v1.RuntimeService/CreateContainer
INFO[…] Pulling OCI artifact from ref: quay.io/crio/seccomp:v2 id=c1f22c59-e30e-4046-931d-a0c0fdc2c8b7 name=/runtime.v1.RuntimeService/CreateContainer
INFO[…] Retrieved OCI artifact seccomp profile of len: 18853 id=c1f22c59-e30e-4046-931d-a0c0fdc2c8b7 name=/runtime.v1.RuntimeService/CreateContainer
INFO[…] Created container 116a316cd9a11fe861dd04c43b94f45046d1ff37e2ed05a4e4194fcaab29ee63: default/pod/container id=c1f22c59-e30e-4046-931d-a0c0fdc2c8b7 name=/runtime.v1.RuntimeService/CreateContainer
export CONTAINER_ID=$(sudo crictl ps --name container -q)
sudo crictl inspect $CONTAINER_ID | jq .info.runtimeSpec.linux.seccomp | head
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 38,
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
對於容器映象,註解 seccomp-profile.kubernetes.cri-o.io
將被視為與 seccomp-profile.kubernetes.cri-o.io/POD
相同,並應用於整個 pod。除此之外,如果對映象使用特定於容器的註解,整個功能也同樣有效,例如,如果一個容器名為 container1
skopeo inspect --raw docker://quay.io/crio/nginx-seccomp:v2-container |
jq '.annotations."seccomp-profile.kubernetes.cri-o.io/container1"'
"quay.io/crio/seccomp:v2"
這個功能的妙處在於,使用者現在可以為特定的容器映象建立 seccomp 配置檔案,並將它們並排儲存在同一個映象倉庫中。將映象與配置檔案連結起來,為在整個應用程式生命週期中維護它們提供了極大的靈活性。
使用 ORAS 推送配置檔案
當使用 ORAS 時,實際建立包含 seccomp 配置檔案的 OCI 物件需要更多的工作。我希望像 Podman 這樣的工具將來能簡化整個過程。目前,容器映象倉庫需要是OCI 相容的,Quay.io 也是如此。CRI-O 期望 seccomp 配置檔案物件具有容器映象媒體型別(application/vnd.cncf.seccomp-profile.config.v1+json
),而 ORAS 預設使用 application/vnd.oci.empty.v1+json
。為了實現這一切,可以執行以下命令
echo "{}" > config.json
oras push \
--config config.json:application/vnd.cncf.seccomp-profile.config.v1+json \
quay.io/crio/seccomp:v2 seccomp.json
生成的映象包含了 CRI-O 所期望的 mediaType
。ORAS 將一個名為 seccomp.json
的單層推送到映象倉庫。配置檔案的名稱並不重要。CRI-O 會選擇第一個層並檢查其是否可以作為 seccomp 配置檔案。
未來的工作
CRI-O 內部像管理常規檔案一樣管理 OCI 製品。這帶來了移動它們、在不再使用時移除它們或擁有除 seccomp 配置檔案之外的任何其他資料的好處。這為 CRI-O 未來基於 OCI 製品的增強功能提供了可能,也讓我們能夠思考將 seccomp 配置檔案作為 OCI 製品中多個層的一部分進行堆疊。v1.30.x 版本中它僅適用於 Unconfined
工作負載的限制是 CRI-O 希望在未來解決的問題。在不犧牲安全性的前提下簡化整體使用者體驗,似乎是 seccomp 在容器工作負載中成功未來的關鍵。
CRI-O 的維護者們很樂意傾聽關於這個新功能的任何反饋或建議!感謝您閱讀這篇部落格文章,歡迎透過 Kubernetes 的 Slack 頻道 #crio 聯絡維護者,或在 GitHub 倉庫中建立 issue。