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

本文将继续介绍 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。

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:

进入 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(一)的更多相关文章
- Spring AOP 源码分析 - 创建代理对象
1.简介 在上一篇文章中,我分析了 Spring 是如何为目标 bean 筛选合适的通知器的.现在通知器选好了,接下来就要通过代理的方式将通知器(Advisor)所持有的通知(Advice)织入到 b ...
- Spring IOC 容器源码分析 - 创建原始 bean 对象
1. 简介 本篇文章是上一篇文章(创建单例 bean 的过程)的延续.在上一篇文章中,我们从战略层面上领略了doCreateBean方法的全过程.本篇文章,我们就从战术的层面上,详细分析doCreat ...
- Spring IOC 容器源码分析 - 创建单例 bean 的过程
1. 简介 在上一篇文章中,我比较详细的分析了获取 bean 的方法,也就是getBean(String)的实现逻辑.对于已实例化好的单例 bean,getBean(String) 方法并不会再一次去 ...
- 【LiteOS】LiteOS任务篇-源码分析-创建任务函数
目录 前言 链接 参考 笔录草稿 部分源码分析 源码分析 LOS_TaskCreate函数 LOS_TaskCreateOnly函数 宏 OS_TCB_FROM_PENDLIST 和 宏 LOS_DL ...
- Netty源码分析--创建Channel(三)
恩~,没错,其实这一篇才是真正的开始分析源码,你打我呀~. 先看一下我Netty的启动类 private void start() throws Exception { EventLoopGroup ...
- Spring IOC 容器源码分析系列文章导读
1. 简介 Spring 是一个轻量级的企业级应用开发框架,于 2004 年由 Rod Johnson 发布了 1.0 版本.经过十几年的迭代,现在的 Spring 框架已经非常成熟了.Spring ...
- Spring AOP 源码分析 - 拦截器链的执行过程
1.简介 本篇文章是 AOP 源码分析系列文章的最后一篇文章,在前面的两篇文章中,我分别介绍了 Spring AOP 是如何为目标 bean 筛选合适的通知器,以及如何创建代理对象的过程.现在我们的得 ...
- 一步步实现windows版ijkplayer系列文章之三——Ijkplayer播放器源码分析之音视频输出——音频篇
一步步实现windows版ijkplayer系列文章之一--Windows10平台编译ffmpeg 4.0.2,生成ffplay 一步步实现windows版ijkplayer系列文章之二--Ijkpl ...
- Spring AOP 源码分析 - 筛选合适的通知器
1.简介 从本篇文章开始,我将会对 Spring AOP 部分的源码进行分析.本文是 Spring AOP 源码分析系列文章的第二篇,本文主要分析 Spring AOP 是如何为目标 bean 筛选出 ...
- Spring AOP 源码分析系列文章导读
1. 简介 前一段时间,我学习了 Spring IOC 容器方面的源码,并写了数篇文章对此进行讲解.在写完 Spring IOC 容器源码分析系列文章中的最后一篇后,没敢懈怠,趁热打铁,花了3天时间阅 ...
随机推荐
- c# Barrier 线程回调
前言 假如现在有一个这样的需求,我一堆小黄人生产小黄丹,而大黄人要一直吃小黄丹. 如果是这样的话,想到就是一堆小黄人作为一个多线程,然后一直制造,然后另外一个大黄人一直检索是否有小黄丹,有就吃掉. 但 ...
- 动手实现自己的http服务器【精简版】
1 package v2; 2 3 import java.io.IOException; 4 import java.io.OutputStream; 5 import java.io.PrintS ...
- NL2SQL实践系列(1):深入解析Prompt工程在text2sql中的应用技巧
NL2SQL实践系列(1):深入解析Prompt工程在text2sql中的应用技巧 NL2SQL基础系列(1):业界顶尖排行榜.权威测评数据集及LLM大模型(Spider vs BIRD)全面对比优劣 ...
- Webpack中常见的Loader?解决了什么问题?
一.是什么 loader 用于对模块的源代码进行转换,在 import 或"加载"模块时预处理文件 webpack做的事情,仅仅是分析出各种模块的依赖关系,然后形成资源列表,最终打 ...
- 1小时打造HaaS版小小蛮驴智能车
简介: 2020年云栖大会上,阿里云发布了一款机器人"小蛮驴",瞬间激起千层浪,无人车,智能物流,机器人等一些概念又火热了一把. 借"小蛮驴"的东风以及火热的H ...
- 喜马拉雅 Apache RocketMQ 消息治理实践
简介:本文通过喜马拉雅的RocketMQ治理实践分享,让大家了解使用消息中间件过程中可能遇到的问题,避免实战中踩坑. 作者:曹融,来自喜马拉雅,从事微服务和消息相关中间件开发. 本文通过喜马拉雅 ...
- Maxcompute造数据-方法详解
简介: 造一点模拟数据的方法 概述 造数据在一些奇怪的场合会被用到.一般我们是先有数据才有基于数据的应用场合,但是反过来如果应用拿到另外一个场景,没有数据功能是没有方法演示的.一般较为真实的数据,脱敏 ...
- 学术顶会再突破!计算平台MaxCompute论文入选国际顶会VLDB 2021
简介: VLDB 2021上,阿里云计算平台MaxCompute参与的论文入选,核心分布式调度执行引擎Fangorn.基于TVR Cost模型的通用增量计算优化器框架Tempura等分别被Indu ...
- dotnet 6 在 System.Text.Json 使用 source generation 源代码生成提升 JSON 序列化性能
这是一个在 dotnet 6 早就引入的功能,此功能的使用方法能简单,提升的效果也很棒.使用的时候需要将 Json 序列化工具类换成 dotnet 运行时自带的 System.Text.Json 进行 ...
- Partition和ReduceTask的关系
先看源码: numPartitions = conf.getNumReduceTasks(); if (numPartitions > 1) { //设置了ReduceTask个数后(大于1), ...