kata 系统架构解读

https://github.com/kata-containers/kata-containers/tree/main/docs/design/architecture

https://github.com/kata-containers/kata-containers/tree/main/docs/design

1. 整体架构

如下图:

kata-containers 的核心 binary 就2个:

  1. containerd-shim-kata-v2,对应源代码目录 src/runtime
  2. kata-agent,对应源码目录 src/agent

containerd 实现了 cri 协议,可以天然直接无缝对接到 kublet 上,而 containerd-shim-kata-v2 实现了 containerd 的 shim-v2 协议,直接实现了对 kata-agent 以及 hypervisor 通讯的封装。

2. 内核

xx

3. kata-agent

xx

4. runtime

kata的runtime其实有2个,一个是kata-runtime,一个就是containerd-shim-kata-v2

4.1. kata-runtime

kata-runtime是kata 1.0时代的产物,按照老的架构文档:https://github.com/kata-containers/documentation/blob/master/design/architecture.md,kata-runtime是一个标准的 OCI 实现,但是 kata 2.0 之后,架构发生了很大的变化,kata-runtime 的核心能力被裁剪掉了,逐渐的只剩下 kata-check 功能了

4.2. containerd-shim-kata-v2

containerd-shim-kata-v2 是一个实现了 containerd shim 协议的组件,向上直接对接 containerd,向下和 hypervisor 以及 kata-agent 通信以实现完整的 oci 能力

containerd-shim-kata-v2 的 main 文件在 src/runtime/cmd/containerd-shim-kata-v2/main.go

架构如下:

4.2.1. containerd-shim

containerd-shim-kata-v2 实现了 containerd 定义的 shim 接口,shim 的接口其实是和 cri 非常类似的,只不过是做了一层协议的 proxy,本质上并没有区别

containerd 会将接收到的所有 cri (来自kublet)请求,转换成对 containerd-shim-kata-v2 的 TaskService 调用

详细定义可以参考这个:https://github.com/containerd/containerd/tree/main/runtime/v2

// https://raw.githubusercontent.com/containerd/containerd/master/api/runtime/task/v2/shim_ttrpc.pb.go
type TaskService interface {
    State(context.Context, *StateRequest) (*StateResponse, error)
    Create(context.Context, *CreateTaskRequest) (*CreateTaskResponse, error)
    Start(context.Context, *StartRequest) (*StartResponse, error)
    Delete(context.Context, *DeleteRequest) (*DeleteResponse, error)
    Pids(context.Context, *PidsRequest) (*PidsResponse, error)
    Pause(context.Context, *PauseRequest) (*emptypb.Empty, error)
    Resume(context.Context, *ResumeRequest) (*emptypb.Empty, error)
    Checkpoint(context.Context, *CheckpointTaskRequest) (*emptypb.Empty, error)
    Kill(context.Context, *KillRequest) (*emptypb.Empty, error)
    Exec(context.Context, *ExecProcessRequest) (*emptypb.Empty, error)
    ResizePty(context.Context, *ResizePtyRequest) (*emptypb.Empty, error)
    CloseIO(context.Context, *CloseIORequest) (*emptypb.Empty, error)
    Update(context.Context, *UpdateTaskRequest) (*emptypb.Empty, error)
    Wait(context.Context, *WaitRequest) (*WaitResponse, error)
    Stats(context.Context, *StatsRequest) (*StatsResponse, error)
    Connect(context.Context, *ConnectRequest) (*ConnectResponse, error)
    Shutdown(context.Context, *ShutdownRequest) (*emptypb.Empty, error)
}

// https://github.com/containerd/containerd/blob/8167751f569c9374a6c3ef5b39a42629577a543a/runtime/v2/shim/shim.go#L72
// Shim server interface
// TODO(2.0): Remove unified shim interface
type Shim interface {
    shimapi.TaskService
    Cleanup(ctx context.Context) (*shimapi.DeleteResponse, error)
    StartShim(ctx context.Context, opts StartOpts) (string, error)
}

明显可以看出,TaskService 接口基本定义了 oci 所依赖的基本能力

比如 container 的 create 接口,所有的 container 元数据,会全部封装到 options 字段里面

