0. 前言

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

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

1. ctr

在介绍创建容器前,首先简单介绍下 ctrctrcontainerd 的命令行客户端,本文会通过 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

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 createctr 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

进入 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 是由 containerdio.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 对象是 containerdio.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(一)的更多相关文章

  1. Spring AOP 源码分析 - 创建代理对象

    1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...

  2. Spring IOC 容器源码分析 - 创建原始 bean 对象

    1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续.在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程.本篇文章,我们就从战术的层面上,详细分析doCreat ...

  3. Spring IOC 容器源码分析 - 创建单例 bean 的过程

    1. 简介 在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)的实现逻辑.对于已实例化好的单例 bean,getBean(String) 方法并不会再一次去 ...

  4. 【LiteOS】LiteOS任务篇-源码分析-创建任务函数

    目录 前言 链接 参考 笔录草稿 部分源码分析 源码分析 LOS_TaskCreate函数 LOS_TaskCreateOnly函数 宏 OS_TCB_FROM_PENDLIST 和 宏 LOS_DL ...

  5. Netty源码分析--创建Channel(三)

    恩~,没错,其实这一篇才是真正的开始分析源码,你打我呀~. 先看一下我Netty的启动类 private void start() throws Exception { EventLoopGroup ...

  6. Spring IOC 容器源码分析系列文章导读

    1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本.经过十几年的迭代,现在的 Spring 框架已经非常成熟了.Spring ...

  7. Spring AOP 源码分析 - 拦截器链的执行过程

    1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...

  8. 一步步实现windows版ijkplayer系列文章之三——Ijkplayer播放器源码分析之音视频输出——音频篇

    一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...

  9. Spring AOP 源码分析 - 筛选合适的通知器

    1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...

  10. Spring AOP 源码分析系列文章导读

    1. 简介 前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解.在写完 Spring IOC 容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了3天时间阅 ...

随机推荐

  1. c# Barrier 线程回调

    前言 假如现在有一个这样的需求,我一堆小黄人生产小黄丹,而大黄人要一直吃小黄丹. 如果是这样的话,想到就是一堆小黄人作为一个多线程,然后一直制造,然后另外一个大黄人一直检索是否有小黄丹,有就吃掉. 但 ...

  2. 动手实现自己的http服务器【精简版】

    1 package v2; 2 3 import java.io.IOException; 4 import java.io.OutputStream; 5 import java.io.PrintS ...

  3. NL2SQL实践系列(1):深入解析Prompt工程在text2sql中的应用技巧

    NL2SQL实践系列(1):深入解析Prompt工程在text2sql中的应用技巧 NL2SQL基础系列(1):业界顶尖排行榜.权威测评数据集及LLM大模型(Spider vs BIRD)全面对比优劣 ...

  4. Webpack中常见的Loader?解决了什么问题?

    一.是什么 loader 用于对模块的源代码进行转换,在 import 或"加载"模块时预处理文件 webpack做的事情,仅仅是分析出各种模块的依赖关系,然后形成资源列表,最终打 ...

  5. 1小时打造HaaS版小小蛮驴智能车

    简介: 2020年云栖大会上,阿里云发布了一款机器人"小蛮驴",瞬间激起千层浪,无人车,智能物流,机器人等一些概念又火热了一把. 借"小蛮驴"的东风以及火热的H ...

  6. 喜马拉雅 Apache RocketMQ 消息治理实践

    ​简介:本文通过喜马拉雅的RocketMQ治理实践分享,让大家了解使用消息中间件过程中可能遇到的问题,避免实战中踩坑. 作者:曹融,来自喜马拉雅,从事微服务和消息相关中间件开发. ​ 本文通过喜马拉雅 ...

  7. Maxcompute造数据-方法详解

    简介: 造一点模拟数据的方法 概述 造数据在一些奇怪的场合会被用到.一般我们是先有数据才有基于数据的应用场合,但是反过来如果应用拿到另外一个场景,没有数据功能是没有方法演示的.一般较为真实的数据,脱敏 ...

  8. 学术顶会再突破!计算平台MaxCompute论文入选国际顶会VLDB 2021

    ​ 简介: VLDB 2021上,阿里云计算平台MaxCompute参与的论文入选,核心分布式调度执行引擎Fangorn.基于TVR Cost模型的通用增量计算优化器框架Tempura等分别被Indu ...

  9. dotnet 6 在 System.Text.Json 使用 source generation 源代码生成提升 JSON 序列化性能

    这是一个在 dotnet 6 早就引入的功能,此功能的使用方法能简单,提升的效果也很棒.使用的时候需要将 Json 序列化工具类换成 dotnet 运行时自带的 System.Text.Json 进行 ...

  10. Partition和ReduceTask的关系

    先看源码: numPartitions = conf.getNumReduceTasks(); if (numPartitions > 1) { //设置了ReduceTask个数后(大于1), ...