
生产环境里摸爬滚打过一阵子的朋友应该都有这种体会集群默认运行时从 Docker 切到 Containerd 之后镜像拉取这个环节的坑会成倍增加。尤其是当你需要用 Harbor 这类私有镜像仓库private registry承接日常的镜像分发时Containerd 与 Harbor 的集成配置就成了绕不开的一道坎。这篇文章我就用一线运维的视角把 Containerd 和 Harbor 从部署到联调、从命令测试到问题排查的完整路径梳理一遍适合正在搭 K8s 生产环境、或者想把团队镜像管理规范化的同行参考。整个方案不复杂但里面的细节足够让人挠头我会把那些不会写进官方文档的坑都翻出来。1. 工业级环境里Containerd 和 Harbor 为什么天生一对1.1 没有私有仓库K8s 集群会有一堆糟心事先说说为什么私有仓库是工业级的刚需。很多刚开始接触容器编排的团队喜欢直接用 Docker Hub 或 quay.io 上的公共镜像开发环境跑跑确实没问题。一旦到了生产你会发现公共仓库有几件很头疼的事首先是限流Docker Hub 对匿名拉取有严格的频率限制一次滚动更新拉上百个节点很容易触发429 Too Many Requests其次是稳定性和速度走公网拉镜像受运营商出口带宽影响节点扩容时经常卡在拉镜像这一步再就是合规与安全生产环境的镜像需要可控、可审计公共仓库里一个镜像被删了或者被污染了你连追溯的载体都没有。Harbor 解决的就是这一层问题。它本质上是一个企业级的镜像分发服务在原生 Docker Registry 之上提供了 RBAC 权限、项目隔离、镜像漏洞扫描、签名、复制、回收等能力。你把镜像推到自己的 Harbor集群节点从内网拉取速度、稳定性、安全审计全部回到你的掌控范围。很多企业内部甚至要求所有镜像必须经过 Harbor 扫描没有高危漏洞才能部署这个在容器安全治理里是硬指标。1.2 Containerd 和 Harbor 的集成点到底在哪搞清楚两件事就明白集成什么了Containerd 是容器运行时负责从仓库拉取镜像、管理镜像层、启动和运行容器Harbor 是镜像仓库负责存储镜像、校验身份、分发元数据和层数据。Containerd 需要访问 Harbor 的 HTTP API 完成镜像仓库登录、自动拉取远端镜像、缓存到本地。这里有个容易混淆的点Containerd 自己原生提供ctr命令行工具但它不走 Kubernetes 的 CRI 接口而 kubelet 用的是crictl或者通过 CRI 插件与 Containerd 交互。两条路线在镜像拉取时的配置行为不一样后面我会单独拆开讲。工业级落地的常见形态是一个 Kubernetes 集群所有节点都用 Containerd 作为运行时日常拉取的全量镜像都来自公司统一部署的 Harbor且很多环境网络甚至不允许直接访问公网仓库必须走私有仓库做中转。1.3 什么情况适合参考这篇文章如果你现在的处境是公司正在从 Docker daemon 迁移到 ContainerdK8s 节点已经或即将使用containerd://运行时Harbor 已经部署好或者正在部署但节点上kubelet一直报ErrImagePull不知道私有仓库地址、证书、认证该怎么配想验证从 Containerd 侧用命令直接往 Harbor 推镜像、拉镜像给 CI/CD 流水线做底层联调。这篇文章都适用。我不假设你有太深的镜像仓库原理基础但至少要用过 Docker知道镜像、Tag、Registry 这些基本概念。Harbor 和 Containerd 的版本号不断在变我会给出一个在大多数环境里验证过相对稳定的组合再告诉你为什么会选这个组合。2. 动手之前的规划版本、域名、证书和端口2.1 生产可用的版本组合我见过不少同学一上来就装最新版结果 K8s 官方还未验证Pod 调度到一起就出各种兼容性问题。镜像仓库和运行时这种底层组件稳定大于追新。先说结论推荐的生产组合组件推荐版本补充说明OSCentOS 7.9 / Ubuntu 20.04内核尽量 4.18 以上OverlayFS 更稳Containerd1.7.x如 1.7.23K8s 1.28~1.32 官方验证充分Harbor2.8.x / 2.9.x2.8 之后 Composer 2 兼容性好Docker仅用于部署 Harbor24.x Docker Compose v2Harbor 自身组件靠 Compose 拉起Kubernetes1.28如需联动与 Containerd 1.7 匹配这里解释一下为什么 Containerd 强烈建议 1.7.x它包含 CRI 插件内置io.containerd.grpc.v1.cri也支持我们后面要讲的registry.configs和certs.d两种私有仓库配置方式老代码里踩过的坑相对收敛。Harbor 2.8 的管理界面和 API 都比较成熟默认带 Trivy 扫描组件正好用上。2.2 域名与证书规划Harbor 的配置核心是hostname这个值会直接作为镜像地址的一部分出现在每次 pull / push 命令里。很多人图省事直接配 IP比如hostname: 192.168.1.10。如果只是内网测试倒也能跑通但一旦涉及 TLS 证书IP 证书的管理和信任链配置会非常别扭而且将来换机器或者做高可用IP 一变动全部节点配置都要改。我的建议是规划一个内部域名例如registry.example.com让该域名解析到 Harbor 所在主机的内网 IP。所有节点的/etc/hosts或内网 DNS 都指向它。镜像地址统一用registry.example.com/library/nginx:1.21这种格式可读性强未来迁移也方便。关联到证书工业级环境必须上 HTTPS这里分两种情况公司有内部 CA签发registry.example.com的证书所有节点把内部 CA 证书加入系统信任链或者让 Containerd 明确信任该 CA 文件没有内部 CA用自签名证书openssl生成这时需要把自签 CA 证书分发到每个节点并告诉 Containerd 那份 CA 文件的位置。我不推荐生产环境用insecure_skip_verify true跳过证书校验虽然网上很多教程这么写。镜像链路属于软件供应链中最关键的一段跳过校验收到的就是一份来源不明的镜像一旦被中间人篡改等于把整个集群的安全底线交了出去。2.3 端口与网络清单Harbor 默认监听 80HTTP和 443HTTPS两个端口。生产最佳实践是80 端口关闭或重定向到 443节点上的 Containerd 只走 443 的 HTTPS 访问。如果你们的网络策略严格我建议至少放通以下链路源目标端口用途运维管理机Harbor 主机443访问 Harbor Web UIK8s 节点Harbor 主机443镜像拉取推送CI 构建机Harbor 主机443CI 推送镜像Harbor 主机外网可选443复制规则同步公网镜像另外注意Harbor 主机的/data目录存放镜像数据工业级环境必须单独挂一块大容量数据盘别跟系统盘放在一起否则镜像一多磁盘 IO 直接拖垮系统。3. Harbor 端部署从 harbor.yml 到首次登录3.1 前置条件Docker 与 ComposeHarbor 本身是若干容器的集合安装脚本通过 Docker Compose 把它们编排起来。所以先确保 Harbor 主机上有可用的 Docker 环境并且 Docker 服务运行正常。用docker info确认一下Server Version不是太旧Docker Compose 用 v2 版本docker compose version。这里有个容易踩的坑Harbor 的离线安装包是 tar 包部署前不需要手动拉镜像但执行install.sh时脚本会检查docker和docker-compose命令是否存在。如果你的 Compose 是 v1 的docker-compose命令不是 v2 的docker compose有些年份的 Harbor 版本还能兼容2.8 之后我开始强烈建议直接上 Compose v2否则后面docker-compose ps查看状态时经常出现命令不匹配的情况。Docker 安装结束后再做两个底层优化这个一般文档里不提给 Docker daemon 配置 log rotation确认json-file的日志不会无限增长把 Harbor 的数据目录做成软链或挂载点方便后续迁移。这些看起来跟 Containerd 集成无关实际上影响了 Harbor 的稳定生命周期。3.2 下载安装包与配置 harbor.yml去 Harbor 的 GitHub releases 页面下载离线安装包注意文件名里带offline-installer的版本。传到服务器上解压目录里有个harbor.yml.tmpl复制成harbor.yml后开始改配置。我自己比较常用的最小配置大概是这样的hostname: registry.example.com http: port: 80 https: port: 443 certificate: /data/cert/registry.example.com.crt private_key: /data/cert/registry.example.com.key # 管理员初始密码安装后建议立即修改 harbor_admin_password: Harbor-Admin123456 database: password: Harbor-DB2024 max_idle_conns: 50 max_open_conns: 100 data_volume: /data/harbor trivy: ignore_unfixed: false skip_update: false offline_scan: false jobservice: max_job_workers: 10 log: level: info local: rotate_count: 30 rotate_size: 200M几个关键点逐个讲。hostname一定写成前面规划的域名而不是 IP。https.certificate和https.private_key指向证书文件的绝对路径执行install.sh的时候这一对文件必须存在且能被 Harbor 容器读到。database.password是 Harbor 内置 PostgreSQL 的密码生产环境别用默认的root123之类的弱口令。data_volume就是镜像实际落盘的位置配到独立数据盘挂载点。还有一点harbor.yml对格式敏感必须用空格缩进不能有 Tab不然脚本会直接报 YAML 解析错误。我见过同事在这上面反复排查了十分钟最后发现是 Tab 缩进的问题。3.3 执行 install.sh 初始化配置改完直接运行sudo ./install.sh脚本会启动全部 Harbor 组件常见的有 Harbor core、registry、PostgreSQL、Redis、Trivy 等。安装过程中最常见的问题是/etc/docker/daemon.json里配置了 insecure-registry 或其他代理参数导致安装脚本预检失败。如果只在本机自测可以先清掉 daemon 里和 registry 相关的配置再执行。安装完成后验证服务状态docker compose ps正常情况下所有组件的状态都应该是running。然后用浏览器访问https://registry.example.com账号admin密码就是harbor_admin_password里配置的值。首次登录后建议立即去“系统管理-偏好设置”里把默认的项目创建类型改一下避免任何用户都能乱建项目开启“机器人账号”支持后面给 CI 用机器人账号推送镜像比暴露 admin 密码安全得多配置垃圾回收定时任务镜像删掉后仓库里的层不会立刻释放需要定期 GC。3.4 创建项目与机器人账号Harbor 的项目Project是隔离镜像的根本单位。镜像地址是仓库域名/项目名/镜像名:Tag。所谓“项目名”就是这里创建的项目标志符。常见的项目规划方式按团队分platform、backend、frontend每个团队一个项目权限独立按环境分dev、staging、prod配合复制规则做环境隔离按业务域分order-service、user-service等。我自己的习惯是“团队项目 镜像前缀”结合比如backend/order-service项目backend只有后端团队能推送但 K8s 节点允许从所有项目拉取。为了不给 K8s 节点配高权限账号最好给节点单独创建只读机器人账号只授予各项目的拉取权限。创建机器人账号的位置在“系统管理-机器人账号”生成后会得到一个形如robot$xxx的账号和一个明文 token这个 token 只在生成时显示一次要立即保存。后面 Containerd 配置认证信息就用这个 token。4. Containerd 接入 Harbor三种配置方式与选择4.1 方式一HTTP 场景下的直配认证如果你的环境不打算启用 HTTPS或者只是内网测试阶段最快速的接入方式就是把 Containerd 的 CRI 配置指向 HTTP 的 Harbor 地址同时带上用户名和密码。先打开 Containerd 的配置文件sudo vi /etc/containerd/config.toml默认情况下里面可能只有几行基础配置需要添加 CRI 的 registry 块。在文件末尾追加类似内容version 2 [plugins.io.containerd.grpc.v1.cri.registry] [plugins.io.containerd.grpc.v1.cri.registry.mirrors] [plugins.io.containerd.grpc.v1.cri.registry.mirrors.registry.example.com] endpoint [http://registry.example.com] [plugins.io.containerd.grpc.v1.cri.registry.configs] [plugins.io.containerd.grpc.v1.cri.registry.configs.registry.example.com.auth] username robot$k8s-pull password 你的机器人token这里的逻辑是mirrors告诉 Containerd 访问某镜像地址时用哪个 endpointconfigs告诉它访问这个仓库时需要怎样的认证信息。用户名和密码可以直接写在auth块里containerd 内部会帮你处理 base64 编码。配置保存后重启 containerd 服务sudo systemctl restart containerd然后用crictl pull拉一个镜像验证。拉取命令不用加任何额外参数因为已经走 CRI 插件CIR 插件会读取上面的 registry 配置。这种模式在纯内网环境最省事但它的问题也很明显镜像数据明文传输同一个二层网络的设备可以抓到推送或拉取的镜像层内容因此只能做临时方案生产环境必须上 TLS。4.2 方式二HTTPS 自签证书的信任链配置工业级环境推荐 HTTPS但企业内部很大概率不是权威 CA 签发的公网证书而是自签 CA。这时候要在 HTTP 模式的基础上增加一个tls块告诉 Containerd 信任哪份 CA 证书。先保证 Harbor 主机上生成的自签 CA 证书文件被复制到了每台节点比如统一放在/etc/containerd/certs/registry.example.com/ca.crt然后修改config.toml[plugins.io.containerd.grpc.v1.cri.registry] [plugins.io.containerd.grpc.v1.cri.registry.mirrors] [plugins.io.containerd.grpc.v1.cri.registry.mirrors.registry.example.com] endpoint [https://registry.example.com] [plugins.io.containerd.grpc.v1.cri.registry.configs] [plugins.io.containerd.grpc.v1.cri.registry.configs.registry.example.com.auth] username robot$k8s-pull password 你的机器人token [plugins.io.containerd.grpc.v1.cri.registry.configs.registry.example.com.tls] ca_file /etc/containerd/certs/registry.example.com/ca.crt注意两个变化endpoint 从http://改成https://configs 块内增加了tls字段ca_file指向自签 CA 路径。containerd拿到这个 CA 后会用它验证 Harbor 服务端证书的签名相当于把自签 CA 临时加入了运行时自己的信任链不需要改动操作系统全局证书库。这里我特别提醒一件事Harbor 服务端证书的域名必须包含registry.example.com如果证书上写的 IP 或者别的域名containerd 在完成 TLS 握手后会校验主机名照样报x509: certificate is valid for IP, not registry.example.com。证书生成时openssl的subjectAltName务必加对。重启 containerd 后可以用crictl pull验证。如果一切正常这个配置就能长期稳定用于 K8s 节点。以后过期前一天记得更新各节点的 CA 文件副本和 Harbor 服务端证书否则节点会整批出现 TLS 握手失败。4.3 方式三certs.d 目录统一管理多个仓库上面两种方式把私有仓库的配置写死在config.toml的 CRI 插件里有一个隐含问题不同仓库、不同认证、不同证书混杂在一起配置文件会越来越臃肿。Containerd 从较早的版本开始支持类似 Docker 的仓库证书目录机制也就是/etc/containerd/certs.d/。在这个机制下不再需要往 CRI 插件的registry.configs里写一堆东西而是给每个仓库单独建目录和hosts.tomlmkdir -p /etc/containerd/certs.d/registry.example.com然后在里面创建hosts.toml文件server https://registry.example.com [host.https://registry.example.com] ca /etc/containerd/certs/registry.example.com/ca.crt [host.https://registry.example.com.header] authorization Basic cm9ib3Qka3ViZS1wdWxsOnlvdXItdG9rZW4这里的cm9ib3Qka3ViZS1wdWxsOnlvdXItdG9rZW4是机器人账号:token的 base64 编码可以用echo -n robot$k8s-pull:your-token | base64算出来填进去。certs.d目录方式最大的优势是配置分离。以后新增一个仓库不用动主干config.toml文件加一个目录就行灰度、下线、回滚都方便。而且它对ctr和 CRI 插件同样生效不像registry.configs只走 CRI 接口ctr命令在这种模式下也能直接识别证书和认证信息这一点非常实用。我个人更推荐生产环境采用这种模式尤其当集群里不同的 Pod 需要从多个不同域名的仓库拉镜像时它能让配置结构保持清晰。但这块的排查坑也不少hosts.toml的文件名必须是仓库域名不能带端口或路径server字段的格式会被严格验证不能写成https://registry.example.com/v2/这种带路径的形式。4.4 配置加载与正确性校验无论你选了哪种配置方式改完配置后的通用步骤是sudo systemctl restart containerd sudo journalctl -u containerd -n 100 --no-pagercontainerd服务重启过程中如果配置有语法错误日志里会直接展示错误原因比如toml: line X: expected character那就说明config.toml的 TOML 语法出问题了。如果服务起来了再执行一个轻量的连通性检查不需要立刻拉镜像先用crictl或ctr拉一个很小的镜像试试crictl pull registry.example.com/library/busybox:1.36拉取成功说明网络能通、证书被信任、认证信息有效、镜像确实存在。这一步失败不要急着压集群业务先把报错记下来往下看排障章节。5. 推送与拉取全流程实测ctr 和 crictl 两条路线5.1 镜像 Tag 与项目规范在生产环境你的 CI 构建机通常已经用 Docker 或 Kaniko 构建好镜像但最终要推进 Harbor得先把镜像地址改成 Harbor 风格的地址。比如本地有个nginx:1.21给 Harbor 项目library打标签docker tag nginx:1.21 registry.example.com/library/nginx:1.21 docker push registry.example.com/library/nginx:1.21这是 Docker 时代的做法。但注意这篇文章的主角是 Containerd所以我们要验证的不只是 Docker push 这条链路而是 K8s 节点侧的 Containerd 能否直接从 Harbor 拉取。因此下面的实操主要用ctr和crictl两条路线演示。5.2 用 ctr 走非 CRI 路线ctr是 containerd 自带的命令。它不经过 CRI 插件因此默认情况下config.toml里 CRI 的registry.configs对它不生效除非你用的是certs.d目录方式。这是最容易搞混的点。在配置了certs.d的节点上用ctr直接拉取sudo ctr -n k8s.io images pull registry.example.com/library/nginx:1.21如果没有配置certs.d只是 HTTP 内网测试场景可以在命令后面手动加--plain-httpsudo ctr -n k8s.io images pull --plain-http registry.example.com/library/nginx:1.21-n k8s.io指定命名空间K8s 通过 CRI 使用 containerd 时镜像都放这个命名空间下自己测试建议也统一放这里方便后面kubelet复用。推送镜像用sudo ctr -n k8s.io images push --plain-http registry.example.com/library/nginx:1.21如果是 HTTPS 且证书已配置直接去掉--plain-http即可。ctr的优点是贴近底层遇到网络问题看的报错很直接缺点是它不具备 kubelet 层面的容器概念仅适合做镜像层的联调验证。5.3 用 crictl 走 CRI 标准路线crictl是 CRI 兼容的 CLI它通过 Unix Socket 与 containerd 的 CRI 插件通信因此config.toml里 CRI 的registry.configs配置对它完整生效。平时排查节点镜像问题我基本都用crictl。拉取命令sudo crictl pull registry.example.com/library/nginx:1.21查看镜像sudo crictl images一条crictl pull成功基本能确定这台节点上的 K8s 组件后续拉取同一个镜像也不会有问题因为 kubelet 最终就是通过同一个 CRI 接口请求 containerd 拉镜像。如果crictl报找不到config.toml或无法连接 CRI Socket检查一下环境变量和配置位置crictl config --runtime-endpoint unix:///run/containerd/containerd.sock sudo crictl pull registry.example.com/library/nginx:1.215.4 Harbor 侧的双向验证镜像推送到 Harbor 后到 Web 界面进入对应项目点击镜像名称能看到 Tag 列表、大小、漏洞扫描结果。这里可以做几件事确认镜像层数、大小是否和本地一致避免“推送成功但内容残缺”的假象用“复制”功能把镜像复制到另一个项目或远端仓库验证多环境分发查看审计日志确认推送动作来自预期的机器人账号确保没有其他人冒用。拉取侧也一样Harbor 的管理界面里能审查“操作日志”记录每个拉取请求是哪台节点发起的。当出现集群节点大量拉取同一镜像时这里能很直观地看到流量来源。另外一个生产环境里非常实用的功能是“镜像代理缓存”。有些团队为了拉取 Docker Hub 基础镜像不绕外网会在 Harbor 里建一个代理项目Proxy Cache上游指向docker.io。这样节点只需要配置docker.io的 mirror 指向 Harbor就可以间接从内网拉取 Docker Hub 公开镜像不会触发 Docker Hub 限流同时还可以对基础镜像做一次扫描再放行。这个功能我现在几乎是默认开启的省心程度远超预期。6. 高频问题与排障实录6.1 错误信息速查表先说结论集成过程里 90% 的问题集中在四类协议不匹配、证书不被信任、认证信息错误、镜像地址拼错。把常见报错整理成一张速查表遇到问题时直接对照着查定位效率能大幅提升报错信息根本原因处理方法http: server gave HTTP response to HTTPS client仓库只开放 HTTP客户端却用 HTTPS 访问使用--plain-http或将 endpoint 改成http://x509: certificate signed by unknown authority自签证书不被运行时信任配置ca_file或在certs.d里加入 CAx509: certificate is valid for xxx, not registry.example.com证书的域名与访问域名不匹配重新签发包含目标域名的 SAN 证书unauthorized: authentication required没有认证信息或认证失败检查机器人账号 token、base64 编码、是否有项目权限pull access denied, repository does not exist项目名或镜像名不存在或无权限访问该项目确认 Harbor 项目名、镜像 Tag、机器人权限failed to resolve reference ... not found镜像在 Harbor 中不存在或 Tag 拼写错误到 Harbor 页面确认镜像完整地址net/http: TLS handshake timeout证书链异常、网络不通、防火墙拦截 443先 curl 仓库地址确认 TLS 能完成failed to authorize: failed to fetch oauth tokenHarbor 项目配置为私有拉取时没有走认证给机器人账号授予该项目的拉取权限6.2 排障案例一自签证书导致的 x509 报错一个真实场景节点配置好config.toml后crictl pull报错failed to do request: x509: certificate signed by unknown authority我当时的第一反应是ca_file路径不对查了/etc/containerd/certs/registry.example.com/ca.crt文件确实存在没多想就把路径和文件都检查了一遍也没发现问题。后来才发现问题出在 Harbor 服务端下发的证书链不完整。Harbor 配置的https.certificate文件里只放了服务端证书没有把自签 CA 证书拼接进去客户端拿到服务端证书后无法向上找到信任锚直接判定为 unknown authority。这个问题的正确处理方式是把服务端证书和 CA 证书进行合并按“服务端证书在前、CA 证书在后”的顺序拼成一个文件重新配置到 Harbor 的certificate字段然后重启 Harbor。简单说就是这类代理类服务通常需要提供完整证书链。如果只给服务端证书客户端系统或运行时必须预装相同的 CA否则就无法完成信任链验证。另外还有一种便捷做法在节点上不用 system CA而是直接用certs.d或ca_file指向那份 CA。此时只要 CA 文件本身有效服务端证书是由这个 CA 签发的达到的效果相同不一定要去动 Harbor 的证书链。6.3 排障案例二HTTP/HTTPS 协议不匹配另一个高频报错是failed to resolve reference registry.example.com/library/nginx:1.21: failed to do request: http: server gave HTTP response to HTTPS client这个报错含义很清晰Harbor 这边harbor.yml只开启了 HTTP或者实际监听的是 80 端口而客户端访问时用了 HTTPS。但我也踩过一个隐藏坑harbor.yml里明明同时配了 HTTP 和 HTTPSHTTPS 的证书路径却写错了于是安装脚本自动禁用 HTTPS实际只有 80 端口提供服务。从 Web 界面看似乎一切正常一用命令行就报 HTTP/HTTPS mismatch。排查时我建议先直接 curl 一下仓库地址curl -v http://registry.example.com/v2/ curl -v https://registry.example.com/v2/哪个能通、返回什么状态码一眼就能判断当前 Harbor 到底监听在哪个协议上。这个动作 5 秒解决比一头扎进容器配置里高效太多。6.4 排障案例三认证失败与权限模型还有一类报错是unauthorized: authentication required即使确认用户名和密码是对的也会出现。原因往往在于机器人账号只被授予了特定项目的“拉取”权限但crictl pull时访问的是其他项目或者该项目的类型是私有项目默认不开放匿名拉取。Harbor 的权限模型要理解到位机器人账号隶属于某个项目不是全局通行证。它在“系统管理-机器人账号”里创建时必须手动勾选它能访问的项目和权限。比如节点用的robot$k8s-pull需要把每个需要拉取的项目都分配到它的权限范围里。所以我前面建议至少创建两个机器人账号一个给集群拉取用只读权限合并到所有应用项目另一个给 CI 推送用可以读写对应项目但绝不能给管理员权限。从安全角度说尽量不要把 K8s 节点的拉取账号直接暴露为 Docker Hub 或 Harbor 的管理员这个账号实际分布在每一台节点上一旦某节点沦陷损失会被控制在可接受范围。6.5 最后把镜像缓存思路捎带上讲完排障我最后想说一个和接入深度绑定的扩展玩法把 Harbor 当成本集群的镜像缓存层。很多 K8s 生产环境的节点带宽并不大几十个节点同时滚动更新时镜像仓库的带宽就是瓶颈。Harbor 的镜像复制功能能提前把镜像同步到各个可用区的仓库实例节点只从就近仓库拉取跨机房流量几乎为零。这个思路不需要改动 Containerd 的配置只需要你在多个 Harbor 实例之间配置复制规则并在每个机房的节点上用同样格式的certs.d或config.toml指向本地 Harbor 域名。落地时建议先单机房试点等稳定后再延展到多机房一步一步来不要一上来就把集群配置改得面目全非。就拿这套 Containerd Harbor 的组合来说稳定是第一原则换一个配置域名的成本远比你想象的高。