因为新版本 k8s 底层容器运行时换成了 containerd,所以记录一下镜像加速配置方法顺便学习下这个看着让人一脸问号的 containerd
相较于传统 Docker 镜像加速配置方法,containerd 的配置实在是略显麻烦啊…

1. containerd 简介

1.1 Kubernetes CRI 与 containerd

在早期的 K8s 版本中,kubelet 组件直接与 Docker Daemon 交互来管理容器。但社区考虑扩展性为了使 K8s 能够支更多样的容器运行时(如 rkt, Kata Containers),社区推出了容器运行时接口(Container Runtime Interface, 即CRI)。CRI 是一套标准的、基于 gRPC 的 API 规范,它解耦了 kubelet 与具体容器运行时之间的强依赖关系。

containerd 正是 CRI 规范的一种标准实现工具。经过近两年发展 kubelet 已经不再与 Docker Daemon 对话,而是通过 CRI 接口,向 containerd 发出指令,如“拉取这个镜像”、“创建 Pod 沙箱”、“启动容器”等。

工作流程如下:

从图中可见,当 K8s 需要部署一个 Pod 时:

  1. kubelet 接收到指令,分析 Pod 定义中所需的容器镜像
  2. kubelet 通过 CRI 的 PullImage RPC 调用,请求 containerd 确保该镜像存在于本地
  3. containerd 检查本地镜像存储。如果镜像不存在,它将负责根据自身的配置从远程镜像仓库拉取镜像

这里的关键点在于,执行镜像拉取操作的直接负责人是 containerd,因此,加速配置必须在 containerd 层面进行,才能对 K8s 集群生效。

1.2 需要加速的镜像

  • registry.k8s.io: K8s 官方镜像仓库,存放着 k8s 核心组件的容器镜像
  • docker.io: 全球最大的公共镜像中心,平时使用到的大部分容器镜像都存在里面
  • gcr.io: Google 镜像仓库,存放 Google 相关的一些容器镜像
  • quay.io: RedHat 运营的镜像仓库,也有许多核心开源项目的镜像存在里面
  • ghcr.io: GitHub 运营的镜像仓库,有很多开源项目的镜像也会选择存放在里面

在国内节点部署、使用 k8s 的时候,前面两个镜像必须要配置加速,否则必然是会镜像拉取失败的,剩下几个按需配置即可。

2. 配置解析

认识一下这个看着比 Docker 麻烦很多的配置文件。

2.1 config.toml 的结构

containerd 的主配置文件位于 /etc/containerd/config.toml,采用 TOML 格式。TOML 格式具有清晰的层级结构和良好的可读性,现在在云原生项目中出现的频率确实是明显多了很多!

containerd 配置文件的整体架构是 “插件化” 形式, 文件里面也是众多插件的配置条目。CRI 服务本身也是其众多插件中的一个,其官方名称为 io.containerd.grpc.v1.cri。因此所有与 CRI 相关的配置,都必须放置在这个插件的命名空间下。

具体来看下这个配置项:

[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]

  • [plugins."..."]:表示这是一个插件配置区块
  • "io.containerd.grpc.v1.cri":指定了配置的目标是 CRI 插件。
  • .registry:表示我们正在配置该插件的 registry(镜像仓库)相关功能
  • .mirrors:进一步指定是配置镜像的 mirrors(这里即是我们的镜像加速器)
  • ."docker.io":明确了此配置块只针对 docker.io 这个上游仓库生效

官方背书: 每个配置项都拥有唯一的、不会产生冲突的路径、具有清晰的配置导向,也为未来新增更多功能插件而不会破坏现有结构提供了保障
在我看来: 真是挺麻烦的!

2.2 进一步分析

关键配置区块:

1
2
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://mirror1.com", "https://mirror2.com"]

简单搂一眼这个部分源码,该配置对应的源码定义在 containerd/pkg/cri/apis/config/config.go 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Registry is registry settings "plugins.cri.registry"
type Registry struct {
// ... 省略 ...
Mirrors map[string]Mirror `toml:"mirrors"`
}

