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.5CRI插件默认已集成对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。可能原因2config.toml中配置的主机地址与镜像引用中的地址不完全匹配。比如配置的是”harbor.com”但拉取时用的是”harbor.com:443”显式指定了端口containerd会视为两个不同的主机。确保完全一致包括端口如果镜像引用里带了端口的话。错误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查看证书信息。错误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.jsonctr可能找不到。解决方法是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通过CRIContainer 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使用了imagePullSecretscontainerd在连接仓库时仍然需要知道是否信任该仓库的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 Secretkubectl create secret docker-registry regcred --docker-serverharbor.example.com --docker-usernameadmin --docker-passwordxxx。在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繁琐但其模块化和清晰的配置分离在大规模、自动化运维的场景下其实提供了更强的可管理性和一致性。