message CreateTaskRequest {
    string id = 1;
    ...
    google.protobuf.Any options = 10;
}

问题:containerd-shim-kata-v2 为什么不直接对接到 cri 协议?既然接口是类似的,那么如果实现了 cri 协议,那是不是可以直接对接到 kublet 了,连 containerd 都省掉了

答:containerd 本身除了调用 oci 完成容器的创建和删除,还实现了容器的生命周期管理,还支持多种 oci-runtime。这些能力都是通用的,并且是containerd-shim-kata-v2 当前没有实现的,因此没有必要重复造轮子

4.2.2. virtcontainer

virtcontainer 提供了一个统一的 hypervisor 抽象层,为了能够对接不同的vmm,比如qemu,firecracker,同时提供容器的操作原语

因为vmm的实现都是类似的,比如尽管 firecracker 也是一种轻量级的vmm,但本质还是kvm虚拟化的实现(核心的kvm虚拟化能力是由内核提供的,firecracker 只是一个轻量级的壳)

4.2.2.1. Sandbox

由于 kata-container 的所有容器操作,都是在虚机里实现的。因此 Sandbox 是 virtcontainer 的核心,基本上所有的 virtcontainer 对外接口都是由 Sandbox 来完成的

Sandbox 的 API 如下,基本包含了

  1. 完整的容器操作接口。(不过容器的相关操作实现,在 Container 这个类里面,下面会讲到)
  2. 以及部分 hypervisor 的接口(正常来说,hypervisor 类的操作应该隐藏在容器的 API 之下)

创建 Sandbox:newSandbox()

删除 Sandbox:s.Delete()

容器类接口:

func (s *Sandbox) GetAllContainers() []VCContainer 
func (s *Sandbox) GetContainer(containerID string) VCContainer 
func (s *Sandbox) WaitProcess(ctx context.Context, containerID, processID string) (int32, error) 
func (s *Sandbox) SignalProcess(ctx context.Context, containerID, processID string, signal syscall.Signal, all bool) error 
func (s *Sandbox) WinsizeProcess(ctx context.Context, containerID, processID string, height, width uint32) error 
func (s *Sandbox) IOStream(containerID, processID string) (io.WriteCloser, io.Reader, io.Reader, error) 
func (s *Sandbox) CreateContainer(ctx context.Context, contConfig ContainerConfig) (VCContainer, error) 
func (s *Sandbox) StartContainer(ctx context.Context, containerID string) (VCContainer, error) 
func (s *Sandbox) StopContainer(ctx context.Context, containerID string, force bool) (VCContainer, error) 
func (s *Sandbox) KillContainer(ctx context.Context, containerID string, signal syscall.Signal, all bool) error 
func (s *Sandbox) DeleteContainer(ctx context.Context, containerID string) (VCContainer, error) 
func (s *Sandbox) StatusContainer(containerID string) (ContainerStatus, error) 
func (s *Sandbox) EnterContainer(ctx context.Context, containerID string, cmd types.Cmd) (VCContainer, *Process, error) 
func (s *Sandbox) UpdateContainer(ctx context.Context, containerID string, resources specs.LinuxResources) error 
func (s *Sandbox) StatsContainer(ctx context.Context, containerID string) (ContainerStats, error) 
func (s *Sandbox) PauseContainer(ctx context.Context, containerID string) error 
func (s *Sandbox) ResumeContainer(ctx context.Context, containerID string) error 
func (s *Sandbox) Status() SandboxStatus 

虚机类的接口,主要是一些设备的热插拔:

