如何在 Kubernetes 中運行不受信任的容器
IT 世界每天都在越來越多地采用基于容器的基礎架構。但是,每個人都不清楚優點,缺點甚至局限性。
考慮到即使是大公司也在靠近基于容器的基礎設施,但是可能的攻擊區域和數據泄露的潛在影響卻無人在意。
Docker(containerd)和 LXC 等技術并不是真正孤立的系統,因為它們與托管的操作系統共享相同的 Linux 內核。
對于潛在的攻擊者來說,在大公司內啟動他們的容器是一個千載難逢的機會。但容器技術自身能讓我們輕松自衛嗎?
當前的容器技術
已經重復了很多次,容器是一種打包、共享和部署應用程序的新方式,而不是所有功能都打包在一個軟件或操作系統中的單一應用程序。
目前,容器沒有利用任何新的東西,但它們是在 Linux 命名空間和 cgroup 之上創建的演變。命名空間創建了一個虛擬和隔離的用戶空間,并為應用程序提供其系統資源的隔離,例如文件系統、網絡和進程。這種抽象允許應用程序獨立啟動,而不會干擾在同一主機上運行的其他應用程序。
所以,多虧了命名空間和 cgroup 的結合,我們絕對可以在一個隔離的環境中啟動許多在同一主機上運行的應用程序。
容器與虛擬機
很明顯,與虛擬機環境相比,容器技術解決了在隔離性、可移植性和精簡架構方面的問題。但我們不要忘記,虛擬機允許我們隔離我們的應用程序,尤其是在內核級別,因此黑客逃離容器并破壞系統的風險遠高于逃離虛擬機。
大多數 Linux 內核漏洞可能適用于容器,這可能允許它們升級和破壞受影響的命名空間以及同一操作系統中的其他命名空間。
這些安全問題導致研究人員嘗試從主機創建真正分離的命名空間。具體稱為“沙盒”,現在有幾種解決方案可以提供這些功能:gVisor 或例如 Kata Containers。
Kubernetes 中的容器運行時
我們可以在容器編排器 Kubernetes 中更深入地研究這類技術。
Kubernetes 使用組件 kubelet 來管理容器。我們可以將其定義為負責提供給它的規范并準時準確地執行其操作的船長。
Kubelet 采用 pod 規范并使其在分配給它們的主機上作為容器運行,并且可以與任何容器運行時交互,只要它符合 OCI 標準(其實現是 RunC)
容器運行時的工作原理
RunC最初嵌入到Docker架構中,于 2015 年作為獨立工具發布。它已成為 DevOps 團隊可以用作容器引擎的一部分的常用的、標準的、跨功能的容器運行時。
RunC 提供了與現有低級 Linux 特性交互的所有功能。它使用命名空間和控制組來創建和運行容器進程。
在下面的段落中,我們將介紹運行時類和核心元素。還有一個 RuntimeClass 處理程序,其默認值為 RunC(對于使用 containerd 作為容器運行時的 Kubernetes 安裝)。
RuntimeClass
顧名思義,運行時類允許我們使用各種容器運行時進行操作。2014 年,Docker 是 Kubernetes 上唯一可用的運行時容器。從 Kubernetes 1.3 版開始,添加了與 Rocket (RKT) 的兼容性,最后在 Kubernetes 1.5 中,引入了容器運行時 Iterface (CRI),它具有標準接口和所有容器運行時的可能性,您可以直接與此接口標準省去了開發者適應各類容器運行時的麻煩和擔心版本維護的麻煩。
事實上,CRI 允許我們將容器運行時部分與 Kubernetes 分離,最重要的是,允許 Kata Containers 和 gVisor 等技術以 containerd 的形式連接到容器運行時。
在 Kubernetes 1.14 中,RuntimeClass 再次作為內置集群資源引入,其核心是處理程序屬性。
處理程序是指接收容器創建請求的程序,對應于容器運行時。
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 字段指向要使用的特定容器運行時或配置。
- 聲明開銷允許集群(包括調度程序)在做出有關 Pod 和資源的決策時考慮它。通過使用這些字段,您可以使用此 RuntimeClass 指定運行 pod 的開銷,并確保在 Kubernetes 中考慮這些開銷。
- 調度字段用于確保 Pod 被調度在正確的節點上。
默認情況下,如果我們有一個帶有 Docker 或 containerd 的集群,我們的處理程序是 runc,但如果我們使用 gVisor,它將是 runc。
在 Kubernetes 中使用 gVisor 隔離 Linux 主機和容器
現在我們將了解如何在 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
重啟容器服務:
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 創建一個 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>
有關更新信息,請關注官方文檔。https://gvisor.dev/docs/user_guide/install/
結論
我們已經看到當前的容器技術存在弱隔離問題。快速修補容器和最低安全上下文特權等常見做法可以有效限制攻擊面。我們甚至應該開始像上面的教程那樣實施運行時安全措施,因為現在可能有多個容器運行時。
當然,這不是每個人都需要的東西,但是當您想要運行不受信任的容器而不以任何方式影響主機時,它肯定會派上用場。
假設你是一個容器托管服務,在同一臺主機上啟動不同客戶的容器。你會因為共享上下文而損害其他客戶嗎?開始思考如何緩解這些問題。