目前的容器技術仍然有許多廣為人知的安全挑戰,其中一個主要的問題是,從單一共享核心獲得效率和性能意味着容器逃逸可能成為一個漏洞。
是以在 2015 年,幾乎在同一個星期,Intel OTC (Open Source Technology Center) 和國内的 HyperHQ 團隊同時開源了兩個基于虛拟化技術的容器實作,分别叫做 Intel Clear Container 和 runV 項目。而在 2017 年,借着 Kubernetes 的東風,這兩個相似的容器運作時項目在中立基金會的撮合下最終合并,就成了現在大家耳熟能詳的 Kata Containers 項目。由于 Kata Containers 的本質就是一個精簡後的輕量級虛拟機,是以它的特點,就是“像虛拟機一樣安全,像容器一樣靈活”。
2018 年,Google 公司則釋出了一個名叫 gVisor 的項目。gVisor 項目給容器程序配置一個用 Go 語言實作的、運作在使用者态的、極小的“獨立核心”。這個核心對容器程序暴露 Linux 核心 ABI,扮演着“Guest Kernel”的角色,進而達到了将容器和主控端隔離開的目的。
KataContainers
首先,我們來看 KataContainers。它的工作原理可以用如下所示的示意圖來描述。
Kata Containers 的本質,就是一個輕量化虛拟機。是以當你啟動一個 Kata Containers 之後,你其實就會看到一個正常的虛拟機在運作。這也就意味着,一個标準的虛拟機管理程式(Virtual Machine Manager, VMM)是運作 Kata Containers 必備的一個元件。在我們上面圖中,使用的 VMM 就是 Qemu。
Docker使用KataContainers
安裝參考連結:
https://github.com/kata-containers/documentation/tree/master/install/docker首先節點需要支援以下四種任意一種cpu虛拟化技術:
- Intel VT-x technology
- ARM Hyp mode
- IBM Power Systems
- IBM Z manframes 如果部署在VMware虛拟機中,需要在主控端開啟嵌套虛拟化的功能,開啟步驟見連結: https://blog.51cto.com/11434894/2389180?source=dra
安裝kataContainer:
ARCH=$(arch)
BRANCH="${BRANCH:-master}"
sudo sh -c "echo 'deb http://download.opensuse.org/repositories/home:/katacontainers:/releases:/${ARCH}:/${BRANCH}/xUbuntu_$(lsb_release -rs)/ /' > /etc/apt/sources.list.d/kata-containers.list"
curl -sL http://download.opensuse.org/repositories/home:/katacontainers:/releases:/${ARCH}:/${BRANCH}/xUbuntu_$(lsb_release -rs)/Release.key | sudo apt-key add -
sudo -E apt-get update
sudo -E apt-get -y install kata-runtime kata-proxy kata-shim
設定docker配置檔案:
cat > /etc/docker/daemon.json << EOF
{
"default-runtime": "kata-runtime",
"runtimes": {
"kata-runtime": {
"path": "/usr/bin/kata-runtime"
}
}
}
EOF
systemctl daemon-reload
systemctl restart docker
運作一個容器,可以看到顯示的核心版本和主控端是不一樣的:
root@cr7-ubuntu:~# docker run busybox uname -a
Kubernetes使用kataContainer
配置containerd使用kataContainer:
cat > /etc/containerd/config.toml << EOF
disabled_plugins = ["restart"]
[plugins.linux]
shim_debug = true
[plugins.cri.containerd.runtimes.kata] #kata這個名字可以自己定義,和runtimeClass指定的名字要一樣
runtime_type = "io.containerd.kata.v2"
[plugins.cri.registry.mirrors."docker.io"]
endpoint = ["https://frz7i079.mirror.aliyuncs.com"]
EOF
systemctl daemon-reload
systemctl restart containerd
配置kubelet使用containerd作為容器運作時:
cat > /etc/systemd/system/kubelet.service.d/0-cri-containerd.conf << EOF
[Service]
Environment="KUBELET_EXTRA_ARGS=--container-runtime=remote --runtime-request-timeout=15m -- container-runtime-endpoint=unix:///run/containerd/containerd.sock"
EOF
systemctl daemon-reload
systemctl restart kubelet
kubernetes建立runtimeClass:
apiVersion: node.k8s.io/v1beta1 # RuntimeClass is defined in the node.k8s.io API group
kind: RuntimeClass
metadata:
name: kata
handler: kata # 這裡與containerd配置檔案中的 [plugins.cri.containerd.runtimes.{handler}] 比對
建立pod:
apiVersion: v1
kind: Pod
metadata:
name: kata-nginx
spec:
runtimeClassName: kata
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
gVisor
相比之下,gVisor 的設計其實要更加“激進”一些。它的原理,可以用如下所示的示意圖來表示清楚。
gVisor 工作的核心,在于它為應用程序、也就是使用者容器,啟動了一個名叫 Sentry 的程序。而 Sentry 程序的主要職責,就是提供一個傳統的作業系統核心的能力,即:運作使用者程式,執行系統調用。是以說,Sentry 并不是使用 Go 語言重新實作了一個完整的 Linux 核心,而隻是一個對應用程序“冒充”核心的系統元件。
Docker使用gVisor
檢視原本的runtime:
root@cr7-ubuntu:~# docker info
...
Runtimes: runc
Default Runtime: runc
...
安裝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
)
cat > /etc/docker/daemon.json << EOF
{
"registry-mirrors": ["https://frz7i079.mirror.aliyuncs.com"],
"runtimes": {
"gvisor": { #這個名字可以自己制定,docker run的時候--runtime使用
"path": "/usr/local/bin/runsc"
}
}
}
EOF
systemctl daemon-reload
systemctl restart docker
檢視此時的runtime:
root@cr7-ubuntu:~# docker info
...
Runtimes: runc gvisor
Default Runtime: runc
...
運作gvisor為runtime的容器:
docker run -itd --name web1 --runtime=gvisor nginx
在主控端上是看不到這個程序的,如果是runc的容器是能看到程序的:
root@cr7-ubuntu:~/gvisor# ps -ef | grep nginx | grep -v grep
#沒有輸出
Kubernetes使用gVisor
配置containerd使用gvisor:
cat > /etc/containerd/config.toml << EOF
disabled_plugins = ["restart"]
[plugins.linux]
shim_debug = true
[plugins.cri.containerd.runtimes.gvisor] #gvisor這個名字可以自己定義,和runtimeClass指定的名字要一樣
runtime_type = "io.containerd.runsc.v1"
[plugins.cri.registry.mirrors."docker.io"]
endpoint = ["https://frz7i079.mirror.aliyuncs.com"]
EOF
systemctl restart containerd
配置crictl使用containerd作為作為容器運作時:
runtime-endpoint: unix:///var/run/containerd/containerd.sock
image-endpoint: ""
timeout: 0
debug: false
cat > /var/lib/kubelet/kubeadm-flags.env << EOF
KUBELET_KUBEADM_ARGS="--network-plugin=cni --pod-infra-container- image=registry.aliyuncs.com/google_containers/pause:3.2 --container-runtime=remote --container-runtime-endpoint=unix:///run/containerd/containerd.sock"
EOF
systemctl daemon-reload
systemctl restart kubelet
kubeadm join将node加入kubernetes叢集,master使用docker作為容器運作時,node使用containerd作為容器運作時。
建立runtimeClass:
apiVersion: node.k8s.io/v1beta1
kind: RuntimeClass
metadata:
name: gvisor
handler: gvisor #對應CRI配置的名稱
建立pod使用gvisor作為runtime:
apiVersion: v1
kind: Pod
metadata:
name: nginx-gvisor
spec:
runtimeClassName: gvisor
containers:
- name: nginx
image: nginx
nodeName: cks3
對比
在性能上,KataContainers 和 KVM 實作的 gVisor 基本不分伯仲,在啟動速度和占用資源上,基于使用者态核心的 gVisor 還略勝一籌。但是,對于系統調用密集的應用,比如重 I/O 或者重網絡的應用,gVisor 就會因為需要頻繁攔截系統調用而出現性能急劇下降的情況。此外,gVisor 由于要自己使用 Sentry 去模拟一個 Linux 核心,是以它能支援的系統調用是有限的,隻是 Linux 系統調用的一個子集。