containerd 源码分析:创建 container(一)

containerd,container · 浏览次数 : 0

小编点评

本文继续介绍 containerd 如何通过 OCI 接口创建容器 container。 containerd(containerd.io)是一个高性能、基于本地缓存的容器运行时,用于简化云原生应用的部署和管理。在 Kubernetes 生态系统中,containerd 作为容器运行时,负责容器生命周期的管理。本文将深入探讨 containerd 如何通过 OCI 接口创建容器。 在 Kubernetes 中,kubelet 负责在节点上运行容器。kubelet 通过 cri 接口与 containerd 交互。在本篇文档中,我们已经了解了 kubelet 和 containerd 的交互过程,以及 containerd 的启动注册流程。 接下来,我们将分析 containerd 创建容器的过程。首先,我们需要了解 ctr,这是 containerd 的命令行客户端,可以通过它进行调试和分析。 1. ctr 是 containerd 的命令行客户端,包括一系列和 containerd 交互的命令。 命令列表: - plugins, plugin:提供关于 containerd 插件的信息 - containers, c, container:管理容器 - images, image, i:管理图像 - run:运行容器 - snapshots, snapshot:管理快照 - tasks, t, task:管理任务 - install:安装新包 - oci:OCI 工具 - shim:与 shim 直接交互 2.ctr 还包括一些子命令,例如: - containers|c|container:实际管理存储在boltDB中的容器元数据。 - tasks|t|task:实际启动容器进程。 3. 通过 ctr c create 和 ctr t start 命令组合,可以使用 ctr run 命令做为调试参数分析完整的创建容器容器的流程。 4.containerd 通过 cri 接口与 containerd 交互。在 containerd 的源码中,我们可以找到与 Kubernetes 层面不同的地方。 5.containerd 的主要组件包括: - runc:提供容器的运行时。 - containers service:处理容器的创建、销毁等操作。 - metadata:提供容器的元数据服务。 6.containerd 通过 local 对象的 Create 方法创建容器。 local 对象是 containerd 的一个插件实例。 7.containerd 的 local 插件实例负责创建容器,具体实现是通过 store.Update 方法进行的。 8.containerd 的 store 更新方法是通过 db 对象的 Update 方法进行操作的。db 是用来保存 container metadata 的数据库。 综上所述,containerd 通过OCI接口创建容器的过程主要包括以下几个方面: 1. 使用 ctr 命令创建容器。 2. containerd 通过 local 插件实例的 Create 方法创建容器。 3. 创建过程中,使用 store.Update 方法更新容器元数据。 4. 最后,通过 containerd 的 clients 创建容器运行时,启动容器进程。 通过以上步骤,我们可以深入了解 containerd 如何通过 OCI 接口创建和管理容器,以及它与 kubelet 之间的交互过程。

正文


0. 前言

Kubernetes:kubelet 源码分析之 pod 创建流程 介绍了 kubelet 创建 pod 的流程,containerd 源码分析:kubelet 和 containerd 交互
介绍了 kubelet 通过 cri 接口和 containerd 交互的过程,containerd 源码分析:启动注册流程 介绍了 containerd 作为高级容器运行时的启动流程。通过这三篇文章熟悉了 kubelet 和 containerd 的行为,对于 containerd 如何通过 OCI 接口创建容器 container 并没有涉及。

image

本文将继续介绍 containerd 是如何创建容器 container 的。

1. ctr

在介绍创建容器前,首先简单介绍下 ctr。ctr 是 containerd 的命令行客户端,本文会通过 ctr 进行调试和分析。

1.1 ctr CLI

作为命令行工具 ctr 包括一系列和 containerd 交互的命令。主要命令如下:

COMMANDS:
   plugins, plugin            provides information about containerd plugins
   containers, c, container   manage containers
   images, image, i           manage images
   run                        run a container
   snapshots, snapshot        manage snapshots
   tasks, t, task             manage tasks
   install                    install a new package
   oci                        OCI tools
   shim                       interact with a shim directly

containers|c|container

不同与 Kubernetes 层面的 container,这里 ctr 命令管理的 containers 实际是管理存储在 boltDB 中的 container metadata。

创建 container:

# ctr c create docker.io/library/nginx:alpine nginx1
# ctr c ls
CONTAINER    IMAGE                             RUNTIME
nginx1       docker.io/library/nginx:alpine    io.containerd.runc.v2

通过 boltbrowser 查看 boltDB 存储的 container metadata,container metadata 存储在目录 /var/lib/containerd/io.containerd.metadata.v1.bolt。

image

tasks|t|task

task 是实际启动容器进程的命令,ctr task start 根据创建的 container 启动容器:

# ctr t start nginx1
/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
...

run

ctr 的 run 命令,实际是 ctr c create 和 ctr t start 命令的组合。

