【containerd 源码分析】containerd cri 启动注册流程源码分析

本文深入探讨了Kubernetes的Container Runtime Interface (CRI)如何与Containerd集成,实现容器和镜像管理。通过CRI插件,Containerd提供了一种标准化的方法来支持多种容器运行时,同时保持与Kubernetes的兼容性。
摘要由CSDN通过智能技术生成

 

 

CRI

      CRI(Container Runtime Interface)是 Kubernetes 定义的与 contianer runtime 进行交互的接口,将 Kubernetes 与特定的容器解耦。Kubernetes早期的版本,对于容器环境的支持是通过 hard code 方式直接调用 Docker API,支持更多的容器运行时和更精简的容器运行时,Kubernetes 提出了CRI。

 

 

 

    Containerd 在 1.0 及以前版本将 dockershim 和 docker daemon 替换为 cri-containerd + containerd,在 1.1 版本直接将 cri-containerd 内置在 Containerd 中,作为一个 CRI 插件


     Containerd 内置的 CRI 插件实现了 Kubelet CRI 接口中的 Image Service 和 Runtime Service,通过内部接口管理容器和镜像,调用 CNI 插件给 Pod 配置网络

// grpcServices are all the grpc services provided by cri containerd.
type grpcServices interface {
   runtime.RuntimeServiceServer
   runtime.ImageServiceServer
}

 

1. init 函数注册 CRI 服务插件

     路径 github.com/containerd/cri/cri.go,注册 CRI 服务插件,类型为 io.containerd.grpc.v1,ID 为 cri

# ctr plugins ls
TYPE                            ID                    PLATFORMS      STATUS    
io.containerd.content.v1        content               -              ok        
io.containerd.grpc.v1           version               -              ok        
io.containerd.grpc.v1           cri                   linux/amd64    ok

// Register CRI service plugin
func init() {
	config := criconfig.DefaultConfig()
	plugin.Register(&plugin.Registration{
		Type:   plugin.GRPCPlugin,
		ID:     "cri",
		Config: &config,
		Requires: []plugin.Type{
			plugin.ServicePlugin,
		},
		InitFn: initCRIService,
	})
}

     1.1 initCRIService 函数

      配置信息,配置文件 /etc/containerd 目录下 config.toml

  • Snapshotter:overlayfs
  • DefaultRuntimeName:runc
  • ContainerdRootDir:/var/lib/containerd
  • ContainerdEndpoint:/run/containerd/containerd.sock
  • RootDir:/var/lib/containerd/io.containerd.grpc.v1.cri
  • StateDir:/run/containerd/io.containerd.grpc.v1.cri
