二十九、K8s最小服务漏洞3-gVisor沙箱
一、為什么需要使用沙箱運行容器
首先,我們來看看整個K8s調(diào)用容器的架構(gòu):
1.架構(gòu)概述
架構(gòu)分為3個部分,分別時High-level container management、High-level conatiner runtime、Low-level contianer runtime。
專注于運行容器的實際容器運行時通常被稱為“Low-level contianer runtime”。支持更多高級功能的運行時,如圖像管理和 gRPC/Web API,通常被稱為"high-level container tools", “high-level container runtimes” 或者通常簡稱為 “container runtimes”
容器是使用Linux 命名空間和cgroups 實現(xiàn)的。命名空間讓我們可以為每個容器虛擬化系統(tǒng)資源,例如文件系統(tǒng)或網(wǎng)絡(luò)。Cgroups 提供了一種方法來限制每個容器可以使用的資源量,例如 CPU 和內(nèi)存。低級運行時負(fù)責(zé)為容器設(shè)置這些命名空間和 cgroup,然后在這些命名空間和 cgroup 中運行命令。
通常,想要在容器中運行應(yīng)用程序的開發(fā)人員需要的不僅僅是低級運行時提供的功能。他們需要有關(guān)圖像格式、圖像管理和共享圖像的 API 和功能。這些功能由高級運行時提供。低級運行時只是沒有為這種日常使用提供足夠的功能。
最后K8s作為容器編排工具,調(diào)用高級進(jìn)行時完成整個環(huán)境的容器部署。
2.Low-level contianer runtime
首先,低級別的容器運行時都需要遵循OCI開放容器標(biāo)準(zhǔn),方便高級別容器運行時進(jìn)行調(diào)用。此外,相比于高級容器運行時,它不具備如下功能:
目前,我們有三個主要的低級容器運行時,其實現(xiàn)的工作有所不同:
3.High-level contianer runtime
高級運行時負(fù)責(zé)容器鏡像的傳輸和管理、解包鏡像以及使用OCI傳遞給低級運行時來運行容器。通常,高級運行時提供一個守護(hù)程序應(yīng)用程序和一個 API,遠(yuǎn)程應(yīng)用程序可以使用它們來邏輯運行容器并監(jiān)視它們。
同時,希望與 Kubernetes 集成的高級容器運行時需要實現(xiàn) CRI。CRI 在 Kubernetes 1.5 中引入,充當(dāng)kubelet和容器運行時之間的橋梁,具體對于每一個高級容器運行時,使用shim(墊片)完成。
目前三個主要的高級容器運行時:
4.High-level container management
本文中指的是K8s。Kubernetes 項目定義了許多標(biāo)準(zhǔn)。與本文相關(guān)的是CRI:容器運行時接口。此接口定義了 Kubernetes 如何與高級容器運行時對話。
kubelet 是一個代理,位于 Kubernetes 集群中的每個工作節(jié)點上。kubelet 負(fù)責(zé)管理其節(jié)點的容器工作負(fù)載。在實際運行工作負(fù)載時,kubelet 使用 CRI 與在同一節(jié)點上運行的容器運行時進(jìn)行通信。通過這種方式,CRI 只是一個抽象層或 API,它允許我們切換容器運行時實現(xiàn),而不是將它們內(nèi)置到 kubelet 中。
5.為什么需要使用沙箱運行容器
最后,再回到最開始的問題,為什么需要使用沙箱運行容器?目前我們1.22.0版本,還可以使用docker作為容器運行時,今后將變?yōu)閏ontainerd。但是,無論時docker還是containerd,默認(rèn)都時通過runc這個低級別容器運行時實現(xiàn)的。而在前面的筆記中,我們看到了,如果使用runc來部署容器,會共享宿主機(jī)的各種空間,比如進(jìn)程空間、內(nèi)核空間。那么,如果我們?nèi)萜鞯倪M(jìn)程也會存在于宿主機(jī)上。那么,如果容器運行的進(jìn)程存在漏洞,一旦被入侵,就會有一定可能性對我們宿主機(jī)造成損壞。所以,我們可以用沙箱的方式運行容器,隔離容器與宿主機(jī)的環(huán)境,在宿主機(jī)內(nèi),也就看到不容器內(nèi)的進(jìn)程了。
而runc部署的容器時不持支持沙箱的,所以我們需要使用另外一個低級別的容器進(jìn)行時gVisor來完成操作。
Google gVisor 是支持 Google 計算平臺 (GPC) App Engine、Cloud Functions 和 CloudML 的沙箱技術(shù)。谷歌意識到在公共云基礎(chǔ)設(shè)施中運行不受信任的應(yīng)用程序的風(fēng)險以及使用虛擬機(jī)沙箱應(yīng)用程序的效率低下,并開發(fā)了一個用戶空間內(nèi)核用來對不受信任的應(yīng)用程序進(jìn)行沙箱處理。gVisor 沙箱通過攔截從應(yīng)用程序到主機(jī)內(nèi)核的所有系統(tǒng)調(diào)用,并在用戶空間中使用 gVisor 的內(nèi)核實現(xiàn)Sentry進(jìn)行處理這些系統(tǒng)調(diào)用。那么就算容器的惡意代碼對內(nèi)核破壞也是容器的內(nèi)核,而非宿主機(jī)的內(nèi)核。
二、配置docker使用gVisor做為runtime
使用一臺單獨的ubuntu設(shè)備作為宿主機(jī)即可。
1.安裝docker
apt-get update apt-get install -y docker-ce如果需要加速,可以添加鏡像加速器:
cat > /etc/docker/daemon.json <<EOF { "registry-mirrors": ["https://frz7i079.mirror.aliyuncs.com"], } EOF重啟進(jìn)程并設(shè)置docker開機(jī)啟動。
systemctl daemon-reload ; systemctl restart docker; systemctl enable docker可以看到,目前docker支持的runtime為runc:
root@vms75:~# docker info | grep runtime WARNING: No swap limit supportRuntimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc2.安裝gVisor
安裝最新的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/bin3.將gVisor設(shè)置為docker的runtime
命令如下:
/usr/local/bin/runsc install sudo systemctl reload docker再次查看/etc/docker/daemon.json文件,可以看到其中已經(jīng)添加了runtimes的新路徑,runsc為新runtime的名稱,可以修改,最終路徑指向了gvisor。
root@vms75:~# cat /etc/docker/daemon.json {"runtimes": {"runsc": {"path": "/usr/local/bin/runsc"}} }可以通過如下的命令測試能夠使用runsc運行容器:
docker run --rm --runtime=runsc hello-world結(jié)果如下,說明runsc安裝成功:
root@vms75:~# docker run --rm --runtime=runsc hello-worldHello from Docker! This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps:1. The Docker client contacted the Docker daemon.2. The Docker daemon pulled the "hello-world" image from the Docker Hub.(amd64)3. The Docker daemon created a new container from that image which runs theexecutable that produces the output you are currently reading.4. The Docker daemon streamed that output to the Docker client, which sent itto your terminal.To try something more ambitious, you can run an Ubuntu container with:$ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID:https://hub.docker.com/For more examples and ideas, visit:https://docs.docker.com/get-started/接著查看docker infor,可以看到如下信息:
Runtimes: runc runscDefault Runtime: runc現(xiàn)在docker存在兩個低等級容器運行時,默認(rèn)是runc,我們可以通過如下的命令其修改為runsc(即gvisor)。
vim /lib/systemd/system/docker.service接著在ExecStart處設(shè)置–default-runtime runsc。
ExecStart=/usr/bin/dockerd --default-runtime runsc -H fd:// --containerd=/run/containerd/containerd.sock重啟docke進(jìn)程:
systemctl daemon-reload ; systemctl restart docker再次查看docker info,可以看到默認(rèn)的runtime已經(jīng)修改為runsc了,后續(xù)使用gVisor運行容器時,不需要再使用–runtime=runsc指定了:
Default Runtime: runsc4.檢查沙箱功能
創(chuàng)建一個nginx鏡像,然后在宿主機(jī)中查看是否有對應(yīng)的進(jìn)程:
查看宿主機(jī)進(jìn)程:
root@vms75:~# docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2eda45493c41 nginx "/docker-entrypoint.…" 2 minutes ago Up 2 minutes 80/tcp web1 root@vms75:~# ps aux | grep -v grep | grep nginx可以看到,雖然我們創(chuàng)建了nginx容器,但是宿主機(jī)上并沒有對應(yīng)的進(jìn)程。
三、配置containerd使用gVisor做為runtime
使用一臺單獨的ubuntu設(shè)備作為宿主機(jī)即可。
1.安裝containerd
apt-get update apt-get install containerd.io cri-tools -y systemctl enable containerd --now containerd config default > /etc/containerd/config.toml/etc/containerd/config.toml為containerd的配置文件,我們可以修改如下的內(nèi)容:
containerd 客戶端工具有ctr 和crictl 兩個,如果想要執(zhí)行crictl命令則需要使用如下命令:
crictl config runtime-endpoint unix:///var/run/containerd/containerd.sock完成后重啟contianerd:
systemctl restart containerd ; systemctl enable containerd現(xiàn)在可以使用ctr或者crictl做一些鏡像管理操作,例如:
需要注意的是,containerd 和docker 相比多了命名空間的概念。當(dāng)使用crictl 命令的時候,都是在k8s.io這個命名空間里的,而ctr 默認(rèn)是在default 這個命名空間里。所以當(dāng)crictl 下載鏡像之后,會自動創(chuàng)建一個k8s.io,而下載的鏡像就是放在k8s.io 里的。
例如,現(xiàn)在我們使用crictl下載鏡像:
crictl pull docker.io/nginx使用ctr i list命令無法查看鏡像,而crictl images則可以:
root@vms74:~# ctr i list REF TYPE DIGEST SIZE PLATFORMS LABELS root@vms74:~# crictl images IMAGE TAG IMAGE ID SIZE docker.io/library/nginx latest f652ca386ed13 56.7MB我們可以查看命名空間:
root@vms74:~# ctr ns list NAME LABELS k8s.io可以看到,這里創(chuàng)建了一個k8s.io的命名空間。如果想要切換命名空間到k8s.io,可以使用如下的命令:
export CONTAINERD_NAMESPACE=k8s.io可以看到無論是ctr或者crictl的命令都和docker中格式不一致。當(dāng)習(xí)慣使用docker后,為了方便使用containerd,我們可以安裝nerdctl工具,讓我們containerd的命令格式和docker一致。最新的二進(jìn)制版本nerdctl可以從如下連接下載:https://github.com/containerd/nerdctl/releases,這里下載nerdctl-0.15.0-linux-amd64.tar.gz即可。
接著下載其依賴的CNI插件,下載地址為:https://github.com/containernetworking/plugins/releases,這里下載即可。
下載完成后,在ubuntu宿主機(jī)中通過如下的命令解壓:
tar zxvf nerdctl-0.15.0-linux-amd64.tar.gz mkdir -p /opt/cni/bin/ tar zxf cni-plugins-linux-amd64-v1.0.1.tgz -C /opt/cni/bin/完成后,將當(dāng)前目錄下的nerdctl拷貝到/bin目錄下即可:
mv nerdctl /bin/然后啟動nerdctl的補全功能,在/etc/profile里添加source <(nerdctl completion bash),最后再使用如下命令即可:
source /etc/profile現(xiàn)在,我們就可以像使用docker一樣的使用containerd了,只需要把docker命令中的docker修改為nerdctl即可。例如,查看鏡像:
root@vms74:~# nerdctl images REPOSITORY TAG IMAGE ID CREATED PLATFORM SIZE nginx latest 9522864dd661 22 minutes ago linux/amd64 149.1 MiB nginx <none> 9522864dd661 22 minutes ago linux/amd64 149.1 MiB sha256 f652ca386ed135a4cbe356333e08ef0816f81b2ac8d0619af01e2b256837ed3e 9522864dd661 22 minutes ago linux/amd64 149.1 MiB刪除鏡像:
root@vms74:~# nerdctl rmi nginx:latest Untagged: docker.io/library/nginx:latest@sha256:9522864dd661dcadfd9958f9e0de192a1fdda2c162a35668ab6ac42b465f0603 Deleted: sha256:9321ff862abbe8e1532076e5fdc932371eff562334ac86984a836d77dfb717f5 Deleted: sha256:0664b7821b6050b321b14cdede97c2079ae45aff22beb4a42f7595294f5be62d Deleted: sha256:c9fcd9c6ced8b793a0ad4f93820c1d51d94c3b1fca93000d93e9e8eefa6fdb38 Deleted: sha256:d3e1dca44e8225cdd06b6bf7cdfc847e3ab9f09ab6aeefb006e2e8f02f0dd26c Deleted: sha256:82caad489ad7bc7e1ae6f17bb1e9ade2bca44a41a07cc8c5587af8a2de2f536a Deleted: sha256:2bed47a66c07ecddfea2bc9c128d81b31272d99b69aff1fb4edc079c4dbf56e7部署nginx容器和查看容器:
root@vms74:~# nerdctl run -d --name=web1 --restart=always nginx docker.io/library/nginx:latest: resolved |++++++++++++++++++++++++++++++++++++++| index-sha256:9522864dd661dcadfd9958f9e0de192a1fdda2c162a35668ab6ac42b465f0603: done |++++++++++++++++++++++++++++++++++++++| manifest-sha256:4424e31f2c366108433ecca7890ad527b243361577180dfd9a5bb36e828abf47: done |++++++++++++++++++++++++++++++++++++++| config-sha256:f652ca386ed135a4cbe356333e08ef0816f81b2ac8d0619af01e2b256837ed3e: done |++++++++++++++++++++++++++++++++++++++| elapsed: 1.1 s total: 0.0 B (0.0 B/s) a03b306cc186ab6e15024d460e8e281d5551f53aa5a1a8f87783e355faaaf535 root@vms74:~# nerdctl ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a03b306cc186 docker.io/library/nginx:latest "/docker-entrypoint.…" 31 seconds ago Up web12.安裝gVisor
安裝最新的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/bin3.將gVisor設(shè)置為containerd的runtime
更新/etc/containerd/config.toml。確保選中containerd-shim-runsc-v1是 ${PATH}或在同一目錄中containerd的二進(jìn)制文件。這里的runsc也就是gvisor。
重新啟動containerd:
systemctl daemon-reload ; systemctl restart containerd查看containerd是否加載了gvisor:
root@vms74:~# crictl info | grep runtime"runtimes": {"runtimeType": "io.containerd.runc.v2","runtimeEngine": "","runtimeRoot": "","runtimeType": "io.containerd.runsc.v1","runtimeEngine": "","runtimeRoot": "",可以看到,目前containerd已經(jīng)支持了runsc。
4.檢查沙箱功能
使用gvisor作為runtime:
run -d --name=web1 --restart=always --runtime=runsc nginx查看宿主機(jī)上是否有對應(yīng)的進(jìn)程,可以看到,沒有對應(yīng)的進(jìn)程。說明整個容器已經(jīng)在沙箱中運行了。
root@vms74:~# ps aux | grep -v grep | grep nginx root@vms74:~#四、在K8s環(huán)境中,使用containerd(使用gVisor)做為runtime
注意:如果K8s使用docker作為high-level conatiner runtime,那么docker不支持使用gVisor作為low-level conatiner runtime。所以這里我們需要使用containerd作為K8s的high-level conatiner runtime。
1.k8s集群環(huán)境:
底層系統(tǒng)為ubuntu18.04,Master node的IP地址為192.168.26.71/24,三個Worker node的IP地址為192.168.26.72/24、192.168.26.73/24,192.168.26.74/24。網(wǎng)絡(luò)通過calico來創(chuàng)建。
目前,已經(jīng)搭建好了master1、worker1和worker2的K8s集群環(huán)境。在master上查看集群信息:可以看到目前三臺設(shè)備都使用docker作為runtime。
root@vms71:~# kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME vms71.rhce.cc Ready control-plane,master 15d v1.22.0 192.168.26.71 <none> Ubuntu 18.04.5 LTS 4.15.0-112-generic docker://20.10.11 vms72.rhce.cc Ready <none> 15d v1.22.0 192.168.26.72 <none> Ubuntu 18.04.5 LTS 4.15.0-112-generic docker://20.10.11 vms73.rhce.cc Ready <none> 15d v1.22.0 192.168.26.73 <none> Ubuntu 18.04.5 LTS 4.15.0-112-generic docker://20.10.11現(xiàn)在,我們需要將worker node3,也就是將再上一步已經(jīng)配置完成的以containerd作為runtime的設(shè)備加入到這個已經(jīng)存在的集群中。
2.加入worker node3
在worker node3上,配置/etc/hosts,關(guān)閉swap:
切換命名空間到k8s.io:
export CONTAINERD_NAMESPACE=k8s.io設(shè)置加入集群所需要的參數(shù)并讓其生效:
cat > /etc/modules-load.d/containerd.conf <<EOF overlay br_netfilter EOFmodprobe overlay modprobe br_netfiltercat <<EOF > /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-ip6tables = 1 net.bridge.bridge-nf-call-iptables = 1 net.ipv4.ip_forward = 1 EOFsysctl -p /etc/sysctl.d/k8s.conf在worker node3上下載calico所需要的鏡像,首先下載對應(yīng)的yaml文件:
curl https://docs.projectcalico.org/manifests/calico.yaml -O然后使用如下命令,查看所需要的鏡像,并下載到本地:
grep image calico.yaml接著安裝K8s:
apt-get install -y kubeadm=1.22.0-00 kubelet=1.22.0-00 kubectl=1.22.0-00 systemctl restart kubelet ; systemctl enable kubelet在master上使用如下命令生成給worker node3加入集群的命令:
root@vms71:~# kubeadm token create --print-join-command kubeadm join 192.168.26.71:6443 --token nvqate.m94p1pzp5obke6sq --discovery-token-ca-cert-hash sha256:8a808cf9415018407a86963ce4af14ce3b0c830c56eaa27ce9b52baa2504116a復(fù)制加入集群的命令到worker node3上。等級一小段時間,然后回到master上,使用命令查看worker node3是否加入成功:
root@vms71:~# kubectl get nodes -owide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME vms71.rhce.cc Ready control-plane,master 15d v1.22.0 192.168.26.71 <none> Ubuntu 18.04.5 LTS 4.15.0-112-generic docker://20.10.11 vms72.rhce.cc Ready <none> 15d v1.22.0 192.168.26.72 <none> Ubuntu 18.04.5 LTS 4.15.0-112-generic docker://20.10.11 vms73.rhce.cc Ready <none> 15d v1.22.0 192.168.26.73 <none> Ubuntu 18.04.5 LTS 4.15.0-112-generic docker://20.10.11 vms74.rhce.cc Ready <none> 9m18s v1.22.0 192.168.26.74 <none> Ubuntu 18.04.5 LTS 4.15.0-112-generic containerd://1.4.12可以看到已經(jīng)加入成功,狀態(tài)為ready,且運行的high-level container runtime為containerd。
3.讓k8s支持gVisor
在worker node3上,修改內(nèi)核文件,讓kubelet也能使用gVisor(runsc):
4.在k8s環(huán)境中部署使用runsc而非runc作為worker node3的low-level container runtime
在master上創(chuàng)建一個runtime class,yaml文件如下:
handler是對應(yīng)的的 CRI (low-level container runtime)配置的名稱,這里指定runsc。應(yīng)用yaml文件后查看是否部署成功:
root@vms71:~# kubectl get runtimeclasses.node.k8s.io NAME HANDLER AGE myclass runsc 12s5.部署pod在worker node3中的沙箱中工作
在master上,為worker node3打一個標(biāo)簽,方便后續(xù)部署pod:
root@vms71:~# kubectl label nodes vms74.rhce.cc xx=xx node/vms74.rhce.cc labeled root@vms71:~# kubectl get nodes -l xx=xx NAME STATUS ROLES AGE VERSION vms74.rhce.cc Ready <none> 24m v1.22.0所使用的yaml文件如下,指定pod將部署在worker node3上,同時指定了runtime class為我們創(chuàng)建的myclass:
apiVersion: v1 kind: Pod metadata:creationTimestamp: nulllabels:run: pod1name: pod1 spec:terminationGracePeriodSeconds: 0runtimeClassName: myclassnodeSelector:xx: xxcontainers:- image: nginximagePullPolicy: IfNotPresentname: pod1resources: {}dnsPolicy: ClusterFirstrestartPolicy: Always status: {}接著使用如下yaml文件創(chuàng)建pod,并查看:
root@vms71:~# kubectl get pods -owide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod1 1/1 Running 0 5s 10.244.183.193 vms74.rhce.cc <none> <none>可以看到,pod1已經(jīng)在worker node3上運行了。那么我們切換到worker node3,查看是否以沙箱的形式運行pod1。使用如下命令查看宿主機(jī)上是否存在nginx的進(jìn)程:
root@vms74:~# ps aux | grep -v grep | grep nginx root@vms74:~#可以看到,pod1已經(jīng)通過runsc以沙箱的形式運行了。
除此之外,KATA這個low-level container runtime也可以將容器環(huán)境和宿主機(jī)環(huán)境隔離,其使用和安裝方法和gvisor十分相似。具體步驟請參考:https://github.com/kata-containers/documentation/blob/master/install/ubuntu-installation-guide.md
整理資料來源:
What’s up with CRI-O, Kata Containers and Podman?:https://merlijn.sebrechts.be/blog/2020-01-docker-podman-kata-cri-o/
Container Runtimes series:https://www.ianlewis.org/en/container-runtimes-part-1-introduction-container-r
Making Containers More Isolated: An Overview of Sandboxed Container Technologies: https://unit42.paloaltonetworks.com/making-containers-more-isolated-an-overview-of-sandboxed-container-technologies/
gvisor install:https://gvisor.dev/docs/user_guide/install/
runtimeclass:https://kubernetes.io/zh/docs/concepts/containers/runtime-class/#2-創(chuàng)建相應(yīng)的-runtimeclass-資源
kata runtime ubuntu: https://github.com/kata-containers/documentation/blob/master/install/ubuntu-installation-guide.md
《老段CKS課程》
總結(jié)
以上是生活随笔為你收集整理的二十九、K8s最小服务漏洞3-gVisor沙箱的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pywebview:使用python构建
- 下一篇: input的button类型,点击页面跳