如何在 Kubernetes 中運行不受信任的容器
IT 世界每天都在越來越多地采用基于容器的基礎(chǔ)架構(gòu)。但是,每個人都不清楚優(yōu)點,缺點甚至局限性。

考慮到即使是大公司也在靠近基于容器的基礎(chǔ)設(shè)施,但是可能的攻擊區(qū)域和數(shù)據(jù)泄露的潛在影響卻無人在意。
Docker(containerd)和 LXC 等技術(shù)并不是真正孤立的系統(tǒng),因為它們與托管的操作系統(tǒng)共享相同的 Linux 內(nèi)核。
對于潛在的攻擊者來說,在大公司內(nèi)啟動他們的容器是一個千載難逢的機會。但容器技術(shù)自身能讓我們輕松自衛(wèi)嗎?
當前的容器技術(shù)
已經(jīng)重復(fù)了很多次,容器是一種打包、共享和部署應(yīng)用程序的新方式,而不是所有功能都打包在一個軟件或操作系統(tǒng)中的單一應(yīng)用程序。
目前,容器沒有利用任何新的東西,但它們是在 Linux 命名空間和 cgroup 之上創(chuàng)建的演變。命名空間創(chuàng)建了一個虛擬和隔離的用戶空間,并為應(yīng)用程序提供其系統(tǒng)資源的隔離,例如文件系統(tǒng)、網(wǎng)絡(luò)和進程。這種抽象允許應(yīng)用程序獨立啟動,而不會干擾在同一主機上運行的其他應(yīng)用程序。
所以,多虧了命名空間和 cgroup 的結(jié)合,我們絕對可以在一個隔離的環(huán)境中啟動許多在同一主機上運行的應(yīng)用程序。
容器與虛擬機
很明顯,與虛擬機環(huán)境相比,容器技術(shù)解決了在隔離性、可移植性和精簡架構(gòu)方面的問題。但我們不要忘記,虛擬機允許我們隔離我們的應(yīng)用程序,尤其是在內(nèi)核級別,因此黑客逃離容器并破壞系統(tǒng)的風險遠高于逃離虛擬機。
大多數(shù) Linux 內(nèi)核漏洞可能適用于容器,這可能允許它們升級和破壞受影響的命名空間以及同一操作系統(tǒng)中的其他命名空間。
這些安全問題導(dǎo)致研究人員嘗試從主機創(chuàng)建真正分離的命名空間。具體稱為“沙盒”,現(xiàn)在有幾種解決方案可以提供這些功能:gVisor 或例如 Kata Containers。
Kubernetes 中的容器運行時
我們可以在容器編排器 Kubernetes 中更深入地研究這類技術(shù)。
Kubernetes 使用組件 kubelet 來管理容器。我們可以將其定義為負責提供給它的規(guī)范并準時準確地執(zhí)行其操作的船長。
Kubelet 采用 pod 規(guī)范并使其在分配給它們的主機上作為容器運行,并且可以與任何容器運行時交互,只要它符合 OCI 標準(其實現(xiàn)是 RunC)

