Containerd到底是干啥的?

参考文献:
https://www.cnblogs.com/sparkdev/p/9063042.html
https://github.com/containerd/containerd/blob/master/docs/getting-started.md
我们可以把 docker 抽象为下图所示的结构(此图来自互联网):

image

从图中可以看出,
docker 对容器的管理和操作基本都是通过 containerd 完成的。

1、那么,containerd 是什么呢?

Containerd 是一个工业级标准的容器运行时,它强调简单性、健壮性和可移植性
Containerd 可以在宿主机中管理完整容器生命周期

  • 容器镜像传输存储
  • 容器的执行管理
  • 存储网络等。

详细点说,Containerd 负责干下面这些事情:

  • 管理容器的生命周期(从创建容器到销毁容器)
  • 拉取/推送容器镜像
  • 存储管理(管理镜像及容器数据的存储)
  • 调用 runC 运行容器(与 runC 等容器运行时交互)
  • 管理容器网络接口及网络

注意:Containerd 被设计成嵌入到一个更大的系统中,而不是直接由开发人员或终端用户使用。

2、为什么需要containerd

我们可以从下面几点来理解为什么需要独立的 containerd:

  • 继续从整体 docker 引擎中分离出的项目(开源项目的思路)
  • 可以被 Kubernets CRI 等项目使用(通用化)
  • 为广泛的行业合作打下基础(就像 runC 一样)

重复一遍
Containerd 被设计成嵌入到一个更大的系统中,而不是直接由开发人员或终端用户使用。所以 containerd 具有宏大的愿景(此图来自互联网):

image

当 containerd 和 runC 成为标准化容器服务的基石后,
上层的应用就可以直接建立在 containerd 和 runC 之上。
上图中展示的容器平台都已经支持 containerd 和 runC 的组合了,相信接下来会有更多类似的容器平台出现。

3、Containerd的技术方向和目标

简洁的基于 gRPC 的 API 和 client library

  • 完整的 OCI 支持(runtimeimage spec)
  • 同时具备稳定性和高性能的定义良好的容器核心功能
  • 一个解耦的系统(让 image、filesystem、runtime 解耦合),实现插件式的扩展和重用

下图展示了 containerd 的架构(此图来自互联网):

image

在架构设计和实现方面,核心开发人员在他们的博客里提到了通过反思 graphdriver 的实现,他们将 containerd 设计成了 snapshotter 的模式,这也使得 containerd 对于 overlay 文件系、snapshot 文件系统的支持比较好。

storage、metadata 和 runtime 的三大块划分非常清晰,通过抽象出 events 的设计,containerd 也得以将网络层面的复杂度交给了上层处理,仅提供 network namespace 相关的一些接口添加和配置 API。
这样做的好处无疑是巨大的,保留最小功能集合的纯粹和高效,而将更多的复杂性及灵活性交给了插件及上层系统。

4、安装并运行containerd

在从概念上对 containerd 有所了解之后,让我们安装最新版的 containerd 并实际把玩一下。
本文的演示环境为 centos7。
注意:containerd 需要调用 runC,所以在安装 containerd 之前请先安装 runC。RunC 的安装请参考笔者博文《RunC到底是干啥的》。
https://www.jianshu.com/p/2f6296190049

4.1、下载并解压containerd程序

