- 一.系統環境
- 二.前言
- 三.安全容器隔離技術簡介
- 四.Gvisor簡介
- 五.容器runtime簡介
- 六.docker容器缺陷
- 七.配置docker使用gVisor作為runtime
- 7.1 安裝docker
- 7.2 升級系統核心
- 7.3 安裝gvisor
- 7.4 配置docker預設的runtime為gVisor
- 7.5 docker使用gVisor作為runtime建立容器
- 八.配置containerd使用gvisor作為runtime
- 8.1 安裝containerd
- 8.2 安裝gVisor
- 8.3 配置containerd支援gVisor
- 8.4 containerd使用gvisor作為runtime建立容器
- 九.在k8s環境裡,配置containerd作為高階別runtime,containerd使用gvisor作為低階別runtime
- 9.1 把ubuntuk8sclient節點加入k8s叢集
- 9.2 配置kubelet使其支援gVisor
- 9.3 建立容器執行時類(Runtime Class)
- 9.4 使用gVisor建立pod
- 十.總結
一.系統環境
本文主要基於Kubernetes1.22.2和Linux作業系統Ubuntu 18.04。
伺服器版本 | docker軟體版本 | Kubernetes(k8s)叢集版本 | gVisor軟體版本 | containerd軟體版本 | CPU架構 |
---|---|---|---|---|---|
Ubuntu 18.04.5 LTS | Docker version 20.10.14 | v1.22.2 | 1.0.2-dev | 1.6.4 | x86_64 |
Kubernetes叢集架構:k8scludes1作為master節點,k8scludes2,k8scludes3作為worker節點。
伺服器 | 作業系統版本 | CPU架構 | 程序 | 功能描述 |
---|---|---|---|---|
k8scludes1/192.168.110.128 | Ubuntu 18.04.5 LTS | x86_64 | docker,kube-apiserver,etcd,kube-scheduler,kube-controller-manager,kubelet,kube-proxy,coredns,calico | k8s master節點 |
k8scludes2/192.168.110.129 | Ubuntu 18.04.5 LTS | x86_64 | docker,kubelet,kube-proxy,calico | k8s worker節點 |
k8scludes3/192.168.110.130 | Ubuntu 18.04.5 LTS | x86_64 | docker,kubelet,kube-proxy,calico | k8s worker節點 |
二.前言
容器技術的發展極大地提高了開發和部署的效率,但容器的安全性一直是一個不容忽視的問題。傳統的Docker容器雖然方便快捷,但在隔離機制上存在一定的缺陷。本文將介紹一種更為安全可靠的容器執行時解決方案——Gvisor。
以沙箱的方式執行容器的前提是已經有一套可以正常執行的Kubernetes叢集,關於Kubernetes(k8s)叢集的安裝部署,可以檢視部落格《Ubuntu 安裝部署Kubernetes(k8s)叢集》https://www.cnblogs.com/renshengdezheli/p/17632858.html。
三.安全容器隔離技術簡介
安全容器是一種執行時技術,為容器應用提供一個完整的作業系統執行環境,但將應用的執行與宿主機作業系統隔離開,避免應用直接訪問主機資源,從而可以在容器主機之間或容器之間提供額外的保護。另外一種安全容器為Kata Containers,相關詳細操作請檢視部落格《以沙箱的方式執行容器:安全容器Kata Containers》。
四.Gvisor簡介
gVisor是由Google開發的一種輕量級的容器隔離技術。它透過在容器與主機作業系統之間插入一個虛擬化層來實現隔離。gVisor提供了一個類似於Linux核心的API,使得容器可以在一個更加受控的環境中執行。它使用了一種稱為“Sandbox”的機制,將容器的系統呼叫轉換為對gVisor的API呼叫,然後再由gVisor轉發給宿主作業系統。這種方式可以有效地隔離容器與主機作業系統之間的資源訪問,提高了容器的安全性。
gVisor的虛擬化層引入了一定的效能開銷,但是相對於傳統的虛擬機器來說,它的效能損失較小。根據Google的測試資料,gVisor的效能損失在10%左右。這主要是因為gVisor使用了一些最佳化技術,如JIT編譯器和快取機制,來減少虛擬化層的開銷。gVisor還支援多核併發,可以在多核系統上實現更好的效能。
gVisor 工作的核心,在於它為應用程序、也就是使用者容器,啟動了一個名叫 Sentry 的程序。 而 Sentry 程序的主要職責,就是提供一個傳統的作業系統核心的能力,即:執行使用者程式,執行系統呼叫。所以說,Sentry 並不是使用 Go 語言重新實現了一個完整的 Linux 核心,而只是一個對應用程序“冒充”核心的系統元件。
在這種設計思想下,我們就不難理解,Sentry 其實需要自己實現一個完整的 Linux 核心網路棧,以便處理應用程序的通訊請求。然後,把封裝好的二層幀直接傳送給 Kubernetes 設定的 Pod 的 Network Namespace 即可。
五.容器runtime簡介
在容器技術中,執行時(Runtime)是管理容器生命週期的軟體。根據其提供的功能複雜度,可以將容器執行時分為低階別執行時和高階別執行時。
低階別執行時(Low-Level Runtime)通常指的是直接與作業系統核心互動的容器執行時管理工具。這些工具負責容器映象的載入、容器的建立、啟動、停止以及容器內部程序的管理。低階別執行時提供的功能主要包括:
- 容器映象管理:處理容器的映象下載、儲存和更新。
- 容器生命週期管理:包括容器的建立、執行、暫停、恢復、停止和刪除。
- 程序和資源隔離:透過作業系統的控制組(cgroups)和名稱空間(namespaces)實現資源的隔離和分配。
- 網路配置:為容器提供網路介面和IP地址,以及容器間的通訊機制。
低階別執行時有runC,lxc,gvisor,kata等等。
高階別執行時(High-Level Runtime)則通常是指在低階別執行時之上的容器編排和管理工具,它們提供了更高階的抽象和更多的管理功能。這些工具通常包括:
- 容器編排:自動化容器的部署、擴充套件和管理。
- 服務發現和負載均衡:自動配置服務間的相互發現和流量分配。
- 儲存編排:管理容器的持久化資料和儲存卷。
- 資源監控和日誌管理:收集容器執行的監控資料和日誌資訊,以供分析和監控使用。
高階別執行時有docker,containerd,podman,ckt,cri-o,高階別執行時會呼叫低階別runtime。
k8s本身是不管理容器的,管理容器需要呼叫高階別執行時,k8s呼叫高階別執行時需要使用shim(墊片)介面,呼叫docker使用dockershim,呼叫containerd使用containerdshim,以此類推,kubelet裡內建了dockershim,k8s1.24的時候要去除dockershim程式碼。
在實際應用中,低階別執行時和高階別執行時通常是協作工作的。低階別執行時負責底層的容器管理,而高階別執行時則在此基礎上提供了更復雜的業務邏輯和自動化管理功能。
六.docker容器缺陷
可以檢視docker預設的執行時,現在預設的runtime是runc。
root@k8scludes1:~# docker info | grep Runtime
Runtimes: runc io.containerd.runc.v2 io.containerd.runtime.v1.linux
Default Runtime: runc
現在宿主機上沒有nginx程序。現在提出一個問題:“在宿主機上使用runc執行一個nginx容器,nginx容器執行著nginx程序,宿主機沒執行nginx程序,在宿主機裡能否看到nginx程序嗎?”
root@k8scludes1:~# ps -ef | grep nginx | grep -v grep
現在有一個nginx映象。
root@k8scludes1:~# docker images | grep nginx
nginx latest 605c77e624dd 5 months ago 141MB
使用nginx映象建立一個容器。關於建立容器的詳細操作,請檢視部落格《一文搞懂docker容器基礎:docker映象管理,docker容器管理》。
root@k8scludes1:~# docker run -dit --name=nginxrongqi --restart=always nginx
7844b98cf01cc1b6ba05c575d284146c47cb3fb66e1fa61d6eeac696f0dbc1c3
root@k8scludes1:~# docker ps | grep nginx
7844b98cf01c nginx "/docker-entrypoint.…" 8 seconds ago Up 6 seconds 80/tcp nginxrongqi
檢視宿主機的nginx程序,宿主機可以看到nginx程序。
docker預設的runtime為runc,透過runc建立出來的容器,會共享宿主機的程序空間和核心空間,容器的程序是暴露給宿主機的,如果容器裡存在漏洞,不法分子會使用容器漏洞影響到宿主機的安全。
root@k8scludes1:~# ps -ef | grep nginx
root 45384 45337 0 15:33 pts/0 00:00:00 nginx: master process nginx -g daemon off;
systemd+ 45465 45384 0 15:33 pts/0 00:00:00 nginx: worker process
systemd+ 45466 45384 0 15:33 pts/0 00:00:00 nginx: worker process
systemd+ 45467 45384 0 15:33 pts/0 00:00:00 nginx: worker process
systemd+ 45468 45384 0 15:33 pts/0 00:00:00 nginx: worker process
root 46215 6612 0 15:34 pts/0 00:00:00 grep --color=auto nginx
以沙箱的方式執行容器,在宿主機裡就看不到容器裡執行的程序了,runc預設是不支援以沙箱的方式執行容器的,所以我們需要配置高階別runtime呼叫其他的低階別runtime執行,以實現沙箱的方式執行容器。
七.配置docker使用gVisor作為runtime
7.1 安裝docker
我們在客戶端機器etcd2(centos系統)上安裝docker。
[root@etcd2 ~]# yum -y install docker-ce
設定docker開機自啟動並現在啟動docker。
[root@etcd2 ~]# systemctl enable docker --now
Created symlink from /etc/systemd/system/multi-user.target.wants/docker.service to /usr/lib/systemd/system/docker.service.
[root@etcd2 ~]# systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
Active: active (running) since 二 2022-06-07 11:07:18 CST; 7s ago
Docs: https://docs.docker.com
Main PID: 1231 (dockerd)
Memory: 36.9M
CGroup: /system.slice/docker.service
└─1231 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
檢視docker版本。
[root@etcd2 ~]# docker --version
Docker version 20.10.12, build e91ed57
配置docker映象加速器。
[root@etcd2 ~]# vim /etc/docker/daemon.json
[root@etcd2 ~]# cat /etc/docker/daemon.json
{
"registry-mirrors": ["https://frz7i079.mirror.aliyuncs.com"]
}
重啟docker。
[root@etcd2 ~]# systemctl restart docker
設定iptables不對bridge的資料進行處理,啟用IP路由轉發功能。
[root@etcd2 ~]# vim /etc/sysctl.d/k8s.conf
[root@etcd2 ~]# cat /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
使配置生效。
[root@etcd2 ~]# sysctl -p /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
現在docker預設的runtime為runc。
[root@etcd2 ~]# docker info | grep -i runtime
Runtimes: runc io.containerd.runc.v2 io.containerd.runtime.v1.linux
Default Runtime: runc
下面開始配置docker使用gvisor作為runtime。
7.2 升級系統核心
檢視作業系統版本。
[root@etcd2 ~]# cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)
檢視系統核心。
[root@etcd2 ~]# uname -r
3.10.0-693.el7.x86_64
gVisor supports x86_64 and ARM64, and requires Linux 4.14.77+ ,安裝gVisor需要Linux核心高於4.14.77,而當前核心版本只有3.10.0,需要升級系統核心。升級系統核心分為離線升級系統核心和線上升級系統核心,在部落格《centos7 離線升級/線上升級作業系統核心》中進行了詳細描述。
本文采用離線升級系統核心的方法。
更新yum源倉庫。
[root@etcd2 ~]# yum -y update
啟用 ELRepo 倉庫,ELRepo 倉庫是基於社群的用於企業級 Linux 倉庫,提供對 RedHat Enterprise (RHEL) 和 其他基於 RHEL的 Linux 發行版(CentOS、Scientific、Fedora 等)的支援。ELRepo 聚焦於和硬體相關的軟體包,包括檔案系統驅動、顯示卡驅動、網路驅動、音效卡驅動和攝像頭驅動等。
匯入ELRepo倉庫的公共金鑰。
[root@etcd2 ~]# rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
安裝ELRepo倉庫的yum源。
[root@etcd2 ~]# rpm -Uvh http://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
從elrepo下載系統核心包,如果不匯入ELRepo倉庫的公共金鑰和安裝ELRepo倉庫的yum源,是下載不了核心包的。
[root@etcd2 ~]# wget https://elrepo.org/linux/kernel/el7/x86_64/RPMS/kernel-lt-5.4.160-1.el7.elrepo.x86_64.rpm
清華的這個映象站可以直接下載。
[root@etcd2 ~]# wget https://mirrors.tuna.tsinghua.edu.cn/elrepo/kernel/el7/x86_64/RPMS/kernel-lt-5.4.197-1.el7.elrepo.x86_64.rpm --no-check-certificate
現在核心包就下載好了。
kernel-ml代表主線版本,總是保持主線最新的核心,kernel-lt代表長期支援版本,支援週期更長,如果你要追求最新的版本,直接選擇帶ml的rpm包即可,如果你要追求穩定且更長的支援週期,直接選擇lt版本即可。
[root@etcd2 ~]# ll -h kernel-lt-5.4.197-1.el7.elrepo.x86_64.rpm*
-rw-r--r-- 1 root root 51M 6月 5 19:47 kernel-lt-5.4.197-1.el7.elrepo.x86_64.rpm
安裝核心包。
[root@etcd2 ~]# rpm -ivh kernel-lt-5.4.197-1.el7.elrepo.x86_64.rpm
警告:kernel-lt-5.4.197-1.el7.elrepo.x86_64.rpm: 頭V4 DSA/SHA256 Signature, 金鑰 ID baadae52: NOKEY
準備中... ################################# [100%]
正在升級/安裝...
1:kernel-lt-5.4.197-1.el7.elrepo ################################# [100%]
核心升級完畢後,需要我們修改核心的啟動順序,預設啟動的順序應該為1,升級以後核心是往前面插入為0,設定GRUB_DEFAULT=0。一般新安裝的核心在第一個位置,所以設定default=0,意思是 GRUB 初始化頁面的第一個核心將作為預設核心。
預設的grub檔案,GRUB_DEFAULT=saved。
[root@etcd2 ~]# cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="gfxterm"
GRUB_CMDLINE_LINUX="rhgb quiet nomodeset"
GRUB_DISABLE_RECOVERY="true"
使 GRUB_DEFAULT=0。
[root@etcd2 ~]# vim /etc/default/grub
[root@etcd2 ~]# cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=0
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="gfxterm"
GRUB_CMDLINE_LINUX="rhgb quiet nomodeset"
GRUB_DISABLE_RECOVERY="true"
設定預設啟動核心,grub2-set-default 0和/etc/default/grub檔案裡的GRUB_DEFAULT=0意思一樣。
[root@etcd2 ~]# grub2-set-default 0
檢視所有的核心。
[root@etcd2 ~]# awk -F\' '$1=="menuentry " {print i++ " : " $2}' /boot/grub2/grub.cfg
0 : CentOS Linux 7 Rescue 12667e2174a8483e915fd89a3bc359fc (5.4.197-1.el7.elrepo.x86_64)
1 : CentOS Linux (5.4.197-1.el7.elrepo.x86_64) 7 (Core)
2 : CentOS Linux (3.10.0-693.el7.x86_64) 7 (Core)
3 : CentOS Linux (0-rescue-80c608ceab5342779ba1adc2ac29c213) 7 (Core)
重新生成grub配置檔案。
[root@etcd2 ~]# vim /boot/grub2/grub.cfg
[root@etcd2 ~]# grub2-mkconfig -o /boot/grub2/grub.cfg
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-5.4.197-1.el7.elrepo.x86_64
Found initrd image: /boot/initramfs-5.4.197-1.el7.elrepo.x86_64.img
Found linux image: /boot/vmlinuz-3.10.0-693.el7.x86_64
Found initrd image: /boot/initramfs-3.10.0-693.el7.x86_64.img
Found linux image: /boot/vmlinuz-0-rescue-12667e2174a8483e915fd89a3bc359fc
Found initrd image: /boot/initramfs-0-rescue-12667e2174a8483e915fd89a3bc359fc.img
Found linux image: /boot/vmlinuz-0-rescue-80c608ceab5342779ba1adc2ac29c213
Found initrd image: /boot/initramfs-0-rescue-80c608ceab5342779ba1adc2ac29c213.img
done
重啟並檢視核心版本。
[root@etcd2 ~]# reboot
可以看到核心升級成功。
[root@etcd2 ~]# uname -r
5.4.197-1.el7.elrepo.x86_64
[root@etcd2 ~]# uname -rs
Linux 5.4.197-1.el7.elrepo.x86_64
7.3 安裝gvisor
檢視CPU架構。
[root@etcd2 ~]# uname -m
x86_64
下載runsc,containerd-shim-runsc-v1,以及對應的校驗和:runsc.sha512,containerd-shim-runsc-v1.sha512。
[root@etcd2 ~]# wget https://storage.googleapis.com/gvisor/releases/release/latest/x86_64/runsc https://storage.googleapis.com/gvisor/releases/release/latest/x86_64/runsc.sha512 https://storage.googleapis.com/gvisor/releases/release/latest/x86_64/containerd-shim-runsc-v1 https://storage.googleapis.com/gvisor/releases/release/latest/x86_64/containerd-shim-runsc-v1.sha512
[root@etcd2 ~]# ll -h runsc* containerd-shim*
-rw-r--r-- 1 root root 25M 5月 17 00:22 containerd-shim-runsc-v1
-rw-r--r-- 1 root root 155 5月 17 00:22 containerd-shim-runsc-v1.sha512
-rw-r--r-- 1 root root 38M 5月 17 00:22 runsc
-rw-r--r-- 1 root root 136 5月 17 00:22 runsc.sha512
使用sha512sum校驗檔案是否完整。
[root@etcd2 ~]# sha512sum -c runsc.sha512 -c containerd-shim-runsc-v1.sha512
runsc: 確定
containerd-shim-runsc-v1: 確定
[root@etcd2 ~]# cat *sha512
f24834bbd4d14d0d0827e31276ff74a1e08b7ab366c4a30fe9c30d656c1ec5cbfc2544fb06698b4749791e0c6f80e6d16ec746963ff6ecebc246dc6e5b2f34ba containerd-shim-runsc-v1
e5bc1c46d021246a69174aae71be93ff49661ff08eb6a957f7855f36076b44193765c966608d11a99f14542612438634329536d88fccb4b12bdd9bf2af20557f runsc
授予可執行許可權。
[root@etcd2 ~]# chmod a+rx runsc containerd-shim-runsc-v1
把檔案移動到/usr/local/bin目錄下。
[root@etcd2 ~]# mv runsc containerd-shim-runsc-v1 /usr/local/bin
安裝gvisor。
[root@etcd2 ~]# /usr/local/bin/runsc install
2022/06/07 13:04:16 Added runtime "runsc" with arguments [] to "/etc/docker/daemon.json".
安裝gvisor之後,/etc/docker/daemon.json檔案會新增runtimes:runsc: "path": "/usr/local/bin/runsc"。
注意:/etc/docker/daemon.json檔案裡的"runtimes":"runsc",runsc可以更改為其他名字,比如:"runtimes":"gvisor"。
[root@etcd2 ~]# cat /etc/docker/daemon.json
{
"registry-mirrors": [
"https://frz7i079.mirror.aliyuncs.com"
],
"runtimes": {
"runsc": {
"path": "/usr/local/bin/runsc"
}
}
}
重新載入配置檔案並重啟docker。
[root@etcd2 ~]# systemctl daemon-reload ;systemctl restart docker
檢視runtime,可以發現Runtimes裡現在已經有runsc了,說明現在docker是支援gvisor這個runtime的。
[root@etcd2 ~]# docker info | grep -i runtime
Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc runsc
Default Runtime: runc
檢視runsc版本。
[root@etcd2 ~]# runsc --version
runsc version release-20220510.0
spec: 1.0.2-dev
7.4 配置docker預設的runtime為gVisor
檢視docker狀態。
[root@etcd2 ~]# systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; vendor preset: disabled)
Active: active (running) since 二 2022-06-07 17:02:18 CST; 12min ago
Docs: https://docs.docker.com
Main PID: 1109 (dockerd)
Memory: 130.7M
CGroup: /system.slice/docker.service
└─1109 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
docker啟動引數如下:
[root@etcd2 ~]# cat /usr/lib/systemd/system/docker.service | grep ExecStart
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
檢視docker幫助,--default-runtime可以指定docker的Default Runtime。
[root@etcd2 ~]# dockerd --help | grep default-runtime
--default-runtime string Default OCI runtime for containers (default "runc")
現在需要修改docker的啟動引數ExecStart,指定docker預設使用runsc作為runtime。
[root@etcd2 ~]# vim /usr/lib/systemd/system/docker.service
#--default-runtime runsc指定docker的Default Runtime為gvisor
[root@etcd2 ~]# cat /usr/lib/systemd/system/docker.service | grep ExecStart
ExecStart=/usr/bin/dockerd --default-runtime runsc -H fd:// --containerd=/run/containerd/containerd.sock
重新載入配置檔案並重啟docker。
[root@etcd2 ~]# systemctl daemon-reload ; systemctl restart docker
現在docker的Default Runtime就為gvisor了。
[root@etcd2 ~]# docker info | grep -i runtime
Runtimes: io.containerd.runc.v2 io.containerd.runtime.v1.linux runc runsc
Default Runtime: runsc
7.5 docker使用gVisor作為runtime建立容器
拉取nginx映象。
[root@etcd2 ~]# docker pull hub.c.163.com/library/nginx:latest
latest: Pulling from library/nginx
5de4b4d551f8: Pull complete
d4b36a5e9443: Pull complete
0af1f0713557: Pull complete
Digest: sha256:f84932f738583e0169f94af9b2d5201be2dbacc1578de73b09a6dfaaa07801d6
Status: Downloaded newer image for hub.c.163.com/library/nginx:latest
hub.c.163.com/library/nginx:latest
[root@etcd2 ~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hub.c.163.com/library/nginx latest 46102226f2fd 5 years ago 109MB
使用nginx映象建立一個容器,預設是使用gVisor(runsc)建立的容器。
如果已經安裝了gVisor,但是docker的Default Runtime為runc,則可以使用--runtime=runsc指定gvisor作為runtime建立容器,即:docker run -dit --runtime=runsc --name=nginxweb --restart=always hub.c.163.com/library/nginx:latest
。
[root@etcd2 ~]# docker run -dit --name=nginxweb --restart=always hub.c.163.com/library/nginx:latest
9a7b9091d0d07052ae972b480687e7a345ae22e0e4968e91133b1ad6ac1d5b3a
檢視容器。
[root@etcd2 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9a7b9091d0d0 hub.c.163.com/library/nginx:latest "nginx -g 'daemon of…" 3 minutes ago Up 3 minutes 80/tcp nginxweb
bc99f286802f quay.io/calico/node:v2.6.12 "start_runit" 3 months ago Up 19 seconds calico-node
gvisor以沙箱的方式執行容器,在宿主機裡就看不到容器裡執行的程序了。
[root@etcd2 ~]# ps -ef | grep nginx
root 9031 2916 0 17:54 pts/1 00:00:00 grep --color=auto nginx
刪除容器。
[root@etcd2 ~]# docker rm -f nginxweb
nginxweb
[root@etcd2 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bc99f286802f quay.io/calico/node:v2.6.12 "start_runit" 3 months ago Up 7 seconds calico-node
八.配置containerd使用gvisor作為runtime
8.1 安裝containerd
如果你熟悉docker,但是不瞭解containerd,請檢視部落格《在centos下使用containerd管理容器:5分鐘從docker轉型到containerd》,裡面有詳細講解。
我們在客戶端機器ubuntuk8sclient(ubuntu系統)上安裝containerd。
更新軟體源。
root@ubuntuk8sclient:~# apt-get update
安裝containerd。
root@ubuntuk8sclient:~# apt-get -y install containerd.io cri-tools
設定containerd開機自啟動並現在啟動containerd。
root@ubuntuk8sclient:~# systemctl enable containerd --now
檢視containerd狀態。
root@ubuntuk8sclient:~# systemctl is-active containerd
active
root@ubuntuk8sclient:~# systemctl status containerd
● containerd.service - containerd container runtime
Loaded: loaded (/lib/systemd/system/containerd.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2022-06-04 15:54:08 CST; 58min ago
Docs: https://containerd.io
Main PID: 722 (containerd)
Tasks: 8
CGroup: /system.slice/containerd.service
└─722 /usr/bin/containerd
containerd的配置檔案為/etc/containerd/config.toml 。
root@ubuntuk8sclient:~# ll -h /etc/containerd/config.toml
-rw-r--r-- 1 root root 886 May 4 17:04 /etc/containerd/config.toml
containerd的預設配置檔案/etc/containerd/config.toml 內容如下:
root@ubuntuk8sclient:~# cat /etc/containerd/config.toml
# Copyright 2018-2022 Docker Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
disabled_plugins = ["cri"]
#root = "/var/lib/containerd"
#state = "/run/containerd"
#subreaper = true
#oom_score = 0
#[grpc]
# address = "/run/containerd/containerd.sock"
# uid = 0
# gid = 0
#[debug]
# address = "/run/containerd/debug.sock"
# uid = 0
# gid = 0
# level = "info"
可以使用containerd config default > /etc/containerd/config.toml生成預設的配置檔案,containerd config default生成的配置檔案內容還是挺多的。
root@ubuntuk8sclient:~# containerd config default > /etc/containerd/config.toml
root@ubuntuk8sclient:~# vim /etc/containerd/config.toml
containerd config dump顯示當前的配置。
root@ubuntuk8sclient:~# containerd config dump
disabled_plugins = []
imports = ["/etc/containerd/config.toml"]
oom_score = 0
plugin_dir = ""
required_plugins = []
root = "/var/lib/containerd"
......
......
address = ""
gid = 0
uid = 0
檢視containerd版本。
root@ubuntuk8sclient:~# containerd --version
containerd containerd.io 1.6.4 212e8b6fa2f44b9c21b2798135fc6fb7c53efc16
root@ubuntuk8sclient:~# containerd -v
containerd containerd.io 1.6.4 212e8b6fa2f44b9c21b2798135fc6fb7c53efc16
修改配置檔案,新增阿里雲映象加速器。
root@ubuntuk8sclient:~# vim /etc/containerd/config.toml
root@ubuntuk8sclient:~# grep endpoint /etc/containerd/config.toml
endpoint = "https://frz7i079.mirror.aliyuncs.com"
SystemdCgroup = false修改為SystemdCgroup = true。
root@ubuntuk8sclient:~# vim /etc/containerd/config.toml
root@ubuntuk8sclient:~# grep SystemdCgroup -B 11 /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
BinaryName = ""
CriuImagePath = ""
CriuPath = ""
CriuWorkPath = ""
IoGid = 0
IoUid = 0
NoNewKeyring = false
NoPivotRoot = false
Root = ""
ShimCgroup = ""
SystemdCgroup = true
有個sandbox的映象,k8s.gcr.io/pause:3.6訪問不了。
root@ubuntuk8sclient:~# grep sandbox_image /etc/containerd/config.toml
sandbox_image = "k8s.gcr.io/pause:3.6"
修改sandbox映象為可以訪問的阿里雲映象。
root@ubuntuk8sclient:~# vim /etc/containerd/config.toml
root@ubuntuk8sclient:~# grep sandbox_image /etc/containerd/config.toml
sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.6"
重新載入配置檔案並重啟containerd服務。
root@ubuntuk8sclient:~# systemctl daemon-reload ; systemctl restart containerd
containerd 客戶端工具有 ctr 和 crictl ,如果使用 crictl 命令的話,需要執行 crictl config runtime-endpoint unix:///var/run/containerd/containerd.sock ,不然會有告警。
root@ubuntuk8sclient:~# crictl config runtime-endpoint unix:///var/run/containerd/containerd.sock
檢視containerd資訊。
root@ubuntuk8sclient:~# crictl info
{
"status": {
"conditions": [
{
"type": "RuntimeReady",
"status": true,
"reason": "",
"message": ""
},
......
"enableUnprivilegedPorts": false,
"enableUnprivilegedICMP": false,
"containerdRootDir": "/var/lib/containerd",
"containerdEndpoint": "/run/containerd/containerd.sock",
"rootDir": "/var/lib/containerd/io.containerd.grpc.v1.cri",
"stateDir": "/run/containerd/io.containerd.grpc.v1.cri"
},
"golang": "go1.17.9",
"lastCNILoadStatus": "cni config load failed: no network config found in /etc/cni/net.d: cni plugin not initialized: failed to load cni config",
"lastCNILoadStatus.default": "cni config load failed: no network config found in /etc/cni/net.d: cni plugin not initialized: failed to load cni config"
}
containerd裡有名稱空間的概念,docker裡沒有名稱空間,對於containerd,在default名稱空間裡拉取的映象和建立的容器,在其他名稱空間是看不到的,如果這個containerd節點加入到k8s環境中,則k8s預設使用k8s.io這個名稱空間。
檢視名稱空間。
root@ubuntuk8sclient:~# ctr ns list
NAME LABELS
moby
plugins.moby
檢視映象。
root@ubuntuk8sclient:~# ctr i list
REF TYPE DIGEST SIZE PLATFORMS LABELS
root@ubuntuk8sclient:~# crictl images
IMAGE TAG IMAGE ID SIZE
使用crictl拉取映象。
root@ubuntuk8sclient:~# crictl pull nginx
Image is up to date for sha256:0e901e68141fd02f237cf63eb842529f8a9500636a9419e3cf4fb986b8fe3d5d
root@ubuntuk8sclient:~# crictl images
IMAGE TAG IMAGE ID SIZE
docker.io/library/nginx latest 0e901e68141fd 56.7MB
ctr和crictl更多命令細節,請檢視部落格《在centos下使用containerd管理容器:5分鐘從docker轉型到containerd》。
containerd 客戶端工具 ctr 和 crictl 不好用,推薦使用nerdctl,nerdctl是containerd的cli客戶端工具,與docker cli大部分相容,用法類似docker命令。
使用nerdctl命令需要兩個安裝包nerdctl-0.20.0-linux-amd64.tar.gz和cni-plugins-linux-amd64-v1.1.1.tgz。
nerdctl-0.20.0-linux-amd64.tar.gz下載地址:https://github.com/containerd/nerdctl/releases 。
網路外掛cni-plugins-linux-amd64-v1.1.1.tgz下載地址:https://github.com/containernetworking/plugins/releases 。
root@ubuntuk8sclient:~# ll -h cni-plugins-linux-amd64-v1.1.1.tgz nerdctl-0.20.0-linux-amd64.tar.gz
-rw-r--r-- 1 root root 35M Jun 5 12:19 cni-plugins-linux-amd64-v1.1.1.tgz
-rw-r--r-- 1 root root 9.8M Jun 5 12:15 nerdctl-0.20.0-linux-amd64.tar.gz
分別進行解壓。
root@ubuntuk8sclient:~# tar xf nerdctl-0.20.0-linux-amd64.tar.gz -C /usr/local/bin/
root@ubuntuk8sclient:~# ls /usr/local/bin/
containerd-rootless-setuptool.sh containerd-rootless.sh nerdctl
root@ubuntuk8sclient:~# mkdir -p /opt/cni/bin
root@ubuntuk8sclient:~# tar xf cni-plugins-linux-amd64-v1.1.1.tgz -C /opt/cni/bin/
root@ubuntuk8sclient:~# ls /opt/cni/bin/
bandwidth bridge dhcp firewall host-device host-local ipvlan loopback macvlan portmap ptp sbr static tuning vlan vrf
配置nerdctl命令tab自動補全,新增source <(nerdctl completion bash)。
root@ubuntuk8sclient:~# vim /etc/profile
root@ubuntuk8sclient:~# cat /etc/profile | head -3
# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).
source <(nerdctl completion bash)
root@ubuntuk8sclient:~# nerdctl completion bash
使配置檔案/etc/profile生效。
root@ubuntuk8sclient:~# source /etc/profile
檢視映象。
root@ubuntuk8sclient:~# nerdctl images
REPOSITORY TAG IMAGE ID CREATED PLATFORM SIZE BLOB SIZE
檢視名稱空間。
root@ubuntuk8sclient:~# nerdctl ns list
NAME CONTAINERS IMAGES VOLUMES LABELS
default 0 0 0
k8s.io 0 4 0
moby 0 0 0
plugins.moby 0 0 0
nerdctl的命令和docker命令很相似,只要把docker命令裡的docker換成nerdctl,基本都能執行成功。
拉取映象。
root@ubuntuk8sclient:~# nerdctl pull hub.c.163.com/library/nginx:latest
root@ubuntuk8sclient:~# nerdctl images
REPOSITORY TAG IMAGE ID CREATED PLATFORM SIZE BLOB SIZE
hub.c.163.com/library/nginx latest 8eeb06742b41 22 seconds ago linux/amd64 115.5 MiB 41.2 MiB
檢視containerd資訊。
root@ubuntuk8sclient:~# nerdctl info
8.2 安裝gVisor
Note: gVisor supports x86_64 and ARM64, and requires Linux 4.14.77+,gvisor要求核心版本大於4.14.77,此機器版本為4.15.0-112-generic,因此不用升級核心。如果需要升級核心,請參考部落格《centos7 離線升級/線上升級作業系統核心》。
root@ubuntuk8sclient:~# uname -r
4.15.0-112-generic
下載gvisor對應的可執行檔案。
root@ubuntuk8sclient:~# wget https://storage.googleapis.com/gvisor/releases/release/latest/x86_64/runsc https://storage.googleapis.com/gvisor/releases/release/latest/x86_64/runsc.sha512 https://storage.googleapis.com/gvisor/releases/release/latest/x86_64/containerd-shim-runsc-v1 https://storage.googleapis.com/gvisor/releases/release/latest/x86_64/containerd-shim-runsc-v1.sha512
root@ubuntuk8sclient:~# ll -h runsc* containerd-shim*
-rwxr-xr-x 1 root root 25M Jun 7 18:24 containerd-shim-runsc-v1*
-rw-r--r-- 1 root root 155 Jun 7 18:24 containerd-shim-runsc-v1.sha512
-rwxr-xr-x 1 root root 38M Jun 7 18:24 runsc*
-rw-r--r-- 1 root root 136 Jun 7 18:24 runsc.sha512
進行檔案校驗。
root@ubuntuk8sclient:~# sha512sum -c runsc.sha512 -c containerd-shim-runsc-v1.sha512
runsc: OK
containerd-shim-runsc-v1: OK
root@ubuntuk8sclient:~# cat *sha512
f24834bbd4d14d0d0827e31276ff74a1e08b7ab366c4a30fe9c30d656c1ec5cbfc2544fb06698b4749791e0c6f80e6d16ec746963ff6ecebc246dc6e5b2f34ba containerd-shim-runsc-v1
e5bc1c46d021246a69174aae71be93ff49661ff08eb6a957f7855f36076b44193765c966608d11a99f14542612438634329536d88fccb4b12bdd9bf2af20557f runsc
授予可執行許可權並移動到/usr/local/bin目錄。
root@ubuntuk8sclient:~# chmod a+rx runsc containerd-shim-runsc-v1
root@ubuntuk8sclient:~# mv runsc containerd-shim-runsc-v1 /usr/local/bin
可以發現現在containerd只支援runc一種runtime。
root@ubuntuk8sclient:~# crictl info | grep -A10 runtimes
"runtimes": {
"runc": {
"runtimeType": "io.containerd.runc.v2",
"runtimePath": "",
"runtimeEngine": "",
"PodAnnotations": [],
"ContainerAnnotations": [],
"runtimeRoot": "",
"options": {
"BinaryName": "",
"CriuImagePath": "",
8.3 配置containerd支援gVisor
需要先修改配置檔案,使containerd支援多種runtime。
原本的內容是plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc,新新增plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc使containerd支援gvisor,runtime_type = "containerd-shim-runsc-v1"就是我們下載的containerd-shim-runsc-v1檔案。
runtime_type = "containerd-shim-runsc-v1"這種寫法後面驗證了一下,在containerd裡建立容器沒問題,但是到k8s裡就有問題,正確的寫法應該是:runtime_type = "io.containerd.runsc.v1"。
root@ubuntuk8sclient:~# vim /etc/containerd/config.toml
root@ubuntuk8sclient:~# cat /etc/containerd/config.toml | grep -A27 "containerd.runtimes.runc"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
BinaryName = ""
CriuImagePath = ""
CriuPath = ""
CriuWorkPath = ""
IoGid = 0
IoUid = 0
NoNewKeyring = false
NoPivotRoot = false
Root = ""
ShimCgroup = ""
SystemdCgroup = true
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = "io.containerd.runsc.v1"
重新載入配置檔案並重啟containerd。
root@ubuntuk8sclient:~# systemctl daemon-reload ;systemctl restart containerd
現在就可以看到containerd支援兩種runtime了:runc和runsc。
root@ubuntuk8sclient:~# crictl info | grep -A36 runtimes
"runtimes": {
"runc": {
"runtimeType": "io.containerd.runc.v2",
"runtimePath": "",
"runtimeEngine": "",
"PodAnnotations": [],
"ContainerAnnotations": [],
"runtimeRoot": "",
"options": {
"BinaryName": "",
"CriuImagePath": "",
"CriuPath": "",
"CriuWorkPath": "",
"IoGid": 0,
"IoUid": 0,
"NoNewKeyring": false,
"NoPivotRoot": false,
"Root": "",
"ShimCgroup": "",
"SystemdCgroup": true
},
"privileged_without_host_devices": false,
"baseRuntimeSpec": "",
"cniConfDir": "",
"cniMaxConfNum": 0
},
"runsc": {
"runtimeType": "containerd-shim-runsc-v1",
"runtimePath": "",
"runtimeEngine": "",
"PodAnnotations": [],
"ContainerAnnotations": [],
"runtimeRoot": "",
"options": null,
"privileged_without_host_devices": false,
"baseRuntimeSpec": "",
"cniConfDir": "",
檢視容器。
root@ubuntuk8sclient:~# nerdctl ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
檢視映象。
root@ubuntuk8sclient:~# nerdctl images
REPOSITORY TAG IMAGE ID CREATED PLATFORM SIZE BLOB SIZE
hub.c.163.com/library/nginx latest 8eeb06742b41 2 days ago linux/amd64 115.5 MiB 41.2 MiB
sha256 e5bc191dff1f971254305a0dbc58c4145c783e34090bbd4360a36d7447fe3ef2 8eeb06742b41 2 days ago linux/amd64 115.5 MiB 41.2 MiB
使用nginx映象建立容器,預設使用runc作為runtime。
root@ubuntuk8sclient:~# nerdctl run -d --name=nginxweb --restart=always hub.c.163.com/library/nginx:latest
bdef5e3fa6e6fb7c08f4df19810a42c81b7bc1bf7a16b3beaca53508ac4cedab
檢視容器。
root@ubuntuk8sclient:~# nerdctl ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bdef5e3fa6e6 hub.c.163.com/library/nginx:latest "nginx -g daemon off;" 4 seconds ago Up nginxweb
containerd預設使用runc作為runtime建立的容器,會共享宿主機的程序空間和核心空間,容器的程序是暴露給宿主機的,如果容器裡存在漏洞,不法分子會使用容器漏洞影響到宿主機的安全。
root@ubuntuk8sclient:~# ps -ef | grep nginx
root 6540 6505 0 21:36 ? 00:00:00 nginx: master process nginx -g daemon off;
systemd+ 6625 6540 0 21:36 ? 00:00:00 nginx: worker process
root 6634 6251 0 21:36 pts/1 00:00:00 grep --color=auto nginx
刪除容器。
root@ubuntuk8sclient:~# nerdctl rm -f nginxweb
nginxweb
刪除容器之後,宿主機就看不到nginx程序了。
root@ubuntuk8sclient:~# nerdctl ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
root@ubuntuk8sclient:~# ps -ef | grep nginx
root 6726 6251 0 21:38 pts/1 00:00:00 grep --color=auto nginx
8.4 containerd使用gvisor作為runtime建立容器
建立容器,--runtime=runsc指定containerd使用gvisor作為runtime建立容器。
root@ubuntuk8sclient:~# nerdctl run -d --runtime=runsc --name=nginxweb --restart=always hub.c.163.com/library/nginx:latest
8ea86e8936374efbb626d11f79a9cb79fb32d9a44fafd71c02556a5ae842cac7
containerd使用gvisor作為runtime,以沙箱的方式執行容器,在宿主機裡就看不到容器裡執行的程序了。
root@ubuntuk8sclient:~# nerdctl ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8ea86e893637 hub.c.163.com/library/nginx:latest "nginx -g daemon off;" 51 seconds ago Up nginxweb
root@ubuntuk8sclient:~# ps -ef | grep nginx
root 7153 6251 0 21:41 pts/1 00:00:00 grep --color=auto nginx
刪除不了正在執行的容器。
root@ubuntuk8sclient:~# nerdctl rm -f nginxweb
WARN[0000] failed to delete task 8ea86e8936374efbb626d11f79a9cb79fb32d9a44fafd71c02556a5ae842cac7 error="unknown error after kill: runsc did not terminate successfully: exit status 128: sandbox is not running\n: unknown"
WARN[0000] failed to remove container "8ea86e8936374efbb626d11f79a9cb79fb32d9a44fafd71c02556a5ae842cac7" error="cannot delete running task 8ea86e8936374efbb626d11f79a9cb79fb32d9a44fafd71c02556a5ae842cac7: failed precondition"
WARN[0000] failed to remove container "8ea86e8936374efbb626d11f79a9cb79fb32d9a44fafd71c02556a5ae842cac7" error="cannot delete running task 8ea86e8936374efbb626d11f79a9cb79fb32d9a44fafd71c02556a5ae842cac7: failed precondition"
WARN[0000] failed to release name store for container "8ea86e8936374efbb626d11f79a9cb79fb32d9a44fafd71c02556a5ae842cac7" error="cannot delete running task 8ea86e8936374efbb626d11f79a9cb79fb32d9a44fafd71c02556a5ae842cac7: failed precondition"
FATA[0000] cannot delete running task 8ea86e8936374efbb626d11f79a9cb79fb32d9a44fafd71c02556a5ae842cac7: failed precondition
先停止容器,再刪除容器。
root@ubuntuk8sclient:~# nerdctl stop nginxweb
nginxweb
root@ubuntuk8sclient:~# nerdctl rm nginxweb
root@ubuntuk8sclient:~# nerdctl ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
九.在k8s環境裡,配置containerd作為高階別runtime,containerd使用gvisor作為低階別runtime
9.1 把ubuntuk8sclient節點加入k8s叢集
注意docker作為k8s的高階別runtime的時候,不支援gvisor作為docker的低階別runtime,只有單機版的時候,gvisor才能作為docker的低階別runtime。
描述一下當前的系統環境:現在有一個k8s叢集,1個master,2個worker,三臺機器都是使用docker作為高階別runtime,現在新增一個新的worker節點,新的worker節點使用containerd作為高階別runtime,gvisor作為containerd的低階別runtime。
現在把ubuntuk8sclient機器加入k8s叢集,ubuntuk8sclient的CONTAINER-RUNTIME為containerd。
檢視叢集節點。
root@k8scludes1:~# kubectl get nodes -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8scludes1 Ready control-plane,master 55d v1.22.2 192.168.110.128 <none> Ubuntu 18.04.5 LTS 4.15.0-112-generic docker://20.10.14
k8scludes2 Ready <none> 55d v1.22.2 192.168.110.129 <none> Ubuntu 18.04.5 LTS 4.15.0-112-generic docker://20.10.14
k8scludes3 Ready <none> 55d v1.22.2 192.168.110.130 <none> Ubuntu 18.04.5 LTS 4.15.0-112-generic docker://20.10.14
先在所有的機器配置IP主機名對映(以ubuntuk8sclient為例)。
root@ubuntuk8sclient:~# vim /etc/hosts
root@ubuntuk8sclient:~# cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 tom
192.168.110.139 ubuntuk8sclient
192.168.110.128 k8scludes1
192.168.110.129 k8scludes2
192.168.110.130 k8scludes3
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
配置軟體源,軟體源如下,最後三行是k8s源。
root@ubuntuk8sclient:~# cat /etc/apt/sources.list
deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
deb https://mirrors.aliyun.com/kubernetes/apt/ kubernetes-xenial main
deb [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu bionic stable
# deb-src [arch=amd64] https://mirrors.aliyun.com/docker-ce/linux/ubuntu bionic stable
apt-key.gpg是k8s的deb源公鑰,載入k8s的deb源公鑰 apt-key add apt-key.gpg。
下載並載入k8s的deb源公鑰:curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - ; apt-get update。但是谷歌的網址訪問不了,我們直接去網上下載apt-key.gpg檔案,載入k8s的deb源公鑰。
root@ubuntuk8sclient:~# cat apt-key.gpg | apt-key add -
OK
更新軟體源。
root@ubuntuk8sclient:~# apt-get update
Linux swapoff命令用於關閉系統交換分割槽(swap area)。如果不關閉swap,就會在kubeadm初始化Kubernetes的時候報錯:“[ERROR Swap]: running with swap on is not supported. Please disable swap”。
root@ubuntuk8sclient:~# swapoff -a ;sed -i '/swap/d' /etc/fstab
root@ubuntuk8sclient:~# cat /etc/fstab
# /etc/fstab: static file system information.
#
# Use 'blkid' to print the universally unique identifier for a
# device; this may be used with UUID= as a more robust way to name devices
# that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point> <type> <options> <dump> <pass>
/dev/mapper/tom--vg-root / ext4 errors=remount-ro 0 1
檢視containerd版本。
root@ubuntuk8sclient:~# containerd -v
containerd containerd.io 1.6.4 212e8b6fa2f44b9c21b2798135fc6fb7c53efc16
registry.aliyuncs.com/google_containers/pause:3.6這個映象需要提前拉取好。
root@ubuntuk8sclient:~# cat /etc/containerd/config.toml | grep pause
pause_threshold = 0.02
sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.6"
拉取映象。
root@ubuntuk8sclient:~# nerdctl pull registry.aliyuncs.com/google_containers/pause:3.6
檢視映象。
root@ubuntuk8sclient:~# nerdctl images | grep pause
registry.aliyuncs.com/google_containers/pause 3.6 3d380ca88645 3 days ago linux/amd64 672.0 KiB 294.7 KiB
root@ubuntuk8sclient:~# crictl images | grep pause
registry.aliyuncs.com/google_containers/pause 3.6 6270bb605e12e 302kB
設定containerd當前名稱空間為k8s.io。
root@ubuntuk8sclient:~# cat /etc/nerdctl/nerdctl.toml | head -3
namespace = "k8s.io"
載入overlay和br_netfilter模組。
root@ubuntuk8sclient:~# cat > /etc/modules-load.d/containerd.conf <<EOF
> overlay
> br_netfilter
> EOF
root@ubuntuk8sclient:~# cat /etc/modules-load.d/containerd.conf
overlay
br_netfilter
root@ubuntuk8sclient:~# modprobe overlay
root@ubuntuk8sclient:~# modprobe br_netfilter
設定iptables不對bridge的資料進行處理,啟用IP路由轉發功能。
root@ubuntuk8sclient:~# cat <<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
> EOF
使配置生效。
root@ubuntuk8sclient:~# sysctl -p /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
為了k8s節點間的通訊,需要安裝cni網路外掛,提前下載好calico映象,calico映象版本要和k8s的那三個節點的calico版本一致。
root@ubuntuk8sclient:~# nerdctl pull docker.io/calico/cni:v3.22.2
root@ubuntuk8sclient:~# nerdctl pull docker.io/calico/pod2daemon-flexvol:v3.22.2
root@ubuntuk8sclient:~# nerdctl pull docker.io/calico/node:v3.22.2
root@ubuntuk8sclient:~# nerdctl pull docker.io/calico/kube-controllers:v3.22.2
root@ubuntuk8sclient:~# nerdctl images | grep calico
calico/cni v3.22.2 757d06fe361c 4 minutes ago linux/amd64 227.1 MiB 76.8 MiB
calico/kube-controllers v3.22.2 751f1a8ba0af 20 seconds ago linux/amd64 128.1 MiB 52.4 MiB
calico/node v3.22.2 41aac6d0a440 2 minutes ago linux/amd64 194.2 MiB 66.5 MiB
calico/pod2daemon-flexvol v3.22.2 413c5ebad6a5 3 minutes ago linux/amd64 19.0 MiB 8.0 MiB
安裝kubelet,kubeadm,kubectl。
- Kubelet 是 kubernetes 工作節點上的一個代理元件,執行在每個節點上;
- Kubeadm 是一個快捷搭建kubernetes(k8s)的安裝工具,它提供了 kubeadm init 以及 kubeadm join 這兩個命令來快速建立 kubernetes 叢集;kubeadm 透過執行必要的操作來啟動和執行一個最小可用的叢集;
- kubectl是Kubernetes叢集的命令列工具,透過kubectl能夠對叢集本身進行管理,並能夠在叢集上進行容器化應用的安裝部署。
root@ubuntuk8sclient:~# apt-get -y install kubelet=1.22.2-00 kubeadm=1.22.2-00 kubectl=1.22.2-00
設定kubelet開機自啟動並現在啟動。
root@ubuntuk8sclient:~# systemctl enable kubelet --now
在k8s的master節點,檢視k8s worker節點加入k8s叢集的token。
root@k8scludes1:~# kubeadm token create --print-join-command
kubeadm join 192.168.110.128:6443 --token rwau00.plx8xdksa8zdnfrn --discovery-token-ca-cert-hash sha256:3f401b6187ed44ff8f4b50aa6453cf3eacc3b86d6a72e3bf2caba02556cb918e
把ubuntuk8sclient節點加入k8s叢集。
root@ubuntuk8sclient:~# kubeadm join 192.168.110.128:6443 --token rwau00.plx8xdksa8zdnfrn --discovery-token-ca-cert-hash sha256:3f401b6187ed44ff8f4b50aa6453cf3eacc3b86d6a72e3bf2caba02556cb918e
[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[preflight] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml'
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Writing kubelet environment file with flags to file "/var/lib/kubelet/kubeadm-flags.env"
[kubelet-start] Starting the kubelet
[kubelet-start] Waiting for the kubelet to perform the TLS Bootstrap...
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.
Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
去k8s master節點檢視是否加入k8s叢集,可以看到ubuntuk8sclient成功加入k8s叢集,並且CONTAINER-RUNTIME為containerd://1.6.4。
root@k8scludes1:~# kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8scludes1 Ready control-plane,master 55d v1.22.2 192.168.110.128 <none> Ubuntu 18.04.5 LTS 4.15.0-112-generic docker://20.10.14
k8scludes2 Ready <none> 55d v1.22.2 192.168.110.129 <none> Ubuntu 18.04.5 LTS 4.15.0-112-generic docker://20.10.14
k8scludes3 Ready <none> 55d v1.22.2 192.168.110.130 <none> Ubuntu 18.04.5 LTS 4.15.0-112-generic docker://20.10.14
ubuntuk8sclient Ready <none> 87s v1.22.2 192.168.110.139 <none> Ubuntu 18.04.5 LTS 4.15.0-112-generic containerd://1.6.4
現在需要配置containerd支援多個runtime,使其支援gvisor。
原本的內容是plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc,新新增plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc使containerd支援gvisor,runtime_type = "containerd-shim-runsc-v1"就是我們下載的containerd-shim-runsc-v1檔案。
root@ubuntuk8sclient:~# cat /etc/containerd/config.toml | grep -A27 "containerd.runtimes.runc"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
BinaryName = ""
CriuImagePath = ""
CriuPath = ""
CriuWorkPath = ""
IoGid = 0
IoUid = 0
NoNewKeyring = false
NoPivotRoot = false
Root = ""
ShimCgroup = ""
SystemdCgroup = true
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = "containerd-shim-runsc-v1"
重新載入配置檔案並重啟containerd。
root@ubuntuk8sclient:~# systemctl daemon-reload ;systemctl restart containerd
現在就可以看到containerd支援兩種runtime了:runc和runsc。
root@ubuntuk8sclient:~# crictl info | grep -A36 runtimes
"runtimes": {
"runc": {
"runtimeType": "io.containerd.runc.v2",
"runtimePath": "",
"runtimeEngine": "",
"PodAnnotations": [],
"ContainerAnnotations": [],
"runtimeRoot": "",
"options": {
"BinaryName": "",
"CriuImagePath": "",
"CriuPath": "",
"CriuWorkPath": "",
"IoGid": 0,
"IoUid": 0,
"NoNewKeyring": false,
"NoPivotRoot": false,
"Root": "",
"ShimCgroup": "",
"SystemdCgroup": true
},
"privileged_without_host_devices": false,
"baseRuntimeSpec": "",
"cniConfDir": "",
"cniMaxConfNum": 0
},
"runsc": {
"runtimeType": "containerd-shim-runsc-v1",
"runtimePath": "",
"runtimeEngine": "",
"PodAnnotations": [],
"ContainerAnnotations": [],
"runtimeRoot": "",
"options": null,
"privileged_without_host_devices": false,
"baseRuntimeSpec": "",
"cniConfDir": "",
9.2 配置kubelet使其支援gVisor
配置kubelet,使其可以支援gvisor作為containerd的低階別runtime,修改kubelet引數,讓其支援runsc作為runtime。
root@ubuntuk8sclient:~# 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
root@ubuntuk8sclient:~# cat /etc/systemd/system/kubelet.service.d/0-cri-containerd.conf
[Service]
Environment="KUBELET_EXTRA_ARGS=--container-runtime=remote --runtime-request-timeout=15m --container-runtime-endpoint=unix:///run/containerd/containerd.sock"
重新載入配置檔案並重啟kubelet。
root@ubuntuk8sclient:~# systemctl daemon-reload ; systemctl restart kubelet
root@ubuntuk8sclient:~# systemctl status kubelet
● kubelet.service - kubelet: The Kubernetes Node Agent
Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
Drop-In: /etc/systemd/system/kubelet.service.d
└─0-cri-containerd.conf, 10-kubeadm.conf
Active: active (running) since Sat 2022-06-11 18:00:31 CST; 14s ago
Docs: https://kubernetes.io/docs/home/
Main PID: 31685 (kubelet)
Tasks: 13 (limit: 1404)
CGroup: /system.slice/kubelet.service
└─31685 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime=remote --con
一切就緒,現在就建立pod。
給ubuntuk8sclient節點定義一個標籤:con=gvisor。
root@k8scludes1:~# kubectl label nodes ubuntuk8sclient con=gvisor
node/ubuntuk8sclient labeled
root@k8scludes1:~# kubectl get node -l con=gvisor
NAME STATUS ROLES AGE VERSION
ubuntuk8sclient Ready <none> 29m v1.22.2
建立目錄存放檔案。
root@k8scludes1:~# mkdir containerd-gvisor
root@k8scludes1:~# cd containerd-gvisor/
編輯pod配置檔案,nodeSelector:con: gvisor 指定pod執行在ubuntuk8sclient節點,使用nginx映象建立pod。
root@k8scludes1:~/containerd-gvisor# vim pod.yaml
root@k8scludes1:~/containerd-gvisor# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: podtest
name: podtest
spec:
#當需要關閉容器時,立即殺死容器而不等待預設的30秒優雅停機時長。
terminationGracePeriodSeconds: 0
#nodeSelector:con: gvisor 指定pod執行在ubuntuk8sclient節點
nodeSelector:
con: gvisor
containers:
- image: hub.c.163.com/library/nginx:latest
#imagePullPolicy: IfNotPresent:表示如果本地已經存在該映象,則不重新下載;否則從遠端 Docker Hub 下載該映象
imagePullPolicy: IfNotPresent
name: podtest
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
建立pod。
root@k8scludes1:~/containerd-gvisor# kubectl apply -f pod.yaml
pod/podtest created
root@k8scludes1:~/containerd-gvisor# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
podtest 1/1 Running 0 16s 10.244.228.1 ubuntuk8sclient <none> <none>
建立pod之後,去ubuntuk8sclient檢視,看看宿主機是否能看到容器裡的nginx程序,宿主機裡看到了pod裡的nginx程序,這說明pod是預設使用runc作為低階別runtime建立pod的。
root@ubuntuk8sclient:~# ps -ef | grep nginx
root 38308 38227 0 18:15 ? 00:00:00 nginx: master process nginx -g daemon off;
systemd+ 38335 38308 0 18:15 ? 00:00:00 nginx: worker process
root 39009 27377 0 18:17 pts/1 00:00:00 grep --color=auto nginx
刪除pod。
root@k8scludes1:~/containerd-gvisor# kubectl delete pod podtest
pod "podtest" deleted
刪除pod之後,宿主機也就沒有nginx程序了。
root@ubuntuk8sclient:~# ps -ef | grep nginx
root 40044 27377 0 18:20 pts/1 00:00:00 grep --color=auto nginx
9.3 建立容器執行時類(Runtime Class)
在k8s裡使用gvisor建立pod,需要使用到容器執行時類(Runtime Class)。
RuntimeClass 是一個用於選擇容器執行時配置的特性,容器執行時配置用於執行 Pod 中的容器。你可以在不同的 Pod 設定不同的 RuntimeClass,以提供效能與安全性之間的平衡。 例如,如果你的部分工作負載需要高階別的資訊保安保證,你可以決定在排程這些 Pod 時,儘量使它們在使用硬體虛擬化的容器執行時中執行。 這樣,你將從這些不同執行時所提供的額外隔離中獲益,代價是一些額外的開銷。
你還可以使用 RuntimeClass 執行具有相同容器執行時,但具有不同設定的 Pod。
注意RuntimeClass是全域性生效的,不受名稱空間限制。
檢視runtimeclass。
root@k8scludes1:~/containerd-gvisor# kubectl get runtimeclass
No resources found
編輯RuntimeClass配置檔案,handler後面寫runtime的名字,我們要使用gvisor就寫runsc。
root@k8scludes1:~/containerd-gvisor# vim myruntimeclass.yaml
#建立runtimeclass,指定使用runsc
root@k8scludes1:~/containerd-gvisor# cat myruntimeclass.yaml
# RuntimeClass 定義於 node.k8s.io API 組
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
# 用來引用 RuntimeClass 的名字
# RuntimeClass 是一個叢集層面的資源
name: myruntimeclass
# 對應的 CRI 配置的名稱
#handler: myconfiguration
#注意:handler後面寫runtime的名字,我們要使用gvisor就寫runsc
handler: runsc
建立runtimeclass。
root@k8scludes1:~/containerd-gvisor# kubectl apply -f myruntimeclass.yaml
runtimeclass.node.k8s.io/myruntimeclass created
root@k8scludes1:~/containerd-gvisor# kubectl get runtimeclass
NAME HANDLER AGE
myruntimeclass runsc 20s
9.4 使用gVisor建立pod
一旦完成叢集中 RuntimeClasses 的配置, 你就可以在 Pod spec 中指定 runtimeClassName 來使用它。
runtimeClassName這一設定會告訴 kubelet 使用所指的 RuntimeClass 來執行該 pod。 如果所指的 RuntimeClass 不存在或者 CRI 無法執行相應的 handler, 那麼 pod 將會進入 Failed 終止 階段。 你可以檢視相應的事件, 獲取執行過程中的錯誤資訊。如果未指定 runtimeClassName ,則將使用預設的 RuntimeHandler,相當於禁用 RuntimeClass 功能特性。
編輯pod配置檔案,runtimeClassName: myruntimeclass指定用myruntimeclass裡的runsc來執行pod。
root@k8scludes1:~/containerd-gvisor# vim pod.yaml
root@k8scludes1:~/containerd-gvisor# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: podtest
name: podtest
spec:
#當需要關閉容器時,立即殺死容器而不等待預設的30秒優雅停機時長。
terminationGracePeriodSeconds: 0
runtimeClassName: myruntimeclass
nodeSelector:
con: gvisor
containers:
- image: hub.c.163.com/library/nginx:latest
#imagePullPolicy: IfNotPresent:表示如果本地已經存在該映象,則不重新下載;否則從遠端 Docker Hub 下載該映象
imagePullPolicy: IfNotPresent
name: podtest
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
建立pod。
root@k8scludes1:~/containerd-gvisor# kubectl apply -f pod.yaml
pod/podtest created
檢視pod,但是建立失敗。
root@k8scludes1:~/containerd-gvisor# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
podtest 0/1 ContainerCreating 0 24s <none> ubuntuk8sclient <none> <none>
檢視pod描述,invalid runtime name containerd-shim-runsc-v1, correct runtime name should be either format like io.containerd.runc.v1
or a full path to the binary: unknown 告訴我們containerd-shim-runsc-v1的格式不對。
root@k8scludes1:~/containerd-gvisor# kubectl describe pod podtest
Name: podtest
Namespace: minsvcbug
Priority: 0
Node: ubuntuk8sclient/192.168.110.139
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: con=gvisor
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 49s default-scheduler Successfully assigned minsvcbug/podtest to ubuntuk8sclient
Warning FailedCreatePodSandBox 22s (x25 over 47s) kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to create containerd task: failed to start shim: failed to resolve runtime path: invalid runtime name containerd-shim-runsc-v1, correct runtime name should be either format like `io.containerd.runc.v1` or a full path to the binary: unknown
刪除pod。
root@k8scludes1:~/containerd-gvisor# kubectl delete pod podtest
pod "podtest" deleted
回到ubuntuk8sclient修改containerd配置檔案,runsc的runtime_type不應該寫為containerd-shim-runsc-v1,而應該是runtime_type = "io.containerd.runsc.v1"。
root@ubuntuk8sclient:~# vim /etc/containerd/config.toml
root@ubuntuk8sclient:~# grep runtime_type /etc/containerd/config.toml
runtime_type = ""
runtime_type = "io.containerd.runc.v2"
runtime_type = "io.containerd.runsc.v1"
runtime_type = ""
重新載入配置檔案並重啟containerd。
root@ubuntuk8sclient:~# systemctl daemon-reload ;systemctl restart containerd
繼續建立pod。
root@k8scludes1:~/containerd-gvisor# kubectl apply -f pod.yaml
pod/podtest created
pod建立成功了。
root@k8scludes1:~/containerd-gvisor# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
podtest 1/1 Running 0 10s 10.244.228.27 ubuntuk8sclient <none> <none>
在宿主機上檢視nginx容器。
root@ubuntuk8sclient:~# nerdctl ps | grep podtest
d4604b2b8b39 registry.aliyuncs.com/google_containers/pause:3.6 "/pause" 46 seconds ago Up k8s://minsvcbug/podtest
dcb76b70a98e hub.c.163.com/library/nginx:latest "nginx -g daemon off;" 45 seconds ago Up k8s://minsvcbug/podtest/podtest
gvisor以沙箱的方式執行容器,在宿主機裡就看不到容器裡執行的程序了。
root@ubuntuk8sclient:~# ps -ef | grep nginx
root 111683 27377 0 02:36 pts/1 00:00:00 grep --color=auto nginx
刪除pod。
root@k8scludes1:~/containerd-gvisor# kubectl delete pod podtest
pod "podtest" deleted
十.總結
Gvisor作為一種安全容器執行時,透過引入沙箱機制,實現了對容器程序的細粒度控制,有效提高了容器的安全性。雖然相較於傳統容器技術,Gvisor可能帶來一定的效能開銷,但其在安全性方面的優勢足以彌補這一不足。