func initCRIService(ic *plugin.InitContext) (interface{}, error) {
	ic.Meta.Platforms = []imagespec.Platform{platforms.DefaultSpec()}
	ic.Meta.Exports = map[string]string{"CRIVersion": constants.CRIVersion}
	ctx := ic.Context
	pluginConfig := ic.Config.(*criconfig.PluginConfig)
	c := criconfig.Config{
		PluginConfig:       *pluginConfig,
		ContainerdRootDir:  filepath.Dir(ic.Root),
		ContainerdEndpoint: ic.Address,
		RootDir:            ic.Root,
		StateDir:           ic.State,
	}

     1.1.1 getServicesOpts

     getServicesOpts 函数从 plugin 上下文获取服务选项,类型为 io.containerd.service.v1,这个时实现了内部的服务

// getServicesOpts get service options from plugin context.
func getServicesOpts(ic *plugin.InitContext) ([]containerd.ServicesOpt, error) {
	plugins, err := ic.GetByType(plugin.ServicePlugin)
	if err != nil {
		return nil, errors.Wrap(err, "failed to get service plugin")
	}

     1.1.1.1 定义 map 一系列服务

  • content-service
  • images-service
  • snapshots-service
  • containers-service
  • tasks-service
  • diff-service
  • namespaces-service
  • leases-service
for s, fn := range map[string]func(interface{}) containerd.ServicesOpt{
	services.ContentService: func(s interface{}) containerd.ServicesOpt {
		return containerd.WithContentStore(s.(content.Store))
	},
	services.ImagesService: func(s interface{}) containerd.ServicesOpt {
		return containerd.WithImageService(s.(images.ImagesClient))
	},
	services.SnapshotsService: func(s interface{}) containerd.ServicesOpt {
		return containerd.WithSnapshotters(s.(map[string]snapshots.Snapshotter))
	},
	services.ContainersService: func(s interface{}) containerd.ServicesOpt {
		return containerd.WithContainerService(s.(containers.ContainersClient))
	},
	services.TasksService: func(s interface{}) containerd.ServicesOpt {
		return containerd.WithTaskService(s.(tasks.TasksClient))
	},
	services.DiffService: func(s interface{}) containerd.ServicesOpt {
		return containerd.WithDiffService(s.(diff.DiffClient))
	},
	services.NamespacesService: func(s interface{}) containerd.ServicesOpt {
		return containerd.WithNamespaceService(s.(namespaces.NamespacesClient))
	},
	services.LeasesService: func(s interface{}) containerd.ServicesOpt {
		return containerd.WithLeasesService(s.(leases.Manager))
	},
}

      1.1.2 containerd.New 函数

      路径 github.com/containerd/containerd/client.go,创建一个 client 连接到 containerd 实例

client, err := containerd.New(
	"",
	containerd.WithDefaultNamespace(constants.K8sContainerdNamespace),
	containerd.WithDefaultPlatform(criplatforms.Default()),
	containerd.WithServices(servicesOpts...),
)

      1.1.3 NewCRIService 函数实例化 criService 

// NewCRIService returns a new instance of CRIService
func NewCRIService(config criconfig.Config, client *containerd.Client) (CRIService, error) {
	var err error
	c := &criService{
		config:             config,
		client:             client,
		os:                 osinterface.RealOS{},
		sandboxStore:       sandboxstore.NewStore(),
		containerStore:     containerstore.NewStore(),
		imageStore:         imagestore.NewStore(client),
		snapshotStore:      snapshotstore.NewStore(),
		sandboxNameIndex:   registrar.NewRegistrar(),
		containerNameIndex: registrar.NewRegistrar(),
		initialized:        atomic.NewBool(false),
	}

     实现了接口 CRIService,包括如下:定义 pkg/server/services.go

// CRIService is the interface implement CRI remote service server.
type CRIService interface {
   Run() error
   // io.Closer is used by containerd to gracefully stop cri service.
   io.Closer
   plugin.Service
   grpcServices
}

     grpcServices 实现了接口 Runtime 和 image 服务

// grpcServices are all the grpc services provided by cri containerd.
type grpcServices interface {
   runtime.RuntimeServiceServer
   runtime.ImageServiceServer
}

 

2. Run 函数启动 CRI 服务

// Run starts the CRI service.
func (c *criService) Run() error {
	logrus.Info("Start subscribing containerd event")
	c.eventMonitor.subscribe(c.client)

	logrus.Infof("Start recovering state")
	if err := c.recover(ctrdutil.NamespacedContext()); err != nil {
		return errors.Wrap(err, "failed to recover state")
	}

	// Start event handler.
	logrus.Info("Start event monitor")
	eventMonitorErrCh := c.eventMonitor.start()

    2.1 recover 函数

     当 CRI down 掉重启进行恢复工作

// NOTE: The recovery logic has following assumption: when the cri plugin is down:
// 1) Files (e.g. root directory, netns) and checkpoint maintained by the plugin MUST NOT be
// touched. Or else, recovery logic for those containers/sandboxes may return error.
// 2) Containerd containers may be deleted, but SHOULD NOT be added. Or else, recovery logic
// for the newly added container/sandbox will return error, because there is no corresponding root
// directory created.
// 3) Containerd container tasks may exit or be stoppped, deleted. Even though current logic could
// tolerant tasks being created or started, we prefer that not to happen.

// recover recovers system state from containerd and status checkpoint.
func (c *criService) recover(ctx context.Context) error {

     2.1.1 对所有 sandbox 进行恢复

    client.Containers 列出所有 sandbox 的容器,根据 lable io.cri-containerd.kind = sandbox

    loadSandbox 加载 sandbox 的 metadata,NewSandbox 实例化 Sandbox 包括 metadata,status, 网络 namespace

    sandboxStore.Add 把 sandbox 加入到缓存中

// Recover all sandboxes.
sandboxes, err := c.client.Containers(ctx, filterLabel(containerKindLabel, containerKindSandbox))
	if err != nil {
		return errors.Wrap(err, "failed to list sandbox containers")
	}

for _, sandbox := range sandboxes {
	sb, err := c.loadSandbox(ctx, sandbox)
	if err != nil {
		log.G(ctx).WithError(err).Errorf("Failed to load sandbox %q", sandbox.ID())
		continue
	}
	log.G(ctx).Debugf("Loaded sandbox %+v", sb)
	if err := c.sandboxStore.Add(sb); err != nil {
		return errors.Wrapf(err, "failed to add sandbox %q to store", sandbox.ID())
	}
	if err := c.sandboxNameIndex.Reserve(sb.Name, sb.ID); err != nil {
		return errors.Wrapf(err, "failed to reserve sandbox name %q", sb.Name)
	}
}

     2.1.2 恢复所有 container

     列出所有 container,根据 label io.cri-containerd.kind = container 过滤

// Recover all containers.
containers, err := c.client.Containers(ctx, filterLabel(containerKindLabel, containerKindContainer))
if err != nil {
	return errors.Wrap(err, "failed to list containers")
}
for _, container := range containers {
	cntr, err := c.loadContainer(ctx, container)
	if err != nil {
		log.G(ctx).WithError(err).Errorf("Failed to load container %q", container.ID())
		continue
	}
	log.G(ctx).Debugf("Loaded container %+v", cntr)
	if err := c.containerStore.Add(cntr); err != nil {
		return errors.Wrapf(err, "failed to add container %q to store", container.ID())
	}
	if err := c.containerNameIndex.Reserve(cntr.Name, cntr.ID); err != nil {
		return errors.Wrapf(err, "failed to reserve container name %q", cntr.Name)
	}
}

     2.1.3 恢复所有 image,加载到 cache

// Recover all images.
cImages, err := c.client.ListImages(ctx)
if err != nil {
	return errors.Wrap(err, "failed to list images")
}
c.loadImages(ctx, cImages)

 

containerd是一个用于管理Linux容器的开源守护程序,它在Kubernetes等容器编排系统中扮演着非常重要的角色。而CRIContainer Runtime Interface)是用于与Kubernetes API交互的标准化接口,与容器运行时进行通信。 要安装containerd CRI,我们需要按照以下步骤进行操作: 1. 安装依赖项:首先,我们需要安装一些依赖项,包括操作系统所需的软件包和工具。这包括golang的安装,以及编译containerd所需的build-essential和git等软件包。 2. 下载containerd源代码:接下来,我们需要从containerd的GitHub仓库中下载源代码。可以使用git命令克隆仓库或者下载源代码的压缩包。 3. 构建和安装containerd:进入containerd源码的根目录,执行make命令进行构建。构建完成后,可以使用make install命令将containerd安装到系统中。安装完成后,可以使用containerd命令进行验证。 4. 配置containerd:在安装containerd之后,我们需要进行一些配置。可以通过编辑containerd的配置文件,通常位于/etc/containerd/config.toml,来进行配置。该文件包含了containerd的各种配置选项,如默认的Runtime类型和镜像存储位置等。 5. 配置CRI使用containerd:接下来,我们需要配置Kubernetes使用containerd作为其CRI。可以通过编辑kubelet的配置文件,通常位于/etc/kubernetes/kubelet.conf,来进行配置。在该配置文件中,可以指定containerd的地址和其他相关选项。 6. 重启服务并验证:完成上述配置后,需要重启kubelet和containerd服务。重启后,可以使用kubectl命令验证Kubernetes是否成功使用containerd作为其CRI。 通过以上步骤,我们可以完成containerd CRI的安装和配置。这将使得Kubernetes能够正常工作并管理容器的生命周期。同时,containerd作为高效、稳定和可扩展的容器运行时,也能够提供更好的容器管理和资源利用效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值