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. Linux systemd 定时任务

    哈喽大家好,我是咸鱼. 说到 Linux 定时任务,大家用得最多的就是 crond 服务,但其实 systemd 也有类似的功能.我们不但可以通过 systemd 来管理服务,还能设置定时任务,那就是 ...

  2. wandb一个简单demo

    wandb绘制曲线:sin函数,cos函数,log函数. wandb绘制本地图片 wandb绘制matplotlib图片 wandb绘制numpy图片 import math import wandb ...

  3. vue中执行异步函数async和await的用法

    在开发中,可能会遇到两个或多个函数异步执行的情况,对于Vue中函数的异步函数执行做了一个小总结,如下: 异步执行使用async和await完成 created() { this.init() }, m ...

  4. NOIP模拟四

    NOIP模拟四 number 题目描述 现有 \(2^n\) 个点,点编号为 \(0\sim2^n-1\). 定义这些点的一张异或图为: 先选定一个集合 \(S\). 对于原图上编号为 \(x\) 和 ...

  5. 基于 eBPF 的 Kubernetes 可观测实践

    简介: 阿里云可观测团队构建了 kubernetes 统一监控,无侵入式地提供多语言.应用性能黄金指标,支持多种协议,结合 Kubernetes 管控层与网络系统层监控,提供全栈一体式的可观测体验.通 ...

  6. Bilibili资深运维工程师:DCDN在游戏应用加速中的实践

    简介: bilibili资深运维工程师李宁分享<DCDN在游戏应用加速中的实践>从bilibili游戏应用的效果和成本入手,深入浅出地分享DCDN全站加速在游戏加速场景中的应用. 日前,云 ...

  7. OpenKruise v1.1:功能增强与上游对齐,大规模场景性能优化

    简介:在 v1.1 版本中,OpenKruise 对不少已有功能做了扩展与增强,并且优化了在大规模集群中的运行性能.以下对 v1.1 的部分功能做简要介绍. 作者:酒祝(王思宇) 云原生应用自动化管理 ...

  8. 项目版本管理的最佳实践:云效飞流Flow篇

    简介: 飞流Flow的最佳实践(使用阿里云云效)为了更好地使用飞流Flow,接下来将结合阿里云云效来讲解飞流Flow的最佳实践 目录 一.分支规约 二.版本号规约 2.1 主版本号(首位版本号) 2. ...

  9. LVGL 定时器

    LVGL 8.0 以后好像取消了自定义任务模块,想要使用多线程只能使用系统的线程. 一.定时器结构体 typedef struct _lv_timer_t { uint32_t period; // ...

  10. 利用python爬取某壳的房产数据

    以无锡的某壳为例进行数据爬取,现在房子的价格起伏很快,买房是人生一个大事,了解本地的房价走势来判断是否应该入手. (建议是近2年不买,本人在21年高位抛了一套房,基本是通过贝壳数据判断房价已经到顶,希 ...