1 容器管理面无感卸载
1.1 简介
容器管理面,即dockerd、containerd、isulad等容器的管理工具,而容器管理面卸载,即是将容器管理面卸载到与容器所在机器(以下称为HOST)之外的另一台机器(以下称为DPU)上运行。
我们使用qtfs将HOST上一些与容器运行相关的目录挂载到DPU上,使得容器管理面工具可以访问到这些目录,为容器准备运行所需要的环境。此外,因为需要挂载远端的proc和sys文件系统,为避免对当前系统运行造成影响,可以创建一个专门的rootfs以作为dockerd、contianerd的运行环境(以下称为/another_rootfs
)。
通过rexec执行容器的拉起、删除等操作,可以将容器管理面和容器分离在不同的两台机器上,对容器进行远程管理。无感卸载的验证可以使用下述两种模式。
1.1.1 测试模式
需准备2台物理机(或虚拟机),两台机器之间网络互通。
其中一台作为DPU模拟,另一台作为HOST模拟。在本文档中用DPU和HOST指代这两台服务器。
说明: 测试模式因为会暴露网络端口且不做连接认证,存在网络安全风险,仅能用于内部测试验证,不要用于实际生产环境。 实际生产环境中应使用封闭通信方式,以防止外界连接风险,如下述vsock通信模式。
1.1.2 vsock模式
需要DPU和HOST通过virtio提供vsock通信方式。
文档当前仅描述基于测试模式的方法,下面的内容依然默认使用测试模式。如果验证环境支持vsock通信(虚拟化环境或支持vsock的DPU-HOST环境),下述测试步骤仍适用,只需要将测试中的IP网络地址修改为vsock cid即可(相关二进制编译过程也无需使用TEST_MODE)。
2 环境搭建
2.1 qtfs文件系统部署
可参考qtfs主页。
说明: 如果使用测试模式,需要在qtfs客户端及服务端ko编译时指定qtfs_TEST_MODE=1,vsock模式无需指定。
qtfs建联需要关闭DPU和HOST侧的防火墙,或在防火墙中开放相关网络端口号。
2.2 UDSPROXYD服务部署
2.2.1 简介
udsproxyd是一个跨主机的unix domain socket代理服务,需要分别部署在HOST和DPU上,在HOST和DPU上的udsproxyd组件是对等的关系,可以实现分布在HOST与DPU上的2个进程之间使用标准uds进行通信,通信进程是无感的,即如果这两个进程在同一主机内通过uds正常通信的功能,拉远到HOST和DPU之间也可以,不需要做代码适配。udsproxyd作为一个跨主机的unix socket服务,可以用LD_PRELOAD=libudsproxy.so
动态库截获的方式对接使用,也可以通过提前进行udsconnect白名单配置无感应用,白名单配置具体有两种方式,将在后面详述。
2.2.2 部署方式
首先,在dpu-utilities工程内编译udsproxyd:
cd qtfs/ipc
make -j UDS_TEST_MODE=1 && make install
说明: 如果使用vsock模式,编译时不需要设置UDS_TEST_MODE
当前最新版本下,qtfs server侧的engine服务已经整合了udsproxyd的能力,所以server侧需要再额外启动udsproxyd。client侧则单独拉起udsproxyd服务:
nohup /usr/bin/udsproxyd <thread num> <addr> <port> <peer addr> <peer port> 2>&1 &
参数解释:
thread num: 线程数量,目前只支持单线程,填1
addr: 本机使用的ip,如果使用vsock通信模式则为cid
port:本机占用的port
peer addr: udsproxyd对端的ip,如果使用vsock模式则为cid
peer port: 对端port
示例:
nohup /usr/bin/udsproxyd 1 192.168.10.10 12121 192.168.10.11 12121 2>&1 &
如果未拉起qtfs的engine服务,想单独测试udsproxyd,则在server端也对等拉起udsproxyd即可:
nohup /usr/bin/udsproxyd 1 192.168.10.11 12121 192.168.10.10 12121 2>&1 &
2.2.3 应用方式
2.2.3.1 独立使用udsproxyd服务
需要在使用uds服务的unix socket应用程序的client端进程启动时添加LD_PRELOAD=libudsproxy.so环境变量,以接管glibc的connect api进行uds对接,或者通过qtcfg增加udsconnect白名单的方式指定系统接管特定目录下的uds连接。
2.2.3.2 无感使用udsproxyd服务
首先为qtfs配置uds服务的白名单,这里说的白名单是unix socket的server端bind的sock文件地址,提供两种方式供选择:
- 通过配置工具qtcfg加载,进入qtfs/qtinfo目录编译工具:
在qtfs的client端执行
make role=client
make install
在qtfs的server端执行
make role=server
make install
配置工具将会自动安装,然后使用qtcfg命令配置白名单,假设需要增加的白名单为"/var/lib/docker",输入:
qtcfg -w udsconnect -x /var/lib/docker
查询白名单为:
qtcfg -w udsconnect -z
删除白名单为:
qtcfg -w udsconnect -y 0
删除白名单时,参数为查询白名单时列出来的index序号。
- 通过配置文件增加,这需要在qtfs或qtfs_server内核模块加载前配置,通过内核模块初始化时读取该文件进行白名单配置。
在/etc/qtfs/whitelist文件中增加如下字段:
[Udsconnect]
/var/lib/docker
说明: 白名单是为了防止不相干的unix socket链接也进行远程连接产生错误,或者浪费不必要的资源,所以白名单尽量设置得精确一些,比如本文中针对容器场景设置为/var/lib/docker比较好,而直接将/var/lib/或/var/或直接将根目录加入的做法是有较大风险和系统影响的。
2.3 REXEC服务部署
2.3.1 简介
rexec是一个用c语言开发的远程执行组件,分为rexec client和rexec server。server端为一个常驻服务进程,client端为一个二进制文件,client端被执行后会基于udsproxyd服务与server端建立uds连接,并由server常驻进程在server端拉起指定程序。在容器管理面卸载中,dockerd卸载到DPU上,当它需要在HOST拉起容器业务进程时调用rexec client进行远程拉起。
2.3.2 部署方法
2.3.2.1 配置环境变量与白名单
在HOST侧配置rexec server的白名单,将文件whitelist放置在/etc/rexec/目录下并修改权限为只读:
chmod 400 /etc/rexec/whitelist
下载dpu-utilities代码后,进入qtfs/rexec主目录下,执行:make && make install
即可安装rexec所需全部二进制到/usr/bin目录下,包括了:rexec、rexec_server
两个二进制可执行文件。
在server端启动rexec_server服务之前,检查是否存在/var/run/rexec目录,没有则创建:
mkdir /var/run/rexec
rexec服务运行时底层通信使用unix socket,因此跨主机的rexec和rexec_server通信依赖上述udsproxyd服务,并且需要在udsproxy中添加相关白名单:
qtcfg -w udsconnect -x /var/run/rexec
2.3.2.2 服务方式
server端可以通过两种方式拉起rexec_server服务。
- 方式1: 配置systemd服务
在/usr/lib/systemd/system/下增加rexec.service文件,内容如下:
然后通过systemctl管理rexec服务。
首次配置服务时:
systemctl daemon-reload
systemctl enable --now rexec
后续重启新启动服务:
systemctl stop rexec
systemctl start rexec
- 方式2: 手动后台拉起
nohup /usr/bin/rexec_server 2>&1 &
3 管理面组件改动
3.1 dockerd相关改动介绍
对dockerd的改动是基于18.09版本的。
在docker中,暂时绕过了网络部分,管理面卸载依赖容器网络组件做针对性适配,本测试方案暂不涉及。
对docker的改动可以参考该目录下的patch文件。
3.2 containerd相关改动介绍
对于containerd的改动是基于containerd-1.2-rc.1版本的。
对containerd的改动可以参考该目录下的patch文件。
4 容器管理面卸载操作指南
说明:
- 在HOST端和DPU端,都要拉起rexec_server;
- HOST端拉起rexec_server,主要是用于DPU创建容器时用rexec拉起containerd-shim;
- DPU拉起rexec_server,则是为了执行containerd-shim对dockerd和containerd的调用。
4.1 准备dockerd和containerd运行的rootfs
注:本步骤仅需在DPU上执行的。
在下面的文档中,我们将这个rootfs称为/another_rootfs
(具体的目录名称,可以根据自己的需求进行调整)。
4.1.1 使用openEuler官方qcow2镜像构造rootfs
建议用openEuler官方提供的qcow2镜像,来准备一个新的rootfs:
4.1.1.1 工具安装
需要用yum安装xz、kpartx、qemu-img
yum install xz kpartx qemu-img
4.1.1.2 下载qcow2镜像
在openEuler官网获取22.03版本openEuler-x86虚拟机镜像(X86架构),或者22.03版本openEuler-arm64虚拟机镜像(ARM架构)。
4.1.1.3 解压qcow2镜像
使用xz -d解压为openEuler-22.03-LTS-
xz -d openEuler-22.03-LTS-x86_64.qcow2.xz
4.1.1.4 挂载qcow2镜像并拷贝文件
- 使用
modprobe nbd maxpart=<任意数字>
来加载nbd模块 qemu-nbd -c /dev/nbd0 <虚拟机镜像的路径>
- 创建任意文件夹
/random_dir
- 执行挂载
mount /dev/nbd0p2 /random_dir
- 拷贝文件
mkdir /another_rootfs
cp -r /random_dir/* /another_rootfs/
此时,虚拟机镜像遍已经挂载到当前文件夹中了。
4.1.1.5 qcow2卸载
在准备好rootfs后,需要卸载qcow2文件,此时,需要执行如下指令:
umount /random_dir
qemu-nbd -d /dev/nbd0
4.1.2 向another_rootfs中安装软件
- 将根目录中的
/etc/resolv.conf
拷贝到/another_rootfs/etc/resolv.conf
- 清空
/another_rootfs/etc/yum.repos.d
中的文件,并将/etc/yum.repos.d/
中的文件拷贝到/another_rootfs/etc/yum.repos.d
- 使用
yum install <软件包> --installroot=/another_rootfs
来安装软件包
yum install --installroot=/another_rootfs iptables
4.2 在Host侧拉起qtfs_server
需要将rexec、containerd-shim、runc、engine拷贝到/usr/bin
下面,这里要注意权限问题,rexec、engine已提供,docker相关二进制根据上述第三章相关patch编译生成。
4.2.1 插入qtfs_server驱动
创建容器管理面所需要的文件夹,然后插入qtfs_server.ko,并拉起engine进程。
可以使用这个脚本来执行此操作,如果执行错误,可能需要dos2unix来将此脚本的格式转换(如下所有脚本皆同理)。
注意,使用实际qtfs路径替换脚本中的模块及二进制路径。
此外在HOST端,还需要创建执行rexec指令的脚本/usr/bin/dockerd
以及/usr/bin/containerd
:
/usr/bin/dockerd:
#!/bin/bash
rexec /usr/bin/dockerd $*
/usr/bin/containerd:
#!/bin/bash
exec /usr/bin/containerd $*
在创建完成后,需要用chmod为这两个脚本赋予执行权限
chmod +x /usr/bin/containerd
chmod +x /usr/bin/dockerd
4.3 挂载Host上依赖目录至DPU
4.3.1 安装软件包
4.3.2.1 在根目录的安装
DPU根目录中(another_rootfs之外):安装iptables和libtool、libcgroup、tar,可以通过yum直接直接安装。
yum install iptables libtool libcgroup tar
也可以下载其所有的依赖包之后,用rpm指令安装,iptables以及libtool的包及依赖包链接如下:iptables, libtool, emacs, autoconf, automake, libtool-ltdl, m4, tar, libcgroup。
在下载上述软件包之后,执行命令
rpm -ivh iptables-1.8.7-5.oe2203.x86_64.rpm libtool-2.4.6-34.oe2203.x86_64.rpm emacs-27.2-3.oe2203.x86_64.rpm autoconf-2.71-2.oe2203.noarch.rpm automake-1.16.5-3.oe2203.noarch.rpm libtool-ltdl-2.4.6-34.oe2203.x86_64.rpm m4-1.4.19-2.oe2203.x86_64.rpm tar-1.34-1.oe2203.x86_64.rpm libcgroup-0.42.2-1.oe2203.x86_64.rpm
4.3.2.2 another_rootfs环境配置
- 在
/another_rootfs
中,需要安装iptables,这个是dockerd启动所必须的依赖
使用yum install <软件包> --installroot=/another_rootfs
来安装软件包
- 并需要将rexec拷贝到
/another_rootfs/usr/bin
下面,并对其添加可执行权限
cp rexec /another_rootfs/usr/bin
chmod +x /another_rootfs/usr/bin/rexec
- 另外,将根据社区docker源码与前述patch编译出来的containerd和dockerd拷贝到
/another_rootfs/usr/bin
下面, 将docker拷贝到/usr/bin
下
cp {YOUR_PATH}/dockerd /another_rootfs/usr/bin
cp {YOUR_PATH}/containerd /another_rootfs/usr/bin
cp {YOUR_PATH}/docker /usr/bin
- 在
/another_rootfs
中删除/another_rootfs/usr/sbin/modprobe
rm -f /another_rootfs/usr/sbin/modprobe
- 在
/another_rootfs
中创建如下三个脚本
containerd-shim创建路径:/another_rootfs/usr/local/bin/containerd-shim
#!/bin/bash
/usr/bin/rexec /usr/bin/containerd-shim $*
remote_kill创建路径:/another_rootfs/usr/bin/remote_kill
#!/bin/bash
/usr/bin/rexec /usr/bin/kill $*
modprobe创建路径:/another_rootfs/usr/sbin/modprobe
#!/bin/bash
/usr/bin/rexec /usr/sbin/modprobe $*
在创建完成后,为其赋予执行权限
chmod +x /another_rootfs/usr/local/bin/containerd-shim
chmod +x /another_rootfs/usr/bin/remote_kill
chmod +x /another_rootfs/usr/sbin/modprobe
4.3.2.3 目录挂载
在DPU上执行此脚本prepare.sh ,将dockerd、containerd所需要的HOST目录挂载到DPU。
并且,需要确保在该脚本被挂载的远程目录在HOST和DPU都存在。
4.4 拉起dockerd和containerd
在DPU上,打开两个窗口,并且都chroot到dockerd和containerd运行所需的/another_rootfs。
chroot /another_rootfs
在两个窗口中用如下的命令先拉起containerd,后拉起dockerd。
containerd
#!/bin/bash
SHIM_HOST=${YOUR_SERVER_IP} containerd --config /var/run/docker/containerd/containerd.toml --address /var/run/containerd/containerd.sock
dockerd
#!/bin/bash
# this need to be done once
/usr/bin/rexec mount -t qtfs /var/lib/docker/overlay2 /another_rootfs/var/lib/docker/overlay2/
SHIM_HOST=${YOUR_SERVER_IP} /usr/bin/dockerd --containerd /var/run/containerd/containerd.sock -s overlay2 --iptables=false --debug 2>&1 | tee docker.log
因为我们已经将/var/run/
和/another_rootfs/var/run/
绑定在一起,可以在正常的rootfs下,通过docker来访问docker.sock接口,从而管理容器。
5 环境恢复
如果需要卸载相关的目录,需要先删除已有容器,再关掉containerd、dockerd,并执行如下指令:
for i in `lsof | grep v1.linux | awk '{print $2}'`
do
kill -9 $i
done
mount | grep qtfs | awk '{print $3}' | xargs umount
mount | grep another_rootfs | awk '{print $3}' | xargs umount
sleep 1
umount /another_rootfs/etc
umount /another_rootfs/sys
pkill udsproxyd
rmmod qtfs