第05关 containerd
Containerd 的前世今生
很久以前,Docker 强势崛起,以“镜像”这个大招席卷全球,对其他容器技术进行致命的降维打击,使其毫无招架之力,就连 Google 也不例外。Google 为了不被拍死在沙滩上,被迫拉下脸面(当然,跪舔是不可能的),希望 Docker 公司和自己联合推进一个开源的容器运行时作为 Docker 的核心依赖,不然就走着瞧。Docker 公司觉得自己的智商被侮辱了,走着瞧就走着瞧,谁怕谁啊!
很明显,Docker 公司的这个决策断送了自己的大好前程,造成了今天的悲剧。
紧接着,Google 联合 Red Hat、IBM 等几位巨佬连哄带骗忽悠 Docker 公司将 libcontainer 捐给中立的社区(OCI,Open Container Intiative),并改名为 runc,不留一点 Docker 公司的痕迹~~
这还不够,为了彻底扭转 Docker 一家独大的局面,几位大佬又合伙成立了一个基金会叫 CNCF(Cloud Native Computing Fundation),这个名字想必大家都很熟了,我就不详细介绍了。CNCF 的目标很明确,既然在当前的维度上干不过 Docker,干脆往上爬,升级到大规模容器编排的维度,以此来击败 Docker。
Docker 公司当然不甘示弱,搬出了 Swarm 和 Kubernetes 进行 PK,最后的结局大家都知道了,Swarm 战败。然后 Docker 公司耍了个小聪明,将自己的核心依赖 Containerd 捐给了 CNCF,以此来标榜 Docker 是一个 PaaS 平台。
很明显,这个小聪明又大大加速了自己的灭亡。
巨佬们心想,想当初想和你合作搞个中立的核心运行时,你死要面子活受罪,就是不同意,好家伙,现在自己搞了一个,还捐出来了,这是什么操作?也罢,这倒省事了,我就直接拿 Containerd 来做文章吧。
首先呢,为了表示 Kubernetes 的中立性,当然要搞个标准化的容器运行时接口,只要适配了这个接口的容器运行时,都可以和我一起玩耍哦,第一个支持这个接口的当然就是 Containerd 啦。至于这个接口的名字,大家应该都知道了,它叫 CRI(Container Runntime Interface)。
这样还不行,为了蛊惑 Docker 公司,Kubernetes 暂时先委屈自己,专门在自己的组件中集成了一个 shim(你可以理解为垫片),用来将 CRI 的调用翻译成 Docker 的 API,让 Docker 也能和自己愉快地玩耍,温水煮青蛙,养肥了再杀。。。
就这样,Kubernetes 一边假装和 Docker 愉快玩耍,一边背地里不断优化 Containerd 的健壮性以及和 CRI 对接的丝滑性。现在 Containerd 的翅膀已经完全硬了,是时候卸下我的伪装,和 Docker say bye bye 了。后面的事情大家也都知道了~~
Docker 这门技术成功了,Docker 这个公司却失败了。
Containerd 和 Docker 组件常用命令是什么?
Containerd 不支持 docker API 和 docker CLI,但是可以通过 ctr 命令实现类似的功能。
| 镜像相关功能 | Docker | Containerd |
|---|---|---|
| 显示本地镜像列表 | docker images | ctr -n k8s.io images ls |
| 下载镜像 | docker pull | ctr -n k8s.io images pull -h |
| 上传镜像 | docker push | ctr -n k8s.io images push -h |
| *删除本地镜像 | docker rmi | ctr -n k8s.io delete |
| 查看镜像详情 | docker inspect IMAGE-ID | crictl inspecti IMAGE-ID |
| *导出离线镜像 | docker save xxx > xxx.tar | ctr -n k8s.io images export nginx-1.21.6.tar docker.io/library/nginx:1.21.6 |
| *导入离线镜像 | docker load -i xxx.tar | ctr -n k8s.io images import xxx.tar |
| 容器相关功能 | Docker | Containerd |
|---|---|---|
| 显示容器列表 | docker ps | crictl ps |
| 创建容器 | docker create | crictl create |
| 启动容器 | docker start | crictl start |
| 停止容器 | docker stop | crictl stop |
| 删除容器 | docker rm | crictl rm |
| 查看容器详情 | docker inspect | crictl inspect |
| attach | docker attach | crictl attach |
| exec | docker exec | crictl exec |
| logs | docker logs | crictl logs |
| stats | docker stats | crictl stats |
| POD 相关功能 | Docker | Containerd |
|---|---|---|
| 显示 POD 列表 | 无 | crictl pods |
| 查看 POD 详情 | 无 | crictl inspectp |
| 运行 POD | 无 | crictl runp |
| 停止 POD | 无 | crictl stopp |
调用链区别有哪些?
- Docker 作为 k8s 容器运行时,调用关系如下:
kubelet --> docker shim (在 kubelet 进程中) --> dockerd --> containerd - Containerd 作为 k8s 容器运行时,调用关系如下:
kubelet --> cri plugin(在 containerd 进程中) --> containerd
其中 dockerd 虽增加了 swarm cluster、 docker build 、 docker API 等功能,但也会引入一些 bug,而与 containerd 相比,多了一层调用。
Containerd配置
Containerd 的默认配置文件为 /etc/containerd/config.toml,我们可以通过命令来生成一个默认的配置:
mkdir /etc/containerd
containerd config default > /etc/containerd/config.toml镜像加速
由于某些不可描述的因素,在国内拉取公共镜像仓库的速度是极慢的,为了节约拉取时间,需要为 Containerd 配置镜像仓库的 mirror。Containerd 的镜像仓库 mirror 与 Docker 相比有两个区别:
- Containerd 只支持通过
CRI拉取镜像的 mirror,也就是说,只有通过crictl或者 Kubernetes 调用时 mirror 才会生效,通过ctr拉取是不会生效的。 Docker只支持为Docker Hub配置 mirror,而Containerd支持为任意镜像仓库配置 mirror。
配置镜像加速之前,先来看下 Containerd 的配置结构,乍一看可能会觉得很复杂,复杂就复杂在 plugin 的配置部分:
[plugins]
[plugins."io.containerd.gc.v1.scheduler"]
pause_threshold = 0.02
deletion_threshold = 0
mutation_threshold = 100
schedule_delay = "0s"
startup_delay = "100ms"
[plugins."io.containerd.grpc.v1.cri"]
disable_tcp_service = true
stream_server_address = "127.0.0.1"
stream_server_port = "0"
stream_idle_timeout = "4h0m0s"
enable_selinux = false
sandbox_image = "k8s.gcr.io/pause:3.1"
stats_collect_period = 10
systemd_cgroup = false
enable_tls_streaming = false
max_container_log_line_size = 16384
disable_cgroup = false
disable_apparmor = false
restrict_oom_score_adj = false
max_concurrent_downloads = 3
disable_proc_mount = false
[plugins."io.containerd.grpc.v1.cri".containerd]
snapshotter = "overlayfs"
default_runtime_name = "runc"
no_pivot = false
[plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]
runtime_type = ""
runtime_engine = ""
runtime_root = ""
privileged_without_host_devices = false
[plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime]
runtime_type = ""
runtime_engine = ""
runtime_root = ""
privileged_without_host_devices = false
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v1"
runtime_engine = ""
runtime_root = ""
privileged_without_host_devices = false
[plugins."io.containerd.grpc.v1.cri".cni]
bin_dir = "/opt/cni/bin"
conf_dir = "/etc/cni/net.d"
max_conf_num = 1
conf_template = ""
[plugins."io.containerd.grpc.v1.cri".registry]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://registry-1.docker.io"]
[plugins."io.containerd.grpc.v1.cri".x509_key_pair_streaming]
tls_cert_file = ""
tls_key_file = ""
[plugins."io.containerd.internal.v1.opt"]
path = "/opt/containerd"
[plugins."io.containerd.internal.v1.restart"]
interval = "10s"
[plugins."io.containerd.metadata.v1.bolt"]
content_sharing_policy = "shared"
[plugins."io.containerd.monitor.v1.cgroups"]
no_prometheus = false
[plugins."io.containerd.runtime.v1.linux"]
shim = "containerd-shim"
runtime = "runc"
runtime_root = ""
no_shim = false
shim_debug = false
[plugins."io.containerd.runtime.v2.task"]
platforms = ["linux/amd64"]
[plugins."io.containerd.service.v1.diff-service"]
default = ["walking"]
[plugins."io.containerd.snapshotter.v1.devmapper"]
root_path = ""
pool_name = ""
base_image_size = ""每一个顶级配置块的命名都是 plugins."io.containerd.xxx.vx.xxx" 这种形式,其实每一个顶级配置块都代表一个插件,其中 io.containerd.xxx.vx 表示插件的类型,vx 后面的 xxx 表示插件的 ID。可以通过 ctr 一览无余:
ctr plugin ls顶级配置块下面的子配置块表示该插件的各种配置,比如 cri 插件下面就分为 containerd、cni 和 registry 的配置,而 containerd 下面又可以配置各种 runtime,还可以配置默认的 runtime。
镜像加速的配置就在 cri 插件配置块下面的 registry 配置块,所以需要修改的部分如下:
[plugins."io.containerd.grpc.v1.cri".registry]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://dockerhub.mirrors.nwafu.edu.cn"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."k8s.gcr.io"]
endpoint = ["https://registry.aliyuncs.com/k8sxio"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."gcr.io"]
endpoint = ["xxx"]- registry.mirrors.“xxx” : 表示需要配置 mirror 的镜像仓库。例如,
registry.mirrors."docker.io"表示配置 docker.io 的 mirror。 - endpoint : 表示提供 mirror 的镜像加速服务。例如,这里推荐使用西北农林科技大学提供的镜像加速服务作为
docker.io的 mirror。
命名空间
除了 k8s 有命名空间以外,Containerd 也支持命名空间。
ctr ns ls
NAME LABELS
default如果不指定,ctr 默认是 default 空间。
目前 Containerd 的定位还是解决运行时,所以目前他还不能完全替代 dockerd,例如使用 Dockerfile 来构建镜像。其实这不是什么大问题,我再给大家介绍一个大招:Containerd 和 Docker 一起用!
Containerd + Docker
事实上,Docker 和 Containerd 是可以同时使用的,只不过 Docker 默认使用的 Containerd 的命名空间不是 default,而是 moby。下面就是见证奇迹的时刻。
首先从其他装了 Docker 的机器或者 GitHub 上下载 Docker 相关的二进制文件,然后使用下面的命令启动 Docker:
dockerd --containerd /run/containerd/containerd.sock --cri-containerd接着用 Docker 运行一个容器:
docker run -d --name nginx nginx:alpine现在再回过头来查看 Containerd 的命名空间:
ctr ns ls
NAME LABELS
default
moby查看该命名空间下是否有容器:
ctr -n moby c ls
CONTAINER IMAGE RUNTIME
b7093d7aaf8e1ae161c8c8ffd4499c14ba635d8e174cd03711f4f8c27818e89a - io.containerd.runtime.v1.linux以后用 Containerd 不耽误我 docker build 了~~
最后提醒一句:Kubernetes 用户不用惊慌,Kubernetes 默认使用的是 Containerd 的 k8s.io 命名空间,所以 ctr -n k8s.io 就能看到 Kubernetes 创建的所有容器啦,也不用担心 crictl 不支持 load 镜像了,因为 ctr -n k8s.io 可以 load 镜像啊,嘻嘻😬
镜像导入/导出操作
注意 k8s 只会使用 k8s.io namespace 中镜像。
使用Containerd时,需要往 k8s.io 导入镜像,containerd worker 终于能正常被调度了
为支持多租户隔离,containerd 有 namespace 概念,不同 namespace 下的 image、container 均不同,直接使用 ctr 操作时,会使用 default namespace
# ctr namespace ls
NAME LABELS
k8s.io
# containerd需要指定命令空间导入镜像
# docker pull nginx:1.21.6 && docker save nginx:1.21.6 > nginx-1.21.6.tar
# ctr -n k8s.io images import nginx-1.21.6.tarcontainerd导出镜像,然后通过docker导入也是可以的
# 先查询要导出的镜像名称全名
# ctr -n k8s.io images ls|grep nginx
docker.io/library/nginx:1.21.6 application/vnd.docker.distribution.manifest.list.v2+json sha256:2bcabc23b45489fb0885d69a06ba1d648aeda973fae7bb981bafbb884165e514 54.1 MiB linux/386,linux/amd64,linux/arm/v5,linux/arm/v7,linux/arm64/v8,linux/mips64le,linux/ppc64le,linux/s390x io.cri-containerd.image=managed
docker.io/library/nginx@sha256:2bcabc23b45489fb0885d69a06ba1d648aeda973fae7bb981bafbb884165e514 application/vnd.docker.distribution.manifest.list.v2+json sha256:2bcabc23b45489fb0885d69a06ba1d648aeda973fae7bb981bafbb884165e514 54.1 MiB linux/386,linux/amd64,linux/arm/v5,linux/arm/v7,linux/arm64/v8,linux/mips64le,linux/ppc64le,linux/s390x io.cri-containerd.image=managed
# 通过ctr导出
# ctr -n k8s.io images export nginx-1.21.6.tar docker.io/library/nginx:1.21.6
# ll -h nginx-1.21.6.tar
-rw-r--r-- 1 root root 55M Oct 18 11:51 nginx-1.21.6.tar
# 然后用docker导入
# docker images|grep nginx
# docker load -i nginx-1.21.6.tar
ad6562704f37: Loading layer [==================================================>] 31.38MB/31.38MB
58354abe5f0e: Loading layer [==================================================>] 25.35MB/25.35MB
53ae81198b64: Loading layer [==================================================>] 601B/601B
57d3fc88cb3f: Loading layer [==================================================>] 893B/893B
747b7a567071: Loading layer [==================================================>] 667B/667B
33e3df466e11: Loading layer [==================================================>] 1.396kB/1.396kB
Loaded image: nginx:1.21.6
# docker images|grep nginx
nginx 1.21.6 0e901e68141f 16 months ago 142MB
# docker rmi nginx:1.21.6
Untagged: nginx:1.21.6
Deleted: sha256:0e901e68141fd02f237cf63eb842529f8a9500636a9419e3cf4fb986b8fe3d5d
Deleted: sha256:1e877fb1acf761377390ab38bbad050a1d5296f1b4f51878c2695d4ecdb98c62
Deleted: sha256:834e54d50f731515065370d1c15f0ed47d2f7b6a7b0452646db80f14ace9b8de
Deleted: sha256:d28ca7ee17ff94497071d5c075b4099a4f2c950a3471fc49bdf9876227970b24
Deleted: sha256:096f97ba95539883af393732efac02acdd0e2ae587a5479d97065b64b4eded8c
Deleted: sha256:de7e3b2a7430261fde88313fbf784a63c2229ce369b9116053786845c39058d5
Deleted: sha256:ad6562704f3759fb50f0d3de5f80a38f65a85e709b77fd24491253990f30b6beContainerd结合docker一起使用的生产案例:
利用docker in docker (简称dind),实际在CRI为Containerd的情况下,还能利用docker 实现打包镜像等功能
# only have docker client ,use dind can be use normal
dindSvc=$(kubectl -n kube-system get svc dind |awk 'NR==2{print $3}')
export DOCKER_HOST="tcp://${dindSvc}:2375/"
export DOCKER_DRIVER=overlay2
export DOCKER_TLS_CERTDIR=""参考
https://icloudnative.io/posts/getting-started-with-containerd