天天看點

gVisor 和 KataContainers

目前的容器技術仍然有許多廣為人知的安全挑戰,其中一個主要的問題是,從單一共享核心獲得效率和性能意味着容器逃逸可能成為一個漏洞。

是以在 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。它的工作原理可以用如下所示的示意圖來描述。

gVisor 和 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虛拟化技術:

安裝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 和 KataContainers

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 系統調用的一個子集。

繼續閱讀