「 Kata Containers 」源码走读 — virtcontainers/agent
based on 3.0.0
agent
src/runtime/virtcontainers/agent.go
从代码结构来看,agent 是一个典型的 CS 架构,客户端部分位于 virtcontainers 库中,与位于 guest VM 中运行的服务端进程进行 RPC 通信(实际为 ttrpc),管理 VM 中容器进程的生命周期。
目前 agent 的实现方式仅有一种:kataAgent。
1 | type kataAgent struct { |
由于 virtcontainers 中 agent 模块为 Kata-agent 服务的客户端部分,复杂逻辑不多,主要为 RPC 请求的封装与 host 侧的准备工作,其中简单的 RPC 请求有:
函数 | 功能 | gRPC 服务 / 方法 | 备注 |
---|---|---|---|
check | 检查 agent server 是否存活 | grpc.Health / Check | |
exec | 在运行的容器中执行命令 | grpc.AgentService / ExecProcess | 期间涉及到 types.Cmd 和 grpc.Process 的转换,两者都包含要在容器中运行的命令的主要信息,包括工作目录、用户、主组、参数和环境变量等 |
stopSandbox | 关停 sandbox | grpc.AgentService / DestroySandbox | sandbox 中的所有容器均会关停 |
startContainer | 启动容器 | grpc.AgentService / StartContainer | |
stopContainer | 关停容器 | grpc.AgentService / RemoveContainer | |
signalProcess | 向进程发送信号 | grpc.AgentService / SignalProcess | 根据入参 all 是否为 true 决定是否向所有进程发送信号 |
winsizeProcess | 设置进程的 tty 大小 | grpc.AgentService / TtyWinResize | |
writeProcessStdin | 将内容写入至进程的标准输入流中 | grpc.AgentService / WriteStdin | |
closeProcessStdin | 关闭进程的标准输入流 | grpc.AgentService / CloseStdin | |
readProcessStdout | 读取进程的标准输出流内容 | grpc.AgentService / ReadStdout | |
readProcessStderr | 读取进程的标准错误流内容 | grpc.AgentService / ReadStderr | |
updateContainer | 更新容器资源配置 | grpc.AgentService / UpdateContainer | 更新容器 OCI spec 中的资源配额 |
waitProcess | 等待进程返回退出码 | grpc.AgentService / WaitProcess | |
onlineCPUMem | 通知上线 CPU 和内存 | grpc.AgentService / OnlineCPUMem | |
memHotplugByProbe | 通过探针接口通知 guest 内核内存热插拔事件 | grpc.AgentService / MemHotplugByProbe | 内存热插拔是分批的,因此通知事件也是分批执行的,其中会根据内存分片大小涉及到内存偏移量 |
statsContainer | 获取容器详情 | grpc.AgentService / StatsContainer | 其中主要关注 hugetlb、blkio、CPU、memory 和 pid 等 cgroup 相关的信息 |
pauseContainer | 暂停容器 | grpc.AgentService / PauseContainer | |
resumeContainer | 恢复容器 | grpc.AgentService / ResumeContainer | |
reseedRNG | 重置随机数生成器 | grpc.AgentService / ReseedRandomDev | 使用新的种子值重置 guest 的随机数生成器 |
updateInterface | 更新网卡信息 | grpc.AgentService / UpdateInterface | |
listInterfaces | 获取所有网卡信息 | grpc.AgentService / ListInterfaces | |
updateRoutes | 更新路由信息 | grpc.AgentService / UpdateRoutes | |
listRoutes | 获取所有路由信息 | grpc.AgentService / ListRoutes | |
getGuestDetails | 获取 guest 详情 | grpc.AgentService / GetGuestDetails | 包含内存块设备大小、agent 版本、agent 是否为 PID 为 1(取决于是否启用 [hypervisor].image 或者 [hypervisor].initrd)、是否支持 seccomp 特性等 |
setGuestDateTime | 同步 host 时间至 guest 中 | grpc.AgentService / SetGuestDateTime | |
copyFile | 复制 host 文件至容器目录中(常用于拷贝至容器 rootfs 中) | grpc.AgentService / CopyFile | 设置 grpc 的每次发包最大为 1024 * 1024,因此需要根据 host 源文件大小,分片请求 |
addSwap | 配置 SWAP 文件 | grpc.AgentService / AddSwap | |
getOOMEvent | 获取 OOM 事件 | grpc.AgentService / GetOOMEvent | |
getAgentMetrics | 获取 agent 和 guest 的指标数据 | grpc.AgentService / GetMetrics | |
getGuestVolumeStats | 获取 guest 中卷数据信息 | grpc.AgentService / GetVolumeStats | |
resizeGuestVolume | 调整 guest 中卷大小 | grpc.AgentService / ResizeVolume | |
getIPTables | 获取 guest 中的 iptables 信息 | grpc.AgentService / GetIPTables | |
setIPTables | 设置 guest 中的 iptables 信息 | grpc.AgentService / SetIPTables |
agent 中声明的 longLiveConn、getAgentURL、setAgentURL、reuseAgent、markDead 均为参数获取与赋值,无复杂逻辑,不作详述。cleanup 暂未实现,save 和 load 用于 kataAgent.state 和 persistapi.AgentState 之间的 agent URL 数据转换。
init
初始化 agent 服务
- disableVMShutdown 是否为 true 取决于是否启用了 [agent].enable_tracing
当 disableVMShutdown 为 true,也就意味着在关停 VM 时,会向 QMP 服务发送 quit 命令,请求关闭 VM 实例;否则,直接 syscall kill 掉 QEMU 进程,不会等待 VM 关闭,因此在启用 [agent].enable_tracing 时,VM 的关停时间会有所增加 - 初始化 agent 实现,返回 disableVMShutdown,后续决定在调用 Hypervisor 的 Stop 操作时的 VM 关闭方式
capabilities
获取 agent 支持的特性
- 设置并返回 agent 默认支持特性,包括块设备特性支持
disconnect
断开与 agent 的连接
- 关闭 agent 客户端,重置 gRPC 路由映射表
createSandbox
sandbox 运行前的准备工作
- 调用 configure,指定共享目录为
/run/kata-containers/shared/sandboxes/<containerID>/shared
startSandbox
启动 sandbox 中的所有容器
- 调用 agent 的 setAgentURL,设置通信 URL
- 读取 sandbox OCI spec 中挂载点为 /etc/resolv.conf 的挂载源文件内容(即位于 host 上用于挂载到容器中的 DNS 配置文件)
- 调用 agent 的 check,检测 agent server 的存活性
- 调用 Network 的 Endpoints,获取 sandbox 中所有网卡,进而调用 Endpoint 的 Properties,获取网卡属性等信息,构建 RPC 通信所需的网卡接口、路由和 ARP neighbor 信息
- 调用 updateInterface,更新 VM 的网卡信息
- 请求 agent server 的 grpc.AgentService 接口的 UpdateRoutes 方法,更新 VM 中路由信息
- 请求 agent server 的 grpc.AgentService 接口的 AddARPNeighbors 方法,更新 VM 中 ARP neighbor 信息
- 调用 Hypervisor 的 Capabilities,检验 hypervisor 是否支持文件系统共享特性
- 如果 [hypervisor].shared_fs 为 virtio-fs 或者 virtio-fs-nydus
- 当 [hypervisor].virtio_fs_cache 不为 none 且 [hypervisor].virtio_fs_cache_size 不为 0 时,挂载参数中会追加 dax
如果 virtio-fs 使用 auto 或者 always,则可以使用选项 dax 挂载 guest 目录,从而允许它直接映射来自 host 的内容。 当设置为 none 时,挂载选项不应包含 dax,以免 virtio-fs 守护进程因无效地址引用而崩溃 - 生成一个类型为 virtiofs、挂载源为 kataShared、挂载点为
/run/kata-containers/shared/containers/(当 [hypervisor].shared_fs 为 virtio-fs-nydus 时, 为 /run/kata-containers/shared/)以及含上述挂载参数的 virtio-fs 挂载信息
- 当 [hypervisor].virtio_fs_cache 不为 none 且 [hypervisor].virtio_fs_cache_size 不为 0 时,挂载参数中会追加 dax
- 如果 [hypervisor].shared_fs 为 virtio-9p,则生成一个类型为 9p、挂载源为 kataShared,挂载点为
/run/kata-containers/shared/containers/、挂载参数为 msize=<[hypervisor].msize_9p> 的 9p 挂载信息
- 如果 [hypervisor].shared_fs 为 virtio-fs 或者 virtio-fs-nydus
- 如果 shmSize 大于 0,则生成一个类型为 tmpfs、挂载源为 shm、挂载点为
/run/kata-containers/sandbox/shm、挂载参数为 size=<shmSize>,noexec,nosuid,nodev,mode=1777 的 ephemeral 挂载信息
shmSize 为 sandbox OCI spec 中 destination 为 /dev/shm,type 为 bind 的挂载点的 source 大小 - 请求 agent server 的 grpc.AgentService 接口的 CreateSandbox 方法,启动 sandbox 中的所有容器
其中参数包含 [hypervisor].guest_hook_path,表示 VM 中 hook 脚本路径,hook 必须按照其 hook 类型存储在 guest_hook_path 的子目录中,例如 guest_hook_path/{prestart,poststart,poststop}。Kata agent 将扫描这些目录查找可执行文件,按字母顺序将其添加到容器的生命周期中,并在 VM 运行时命名空间中执行
createContainer
创建容器
调用 fsShare 的 ShareRootFilesystem,创建容器 rootfs 的共享挂载
针对容器中每一个共享挂载信息
- 忽略挂载源为系统类别的,例如 /proc 或者 /sys
- 如果待挂载的是块设备类型,则调用 devManager 的 AttachDevice,attach 设备,而不会作为共享挂载
- 忽略挂载类型不为 bind 的挂载信息
- 忽略挂载点为 /dev/shm 的挂载信息
将 /dev/shm 作为一个绑定挂载传递到容器中不是一个合理的方式,因为它不需要从 host 的 9p 挂载中传递。相反,需要在容器内部分配内存来处理 /dev/shm - 忽略挂载点为 /dev 或者 /dev/ 目录层级下的设备文件、目录等非常规文件
- 调用 fsShare 的 ShareFile,将位于 host 的挂载点文件共享至 guest 中
- 如果挂载源为 Kubernetes ConfigMap 或者 Secret 资源(路径中会包含 kubernetes.io~configmap 和 kubernetes.io~secret 特征)并且调用 Hypervisor 的 Capabilities,判断 hypervisor 支持文件系统共享特性,则创建
/run/kata-containers/shared/sandboxes/<containerID>/mounts/watchable 目录,并生成一个类型为 bind、挂载源为 <guestPath>(即步骤 6 返回的位于 guest 中的文件路径),挂载点为 /run/kata-containers/shared/containers/watchable/<base guestPath> 的 watchable-bind 挂载信息,并替换原挂载源为 watchable-bind 的挂载点
virtiofs 不支持 inotify 机制,因此这是一种解决方案,用于让 virtiofs 可以间接感知到 Kubernetes ConfigMap 和 Secret 文件的变化。具体来说,是将这两种资源文件重新挂载,并将原本的 OCI spec 中声明的挂载源替换成新的挂载点
针对容器中每一个临时挂载信息(即 ephemeral),生成一个类型为 tmpfs、挂载源为 tmpfs、挂载点为
/run/kata-containers/sandbox/ephemeral/<base source>、挂载参数为 fsgid=<gid> 的 ephemeral 挂载信息
如果卷的 gid 不是根组(默认组),这意味着在该本地卷上设置了特定的 fsGroup,那么它应该传递给 guest针对容器中每一个本地挂载信息(即 local,用于 VM 中多容器的文件共享),解析 /proc/mounts 文件内容,获取文件系统类型为 hugetlbfs 的挂载参数,用于进一步解析大页大小,生成一个类型为 hugetlbfs、挂载源为 nodev、挂载点为
/run/kata-containers/sandbox/ephemeral/<base source>、挂载参数为 pagesize=<pagesize>,size=<size> 的 ephemeral 挂载信息 针对容器中每一个本地挂载信息(即 local,用于 VM 中多容器的文件共享),生成一个类型为 local、挂载源为 local、挂载点为
/run/kata-containers/shared/containers/<sandboxID>/rootfs/local/<base source>、挂载参数为 mode=0777,fsgid=<gid> 的 local 挂载信息 修正容器 OCI spec 中的信息,更新和忽略其中的挂载点信息
更新和忽略的信息均来自步骤 2,其中如果步骤 2-7 有变更,则需要更新;如果步骤 2-6 调用返回为 nil,则需要忽略针对容器的每一个设备信息,调用 devManager 的 GetDeviceByID,获取设备对象,进一步根据其设备类型(例如 block、vhost-user-blk-pci 和 vfio),调用 device 的 GetDeviceInfo,获取设备信息
针对容器的每一个块设备信息(步骤 2-2 未做处理),将设备的挂载源更新为 /run/kata-containers/sandbox/storage/<source>,更新至 OCI spec 中
校验当禁用 [runtime].disable_guest_seccomp 时,Kata agent 是否支持 seccomp 特性
请求 agent server 的 grpc.AgentService 接口的 CreateContainer 方法,聚合上述的挂载点、设备、OCI spec 等信息创建容器
configure
设置 agent 配置信息
- 调用 Hypervisor 的 GenerateSocket,生成用于 host 和 guest 通信的 socket 地址
- 调用 Hypervisor 的 AddDevice,根据生成的 socket 的类型,为 VM 添加对应类型的设备(例如 vhost-vsock 或 virtio-vsock)
- 调用 Hypervisor 的 Capabilities,检验 hypervisor 是否支持文件系统共享特性,创建指定共享目录,调用 Hypervisor 的 AddDevice,为 VM 添加 filesystem 类型的设备,其中,挂载标签为 kataShared,挂载源为此共享目录,例如
/run/kata-containers/shared/sandboxes/<containerID>/shared
configureFromGrpc
设置 agent 配置信息
- 调用 Hypervisor 的 GenerateSocket,生成用于 host 和 guest 通信的 socket 地址
「 Kata Containers 」源码走读 — virtcontainers/agent
http://shenxianghong.github.io/2023/07/26/2023-07-26 Kata Containers 源码走读 - virtcontainers agent/