☰
Containerd与Harbor集成实战:私有镜像仓库配置与排障全指南
2026/9/30 12:06:18 网站建设 项目流程

生产环境里摸爬滚打过一阵子的朋友应该都有这种体会:集群默认运行时从 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 迁移到 Containerd,K8s 节点已经或即将使用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.23)K8s 1.28~1.32 官方验证充分
Harbor2.8.x / 2.9.x2.8 之后 Composer 2 兼容性好
Docker(仅用于部署 Harbor)24.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 默认监听 80(HTTP)和 443(HTTPS)两个端口。生产最佳实践是:80 端口关闭或重定向到 443,节点上的 Containerd 只走 443 的 HTTPS 访问。如果你们的网络策略严格,我建议至少放通以下链路:

源目标端口用途
运维管理机Harbor 主机443访问 Harbor Web UI
K8s 节点Harbor 主机443镜像拉取推送
CI 构建机Harbor 主机443CI 推送镜像
Harbor 主机外网(可选)443复制规则同步公网镜像

另外注意,Harbor 主机的/data目录存放镜像数据,工业级环境必须单独挂一块大容量数据盘,别跟系统盘放在一起,否则镜像一多磁盘 IO 直接拖垮系统。

3. Harbor 端部署:从 harbor.yml 到首次登录

3.1 前置条件:Docker 与 Compose

Harbor 本身是若干容器的集合,安装脚本通过 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-Admin@123456 database: password: Harbor-DB@2024 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 访问某镜像地址时用哪个 endpoint;configs告诉它访问这个仓库时需要怎样的认证信息。用户名和密码可以直接写在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.toml:

mkdir -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-pager

containerd服务重启过程中如果配置有语法错误,日志里会直接展示错误原因,比如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-http:

sudo 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.21

5.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里加入 CA
x509: 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 和 HTTPS,HTTPS 的证书路径却写错了,于是安装脚本自动禁用 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 的组合来说,稳定是第一原则,换一个配置域名的成本远比你想象的高。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询