Containerd 是一个工业级
标准的容器运行时
,它强调简单性、健壮性和可移植性
。
Containerd 可以在宿主机中管理完整
的容器生命周期
:
镜像
的传输
和存储
、执行
和管理
、存储
和网络
等。详细点说,Containerd 负责干下面这些事情:
生命周期(从创建容器到销毁容器)
拉取/推送
容器镜像存储管理
(管理镜像及容器数据的存储)运行容器
(与 runC 等容器运行时交互)注意:Containerd 被设计成嵌入到一个更大的系统中,而不是直接由开发人员或终端用户使用。
我们可以从下面几点来理解为什么需要独立的 containerd:
重复一遍
:
Containerd 被设计成嵌入到一个更大的系统中,而不是直接由开发人员或终端用户使用。所以 containerd 具有宏大的愿景(此图来自互联网):
简洁的基于 gRPC 的 API 和 client library
runtime
和 image spec
)容器核心
功能解耦
的系统(让 image、filesystem、runtime
解耦合),实现插件式的扩展和重用
下图展示了 containerd 的架构(此图来自互联网):
storage、metadata 和 runtime 的三大块划分非常清晰,通过抽象出 events 的设计,containerd 也得以将网络层面的复杂度交给了上层处理,仅提供 network namespace 相关的一些接口添加和配置 API。
这样做的好处无疑是巨大的,保留最小功能集合的纯粹和高效,而将更多的复杂性及灵活性交给了插件及上层系统。
在从概念上对 containerd 有所了解之后,让我们安装最新版的 containerd 并实际把玩一下。
本文的演示环境为 centos7。
注意:containerd 需要调用 runC,所以在安装 containerd 之前请先安装 runC。RunC 的安装请参考笔者博文《RunC到底是干啥的》。
https://www.jianshu.com/p/2f6296190049
从 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 的配置文件默认为 /etc/containerd/config.toml。
这里我们可以通过命令来生成一个默认的配置文件:
mkdir /etc/containerd
containerd config default > /etc/containerd/config.toml
对于演示来说,这个默认的配置已经足够了。
创建文件 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 已经安装成功!
可以使用类似 runC 的方式运行容器,也就是使用现成的客户端工具 ctr,由于用法与 runC 非常相似,所以这里不再赘述。
Containerd 还提供了 client package 用于在代码中集成 containerd 客户端,
下面的 demo 就采用 golang 和 client package 在代码中访问 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")
在创建客户端对象后我们就可以从 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
。
有了 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 调用来删除容器以及它的快照。
一个 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)还没有启动。
在这个时机,我们可以为容器设置网卡
,还可以配置工具来对容器进行监控
等。
当要结束容器的运行时,可以调用 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)。
可以看出,在容器技术逐步标准化后,containerd 在相关的技术栈中将占据非常重要的地位,containerd 提供的核心服务很可能
成为底层管理容器
的标准。
届时,更上层的容器化应用平台将直接使用 containerd 提供的基础服务。
也就是说,containerd的目标是做容器技术的标准,除了runc的功能,还做镜像的拉取
,上传
,管理容器网络接口及网络、镜像的存储,容器数据的存储
等
有疑问加站长微信联系