如何在 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)嗎?
當(dāng)前的容器技術(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ò)和進(jìn)程。這種抽象允許應(yīng)用程序獨立啟動,而不會干擾在同一主機上運行的其他應(yīng)用程序。
所以,多虧了命名空間和 cgroup 的結(jié)合,我們絕對可以在一個隔離的環(huán)境中啟動許多在同一主機上運行的應(yīng)用程序。
容器與虛擬機
很明顯,與虛擬機環(huán)境相比,容器技術(shù)解決了在隔離性、可移植性和精簡架構(gòu)方面的問題。但我們不要忘記,虛擬機允許我們隔離我們的應(yīng)用程序,尤其是在內(nèi)核級別,因此黑客逃離容器并破壞系統(tǒng)的風(fēng)險遠(yuǎn)高于逃離虛擬機。
大多數(shù) Linux 內(nèi)核漏洞可能適用于容器,這可能允許它們升級和破壞受影響的命名空間以及同一操作系統(tǒng)中的其他命名空間。
這些安全問題導(dǎo)致研究人員嘗試從主機創(chuàng)建真正分離的命名空間。具體稱為“沙盒”,現(xiàn)在有幾種解決方案可以提供這些功能:gVisor 或例如 Kata Containers。
Kubernetes 中的容器運行時
我們可以在容器編排器 Kubernetes 中更深入地研究這類技術(shù)。
Kubernetes 使用組件 kubelet 來管理容器。我們可以將其定義為負(fù)責(zé)提供給它的規(guī)范并準(zhǔn)時準(zhǔn)確地執(zhí)行其操作的船長。
Kubelet 采用 pod 規(guī)范并使其在分配給它們的主機上作為容器運行,并且可以與任何容器運行時交互,只要它符合 OCI 標(biāo)準(zhǔn)(其實現(xiàn)是 RunC)
容器運行時的工作原理
RunC最初嵌入到Docker架構(gòu)中,于 2015 年作為獨立工具發(fā)布。它已成為 DevOps 團隊可以用作容器引擎的一部分的常用的、標(biāo)準(zhǔn)的、跨功能的容器運行時。
RunC 提供了與現(xiàn)有低級 Linux 特性交互的所有功能。它使用命名空間和控制組來創(chuàng)建和運行容器進(jìn)程。
在下面的段落中,我們將介紹運行時類和核心元素。還有一個 RuntimeClass 處理程序,其默認(rèn)值為 RunC(對于使用 containerd 作為容器運行時的 Kubernetes 安裝)。
RuntimeClass
顧名思義,運行時類允許我們使用各種容器運行時進(jìn)行操作。2014 年,Docker 是 Kubernetes 上唯一可用的運行時容器。從 Kubernetes 1.3 版開始,添加了與 Rocket (RKT) 的兼容性,最后在 Kubernetes 1.5 中,引入了容器運行時 Iterface (CRI),它具有標(biāo)準(zhǔn)接口和所有容器運行時的可能性,您可以直接與此接口標(biāo)準(zhǔn)省去了開發(fā)者適應(yīng)各類容器運行時的麻煩和擔(dān)心版本維護(hù)的麻煩。
事實上,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é)點上。
默認(rèn)情況下,如果我們有一個帶有 Docker 或 containerd 的集群,我們的處理程序是 runc,但如果我們使用 gVisor,它將是 runc。
在 Kubernetes 中使用 gVisor 隔離 Linux 主機和容器
現(xiàn)在我們將了解如何在 Kubernetes 集群中擁有多個容器運行時,并為敏感工作負(fù)載選擇更嚴(yán)格的容器運行時。
在本教程中,我使用了之前的項目,在該項目中我使用 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)看到當(dāng)前的容器技術(shù)存在弱隔離問題??焖傩扪a容器和最低安全上下文特權(quán)等常見做法可以有效限制攻擊面。我們甚至應(yīng)該開始像上面的教程那樣實施運行時安全措施,因為現(xiàn)在可能有多個容器運行時。
當(dāng)然,這不是每個人都需要的東西,但是當(dāng)您想要運行不受信任的容器而不以任何方式影響主機時,它肯定會派上用場。
假設(shè)你是一個容器托管服務(wù),在同一臺主機上啟動不同客戶的容器。你會因為共享上下文而損害其他客戶嗎?開始思考如何緩解這些問題。