容器運行時的工作原理
RunC最初嵌入到Docker架構(gòu)中,于 2015 年作為獨立工具發(fā)布。它已成為 DevOps 團隊可以用作容器引擎的一部分的常用的、標準的、跨功能的容器運行時。
RunC 提供了與現(xiàn)有低級 Linux 特性交互的所有功能。它使用命名空間和控制組來創(chuàng)建和運行容器進程。
在下面的段落中,我們將介紹運行時類和核心元素。還有一個 RuntimeClass 處理程序,其默認值為 RunC(對于使用 containerd 作為容器運行時的 Kubernetes 安裝)。
RuntimeClass
顧名思義,運行時類允許我們使用各種容器運行時進行操作。2014 年,Docker 是 Kubernetes 上唯一可用的運行時容器。從 Kubernetes 1.3 版開始,添加了與 Rocket (RKT) 的兼容性,最后在 Kubernetes 1.5 中,引入了容器運行時 Iterface (CRI),它具有標準接口和所有容器運行時的可能性,您可以直接與此接口標準省去了開發(fā)者適應(yīng)各類容器運行時的麻煩和擔心版本維護的麻煩。
事實上,CRI 允許我們將容器運行時部分與 Kubernetes 分離,最重要的是,允許 Kata Containers 和 gVisor 等技術(shù)以 containerd 的形式連接到容器運行時。
在 Kubernetes 1.14 中,RuntimeClass 再次作為內(nèi)置集群資源引入,其核心是處理程序?qū)傩浴?/p>
處理程序是指接收容器創(chuàng)建請求的程序,對應(yīng)于容器運行時。
kind: RuntimeClass
apiVersion: node.k8s.io/v1
metadata:
name: #RuntimeClass Name
handler: #container runtime for example: runc
overhead:
podFixed:
memory: "" # 64Mi
cpu: "" # 250m
scheduling:
nodeSelector:
<key>: <value> # container-rt: gvisor
- handler 字段指向要使用的特定容器運行時或配置。
- 聲明開銷允許集群(包括調(diào)度程序)在做出有關(guān) Pod 和資源的決策時考慮它。通過使用這些字段,您可以使用此 RuntimeClass 指定運行 pod 的開銷,并確保在 Kubernetes 中考慮這些開銷。
- 調(diào)度字段用于確保 Pod 被調(diào)度在正確的節(jié)點上。
默認情況下,如果我們有一個帶有 Docker 或 containerd 的集群,我們的處理程序是 runc,但如果我們使用 gVisor,它將是 runc。
在 Kubernetes 中使用 gVisor 隔離 Linux 主機和容器
現(xiàn)在我們將了解如何在 Kubernetes 集群中擁有多個容器運行時,并為敏感工作負載選擇更嚴格的容器運行時。
在本教程中,我使用了之前的項目,在該項目中我使用 containerd 安裝了 Kubernetes 集群。
https://github.com/alessandrolomanto/k8s-vanilla-containerd
初始化 Kubernetes 集群:
make vagrant-start
啟動機器后,驗證所有組件是否已啟動并運行:
vagrant ssh master
kubectl get nodes
NAME STATUS ROLES AGE VERSION
master Ready control-plane,master 7m59s v1.21.0
worker1 Ready <none> 5m50s v1.21.0
worker2 Ready <none> 3m51s v1.21.0
在 worker1 上安裝gVisor:
ssh worker1 # Vagrant default password: vagrant
sudo su
安裝最新的 gVisor 版本:
(
set -e
ARCH=$(uname -m)
URL=https://storage.googleapis.com/gvisor/releases/release/latest/${ARCH}
wget ${URL}/runsc ${URL}/runsc.sha512 \\
${URL}/containerd-shim-runsc-v1 ${URL}/containerd-shim-runsc-v1.sha512
sha512sum -c runsc.sha512 \\
-c containerd-shim-runsc-v1.sha512
rm -f *.sha512
chmod a+rx runsc containerd-shim-runsc-v1
sudo mv runsc containerd-shim-runsc-v1 /usr/local/bin
)
FINISHED --2022-04-28 07:24:44--
Total wall clock time: 5.2s
Downloaded: 4 files, 62M in 3.1s (20.2 MB/s)
runsc: OK
containerd-shim-runsc-v1: OK
配置容器運行時:
cat <<EOF | sudo tee /etc/containerd/config.toml
version = 2
[plugins."io.containerd.runtime.v1.linux"]
shim_debug = true
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
runtime_type = "io.containerd.runsc.v1"
EOF
重啟容器服務(wù):
sudo systemctl restart containerd
為 gVisor 安裝 RuntimeClass:
cat <<EOF | kubectl apply -f -
apiVersion: node.k8s.io/v1beta1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc
EOF
驗證:
vagrant@master:~$ kubectl get runtimeclass
NAME HANDLER AGE
gvisor runsc 17s
使用 gVisor RuntimeClass 創(chuàng)建一個 Pod:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: nginx-gvisor
spec:
runtimeClassName: gvisor
containers:
- name: nginx
image: nginx
EOF
驗證 Pod 是否正在運行:
kubectl get pod nginx-gvisor -o wide
vagrant@master:~$ kubectl get pod nginx-gvisor -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-gvisor 1/1 Running 0 31s 192.168.235.129 worker1 <none> <none>
有關(guān)更新信息,請關(guān)注官方文檔。https://gvisor.dev/docs/user_guide/install/
結(jié)論
我們已經(jīng)看到當前的容器技術(shù)存在弱隔離問題。快速修補容器和最低安全上下文特權(quán)等常見做法可以有效限制攻擊面。我們甚至應(yīng)該開始像上面的教程那樣實施運行時安全措施,因為現(xiàn)在可能有多個容器運行時。
當然,這不是每個人都需要的東西,但是當您想要運行不受信任的容器而不以任何方式影響主機時,它肯定會派上用場。
假設(shè)你是一個容器托管服務(wù),在同一臺主機上啟動不同客戶的容器。你會因為共享上下文而損害其他客戶嗎?開始思考如何緩解這些問題。




