从 github 上下载 containerd 包(https://github.com/containerd/containerd/releases),
当前的最新版本为 v1.3.0。
不过我们使用v1.1.0测试即可(https://github.com/containerd/containerd/tags?after=v1.2.0-beta.1),
然后把下载到的压缩包解压到 /usr/local 目录下:

tar -zxvf containerd-1.1.0.linux-amd64.tar.gz

当前 containerd 的安装包中一共有五个文件,通过上面的命令它们被安装到了 /usr/local/bin 目录中:

  • containerd:即容器的运行时,以 gRPC 协议的形式提供满足 OCI 标准的 API
  • containerd-release 和 containerd-stress:分别是 containerd 项目的发行版发布工具以及压力测试工具
  • containerd-shim:这是每一个容器的运行时载体,我们在 docker 宿主机上看到的 shim 也正是代表着一个个通过调用 containerd 启动的 docker 容器。
  • ctr:它是一个简单的 CLI 接口,用作 containerd 本身的一些调试用途,投入生产使用时还是应该配合docker 或者 cri-containerd 部署
image

4.2、生成containerd配置文件

Containerd 的配置文件默认为 /etc/containerd/config.toml。
这里我们可以通过命令来生成一个默认的配置文件:

mkdir /etc/containerd
containerd config default > /etc/containerd/config.toml

对于演示来说,这个默认的配置已经足够了。

4.3、将containerd服务交给systemd运行,需要配置文件

创建文件 containerd.service:

touch /lib/systemd/system/containerd.service

编辑其内容如下:

[Unit]
Description=containerd container runtime
Documentation=https://containerd.io
After=network.target

[Service]
ExecStartPre=/sbin/modprobe overlay
ExecStart=/usr/local/bin/containerd
Delegate=yes
KillMode=process
LimitNOFILE=1048576
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNPROC=infinity
LimitCORE=infinity

[Install]
WantedBy=multi-user.target

执行下面的命令启动 containerd 服务并查看服务的状态:

systemctl daemon-reload
systemctl enable containerd.service
systemctl start containerd.service
systemctl status containerd.service

至此 containerd 已经安装成功!

image

image

5、演示demo

可以使用类似 runC 的方式运行容器,也就是使用现成的客户端工具 ctr,由于用法与 runC 非常相似,所以这里不再赘述。
Containerd 还提供了 client package 用于在代码中集成 containerd 客户端,
下面的 demo 就采用 golang 和 client package 在代码中访问 containerd 服务来创建并运行容器!

5.1、链接containerd服务

创建 main.go 文件,内容如下:

package main

import (
    "log"
    "github.com/containerd/containerd"
)

func main() {
    if err := redisExample(); err != nil {
        log.Fatal(err)
    }
}

func redisExample() error {
    client, err := containerd.New("/run/containerd/containerd.sock")
    if err != nil {
        return err
    }
    defer client.Close()
    return nil
}

上面代码中使用默认的 containerd 套接字创建了一个客户端对象。
因为 containerd daemon 通过 gRPC 协议提供服务,所以我们需要创建一个用于调用客户端方法的上下文。
在创建上下文之后,我们还应该为我们的 demo 设置一个 namespace,创建单独的 namespace 可以与用户的资源进行隔离以免发生冲突:

ctx := namespaces.WithNamespace(context.Background(), "demo")

5.2、拉取redis镜像

在创建客户端对象后我们就可以从 dockerhub 上拉取容器镜像了,这里我们拉取一个 redis 镜像:

image, err := client.Pull(ctx, "docker.io/library/redis:alpine", containerd.WithPullUnpack)
if err != nil {
    return err
}

使用客户端的 Pull 方法从 dockerhub 上拉取 redis 镜像,这个方法支持 Opts 模式,所以我们可以指定 containerd.WithPullUnpackso 让下载完成后直接把镜像解压缩为一个snapshotter作为即将运行的容器的rootfs

5.3、创建OCI Spec和容器

有了 rootfs 还需要运行 OCI 容器所需的 OCI runtime spec,我们通过 NewContainer 方法可以使用默认的 OCI runtime spec 直接创建容器对象。
当然,也可以通过 Opts 模式的参数修改默认值:

container, err := client.NewContainer(
    ctx,
    "redis-server",
    containerd.WithImage(image),
    containerd.WithNewSnapshot("redis-server-snapshot", image),
    containerd.WithNewSpec(oci.WithImageConfig(image)),
)
if err != nil {
    return err
}
defer container.Delete(ctx, containerd.WithSnapshotCleanup)

当我们为容器创建一个 snapshot 时需要提供 snapshot 的 ID及其父镜像。
通过提供一个单独的 snapshot ID,而不是容器 ID,我们可以轻松地在不同的容器中重用现有的 snapshot。
在完成这个示例之后,我们还添加了 defer container.Delete 调用来删除容器以及它的快照。

5.4、创建并运行容器的task

一个 container 对象只是包含了运行一个容器所需的资源及配置的数据结构,一个容器真正的运行起来是由 Task 对象实现的:

task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStdio))
if err != nil {
    return err
}
defer task.Delete(ctx)

此时容器的状态相当于我们在《RunC 简介》一文中介绍的 "created"。
这意味着 namespaces、rootfs 和容器的配置都已经初始化成功了,只是用户进程(这里是 redis-server)还没有启动。
在这个时机,我们可以为容器设置网卡,还可以配置工具来对容器进行监控等。

5.5、让运行中的task退出

当要结束容器的运行时,可以调用 task.Kill 方法。其实就是向容器中运行的进程发送信号:

// 让容器先运行一会儿
time.Sleep(3 * time.Second)

if err := task.Kill(ctx, syscall.SIGTERM); err != nil {
    return err
}

status := <-exitStatusC
code, exitedAt, err := status.Result()
if err != nil {
    return err
}
fmt.Printf("redis-server exited with status: %d\n", code)

向容器发送结束的信号后,代码等待容器结束,并输出返回码。最后我们删除 task 对象:

status, err := task.Delete(ctx)

完整的 demo 代码请参考这里(https://github.com/containerd/containerd/blob/master/docs/getting-started.md)。

6、总结

可以看出,在容器技术逐步标准化后,containerd 在相关的技术栈中将占据非常重要的地位,containerd 提供的核心服务很可能成为底层管理容器的标准。
届时,更上层的容器化应用平台将直接使用 containerd 提供的基础服务。

也就是说,containerd的目标是做容器技术的标准,除了runc的功能,还做镜像的拉取上传,管理容器网络接口及网络、镜像的存储,容器数据的存储

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349