func (s *Sandbox) ID() string 
func (s *Sandbox) GetHypervisorPid() (int, error) 
func (s *Sandbox) AddInterface(ctx context.Context, inf *pbTypes.Interface) (*pbTypes.Interface, error) 
func (s *Sandbox) RemoveInterface(ctx context.Context, inf *pbTypes.Interface) (*pbTypes.Interface, error) 
func (s *Sandbox) ListInterfaces(ctx context.Context) ([]*pbTypes.Interface, error) 
func (s *Sandbox) UpdateRoutes(ctx context.Context, routes []*pbTypes.Route) ([]*pbTypes.Route, error) 
func (s *Sandbox) ListRoutes(ctx context.Context) ([]*pbTypes.Route, error) 
func (s *Sandbox) HotplugAddDevice(ctx context.Context, device api.Device, devType config.DeviceType) error 
func (s *Sandbox) HotplugRemoveDevice(ctx context.Context, device api.Device, devType config.DeviceType) error 
func (s *Sandbox) GetAndSetSandboxBlockIndex() (int, error) 
func (s *Sandbox) UnsetSandboxBlockIndex(index int) error 
func (s *Sandbox) AppendDevice(ctx context.Context, device api.Device) error 
func (s *Sandbox) AddDevice(ctx context.Context, info config.DeviceInfo) (api.Device, error) 
func (s *Sandbox) GetHypervisorType() string 

4.2.2.2. Container

Container 类是 virtcontainer 里的第二个关键数据结构。所有 Sandbox 和容器相关的操作,基本都由 Container 结构来完成

以创建容器为例

Sandbox代码:

// CreateContainer creates a new container in the sandbox
// This should be called only when the sandbox is already created.
// It will add new container config to sandbox.config.Containers
func (s *Sandbox) CreateContainer(ctx context.Context, contConfig ContainerConfig) (VCContainer, error) {
    // Update sandbox config to include the new container's config
    s.config.Containers = append(s.config.Containers, contConfig)

    var err error

    // Create the container object, add devices to the sandbox's device-manager:
    c, err := newContainer(ctx, s, &s.config.Containers[len(s.config.Containers)-1])
    if err != nil {
        return nil, err
    }

    // create and start the container
    if err = c.create(ctx); err != nil {
        return nil, err
    }

    // Add the container to the containers list in the sandbox.
    if err = s.addContainer(c); err != nil {
        return nil, err
    }
    // ...
}

而 c.create() 的实现就比较详细了。(但!!,后面讲 kataAgent 再说)

4.2.2.3. kataAgent

virtcontainer 的 kataAgent 是一个 kata agent 的 proxy,因为真正的 kata-agent 代码是在 src/agent 目录下的,是一个独立运行在每个 vm 内的一个进程,所有容器的操作最终都会由 vm 内的 kata-agent 来完成。

因此,为了实现9和 vm 内的 kata-agent 通信,virtcontainer 实现了一个简单的 grpc 代理,叫 kataAgent

上面我们看到的所有 Sandbox 定义里容器相关的操作,最终都会转换成对 kata-agent 的 sendReq grpc 调用

func (k *kataAgent) createContainer(ctx context.Context, sandbox *Sandbox, c *Container) (p *Process, err error) {
    k.buildContainerRootfs(ctx, sandbox, c, rootPathParent)
    c.mountSharedDirMounts(ctx, sharedDirMounts, ignoredMounts)
    k.handleShm(ociSpec.Mounts, sandbox)
    k.handleEphemeralStorage(ociSpec.Mounts)
    k.handleLocalStorage(ociSpec.Mounts, sandbox.id, c.rootfsSuffix)
    k.replaceOCIMountSource(ociSpec, sharedDirMounts)
    k.removeIgnoredOCIMount(ociSpec, ignoredMounts)
    k.appendDevices(ctrDevices, c)
    k.handleBlockVolumes(c)
    k.replaceOCIMountsForStorages(ociSpec, volumeStorages)
    k.constrainGRPCSpec(grpcSpec, passSeccomp, sandbox.config.VfioMode == config.VFIOModeGuestKernel)

    req := &grpc.CreateContainerRequest{
        ContainerId:  c.id,
        ExecId:       c.id,
        Storages:     ctrStorages,
        Devices:      ctrDevices,
        OCI:          grpcSpec,
        SandboxPidns: sharedPidNs,
    }

    if _, err = k.sendReq(ctx, req); err != nil {
        return nil, err
    }
}

笔者注:不过细看 kataAgent 的实现的时候,我觉得 kataAgent 和 Container 类的边界不是很清晰,kataAgent 其实做了很多 Container 负责的工作,比如构建容器的 rootfs 之类的,这些实现其实可以完全移出来,放到 Container 类里面

