containerd系统分析(一)-系统组成-CSDN博客
containerd系统分析(二)-镜像管理-CSDN博客
containerd系统分析(三)-容器创建流程分析-CSDN博客
containerd系统分析(四)-容器启动流程分析-CSDN博客
containerd系统分析(五)-网络分析-CSDN博客
containerd系统分析(六)-CRI接口-CSDN博客
containerd实现了CRI接口,上层应用通过CRI接口调用containerd的容器服务。
cri创建sandbox总结如下:containerd通过内置CRI插件,实现了gprc的接口/runtime.v1.RuntimeService/RunPodSandbox。
这个接口实现了sandbox容器的创建和启动。
RunPodSandbox负责sandbox创建,最终是通过类似于ctr调用containerd的container接口进行容器创建,通过task接口创建任务启动、启动任务,创建网络等操作,在一系列操作完成后,sandbox就可以用了。
具体流程如下:
//构建sandbox内存实体
sandbox := sandboxstore.NewSandbox(
sandboxstore.Metadata{
ID: id,
Name: name,
Config: config,
//这个runtimeHandler是由k8s创建pod时用户指定的runtimeclass,也有可能是空的
RuntimeHandler: r.GetRuntimeHandler(),
},
sandboxstore.Status{
State: sandboxstore.StateUnknown,
},
)
image, err := c.ensureImageExists(ctx, c.config.SandboxImage, config)
sandbox.NetNS, err = netns.NewNetNS(netnsMountDir)
sandbox.NetNS, err = netns.NewNetNS(netnsMountDir)
specOpts = append(specOpts, oci.WithLinuxNamespace(
runtimespec.LinuxNamespace{
Type: runtimespec.NetworkNamespace,
Path: nsPath,
}))
opts := []containerd.NewContainerOpts{
//指定snapshotter
containerd.WithSnapshotter(c.runtimeSnapshotter(ctx, ociRuntime)),
//创建一个新的snapshot
customopts.WithNewSnapshot(id, containerdImage, snapshotterOpt),
//设定container.spec
containerd.WithSpec(spec, specOpts...),
//设定container.label
containerd.WithContainerLabels(sandboxLabels),
containerd.WithContainerExtension(sandboxMetadataExtension, &sandbox.Metadata),
//设定运行时的信息
containerd.WithRuntime(ociRuntime.Type, runtimeOpts)}
//root dir在/var/lib/containerd/io.containerd.grpc.v1.cri/{id}下
sandboxRootDir := c.getSandboxRootDir(id)
//stateDir, /run/containerd/io.containerd.grpc.v1.cri/sandboxes/{id}
//config.StateDir的生成规则和rootDir的一样的,都是在插件注册的时候生成,由{stateRoot}/{plugintype=io.containerd.grpc.v1}.{ID=cri}
//stateDir放的是运行时的信息
volatileSandboxRootDir := c.getVolatileSandboxRootDir(id)
taskOpts := c.taskOpts(ociRuntime.Type)
task, err := container.NewTask(ctx, containerdio.NullIO, taskOpts...)
nric, err := nri.New()
if err != nil {
return nil, fmt.Errorf("unable to create nri client: %w", err)
}
if nric != nil {
nriSB := &nri.Sandbox{
ID: id,
Labels: config.Labels,
}
//处理处于创建状态的sandbox task
if _, err := nric.InvokeWithSandbox(ctx, task, v1.Create, nriSB); err != nil {
return nil, fmt.Errorf("nri invoke: %w", err)
}
}
cri实现了/runtime.v1.RuntimeService/CreateContainer接口
这里实现创建容器,与普通创建容器不同的,是这里创建容器在设置spec时,要共享sandbox的网络配置、host信息。
流程如下:
cri实现/runtime.v1.RuntimeService/StartContainer,执行动作是调用了containerd的newTask和startTask
详细分析如下:
containerd在创建任务时,与普通的容器创建任务不同的是,在创建shim时,要用前面sandbox的shim socket去创建容器,其它的都相同,也就是得复用了之前sandbox的shim,毕竟那个shim已经运行在那了,直接用就行了。构建创建容器的报文,指定好bundle和相关的参数。
// This container belongs to sandbox which supposed to be already started via sandbox API.
if opts.SandboxID != "" {
process, err := m.Get(ctx, opts.SandboxID)
if err != nil {
return nil, fmt.Errorf("can't find sandbox %s", opts.SandboxID)
}
// Write sandbox ID this task belongs to.
if err := os.WriteFile(filepath.Join(bundle.Path, "sandbox"), []byte(opts.SandboxID), 0600); err != nil {
return nil, err
}
address, err := shimbinary.ReadAddress(filepath.Join(m.state, process.Namespace(), opts.SandboxID, "address"))
if err != nil {
return nil, fmt.Errorf("failed to get socket address for sandbox %q: %w", opts.SandboxID, err)
}
// Use sandbox's socket address to handle task requests for this container.
if err := shimbinary.WriteAddress(filepath.Join(bundle.Path, "address"), address); err != nil {
return nil, err
}
shim, err := loadShim(ctx, bundle, func() {})
if err != nil {
return nil, fmt.Errorf("failed to load sandbox task %q: %w", opts.SandboxID, err)
}
if err := m.shims.Add(ctx, shim); err != nil {
return nil, err
}
return shim, nil
}
containerd实现的cri插件实现了接口/runtime.v1.ImageService/PullImage进行地镜像的下载
/runtime.v1.ImageService/RemoveImage进行地镜像的下载
具体的实现方式与ctr image pull /ctr image rm
镜像操作是一样的
containerd的CRI接口实现的内容大体上和ctr实现的内容差不多,但在普通容器的启动与ctr的实现有所区别。containerd遵循CRI实现时,实际上是按pod的维度进行实现,每个pod有一个infra容器,也就是sandbox,在启动普通容器时,实际上是每个pod有一个shim,这一点和ctr创建的容器有明显的区别。