Containerd配置国内镜像源
前言
k8s从1.24开始,删除了对 Docker(Dockershim)的容器运行时接口(CRI)的支持。作者在搭建1.28版本k8s集群时遇到同样的问题,无法从国外镜像源下载k8s核心依赖组件,故写这篇文章
Containerd版本和系统环境
系统环境
Ubuntu20.04 LTS + Containerd 1.6.24
root@master:/home/guanwu# cat /proc/version
Linux version 6.2.0-37-generic (buildd@bos03-amd64-055) (x86_64-linux-gnu-gcc-11 (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #38~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Nov 2 18:01:13 UTC 2
root@master:/home/guanwu# crictl version
Version: 0.1.0
RuntimeName: containerd
RuntimeVersion: 1.6.24
RuntimeApiVersion: v1
Containerd镜像配置方式
Containerd 默认配置文件路径是 /etc/containerd/config.toml
Containerd配置文件详情可以参考官方文档,请选择对应版本,这里坑比较多,不再细说,1.6.24版本点击我跳转 配置镜像加速可以修改如下配置,这是官方文档的一个说明:
# Config file is parsed as version 1 by default.
# To use the long form of plugin names set "version = 2"
# explicitly use v2 config format
version = 2
[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".registry.mirrors."test.https-registry.io"]
endpoint = ["https://HostIP1:Port1"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."test.http-registry.io"]
endpoint = ["http://HostIP2:Port2"]
# wildcard matching is supported but not required.
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."*"]
endpoint = ["https://HostIP3:Port3"]
以下是我个人配置文件
[plugins."io.containerd.grpc.v1.cri".registry]
[plugins."io.containerd.grpc.v1.cri".registry.auths]
[plugins."io.containerd.grpc.v1.cri".registry.configs]
[plugins."io.containerd.grpc.v1.cri".registry.headers]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors] #主要在这下面配置镜像加速服务
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint=["https://registry-1.docker.io", "https://xxx.mirror.aliyuncs.com"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.k8s.io"]
endpoint=["https://xxx.mirror.aliyuncs.com", "https://k8s.m.daocloud.io", "https://docker.mirrors.ustc.edu.cn","https://hub-mirror.c.163.com"]
镜像加速可以选择国内的多个加速源,比如我这里regitry.k8s.io仓库配置了多个加速源,这是因为如果某个加速源获取不到镜像会自动从下一个获取。
阿里云加速源xxx.mirror.aliyuncs.com 需要登录阿里云网站获取,具体可以参考这里获取help.aliyun.com/zh/acr/user…
具体看下图
验证
输入如下命令
root@master:/etc/containerd# crictl info
可以看到如下输出:
"registry": {
"configPath": "",
"mirrors": {
"docker.io": {
"endpoint": [
"https://registry-1.docker.io",
"https://45hrqeao.mirror.aliyuncs.com"
]
},
"registry.k8s.io": {
"endpoint": [
"https://45hrqeao.mirror.aliyuncs.com",
"https://k8s.m.daocloud.io",
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com"
]
}
},
拉取镜像验证
root@master:/etc/containerd# crictl --debug=true pull nginx:1.9.3
DEBU[0000] get image connection
DEBU[0000] PullImageRequest: &PullImageRequest{Image:&ImageSpec{Image:nginx:1.9.3,Annotations:map[string]string{},},Auth:nil,SandboxConfig:nil,}
DEBU[0038] PullImageResponse: &PullImageResponse{ImageRef:sha256:f600a45bb5324a547c56459757824a60fc90b57da638228dd4d247b60ef2f201,}
Image is up to date for sha256:f600a45bb5324a547c56459757824a60fc90b57da638228dd4d247b60ef2f201
root@master:/etc/containerd# crictl image list
IMAGE TAG IMAGE ID SIZE
docker.io/library/nginx 1.9.3
root@master:/etc/containerd# crictl pull registry.k8s.io/coredns:1.6.7
Image is up to date for sha256:67da37a9a360e600e74464da48437257b00a754c77c40f60c65e4cb327c34bd5
root@master:/etc/containerd# crictl image list
IMAGE TAG IMAGE ID SIZE
registry.k8s.io/coredns 1.6.7 67da37a9a360e 13.6MB
root@master:/etc/containerd#
完整配置
disabled_plugins = []
imports = []
oom_score = 0
plugin_dir = ""
required_plugins = []
root = "/var/lib/containerd"
state = "/run/containerd"
temp = ""
version = 2
[cgroup]
path = ""
[debug]
address = ""
format = ""
gid = 0
level = ""
uid = 0
[grpc]
address = "/run/containerd/containerd.sock"
gid = 0
max_recv_message_size = 16777216
max_send_message_size = 16777216
tcp_address = ""
tcp_tls_ca = ""
tcp_tls_cert = ""
tcp_tls_key = ""
uid = 0
[metrics]
address = ""
grpc_histogram = false
[plugins]
[plugins."io.containerd.gc.v1.scheduler"]
deletion_threshold = 0
mutation_threshold = 100
pause_threshold = 0.02
schedule_delay = "0s"
startup_delay = "100ms"
[plugins."io.containerd.grpc.v1.cri"]
device_ownership_from_security_context = false
disable_apparmor = false
disable_cgroup = false
disable_hugetlb_controller = true
disable_proc_mount = false
disable_tcp_service = true
enable_selinux = false
enable_tls_streaming = false
enable_unprivileged_icmp = false
enable_unprivileged_ports = false
ignore_image_defined_volumes = false
max_concurrent_downloads = 3
max_container_log_line_size = 16384
netns_mounts_under_state_dir = false
restrict_oom_score_adj = false
sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"
selinux_category_range = 1024
stats_collect_period = 10
stream_idle_timeout = "4h0m0s"
stream_server_address = "127.0.0.1"
stream_server_port = "0"
systemd_cgroup = false
tolerate_missing_hugetlb_controller = true
unset_seccomp_profile = ""
[plugins."io.containerd.grpc.v1.cri".cni]
bin_dir = "/opt/cni/bin"
conf_dir = "/etc/cni/net.d"
conf_template = ""
ip_pref = ""
max_conf_num = 1
[plugins."io.containerd.grpc.v1.cri".containerd]
default_runtime_name = "runc"
disable_snapshot_annotations = true
discard_unpacked_layers = false
ignore_rdt_not_enabled_errors = false
no_pivot = false
snapshotter = "overlayfs"
[plugins."io.containerd.grpc.v1.cri".containerd.default_runtime]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = ""
[plugins."io.containerd.grpc.v1.cri".containerd.default_runtime.options]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes]
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
BinaryName = ""
CriuImagePath = ""
CriuPath = ""
CriuWorkPath = ""
IoGid = 0
IoUid = 0
NoNewKeyring = false
NoPivotRoot = false
Root = ""
ShimCgroup = ""
SystemdCgroup = true
[plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime]
base_runtime_spec = ""
cni_conf_dir = ""
cni_max_conf_num = 0
container_annotations = []
pod_annotations = []
privileged_without_host_devices = false
runtime_engine = ""
runtime_path = ""
runtime_root = ""
runtime_type = ""
[plugins."io.containerd.grpc.v1.cri".containerd.untrusted_workload_runtime.options]
[plugins."io.containerd.grpc.v1.cri".image_decryption]
key_model = "node"
[plugins."io.containerd.grpc.v1.cri".registry]
[plugins."io.containerd.grpc.v1.cri".registry.auths]
[plugins."io.containerd.grpc.v1.cri".registry.configs]
[plugins."io.containerd.grpc.v1.cri".registry.headers]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint=["https://registry-1.docker.io", "https://45hrqeao.mirror.aliyuncs.com"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.k8s.io"]
endpoint=["https://45hrqeao.mirror.aliyuncs.com", "https://k8s.m.daocloud.io", "https://docker.mirrors.ustc.edu.cn","https://hub-mirror.c.163.com"]
[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.internal.v1.tracing"]
sampling_ratio = 1.0
service_name = "containerd"
[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"]
no_shim = false
runtime = "runc"
runtime_root = ""
shim = "containerd-shim"
shim_debug = false
[plugins."io.containerd.runtime.v2.task"]
platforms = ["linux/amd64"]
sched_core = false
[plugins."io.containerd.service.v1.diff-service"]
default = ["walking"]
[plugins."io.containerd.service.v1.tasks-service"]
rdt_config_file = ""
[plugins."io.containerd.snapshotter.v1.aufs"]
root_path = ""
[plugins."io.containerd.snapshotter.v1.btrfs"]
root_path = ""
[plugins."io.containerd.snapshotter.v1.devmapper"]
async_remove = false
base_image_size = ""
discard_blocks = false
fs_options = ""
fs_type = ""
pool_name = ""
root_path = ""
[plugins."io.containerd.snapshotter.v1.native"]
root_path = ""
[plugins."io.containerd.snapshotter.v1.overlayfs"]
mount_options = []
root_path = ""
sync_remove = false
upperdir_label = false
[plugins."io.containerd.snapshotter.v1.zfs"]
root_path = ""
[plugins."io.containerd.tracing.processor.v1.otlp"]
endpoint = ""
insecure = false
protocol = ""
[proxy_plugins]
[stream_processors]
[stream_processors."io.containerd.ocicrypt.decoder.v1.tar"]
accepts = ["application/vnd.oci.image.layer.v1.tar+encrypted"]
args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"]
env = ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"]
path = "ctd-decoder"
returns = "application/vnd.oci.image.layer.v1.tar"
[stream_processors."io.containerd.ocicrypt.decoder.v1.tar.gzip"]
accepts = ["application/vnd.oci.image.layer.v1.tar+gzip+encrypted"]
args = ["--decryption-keys-path", "/etc/containerd/ocicrypt/keys"]
env = ["OCICRYPT_KEYPROVIDER_CONFIG=/etc/containerd/ocicrypt/ocicrypt_keyprovider.conf"]
path = "ctd-decoder"
returns = "application/vnd.oci.image.layer.v1.tar+gzip"
[timeouts]
"io.containerd.timeout.bolt.open" = "0s"
"io.containerd.timeout.shim.cleanup" = "5s"
"io.containerd.timeout.shim.load" = "5s"
"io.containerd.timeout.shim.shutdown" = "3s"
"io.containerd.timeout.task.state" = "2s"
[ttrpc]
address = ""
gid = 0
uid = 0
掘金
总结
配置镜像加速踩了不少坑,网上不少抄来抄去的,挺浪费时间的,总的来说,多去官方文档查阅
参考
配置containerd使用可用镜像源
从上周开始,docker hub在中国已无法访问,镜像自然也拉取不了了。
我负责的一个项目需要使用kubernetes的容器运行时containerd从docker hub拉取alpine:3镜像,正在思考如何才能在无网环境拉取到这个镜像,现在连外网也拉不了了。于是开始着手处理这个问题。
Plan A
首先想到的是在配置文件中增加一个镜像站的配置,当检测到某些环境变量的时候,就在需要拉的镜像前加上这个镜像站,实现根据配置不同,从不同的镜像站拉取镜像。
然而做起来却比我想的要复杂,配置镜像的函数根本就没有传入config字典(config字典从配置文件得到),所以就没法得到镜像站配置。
def build_pod_request_obj(self, context=None):
"""Post-hook for pod spec creation,
used to attach resource limits to xcom sidecar and set owner object
"""
pod = super().build_pod_request_obj(context)
# Set resource limits for sidecar
c = pod.spec.containers[-1]
assert c.name == 'airflow-xcom-sidecar'
c.image = 'alpine:3' # By setting a tag it will not be fetched again if already present
c.resources.requests = {"cpu": "5m", 'memory': '64Mi'}
c.resources.limits = {"cpu": "1000m", 'memory': '64Mi'}
并且这还是个父类的成员函数在子类中的重写,一时不知道如何修改。
修改containerd镜像源
正发愁之际,突然想到,最终负责拉取镜像的是与kubernetes对接的containerd,既然docker可以添加镜像源,那么containerd也应该可以。问了问chatgpt,发现需要如此修改/etc/containerd/config.toml:
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://atomhub.openatom.cn"]
这样向docker.io的拉取请求就会被分别发送到atomhub.openatom.cn。atomhub.openatom.cn是一个目前能访问到的源。配置完以后systemctl daemon-reload && systemctl restart containerd,
配置生效了,但死活就还是拉不下来。
ctr image pull docker.io/alpine:3
在google上搜索了很久以后,才终于找到了答案。原来不应该用命令行ctr,而应该用crictl,因为我们修改的是containerd的cri配置(container runtime interface),使cri使用mirror,而containerd自带的命令行ctr是不受影响的,kubernetes的命令行crictl与containerd通过cri连接,因此使用crictl才是正确的选择。
crictl pull docker.io/alpine:3
这样的话就没问题了。
路径问题
然后问题还没有完全解决。crictl pull docker.io/alpine:3可以成功拉取镜像,但crictl pull alpine:3却不行,实际的代码中要执行的是后面的命令。
root@des206:/etc/containerd# crictl pull alpine:3
E0615 15:32:17.075323 1813746 remote_image.go:242] "PullImage from image service failed" err="rpc error: code = Unknown desc = failed to pull and unpack image \"docker.io/library/alpine:3\": failed to copy: httpReadSeeker: failed open: unexpected status code https://atomhub.openatom.cn/v2/library/alpine/blobs/sha256:4fc1d548892b0a8c8020fa151a38283cd62b9f8bfb1101f70f951b8234fafdb0?ns=docker.io: 502 Bad Gateway" image="alpine:3"
FATA[0003] pulling image: rpc error: code = Unknown desc = failed to pull and unpack image "docker.io/library/alpine:3": failed to copy: httpReadSeeker: failed open: unexpected status code https://atomhub.openatom.cn/v2/library/alpine/blobs/sha256:4fc1d548892b0a8c8020fa151a38283cd62b9f8bfb1101f70f951b8234fafdb0?ns=docker.io: 502 Bad Gateway
猜测是路径问题,把配置改成如下:
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://atomhub.openatom.cn"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io/library"]
endpoint = ["https://atomhub.openatom.cn/library"]
这样不管crictl pull docker.io/alpine:3还是crictl pull alpine:3就都能成功了。
root@des206:/etc/containerd# crictl pull docker.io/alpine:3
Image is up to date for sha256:df5a07b2eb43237722c03f02e718acc70a1e54a0ddaa8c5704cdbb32f6adfe6a
root@des206:/etc/containerd# crictl pull alpine:3
Image is up to date for sha256:df5a07b2eb43237722c03f02e718acc70a1e54a0ddaa8c5704cdbb32f6adfe6a
博客和文章 · LMAX-Exchange/disruptor 维基
Containerd 如何配置 Proxy?-阿里云开发者社区
前言
在某些 air gap 场景中,往往需要离线或使用代理 (Proxy), 例如:
- 需要通过 Proxy pull 容器镜像:
- Docker Hub:
docker.io - Quay:
quay.io - GCR:
gcr.io - GitHub 镜像库:
ghcr.io
- 在某些企业环境中,需要通过代理访问外部服务
Docker 如何配置代理想必大家都很清楚,但是自从 Kubernetes 1.20 版本以后开始弃用 Docker, containerd 逐渐成为主流 CRI.
所以我们下面介绍一下如何配置 contaienrd 的 Proxy.
📝Notes:
还有一种场景需要 containerd 配置 proxy, 就是将 Dragonfly 和 containerd 结合使用 的时候。
Containerd 配置 Proxy 步骤
这里以通过 systemd 安装的 containerd 为例。
containerd 的配置一般位于 /etc/containerd/config.toml 下,service 文件位于:/etc/systemd/system/containerd.service
配置 Proxy 可以通过 service 环境变量方式配置,具体如下:
创建或编辑文件:/etc/systemd/system/containerd.service.d/http-proxy.conf
内容如下:
|
配置后保存重启即可:
|
最佳实践:Proxy 中 NO_PROXY 的推荐配置
在配置 Proxy 时要特别注意,哪些要走 Proxy, 哪些不走 Proxy 要非常明确,避免出现网络访问异常甚至业务异常。
这里有个推荐 NO_PROXY 配置:
- 本地地址和网段:
localhost和127.0.0.1或127.0.0.0/8 - Kubernetes 的默认域名后缀:
.svc和.cluster.local - Kubernetes Node 的网段甚至所有应该不用 proxy 访问的 node 网段:
<nodeCIDR> - APIServer 的内部 URL:
<APIServerInternalURL> - Service Network:
<serviceNetworkCIDRs> - (如有)etcd 的 Discovery Domain:
<etcdDiscoveryDomain> - Cluster Network:
<clusterNetworkCIDRs> - 其他特定平台相关网段(如 DevOps, Git/ 制品仓库。…):
<platformSpecific> - 其他特定
NO_PROXY网段:<REST_OF_CUSTOM_EXCEPTIONS> - 常用内网网段:
10.0.0.0/8172.16.0.0/12192.168.0.0/16
最终配置如下:
|
🎉🎉🎉
总结
Kubernetes 1.20 以上,企业 air gap 场景下可能会需要用到 containerd 配置 Proxy.
本文介绍了其配置方法,以及配置过程中 NO_PROXY 的最佳实践。

浙公网安备 33010602011771号