4.3. Hypervisor

kata-containers 目前支持 2种hypervisor,一种是最常见的qemu,一种是fc(firecracker)

Hypervisor 类是一个 interface{},定义了要实现一个完整的 vmm,所必要的全部接口能力。不管是fc还是qemu,都需要实现这些接口

贴几个最核心的API

// hypervisor is the virtcontainers hypervisor interface.
// The default hypervisor implementation is Qemu.
type Hypervisor interface {
        // 创建、启动、停止    
    CreateVM(ctx context.Context, id string, networkNS NetworkNamespace, hypervisorConfig *HypervisorConfig) error
    StartVM(ctx context.Context, timeout int) error
    StopVM(ctx context.Context, waitOnly bool) error
        // 暂停、恢复              

    PauseVM(ctx context.Context) error
    ResumeVM(ctx context.Context) error
        // 设备热插拔         

    AddDevice(ctx context.Context, devInfo interface{}, devType DeviceType) error
    HotplugAddDevice(ctx context.Context, devInfo interface{}, devType DeviceType) (interface{}, error)
    HotplugRemoveDevice(ctx context.Context, devInfo interface{}, devType DeviceType) (interface{}, error)
    ResizeMemory(ctx context.Context, memMB uint32, memoryBlockSizeMB uint32, probe bool) (uint32, MemoryDevice, error)
    ResizeVCPUs(ctx context.Context, vcpus uint32) (uint32, uint32, error)

    // generate the socket to communicate the host and guest
    GenerateSocket(id string) (interface{}, error)
}

4.3.1. qemu

qemu 是最常见的 hypervisor,qemu 相关的代码全部在 virtcontainers/qemu*.go 代码里

不过这里的 qemu 只是一个壳,是对原生 qemu 组件的一个 rpc 封装,不真的去写一坨虚拟化相关的代码。启停虚机直接调的是 qemu-system-x86_64 二进制工具,设备热插拔是通过 qmp 协议和 qemu-system-x86_64 进程通信来完成的

qemu-system-x86_64 是 qemu 在 64 位架构上的实现,也是使用最广泛的。kata还支持 amd64 arm64 s390 等其他常见的硬件平台

qemu 结构:

// qemu is an Hypervisor interface implementation for the Linux qemu hypervisor.
// nolint: govet
type qemu struct {
    arch qemuArch
    virtiofsd Virtiofsd 
    store persistapi.PersistDriver 
    state QemuState
    qmpMonitorCh qmpChannel
}

核心就那么几个:

  1. qemuArch:前面提到 kata-containers 支持多种体系结构,x86 x64 arm s390。但是绝大部分体系架构上的虚拟化实现是一样的,只有一些细微差别
  2. virtiofsd:我们说的传统意义上的虚拟化,其实说的是计算(cpu、内存)的虚拟化。qemu本身不实现 io 的虚拟化,需要依赖 virtiofsd 来完成 io 的虚拟化。虚机内所有磁盘设备的io操作,都会转发给virtiofsd来完成真正意义上的落盘操作
  3. PersistDriver:持久化,保存了qemu虚机相关的所有元数据:配置,container信息,设备信息,网络信息,等等

4.3.1. firecracker

fc是aws开源的,轻量级的虚拟化实现。基本上实现了对qemu的深度裁剪,并用rust语言重写了。

fc的结构如下:

// firecracker is an Hypervisor interface implementation for the firecracker VMM.
type firecracker struct {
    pendingDevices []firecrackerDevice // Devices to be added before the FC VM ready

    firecrackerd *exec.Cmd           //Tracks the firecracker process itself
    fcConfig     *types.FcConfig     // Parameters configured before VM starts
    connection   *client.Firecracker //Tracks the current active connection

    
}

这里的 firecrackerd 类似于 qemu_system_x86_64,每个 vm 都会起一个 firecrackerd 进程。这里的 client.Firecracker 类似于 qemu 的 qmp 通信

4.3. kata-monitor

xx

5. 容器网络

xx

6. 存储

xx

5. 其他参考

  1. shim-v2 协议:https://github.com/containerd/containerd/tree/main/runtime/v2
  2. xx
发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注