ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Containerd对接Harbor镜像仓库:配置原理与实战排障

Containerd对接Harbor镜像仓库:配置原理与实战排障 手里管着一批K8s节点的人日常跟镜像仓库打交道最多的动作就是配置拉取认证、处理自签名证书报错、纠结到底要不要把TLS校验关掉。Containerd成为K8s标准运行时之后原先那套针对Docker的配置方式已经不能直接照搬很多人在这一步卡住明明Harbor部署好了仓库也能访问Kubelet却始终报ErrImagePull。这篇文章把Containerd对接Harbor私有镜像仓库这件事从头到尾拆干净讲清楚配置原理、证书信任链路、认证信息写入方式以及镜像推拉全流程验证。适合正在从Docker迁移到Containerd、或者刚接触K8sContainerdHarbor这套组合的运维和开发工程师读完可以直接照着配置省掉一堆盲试的时间。1. 方案选型Containerd对接Harbor先把配置入口找准1.1 Docker与Containerd配置思路的差异用Docker的时候私有仓库配置集中在/etc/docker/daemon.json里一个insecure-registries加一个registry-mirrors基本就完事。但Containerd不一样它的配置入口是/etc/containerd/config.toml并且内部还区分了两套体系一套是CRI插件plugins.io.containerd.grpc.v1.cri负责Kubelet的镜像拉取请求另一套是底层镜像服务distribution负责实际的registry交互。很多人习惯性去网上找一个旧的配置片段直接贴进去发现不生效甚至重启Containerd后直接起不来多半是因为没搞懂这两套体系的关系。CRI插件解析Kubelet传过来的镜像名然后交给底层的distribution服务去跟registry通信而distribution服务读的又是镜像仓库相关的独立配置段。版本不同字段名也不同早期版本用mirrors和configs内联写在config.toml里新版本则推荐用config_path指向一个目录单独管理每个镜像仓库的配置。这就是为什么Docker的配置经验在这里派不上用场的根本原因。在方案选型上我的建议很直接能上HTTPS就绝对不要用skip_verify跳过校验能让config_path就绝对不要把所有仓库塞在一个内联配置里。生产环境把TLS校验关掉等于把镜像内容暴露在传输链路上而且一旦出现中间人劫持下发的镜像就是不可信的。这个风险比多配几行证书路径要大得多不值得省。1.2 两种Containerd配置方式的取舍当前主流Containerd版本1.6及以上支持两种写法第一种是内联配置直接在config.toml的[plugins.io.containerd.grpc.v1.cri.registry]下面写mirrors和configs。优点是单文件搞定缺点是仓库多了以后这个文件会变得非常臃肿而且每一处缩进和字符都要小心改错一个逗号整个配置就废掉。第二种是config_path模式。在/etc/containerd/certs.d/下建一个以镜像仓库地址命名的目录每个目录里面放一个hosts.toml文件单独描述这个仓库的TLS信任、认证信息、主机替换规则。优点极其明显改一个仓库不影响其他仓库文件结构清晰排查问题的时候直接定位到对应目录配合Ansible之类的工具批量下发也方便。我个人强烈推荐第二种。从维护角度看生产环境镜像仓库不止一个测试环境有个Harbor生产环境可能还有另一个Harbor再加上偶尔要临时拉一个内部nexus仓库的镜像用config_path管理的体验是内联配置完全比不了的。后面的实操部分全部基于config_path展开这也是当前Containerd文档主推的方式。2. Harbor仓库端准备证书、账号、项目一个都不能少2.1 Harbor的HTTPS证书方案Harbor本身支持HTTP和HTTPS两种暴露方式但既然Containerd侧要配置TLS信任链仓库端就必须先把HTTPS证书准备好。常见的做法是内部CA签发的证书或者用正式的商业证书。内部CA签发的流程大致是先创建一个CA根证书然后用这个CA去签发Harbor服务器证书。服务器证书的Common Name或者Subject Alternative Name必须要包含Harbor的域名最好把IP也加进去。比如域名是harbor.internal.example.comIP是192.168.31.80那么SAN里这两项都要有否则后续验证证书时会出现域名对不上的报错。签好证书后在Harbor的配置文件harbor.yml里指定证书和私钥的路径hostname: harbor.internal.example.com port: 443 http: port: 80 https: port: 443 certificate: /data/cert/harbor.internal.example.com.crt private_key: /data/cert/harbor.internal.example.com.key配置完执行./install.sh重新部署Harbor就会用指定的证书启动HTTPS服务。验证方法很简单在任意一台机器上执行curl -v https://harbor.internal.example.com/v2/返回401 Unauthorized就说明证书链路和Harbor服务都没问题这个报错是正常的因为还没带认证信息。证书分发这一步特别容易漏。Harbor部署完Containerd节点上是没有这个CA根证书的所以后续拉取镜像时必然报x509: certificate signed by unknown authority。需要把CA根证书不是服务器证书复制到每一个Containerd节点的指定目录比如/etc/containerd/certs.d/ca.crt并确保文件权限为0644。2.2 Harbor账号体系与项目权限Harbor里的账号模型分两种一种是普通用户账号在UI上创建有密码另一种是机器人账号通过API或UI创建适合给CI/CD系统用。对Containerd节点来说它需要的是一个能拉取镜像的账号推镜像的通常是CI系统职责其实可以分开。在项目权限设计上我通常这样做镜像拉取用只读账号镜像推送用单独的账号或机器人账号。这样即使某个节点被攻破了攻击者也只能拉不能推能减少供应链投毒这种最恶劣的情况。创建账号后还需要在Harbor的项目里把该账号加为成员并赋予对应的角色。注意Harbor的项目权限是独立的一个账号只有在被加入某个项目后才有权限操作这个项目下的镜像。常见问题是账号明明存在但拉取时报401 Unauthorized查到最后往往是忘了把账号添加进目标项目。在实际运维中我还习惯给每个K8s集群分配独立的机器人账号这样审计日志里面能清楚看到哪个集群在拉什么镜像排查问题的时候特别有用。当然账号数量多的话要做好命名规范比如robot$k8s-prod-cluster01$pull这种格式一眼就能看出用途。3. Containerd核心配置config_path模式下的registry配置拆解3.1 基础配置开启config_path在/etc/containerd/config.toml里增加以下内容version 2 [plugins.io.containerd.grpc.v1.cri.registry] config_path /etc/containerd/certs.d这一段的意思是CRI插件遇到镜像仓库地址时会去/etc/containerd/certs.d/下找以此仓库地址命名的目录读取里面的hosts.toml文件来决定怎么连接这个仓库。如果是默认的Docker Hub会去查docker.io目录如果是自定义的harbor.internal.example.com就去查s/harbor.internal.example.com目录。配置完先执行containerd config dump检查一下当前生效的配置再重启Containerdsystemctl restart containerd这里有一个很多人容易犯的错误改完config.toml后一定要确认version 2存在。如果配置里没有显式指定版本某些版本的Containerd会默认按老格式解析导致自定义的config_path不生效。3.2hosts.toml文件的实际写法在/etc/containerd/certs.d/harbor.internal.example.com/目录下创建hosts.toml内容如下server https://harbor.internal.example.com [host.https://harbor.internal.example.com] capabilities [pull, resolve, push] ca /etc/containerd/certs.d/ca.crt逐行解释一下这个文件server字段声明了默认的registry地址。当你执行ctr images pull harbor.internal.example.com/library/nginx:1.25时Containerd会先用这个server去找对应的配置。[host...]这个段是核心。每一段定义了一个实际连接的目标地址。capabilities是一个列表包含pull、resolve、push三个可选能力。其中resolve表示能解析镜像的manifest和tagpull表示能拉取layerpush表示能推送。如果只配置了pull而没有push当执行nginx:1.25的推送操作时Containerd会明确拒绝。ca字段指定了CA证书路径。注意这里填的是用于校验服务器证书的CA证书不是给Containerd自己用的客户端证书。如果Harbor使用的CA根证书和Containerd节点的系统CA库不一致就必须把这个ca路径指向对应文件否则即使节点的/etc/pki/tls/certs里装了CAContainerd仍然不会自动信任。很多教程在这里直接写skip_verify true来省事。但正如前面说的生产环境不建议这样做。正确的做法是保证ca路径正确skip_verify不配置或者显式设为false。3.3 认证信息配置与多仓库管理拉取私有项目镜像时光有TLS还不够还需要认证。Containerd的认证信息配置在host段内有两种等价的方式。第一种是写成header形式[host.https://harbor.internal.example.com.header] authorization Basic base64编码的用户名:密码base64编码的用户名:密码是UR L安全的Base64不带等号填充。可以用下面命令生成echo -n pulluser:MyPassw0rd | base64 -w0输出形如cHVsbHVzZXI6TXlQYXNzdzByZA。注意这里的Base64编码是标准的如果编码结果里有/或字符在TOML里是允许直接写的但为了避免后续解析出错我更推荐用UR L安全的Base64结果或者直接使用下面推荐的第二种方式。第二种是使用标准仓库credentials格式其实Containerd的config_path也支持读取Docker风格的凭证文件。如果你已经在节点上用docker login登录过Harbor那么可以这样配置[host.https://harbor.internal.example.com] capabilities [pull, resolve, push] ca /etc/containerd/certs.d/ca.crt [host.https://harbor.internal.example.com.auth] username pulluser password MyPassw0rd这是最直观的写法直接填明文账号密码。实际项目中为了不把密码留在明文配置文件里更稳妥的做法是配合Ansible Vault或者KMS解密后渲染配置文件。但从效果上讲两种方式对Containerd完全等效。需要注意的是Containerd不像Docker那样每次拉取都提示登录。认证信息是静态配置在hosts.toml里的一旦密码变更必须手动更新这个文件。所以建议运维侧做好密码管理制度定期轮换Harbor密码后同步更新所有节点的hosts.toml。管理多个仓库时config_path目录结构如下/etc/containerd/certs.d/ ├── docker.io/ │ └── hosts.toml ├── harbor.internal.example.com/ │ └── hosts.toml └── registry.other.example.net/ └── hosts.toml每个目录各自独立互不影响。这种结构在批量下发配置时非常方便用Ansible的template模块逐台渲染就行。4. 实操验证镜像推拉全流程与平滑切换4.1 推送镜像到HarborContainerd生态里镜像管理工具有三件套ctr是Containerd的原生CLIcrictl是跟CRI接口打交道的工具nerdctl是兼容Docker命令风格的第三方CLI。平时验证私有仓库配置我最常用的是nerdctl因为它能直接复用Docker的命令习惯。推送前需要先给镜像打上Harbor地址的tag。假如本地有一个nginx:1.25的镜像执行nerdctl tag nginx:1.25 harbor.internal.example.com/library/nginx:1.25 nerdctl push harbor.internal.example.com/library/nginx:1.25推的时候nerdctl会自动读取Containerd配置里harbor.internal.example.com对应的hosts.toml。如果capabilities里包含了push并且认证信息正确推送就会成功。实际操作中我遇到了一个有意思的坑有些版本的nerdctl在hosts.toml里配置了capabilities [pull, resolve]没有push执行nerdctl push时会提示权限不足但错误提示并不明显只显示failed to push。排查了半天才发现是capabilities缺了push。所以推送前先确认一下自己的hosts.toml是不是只配置了拉取能力。如果用ctr推送命令是ctr images push --plain-httpfalse harbor.internal.example.com/library/nginx:1.25但ctr不读取CRI插件配置里的config_path所以它推送时走的TLS信任是Containerd底层distribution的配置跟config_path不是一套。要避免这种混乱统一用nerdctl或crictl是最省心的。4.2 从Harbor拉取镜像并创建容器验证拉取最直接的方式是用crictl因为它走的就是Kubelet那套CRI接口和实际运行场景完全一致。crictl pull harbor.internal.example.com/library/nginx:1.25如果配置没问题命令会正常返回然后用crictl images确认镜像已经落到本地。注意此时Containerd会先去Harbor查manifest然后把layer拉下来存到本地content store里。再进一步在K8s里创建一个测试Pod来验证。在Pod的spec.containers[].image字段写上完整地址apiVersion: v1 kind: Pod metadata: name: nginx-test spec: containers: - name: nginx image: harbor.internal.example.com/library/nginx:1.25 imagePullPolicy: Always如果节点上没有配置imagePullSecrets那么Kubelet会直接用Containerd节点上的仓库凭据去拉取。因为我们已经通过hosts.toml把认证信息配好了所以这里不需要额外配置imagePullSecrets。这个特性是Containerd比Docker模式在K8s集成上更舒服的地方不需要在每个Namespace里疲于维护Secret。但要注意如果你在K8s里同时配置了imagePullSecrets这个Secret的优先级会高于hosts.toml里的静态认证信息。一旦Secret失效或者被删除就算hosts.toml里的认证正确也会出现401。我之前就遇到过这样的问题几个Namespace里都设置了同一个Secret后来Secret过期了全部拉取失败排查了很久才发现是Secret优先级导致最后统一改成只用hosts.toml的认证问题就消失了。创建Podkubectl apply -f nginx-test.yaml kubectl get pod nginx-test状态变成Running说明整条链路已经打通。此时在节点上执行crictl info | grep -A10 harbor可以看到Containerd已经识别到该仓库并完成了验证。4.3 从Docker配置迁移到Containerd的要点很多团队是已经有了一套跑在Docker上的Harbor配置现在要切到Containerd。迁移时最大的问题是daemon.json里的配置不能直接复制过来。daemon.json里的insecure-registries对应的是Containerd里的config_path目录中的TLS信任配置但语义不完全等价。Docker跳过TLS校验时只要一个数组元素写进去就行Containerd却需要明确的ca路径或skip_verify配置两者深度不一样。直接照搬的结果往往是Docker能拉Containerd一直报TLS错误。迁移顺序我建议是这样的先在Containerd节点上创建/etc/containerd/certs.d/目录把旧Harbor的所有仓库条目按域名建目录。把Harbor的CA根证书复制到统一目录还是在/etc/containerd/certs.d/ca.crt。逐个仓库生成hosts.toml把原daemon.json里的insecure-registries条目全部对应配置好TLS信任。原daemon.json里如果配置了auths认证来自docker login要手动转成host段的username和password不能直接把整个auths内容粘贴进TOML。在迁移过程中Containerd进程可以不停止。config.toml的变更在重启Containerd之后生效但正在运行的Pod和容器不会因此中断。Containerd重启时运行中的Pod还会继续跑Kubelet会在这段时间内无法跟Containerd通信所以如果K8s集群里有正在发版或滚动更新的服务还是尽量选在低峰期操作或者滚动重启节点。5. 常见问题速查TLS、认证、超时与DNS把使用中踩过的高频问题整理成一个速查表遇到对应报错直接对照处理能省一大半排障时间。现象排查方向解决参考x509: certificate signed by unknown authorityCA证书未被Containerd信任检查hosts.toml里的ca路径是否正确CA证书是否与Harbor签发证书匹配x509: certificate is valid for harbor.internal, not x.x.x.x证书SAN不匹配重新签发证书把域名和IP都加进SAN401 Unauthorized认证信息错误或账号无项目权限检查hosts.toml的账号密码确认账号已被加入目标Harbor项目并分配权限failed to resolve reference镜像路径错误或仓库未找到确认镜像地址包含项目名检查Harbor项目是否为公开/私有正确配置i/o timeout网络不通或DNS解析失败在节点上用curl直接请求Harbor地址检查防火墙和安全组规则确认DNS能解析到Harbor IPconnection refusedHarbor服务未启动或端口未监听登录Harbor服务器执行docker-compose ps确认容器状态检查端口绑定not found镜像不存在或仓库名错误在Harbor UI中确认项目的镜像列表和tag名注意大小写几个细节补充x509 signed by unknown authority这个报错是最常见的。它表示Containerd并没有读到我们指定的CA文件或者读到了但该CA不是签发Harbor服务器证书的那个根证书。遇到这个报错第一件事不是改skip_verify而是用openssl s_client -connect harbor.internal.example.com:443 -showcerts查看Harbor实际返回的证书链跟ca.crt比对一下指纹openssl x509 -in /etc/containerd/certs.d/ca.crt -noout -fingerprint确认指纹一致后再看hosts.toml的路径配置。另一个高频问题是把HTTP和HTTPS搞混。Harbor如果用HTTP方式暴露在80端口而hosts.toml里写了https://harbor.internal.example.comContainerd连上去必然失败。此时要么把Harbor切到HTTPS要么在host.toml里写http://harbor.internal.example.com并在[host...]里配置skip_verify true或caHTTP不需要TLS校验。但如前面强调生产环境强烈不建议走HTTP。K8s中配置了imagePullSecrets导致认证冲突的情况也值得单独记录。在Pod的yaml里如果配置了imagePullSecretsKubelet会优先使用Secret中的dockerconfigjson数据去认证而不走hosts.toml里的静态认证。当Secret里的凭据过期或错误时即使节点的hosts.toml是正确的也会出现401。排查时记得检查Namespace中是否存在Secret并确认其内容是否仍然有效kubectl get secret -n namespace -o jsonpath{.items[*].data..dockerconfigjson} | base64 -d密钥轮换的节奏如果比较频繁建议直接去掉imagePullSecrets统一走节点级hosts.toml认证。如果一个集群内存在多个不同Harbor账号的诉求比如不同项目组想隔离权限那还是保留Secret方案但要注意跟上轮换节奏。还有一个隐蔽问题是DNS缓存。如果Harbor域名的解析记录变更过节点上系统DNS或者/etc/hosts配置更新不及时会出现i/o timeout或连接超时。排查时直接在节点上执行getent hosts harbor.internal.example.com确认解析到的IP是否正确必要时在/etc/hosts里临时绑定IP验证连通性确定无误后再调整DNS服务器。镜像拉取超时也是生产环境常见问题。Harbor仓库中镜像特别大或者网络带宽受限拉取到一半报context deadline exceeded。这通常不是配置问题而是网络或磁盘I/O瓶颈。可以试着在config.toml里调整拉取超时时间但更彻底的办法是从带宽、Harbor存储后端饱和度和节点磁盘I/O三个方向排查。如果局域网带宽足够通常是Harbor存储用的NFS或S3性能不达标导致优先优化存储后端比调containerd参数更有效。6. 实操经验总结这些细节让配置更稳最后分享几个在真实维护过程中积累的习惯这些经验在官方文档里往往不会写这么细但对实际运维很有帮助。第一关于CA证书目录的组织。我习惯在/etc/containerd/下单独建一个certs.d/目录存放所有CA文件hosts.toml里的ca路径统一指向这个目录下的固定文件名。节点数量多的时候配合Ansible批量下发一次变更所有节点同步不会出现某个节点漏配的问题。证书文件权限要设置成0644如果是root身份创建的还要确认containerd进程能读到。第二每次升级Containerd版本之前先备份/etc/containerd/config.toml和整个certs.d/目录。Containerd版本升级偶尔会改变配置解析逻辑比如从1.5升级到1.6时老配置里的mirrors写法在新版本中兼容性没问题但1.6之后官方推荐的方向已经转向config_path。提前备份能在升级后出现异常时快速回滚避免线上故障。第三给hosts.toml里的认证信息做一个变更记录。密码轮换后除了更新文件还要注意检查Harbor侧账号状态。我遇到过账号被禁用但节点上的配置还是旧密码的情况拉取时不停报401折腾半天才在Harbor UI里发现机器人账号已经过期。建议在运维Wiki或工单系统里记录每个仓库账号的使用对象和轮换日期避免这种低级问题。第四需要在初始化一个新节点时快速验证Containerd与Harbor的连通性可以提前准备一组测试命令。我会先执行ctr version确认containerd正常再执行crictl pull从Harbor拉一个小镜像比如hello-world或busybox确认无误后再让Kubelet启动工作负载。这套验证流程五分钟内完成能提前暴露大部分配置问题而不是等Pod一直卡在ImagePullBackOff才回头查。Containerd与Harbor集成的链路并不复杂核心就是证书信任、认证配置、路径命名三件事。把这三件事理清楚私有仓库在K8s集群里就能像Docker Hub一样顺畅使用。
返回列表