// Mirror contains the configuration for a registry mirror.
type Mirror struct {
// Endpoint specifies the endpoints of a mirror. The scheme part is optional.
// When the scheme is not specified, "https://"" is used.
// The endpoint will be tried in order.
Endpoint []string `toml:"endpoint"`
// ... 省略 ...
}

从源码中我们可以得到几个关键信息:

  1. Mirrors 是一个 map[string]Mirror 类型:key(即 "docker.io")是原始仓库地址,valueMirror 结构体。从这里可以看出我们可以为不同仓库配置不同加速镜像源
  2. Endpoint 是一个 []string 类型(字符串切片): 这表明 containerd 原生即支持配置多个 endpoint
  3. 源码注释明确指出 “The endpoint will be tried in order.”: containerd 会按照数组中定义的顺序,依次尝试连接 endpoint 来拉取镜像。一旦其中一个成功,它就会停止尝试并开始下载。

3. 快速配置

上面都是顺带简单了解下 containerd 的内容,现在回到镜像加速源具体配置方法

3.1 生成默认配置

1
2
3
4
mkdir -p /etc/containerd

# 生成默认配置文件
containerd config default | sudo tee /etc/containerd/config.toml

3.2 配置方法

编辑 /etc/containerd/config.toml 文件,找到 [plugins."io.containerd.grpc.v1.cri".registry.mirrors] 部分,替换补充以下内容。这是一个涵盖了绝大部分场景的推荐配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# /etc/containerd/config.toml

# 确保使用 v2 版本的配置格式
version = 2

# ... 省略其他配置 ...

[plugins."io.containerd.grpc.v1.cri".registry]
# config_path不再是推荐的主要配置方式,直接在下面定义mirrors更清晰
config_path = ""

[plugins."io.containerd.grpc.v1.cri".registry.mirrors]

# 1. k8s 官方镜像 -- registry.k8s.io
# 必须配置,这是K8s核心组件的来源。推荐使用阿里云的这个代理非常稳定
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.k8s.io"]
endpoint = ["registry.aliyuncs.com/k8s-gcr-io"]

# 2. DockerHub -- docker.io
# 必须配置,绝大部分应用镜像的来源,配置多个作为冗余避免爱发电暴毙
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = [
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com",
"https://docker.m.daocloud.io"
]

# 3. gcr.io
# 按需可选配置
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."gcr.io"]
endpoint = ["gcr.proxy.ustclug.org"]

# 4. quay.io
# 按需可选配置
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."quay.io"]
endpoint = ["quay.proxy.ustclug.org"]

# 5. ghcr.io
# 按需可选配置
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."ghcr.io"]
endpoint = ["ghcr.proxy.ustclug.org"]

# sandbox_image 也推荐配置使用国内镜像;要注意 pause 镜像版需要与 k8s 使用的版本匹配
[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.aliyuncs.com/k8s-gcr-io/pause:3.9"

额外说明:

  • sandbox_image: 这是 Pod 沙箱的镜像。kubeadm 等工具在初始化时会检查并拉取它。强烈建议将其也指向国内的镜像地址,否则集群初始化可能会在第一步就失败…
  • 镜像源选择: 上述配置的镜像仅为个人设置且当前时间点(2024-11-06)可用的,有些镜像是爱发电的,不保证哪天会突然暴毙!如已有失效的自行更改就好

3.3 验证

修改配置后,必须重启 containerd 才能使其加载新配置

1
2
systemctl daemon-reload
systemctl restart containerd

这里注意下验证配置是否生效,不能使用 docker pull,而应该使用 crictl 工具。crictl 是一个专门用于与 CRI 兼容的容器运行时进行交互的命令行工具

1
2
3
4
5
# 拉取一个docker.io的镜像进行测试
crictl pull busybox

# 再拉取一个pause镜像进行测试
crictl pull registry.aliyuncs.com/k8s-gcr-io/pause:3.9

如果命令能够快速成功返回,并且没有出现网络超时错误,说明配置已正确生效