1. 从一次部署失败说起:为什么containerd不认我的私有仓库?
最近在给一个内部AI项目做容器化部署,环境用的是containerd。模型镜像都推到了我们自己搭建的Harbor私有仓库里,满心以为一条简单的ctr image pull命令就能搞定,结果却吃了闭门羹,直接报错failed to resolve reference或者提示unauthorized。这场景估计不少用containerd替代Docker做底层运行时的朋友都遇到过。表面上看,Harbor仓库地址能ping通,镜像tag也确认无误,但containerd就是拉不下来。问题的根子,其实就出在containerd那个看似简单、实则关键的配置文件——config.toml上。
Docker用户可能对/etc/docker/daemon.json很熟悉,加个insecure-registries配置就能让Docker守护进程信任自签证书的私有仓库。但containerd的配置逻辑完全不同,它不会自动继承Docker的配置,也有一套自己的证书和认证管理机制。如果你直接从Docker环境迁移过来,或者初次搭建Kubernetes集群(默认使用containerd),就很容易在这个环节踩坑。简单来说,想让containerd从你的私有Harbor仓库拉取镜像,核心就是正确配置config.toml文件中的registry相关段落,明确告诉containerd:“嘿,这个仓库地址我信任,这是访问它需要的凭证。”
这个过程涉及几个关键点:首先是仓库地址的识别与信任配置,特别是对于使用自签名HTTPS证书或干脆用HTTP的Harbor;其次是认证信息的配置,如何安全地提供用户名密码;最后是配置生效的机制,改完文件可不是万事大吉。接下来,我们就一步步拆解,把每个环节的“为什么”和“怎么做”都搞清楚。
2. 解剖containerd的镜像拉取逻辑:config.toml是关键枢纽
要解决问题,得先理解containerd是怎么工作的。Containerd在设计上追求模块化和清晰的责任边界,它的镜像拉取功能主要由containerd.io这个组件处理,而配置则集中管理在config.toml文件中。这个文件通常位于/etc/containerd/config.toml。如果没有,你可以用containerd config default > /etc/containerd/config.toml命令生成一个默认配置。
镜像拉取的过程,可以粗略理解为:当你执行ctr image pull my-harbor.com/library/nginx:latest时,containerd会解析这个镜像引用(reference)。它首先会提取出my-harbor.com这个主机地址,然后去config.toml中[plugins.”io.containerd.grpc.v1.cri”.registry]这个核心区域(对于通过CRI接口调用,比如Kubernetes,主要看这里)或者顶层的[plugins.”io.containerd.transfer.v1.local”.registry]查找针对这个主机的配置。配置决定了去哪里找这个仓库、是否信任其TLS证书、以及用什么身份去访问。
这里有一个非常重要的概念:镜像仓库的“作用域”(scope)和“主机”(host)配置。在config.toml里,你不会直接配置一个完整的镜像URL,而是配置一个仓库主机地址(比如”my-harbor.com”)对应的策略。对于Harbor这类私有仓库,我们通常需要配置两样东西:
- TLS配置:告诉containerd是否验证以及如何验证该仓库的HTTPS证书。对于内部测试环境用的自签名证书,或者直接使用HTTP的仓库,这里需要特殊处理。
- 认证配置:告诉containerd访问这个仓库需要的用户名和密码。containerd支持从本地文件(
auths配置)或外部助手(credential helper)获取凭证。
默认的config.toml通常只配置了Docker Hub的镜像加速器(mirror),对私有仓库是“一无所知”的状态。这就是为什么直接拉取会失败。下面我们进入实操环节,看看如何针对常见的Harbor部署场景来修改配置。
3. 实战配置:针对HTTP与自签名HTTPS Harbor的config.toml修改
Harbor的访问方式主要分两种:HTTP和HTTPS(可能自签名)。配置方式因协议而异,我们分情况讨论。在修改任何配置之前,强烈建议先备份原文件:cp /etc/containerd/config.toml /etc/containerd/config.toml.bak。
3.1 场景一:Harbor使用HTTP协议(非加密,常用于内网测试)
在内部开发或测试环境,为了简化,有时会直接使用HTTP协议部署Harbor。此时,containerd必须被告知“忽略对该主机地址的TLS验证”,实际上就是将其视为不安全(insecure)的仓库。
你需要定位到config.toml中的[plugins.”io.containerd.grpc.v1.cri”.registry.configs]部分。如果不存在,就创建它。然后为你Harbor的IP或域名添加一个[plugins.”io.containerd.grpc.v1.cri”.registry.configs.”<你的harbor地址>”.tls]的配置,并将其设为不安全。
这里有一个关键细节:config.toml是TOML格式,对嵌套表(table)的创建顺序有要求。通常,你需要确保[plugins.”io.containerd.grpc.v1.cri”.registry]这个表存在,然后在其下创建configs子表,再在configs下创建以你的Harbor地址为键的子表。
一个配置示例如下:
[plugins."io.containerd.grpc.v1.cri".registry] [plugins."io.containerd.grpc.v1.cri".registry.configs] [plugins."io.containerd.grpc.v1.cri".registry.configs."192.168.1.100:8080".tls] insecure_skip_verify = true为什么是insecure_skip_verify = true?这个选项直接跳过了对服务端证书的所有验证,包括证书是否由可信机构签发、域名是否匹配、是否过期等。这仅在完全信任的网络环境中用于HTTP或自签名HTTPS仓库。对于HTTPS+自签名证书的场景,有更优的解决方案(见下文)。
注意:如果你的Harbor地址是域名,且通过HTTP访问,配置方式相同。但请注意,containerd的镜像引用是严格基于
host:port的。如果你在Harbor中配置了项目(project),比如地址是my-harbor.com/project-a,那么在config.toml中配置的主机地址仍然是my-harbor.com,项目路径是镜像名的一部分。
3.2 场景二:Harbor使用自签名HTTPS证书(更常见的安全内网部署)
生产或预发布环境更推荐使用HTTPS,即使证书是自签名的。对于自签名证书,最佳实践不是简单地跳过验证(insecure_skip_verify),而是将你的自签名CA证书添加到containerd信任的根证书列表中。这样既保持了TLS加密通信的安全性,又建立了信任。
操作步骤如下:
获取Harbor服务器的CA证书。如果你是自己签发的,找到你的CA证书文件(如
ca.crt)。如果是Harbor安装程序生成的,通常可以在Harbor服务器上的/data/cert/或安装目录的ssl子目录下找到.crt文件。将CA证书复制到containerd的证书目录。Containerd会读取系统证书库以及它自己的专属目录。一个可靠的位置是
/etc/containerd/certs.d/。你需要在这个目录下为你的Harbor仓库创建一个特定的子目录结构。目录结构规则是:
/etc/containerd/certs.d/<host:port>/。例如,对于harbor.example.com:8443,你需要创建目录:mkdir -p /etc/containerd/certs.d/harbor.example.com:8443将CA证书文件放入该目录,并重命名为
ca.crt。cp /path/to/your-ca.crt /etc/containerd/certs.d/harbor.example.com:8443/ca.crt此时,通常无需在
config.toml中为该主机配置特殊的tls选项。因为containerd会自动发现并使用该目录下的ca.crt来验证连接。这是一种更干净、更标准的做法。
为什么推荐这种方式而非insecure_skip_verify?因为insecure_skip_verify完全禁用了TLS验证,存在中间人攻击的风险。而添加CA证书到信任链,只是扩展了containerd信任的证书颁发机构范围,TLS协议本身的安全性(加密、完整性)依然完好。这是安全性与便利性之间更好的平衡。
3.3 配置认证信息:如何安全地提供用户名密码?
无论是HTTP还是HTTPS,如果Harbor仓库设置了访问权限(默认是开启的),你都需要配置认证信息。Containerd支持多种方式,最常用的是通过config.toml直接配置,或者使用~/.docker/config.json文件(需要containerd做相应配置以读取)。
方法一:在config.toml中配置auths(适合固定凭证)
在[plugins.”io.containerd.grpc.v1.cri”.registry.configs.”<host:port>”]表下,可以添加auth字段。注意,这里的密码是明文存储的,所以务必确保配置文件权限安全(如chmod 600 /etc/containerd/config.toml)。
[plugins."io.containerd.grpc.v1.cri".registry] [plugins."io.containerd.grpc.v1.cri".registry.configs] [plugins."io.containerd.grpc.v1.cri".registry.configs."harbor.example.com".auth] username = "admin" password = "Harbor12345"方法二:配置containerd使用Docker的认证文件(推荐,与Docker生态统一)
如果你同时使用Docker和containerd,或者习惯用docker login管理凭证,可以配置containerd去读取Docker的认证文件。这需要在config.toml中配置cred_helper。
首先,用docker login harbor.example.com登录你的Harbor仓库,这会在~/.docker/config.json中生成加密的凭证。
然后,在config.toml的[plugins.”io.containerd.grpc.v1.cri”.registry]部分,配置config_path指向该文件。但更常见的做法是,containerd的CRI插件默认就会尝试读取~/.docker/config.json。为了更明确,你可以这样配置:
[plugins."io.containerd.grpc.v1.cri".registry] [plugins."io.containerd.grpc.v1.cri".registry.configs] # 可以留空或配置特定主机,credential helper会兜底 [plugins."io.containerd.grpc.v1.cri".registry.auths] # 这里通常不直接配密码,而是指定helper [plugins."io.containerd.grpc.v1.cri".registry.config_path] # 这个配置项已废弃或不常用,新版containerd通常自动探测实际上,对于高版本containerd(如1.5+),CRI插件默认已集成对Docker credential helpers的支持。只要~/.docker/config.json存在且包含对应仓库的认证信息,containerd在拉取镜像时就会自动使用。这是一种更安全、更便捷的方式,避免了在多个地方管理密码。
实操心得:我个人的经验是,在Kubernetes节点上,如果要用containerd拉取私有镜像,更标准的做法是使用Kubernetes的
imagePullSecrets。但在节点层面直接调试containerd时,上述两种config.toml的配置方法更直接。如果选择在config.toml中写明文密码,务必结合系统权限和审计策略。
4. 配置生效与排错:重启服务与验证拉取
修改完config.toml后,配置并不会自动生效。你需要重启containerd服务来加载新的配置。
sudo systemctl restart containerd重启后,务必检查服务状态,确保没有因为配置语法错误而启动失败。
sudo systemctl status containerd如果状态是active (running),就可以进行验证了。使用ctr命令(containerd的命令行工具)来测试拉取:
sudo ctr image pull harbor.example.com/library/nginx:latest如果一切配置正确,你会看到镜像层被逐一下载的进度信息。如果失败,请根据错误信息进行排查。
常见排错步骤与踩坑点:
错误:
failed to resolve reference ... not found- 可能原因1:镜像引用写错了。仔细检查Harbor地址、端口、项目名称、镜像名和tag。Harbor的项目名是镜像路径的一部分,例如
harbor.com/myproject/nginx:latest。 - 可能原因2:
config.toml中配置的主机地址与镜像引用中的地址不完全匹配。比如配置的是”harbor.com”,但拉取时用的是”harbor.com:443”(显式指定了端口),containerd会视为两个不同的主机。确保完全一致,包括端口(如果镜像引用里带了端口的话)。
- 可能原因1:镜像引用写错了。仔细检查Harbor地址、端口、项目名称、镜像名和tag。Harbor的项目名是镜像路径的一部分,例如
错误:
x509: certificate signed by unknown authority- 可能原因:对于HTTPS仓库,没有正确配置证书。如果你用的是自签名证书,但没有按照3.2节的方法将CA证书放入
/etc/containerd/certs.d/,或者放错了目录结构,就会报此错。 - 排查:检查目录
/etc/containerd/certs.d/<your-harbor-host:port>/是否存在,里面的ca.crt文件是否有效。可以用openssl x509 -in ca.crt -text查看证书信息。
- 可能原因:对于HTTPS仓库,没有正确配置证书。如果你用的是自签名证书,但没有按照3.2节的方法将CA证书放入
错误:
unauthorized: authentication required- 可能原因:认证失败。
config.toml中的auth配置错误,或者Docker的config.json里没有对应仓库的凭证,或者凭证已过期。 - 排查:
- 如果使用
config.toml明文配置,检查用户名密码是否正确,以及配置的缩进和TOML格式是否正确。 - 如果依赖Docker凭证,执行
cat ~/.docker/config.json | grep harbor.example.com看看是否有对应条目。如果没有,用docker login重新登录。 - 注意:
ctr命令默认以root用户运行,它读取的是/root/.docker/config.json。如果你是用非root用户执行的docker login,凭证会保存在~/.docker/config.json(如/home/username/.docker/config.json),ctr可能找不到。解决方法是:sudo docker login,或者将认证文件复制到root目录(需注意安全):sudo cp ~/.docker/config.json /root/.docker/。
- 如果使用
- 可能原因:认证失败。
服务重启失败
- 可能原因:
config.toml文件存在语法错误。TOML格式对缩进不敏感,但对表([table])的声明和键值对的格式很严格。 - 排查:使用
containerd config dump命令可以验证并输出当前加载的配置。更好的方法是使用tomlv或在线TOML校验器检查语法。一个常见的错误是重复定义同一个表([plugins...])。
- 可能原因:
5. 进阶:Kubernetes集群与Containerd的集成考量
如果你配置containerd是为了给Kubernetes集群使用,那么还需要注意Kubelet的配置。Kubelet通过CRI(Container Runtime Interface)与containerd通信。当我们修改了containerd的config.toml并重启后,理论上Kubelet创建的Pod就能从配置好的私有仓库拉取镜像了。
但是,在Kubernetes中管理私有仓库认证,更主流、更云原生的做法是使用imagePullSecrets。这是一个挂在PodSpec或ServiceAccount上的Secret,里面包含了访问私有仓库的dockerconfigjson。Kubelet会在拉取镜像时,使用这个Secret中的凭证,并通过CRI传递给containerd。
那么,config.toml的配置和imagePullSecrets是什么关系?
config.toml是节点级别的全局配置:它定义了该节点上containerd运行时对所有容器镜像仓库的默认行为(如信任哪些仓库的TLS证书)。即使Pod使用了imagePullSecrets,containerd在连接仓库时,仍然需要知道是否信任该仓库的TLS证书。因此,对于自签名HTTPS的Harbor,在config.toml或/etc/containerd/certs.d/中配置CA证书通常是必须的。而认证信息(用户名密码)则优先由imagePullSecrets提供。imagePullSecrets是Pod/命名空间级别的认证配置:它提供了动态的、细粒度的认证管理。这样就不需要把仓库密码明文写在所有节点的config.toml里,安全性更高,也更符合Kubernetes的声明式管理哲学。
实操建议:在Kubernetes生产环境中,最佳实践是:
- 在所有节点上,通过将CA证书放入
/etc/containerd/certs.d/<host:port>/ca.crt的方式,解决自签名证书的信任问题(对应config.toml的TLS配置)。 - 创建包含Harbor登录凭证的Kubernetes Secret:
kubectl create secret docker-registry regcred --docker-server=harbor.example.com --docker-username=admin --docker-password=xxx。 - 在Pod的
spec中,或Pod使用的ServiceAccount中,引用这个regcredSecret作为imagePullSecrets。
这样,Kubelet调度Pod到某个节点时,会使用Secret中的凭证,并通过CRI告诉containerd去拉取镜像。containerd在连接harbor.example.com时,因为已经信任了其CA证书,所以TLS握手成功,再结合Kubelet传来的认证信息,就能顺利完成镜像拉取。
6. 配置文件管理:版本控制与自动化部署
当你的集群节点数量增多时,手动登录每台机器修改config.toml和放置证书文件会成为运维噩梦。因此,需要将这套配置自动化。
- 配置即代码:将标准的
config.toml模板和CA证书文件纳入版本控制系统(如Git)。模板里可以使用占位符,方便后续替换。 - 使用配置管理工具:使用Ansible, SaltStack, Chef或Puppet等工具,在部署或初始化节点时,将模板文件渲染并推送到各节点的
/etc/containerd/目录下。同时,将CA证书文件分发到/etc/containerd/certs.d/<host:port>/目录。 - 容器化部署考虑:如果你使用像KubeSpray、RKE2、k3s这类工具部署Kubernetes,它们通常提供了配置containerd的选项或hook,可以在集群部署过程中自动完成这些配置。研究你所选工具的文档,找到配置私有仓库信任和认证的最佳方式。
- DaemonSet辅助:对于证书分发,甚至可以运行一个DaemonSet,挂载包含CA证书的ConfigMap,并在每个节点上启动一个init容器,将证书拷贝到宿主机的
/etc/containerd/certs.d/目录。这是一种更“Kubernetes原生”的证书管理方式,但需要注意容器对宿主机目录的写入权限和安全策略。
修改containerd配置并重启服务属于“节点配置”范畴,在不可变基础设施的理念下,更好的做法是将这些配置固化到虚拟机镜像或系统镜像中,而不是在运行时频繁修改。这能保证节点的一致性和可追溯性。
最后,每次修改config.toml这类核心配置文件后,除了重启containerd,还要记得测试相关的核心功能。对于Kubernetes节点,可以部署一个简单的Pod,指定镜像来自你的私有Harbor,观察其创建和拉取镜像的日志,这是最直接的验收方式。整个流程走通后,你会发现containerd的配置虽然初看比Docker繁琐,但其模块化和清晰的配置分离,在大规模、自动化运维的场景下,其实提供了更强的可管理性和一致性。