接下来,使用 ctr run 命令做为调试参数分析完整的创建 container 容器的流程。

1.2 ctr 调试

ctr 代码集中在 containerd 项目中,配置 ctr 的调试参数:

{
   "version": "0.2.0",
   "configurations": [
      {
         "name": "ctr",
         "type": "go",
         "request": "launch",
         "mode": "auto",
         "program": "${fileDirname}",
         "args": ["run", "docker.io/library/nginx:alpine", "nginx1"]
      }
   ]
}

调试 ctr:

image

进入 run.Command 看其中做了什么。

// containerd/cmd/ctr/commands/run/run.go
// Command runs a container
var Command = &cli.Command{
	Name:      "run",
	Usage:     "Run a container",
   ...
   Action: func(context *cli.Context) error {
      ...
      // step1: 创建访问 containerd 的 client
      client, ctx, cancel, err := commands.NewClient(context)
		if err != nil {
			return err
		}
		defer cancel()

      // step2: 创建 container
      container, err := NewContainer(ctx, client, context)
		if err != nil {
			return err
		}
      ...

      opts := tasks.GetNewTaskOpts(context)
		ioOpts := []cio.Opt{cio.WithFIFODir(context.String("fifo-dir"))}
      // step3: 创建 task
		task, err := tasks.NewTask(ctx, client, container, context.String("checkpoint"), con, context.Bool("null-io"), context.String("log-uri"), ioOpts, opts...)
		if err != nil {
			return err
		}

      ...
      // step4: 启动 task
      if err := task.Start(ctx); err != nil {
			return err
		}
      ...
   }
}

在 NewContainer 中根据 client 创建 container。接着根据 container 创建 task,然后启动该 task 来启动容器。

1.2.1 创建 container

进入 NewContainer:

// containerd/cmd/ctr/commands/run/run_unix.go
func NewContainer(ctx gocontext.Context, client *containerd.Client, context *cli.Context) (containerd.Container, error) {
   ...
   return client.NewContainer(ctx, id, cOpts...)
}

// containerd/client/client.go
func (c *Client) NewContainer(ctx context.Context, id string, opts ...NewContainerOpts) (Container, error) {
   ...
   container := containers.Container{
		ID: id,
		Runtime: containers.RuntimeInfo{
			Name: c.runtime,
		},
	}
   ...
   // 调用 containerd 接口创建 container
   r, err := c.ContainerService().Create(ctx, container)
	if err != nil {
		return nil, err
	}
	return containerFromRecord(c, r), nil
}

重点在 Client.ContainerService().Create:

// containerd/client/containerstore.go
func (r *remoteContainers) Create(ctx context.Context, container containers.Container) (containers.Container, error) {
	created, err := r.client.Create(ctx, &containersapi.CreateContainerRequest{
		Container: containerToProto(&container),
	})
	if err != nil {
		return containers.Container{}, errdefs.FromGRPC(err)
	}

	return containerFromProto(created.Container), nil
}

// containerd/api/services/containers/v1/containers_grpc.pb.go
func (c *containersClient) Create(ctx context.Context, in *CreateContainerRequest, opts ...grpc.CallOption) (*CreateContainerResponse, error) {
	out := new(CreateContainerResponse)
	err := c.cc.Invoke(ctx, "/containerd.services.containers.v1.Containers/Create", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

调用 /containerd.services.containers.v1.Containers/Create grpc 接口创建 container。container 并不是容器进程,而是存储在数据库中的 container metadata。

/containerd.services.containers.v1.Containers/Create 是由 containerd 的 io.containerd.grpc.v1.containers 插件提供的服务:

// containerd/plugins/services/service.go
func (s *service) Create(ctx context.Context, req *api.CreateContainerRequest) (*api.CreateContainerResponse, error) {
	return s.local.Create(ctx, req)
}

插件实例调用 local 对象的 Create 方法创建 container。查看 local 对象具体指的什么。

// containerd/plugins/services/service.go
func init() {
	registry.Register(&plugin.Registration{
		Type: plugins.GRPCPlugin,
		ID:   "containers",
		Requires: []plugin.Type{
			plugins.ServicePlugin,
		},
		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
         // plugins.ServicePlugin:io.containerd.service.v1
         // services.ContainersService:containers-service
			i, err := ic.GetByID(plugins.ServicePlugin, services.ContainersService)
			if err != nil {
				return nil, err
			}
			return &service{local: i.(api.ContainersClient)}, nil
		},
	})
}

local 对象是 containerd 的 io.containerd.service.v1.containers-service 插件的实例。查看该实例的 Create 方法。

// containerd/plugins/services/containers/local.go
func (l *local) Create(ctx context.Context, req *api.CreateContainerRequest, _ ...grpc.CallOption) (*api.CreateContainerResponse, error) {
	var resp api.CreateContainerResponse

	if err := l.withStoreUpdate(ctx, func(ctx context.Context) error {
		container := containerFromProto(req.Container)

		created, err := l.Store.Create(ctx, container)
		if err != nil {
			return err
		}

		resp.Container = containerToProto(&created)

		return nil
	}); err != nil {
		return &resp, errdefs.ToGRPC(err)
	}
	...

	return &resp, nil
}

local.Create 调用 local.withStoreUpdate 方法创建 container。

// containerd/plugins/services/containers/local.go
func (l *local) withStoreUpdate(ctx context.Context, fn func(ctx context.Context) error) error {
	return l.db.Update(l.withStore(ctx, fn))
}

local.withStoreUpdate 调用 db 对象的 Update 方法创建 container。

// containerd/plugins/services/containers/local.go
func init() {
	registry.Register(&plugin.Registration{
		...
		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
			m, err := ic.GetSingle(plugins.MetadataPlugin)
			if err != nil {
				return nil, err
			}
			ep, err := ic.GetSingle(plugins.EventPlugin)
			if err != nil {
				return nil, err
			}

			db := m.(*metadata.DB)
			return &local{
				Store:     metadata.NewContainerStore(db),
				db:        db,
				publisher: ep.(events.Publisher),
			}, nil
		},
	})
}

