參考文獻(xiàn):
https://www.cnblogs.com/sparkdev/p/9063042.html
https://github.com/containerd/containerd/blob/master/docs/getting-started.md
我們可以把 docker 抽象為下圖所示的結(jié)構(gòu)(此圖來自互聯(lián)網(wǎng)):
從圖中可以看出,
docker 對容器的管理和操作基本都是通過 containerd 完成的。
1、那么,containerd 是什么呢?
Containerd 是一個工業(yè)級
標(biāo)準(zhǔn)的容器運(yùn)行時
,它強(qiáng)調(diào)簡單性、健壯性和可移植性
。
Containerd 可以在宿主機(jī)中管理完整
的容器生命周期
:
- 容器
鏡像
的傳輸
和存儲
、 - 容器的
執(zhí)行
和管理
、 -
存儲
和網(wǎng)絡(luò)
等。
詳細(xì)點(diǎn)說,Containerd 負(fù)責(zé)干下面這些事情:
- 管理容器的
生命周期(從創(chuàng)建容器到銷毀容器)
-
拉取/推送
容器鏡像 -
存儲管理
(管理鏡像及容器數(shù)據(jù)的存儲) - 調(diào)用 runC
運(yùn)行容器
(與 runC 等容器運(yùn)行時交互) - 管理容器網(wǎng)絡(luò)接口及網(wǎng)絡(luò)
注意:Containerd 被設(shè)計(jì)成嵌入到一個更大的系統(tǒng)中,而不是直接由開發(fā)人員或終端用戶使用。
2、為什么需要containerd
我們可以從下面幾點(diǎn)來理解為什么需要獨(dú)立的 containerd:
- 繼續(xù)從整體 docker 引擎中分離出的項(xiàng)目(開源項(xiàng)目的思路)
- 可以被 Kubernets CRI 等項(xiàng)目使用(通用化)
- 為廣泛的行業(yè)合作打下基礎(chǔ)(就像 runC 一樣)
重復(fù)一遍
:
Containerd 被設(shè)計(jì)成嵌入到一個更大的系統(tǒng)中,而不是直接由開發(fā)人員或終端用戶使用。所以 containerd 具有宏大的愿景(此圖來自互聯(lián)網(wǎng)):
當(dāng) containerd 和 runC 成為標(biāo)準(zhǔn)化容器服務(wù)的基石后,
上層的應(yīng)用就可以直接建立在 containerd 和 runC 之上。
上圖中展示的容器平臺都已經(jīng)支持 containerd 和 runC 的組合了,相信接下來會有更多類似的容器平臺出現(xiàn)。
3、Containerd的技術(shù)方向和目標(biāo)
簡潔的基于 gRPC 的 API 和 client library
- 完整的 OCI 支持(
runtime
和image spec
) - 同時具備穩(wěn)定性和高性能的定義良好的
容器核心
功能 - 一個
解耦
的系統(tǒng)(讓image、filesystem、runtime
解耦合),實(shí)現(xiàn)插件式的擴(kuò)展和重用
下圖展示了 containerd 的架構(gòu)(此圖來自互聯(lián)網(wǎng)):
在架構(gòu)設(shè)計(jì)和實(shí)現(xiàn)方面,核心開發(fā)人員在他們的博客里提到了通過反思 graphdriver 的實(shí)現(xiàn),他們將 containerd 設(shè)計(jì)成了 snapshotter 的模式,這也使得 containerd 對于 overlay 文件系、snapshot 文件系統(tǒng)的支持比較好。
storage、metadata 和 runtime 的三大塊劃分非常清晰,通過抽象出 events 的設(shè)計(jì),containerd 也得以將網(wǎng)絡(luò)層面的復(fù)雜度交給了上層處理,僅提供 network namespace 相關(guān)的一些接口添加和配置 API。
這樣做的好處無疑是巨大的,保留最小功能集合的純粹和高效,而將更多的復(fù)雜性及靈活性交給了插件及上層系統(tǒng)。
4、安裝并運(yùn)行containerd
在從概念上對 containerd 有所了解之后,讓我們安裝最新版的 containerd 并實(shí)際把玩一下。
本文的演示環(huán)境為 centos7。
注意:containerd 需要調(diào)用 runC,所以在安裝 containerd 之前請先安裝 runC。RunC 的安裝請參考筆者博文《RunC到底是干啥的》。
http://www.lxweimin.com/p/2f6296190049
4.1、下載并解壓containerd程序
從 github 上下載 containerd 包(https://github.com/containerd/containerd/releases),
當(dāng)前的最新版本為 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
當(dāng)前 containerd 的安裝包中一共有五個文件,通過上面的命令它們被安裝到了 /usr/local/bin 目錄中:
- containerd:即容器的運(yùn)行時,以 gRPC 協(xié)議的形式提供滿足 OCI 標(biāo)準(zhǔn)的 API
- containerd-release 和 containerd-stress:分別是 containerd 項(xiàng)目的發(fā)行版發(fā)布工具以及壓力測試工具
- containerd-shim:這是每一個容器的運(yùn)行時載體,我們在 docker 宿主機(jī)上看到的 shim 也正是代表著一個個通過調(diào)用 containerd 啟動的 docker 容器。
- ctr:它是一個簡單的 CLI 接口,用作 containerd 本身的一些調(diào)試用途,投入生產(chǎn)使用時還是應(yīng)該配合docker 或者 cri-containerd 部署
4.2、生成containerd配置文件
Containerd 的配置文件默認(rèn)為 /etc/containerd/config.toml。
這里我們可以通過命令來生成一個默認(rèn)的配置文件:
mkdir /etc/containerd
containerd config default > /etc/containerd/config.toml
對于演示來說,這個默認(rèn)的配置已經(jīng)足夠了。
4.3、將containerd服務(wù)交給systemd運(yùn)行,需要配置文件
創(chuàng)建文件 containerd.service:
touch /lib/systemd/system/containerd.service
編輯其內(nèi)容如下:
[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
執(zhí)行下面的命令啟動 containerd 服務(wù)并查看服務(wù)的狀態(tài):
systemctl daemon-reload
systemctl enable containerd.service
systemctl start containerd.service
systemctl status containerd.service
至此 containerd 已經(jīng)安裝成功!
5、演示demo
可以使用類似 runC 的方式運(yùn)行容器,也就是使用現(xiàn)成的客戶端工具 ctr,由于用法與 runC 非常相似,所以這里不再贅述。
Containerd 還提供了 client package 用于在代碼中集成 containerd 客戶端,
下面的 demo 就采用 golang 和 client package 在代碼中訪問 containerd 服務(wù)來創(chuàng)建并運(yùn)行容器!
5.1、鏈接containerd服務(wù)
創(chuàng)建 main.go 文件,內(nèi)容如下:
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
}
上面代碼中使用默認(rèn)的 containerd 套接字創(chuàng)建了一個客戶端對象。
因?yàn)?containerd daemon 通過 gRPC 協(xié)議提供服務(wù),所以我們需要創(chuàng)建一個用于調(diào)用客戶端方法的上下文。
在創(chuàng)建上下文之后,我們還應(yīng)該為我們的 demo 設(shè)置一個 namespace,創(chuàng)建單獨(dú)的 namespace 可以與用戶的資源進(jìn)行隔離以免發(fā)生沖突:
ctx := namespaces.WithNamespace(context.Background(), "demo")
5.2、拉取redis鏡像
在創(chuàng)建客戶端對象后我們就可以從 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
作為即將運(yùn)行的容器的rootfs
。
5.3、創(chuàng)建OCI Spec和容器
有了 rootfs 還需要運(yùn)行 OCI 容器所需的 OCI runtime spec,我們通過 NewContainer 方法可以使用默認(rèn)的 OCI runtime spec 直接創(chuàng)建容器對象。
當(dāng)然,也可以通過 Opts 模式的參數(shù)修改默認(rèn)值:
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)
當(dāng)我們?yōu)槿萜鲃?chuàng)建一個 snapshot 時需要提供 snapshot 的 ID及其父鏡像。
通過提供一個單獨(dú)的 snapshot ID,而不是容器 ID,我們可以輕松地在不同的容器中重用現(xiàn)有的 snapshot。
在完成這個示例之后,我們還添加了 defer container.Delete 調(diào)用來刪除容器以及它的快照。
5.4、創(chuàng)建并運(yùn)行容器的task
一個 container 對象只是包含了運(yùn)行一個容器所需的資源及配置的數(shù)據(jù)結(jié)構(gòu),一個容器真正的運(yùn)行起來是由 Task 對象實(shí)現(xiàn)的:
task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStdio))
if err != nil {
return err
}
defer task.Delete(ctx)
此時容器的狀態(tài)相當(dāng)于我們在《RunC 簡介》一文中介紹的 "created"。
這意味著 namespaces、rootfs 和容器的配置
都已經(jīng)初始化成功了,只是用戶進(jìn)程(這里是 redis-server)還沒有啟動。
在這個時機(jī),我們可以為容器設(shè)置網(wǎng)卡
,還可以配置工具來對容器進(jìn)行監(jiān)控
等。
5.5、讓運(yùn)行中的task退出
當(dāng)要結(jié)束容器的運(yùn)行時,可以調(diào)用 task.Kill 方法。其實(shí)就是向容器中運(yùn)行的進(jìn)程發(fā)送信號:
// 讓容器先運(yùn)行一會兒
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)
向容器發(fā)送結(jié)束的信號后,代碼等待容器結(jié)束,并輸出返回碼。最后我們刪除 task 對象:
status, err := task.Delete(ctx)
完整的 demo 代碼請參考這里(https://github.com/containerd/containerd/blob/master/docs/getting-started.md)。
6、總結(jié)
可以看出,在容器技術(shù)逐步標(biāo)準(zhǔn)化后,containerd 在相關(guān)的技術(shù)棧中將占據(jù)非常重要的地位,containerd 提供的核心服務(wù)很可能
成為底層管理容器
的標(biāo)準(zhǔn)。
屆時,更上層的容器化應(yīng)用平臺將直接使用 containerd 提供的基礎(chǔ)服務(wù)。
也就是說,containerd的目標(biāo)是做容器技術(shù)的標(biāo)準(zhǔn),除了runc的功能,還做鏡像的拉取
,上傳
,管理容器網(wǎng)絡(luò)接口及網(wǎng)絡(luò)、鏡像的存儲,容器數(shù)據(jù)的存儲
等