db 对象是 io.containerd.metadata.v1 插件的实例,该插件通过 boltDB 提供 metadata 存储服务。

metadata 插件实际调用的是匿名函数 fn 的内容,在 fn 中通过 l.Store.Create(ctx, container) 将 container 的 metadata 信息注册到 boltDB 数据库中。

创建 container 的过程实际是将 container 信息注册到 boltDB 的过程。


与containerd 源码分析:创建 container(一)相似的内容:

containerd 源码分析:创建 container(一)

0. 前言 Kubernetes:kubelet 源码分析之 pod 创建流程 介绍了 kubelet 创建 pod 的流程,containerd 源码分析:kubelet 和 containerd 交互 介绍了 kubelet 通过 cri 接口和 containerd 交互的过程,contain

containerd 源码分析:创建 container(三)

文接 containerd 源码分析:创建 container(二) 1.2.2.2 启动 task 上节介绍了创建 task,task 创建之后将返回 response 给 ctr。接着,ctr 调用 task.Start 启动容器。 // containerd/client/task.go fu

containerd 源码分析:kubelet 和 containerd 交互

0. 前言 Kubernetes:kubelet 源码分析之创建 pod 流程 介绍了 kubelet 创建 pod 的流程,其中介绍了 kubelet 调用 runtime cri 接口创建 pod。containerd 源码分析:启动注册流程 介绍了 containerd 作为一种行业标准的高级

containerd 源码分析:启动注册流程

0. 前言 containerd 是一个行业标准的容器运行时,其强调简单性、健壮性和可移植性。本文将从 containerd 的代码结构入手,查看 containerd 的启动注册流程。 1. 启动注册流程 1.1 containerd 首先以调试模式运行 containerd: // contai

Tomcat处理http请求之源码分析

本文将从请求获取与包装处理、请求传递给Container、Container处理请求流程,这3部分来讲述一次http穿梭之旅。

[转帖]【k8s】二、containerd的安装

目录 前言 安装containerd 解压安装 配置成systemd任务 安装runc ​编辑 安装cni 配置containerd镜像源 containerd基本使用 拓展阅读 nerdctl工具安装及使用 整体脚本 总结 写在后面 前言 上一篇文章,我们介绍了虚拟机的基础环境以及基础的网络配置,

Dubbo架构设计与源码解析(一) 架构设计

作者:黄金 一、架构演变 单应用架构 > 垂直架构 > 分布式架构 > 微服务架构 > 云原生架构   二、Dubbo总体架构   1、角色职能 • Container:服务容器 (tomcat、jetty、weblogic) • Provider:服务提供者 •Consumer:服务消

opengrok源代码在线阅读平台搭建及字体修改

服务搭建 我所编写的docker-compose.yml如下,成功运行后将源码目录移动至 /data/opengrok/src ,重启容器使得opengrok快速更新索引 services: opengrok: container_name: opengrok # 1.6版本在使用中还算稳定 ima

[转帖]containerd_v1.6.0+nerdctl+buildkit 二进制安装,支持多CPU并发构建

一、安装containerd # yum install libseccomp -y #下载containerd curl -L https://github.com/containerd/containerd/releases/download/v1.6.0/cri-containerd-cni-

[转帖]Containerd安装与使用

https://www.cnblogs.com/punchlinux/p/16496094.html Containerd 的技术方向和目标 简洁的基于 gRPC 的 API 和 client library 完整的 OCI 支持(runtime 和 image spec) 同时具备稳定性和高性能的