
1. 先把需求摸清楚私有镜像仓库到底要解决什么问题云主机上搭一套私有 Docker 镜像仓库这件事听起来像运维活但真正动手的人往往是开发者自己——团队里没人愿意把公司内部镜像丢到公共仓库也没有预算一开始就买商业方案于是先在云上开一台机器把镜像仓库跑起来边学边用。我这次做的就是这个在云主机的实例上用 Harbor 搭一套带 Web 界面、带用户权限、带镜像扫描能力的私有仓库同时把整条 login / tag / push / pull 链路走通。这个仓库能解决三件具体的麻烦内部镜像不再散落在各人笔记本上、CI 机器不用重复从外网拉基础镜像、私有镜像有独立的访问控制。它适合谁我的判断是三类人刚接触容器、想弄明白镜像仓库底层是怎么运转的初学者小团队里被安排“顺手把仓库搭一下”的后端或运维以及已经用过托管版镜像服务、想搞清楚里面那些项目、成员、机器人账号概念从哪来的人。整篇内容我会按“选型 — 环境 — 部署 — 接入 — 踩坑 — 加固”的顺序写每一步都给到可以直接抄的参数和命令中间穿插我自己踩过的坑你可以照着复现也可以只看自己卡住的那一段。需要先说明一个前提云厂商之间的差异主要在控制台点选和网络配置上实例内部的路径、端口、组件和开源版本的行为是一致的。所以下文统一称呼为“云主机”“实例”具体的规格选型和安全组配置你按手头那家云的控制台界面去找对应项就行逻辑完全对得上。1.1 四种方案摆在天平上我为什么落在 Harbor把可选项铺开其实只有四条路。第一条是本地起一个registry:2容器五行命令搞定但它只有存储和推拉能力没有用户体系、没有 Web 界面、没有项目隔离删镜像还得手动调 API学两天就摸到天花板了。第二条是直接用云厂商的托管镜像服务省心但按量计费、权限模型是别人设计的学不到东西也不适合“学习”这个目标。第三条是在自己机器上用 Docker Desktop 自带的小仓库只能本机用没有意义。第四条就是 Harbor它是从开源镜像仓库项目演进出来的完整发行版带 Nginx、Core、Registry、PostgreSQL、Redis、JobService、Trivy 一整套组件用 Compose 拉起一台机器就能跑。我选 Harbor 的关键理由不是功能多而是它的组件拆分方式本身就是一堂容器架构课。你会发现 Harbor 把“认证”和“存储”分开了Core 服务负责用户、项目、权限、APIRegistry 只负责最原始的 Blob 与 Manifest 存取JobService 干异步的复制、扫描、GC 这类重活Nginx 在最前面做 TLS 终止和路由。理解了这个分层你再看任何一个镜像仓库产品的文档都能对上号。这是学习场景下最值回票价的部分。另外 Harbor 官方提供离线安装包里面把镜像都打包好了不用一个个去docker pull在带宽不稳定的环境里这一点非常关键。整个部署过程用prepare生成 compose 文件、用install.sh一键拉起失败时日志位置固定排查路径清晰很适合拿来练手。1.2 上线前必须钉死的四件事很多人部署失败不是因为技术难而是四件事没提前定域名、证书、存储目录、端口。我这边的定法是这样的——域名用一个只在内部解析的名字比如harbor.internal故意不用公网域名避免证书签发和被扫描证书用自签学习环境没必要走自动签发流程但自签证书的 SAN 一定要写全后面会细讲存储目录统一放在独立数据盘的/data下Harbor 的数据卷放/data/harborDocker 引擎自己的数据目录放/data/docker这样系统盘只装系统重建实例时数据盘可以直接挂回来端口上HTTPS 占 443HTTP 占 8443 并且只做跳转用。这里有个容易被忽略的细节Harbor 配置里的hostname字段不能带端口。写成harbor.internal:443会让生成的证书配置、令牌服务和仓库地址全部错位症状是网页能打开但docker push报 401 或 404。这个坑我第一遍就踩了后面会再提一次。还有一件事需要提前想清楚这台机器是“学习环境”还是“准生产环境”。学习环境可以单机、可以自签、可以只做冷备份一旦有同事真的往里推镜像你就得考虑备份、磁盘配额、垃圾回收周期。我的建议是先用单机把链路走通把备份脚本写好再考虑往高可用架构演进——单机 Harbor 挂了的恢复动作其实比很多人想象的简单只要harbor.yml、证书、数据卷和数据库备份四样东西在。2. 云主机与基础环境的落地准备这一节全部是“动手之前的体力活”但它们决定了后面能不能顺。我见过太多人在 Harbor 起不来之后去翻日志最后发现是内存不够被 OOM Killer 杀掉了 Core 服务或者磁盘写满导致 PostgreSQL 拒绝写入。所以环境和规格这部分值得单独花时间。2.1 实例规格怎么估从内存和磁盘两头算先说内存。Harbor 默认组件跑起来Nginx 大约 30MB、Core 约 100MB、Registry 约 60MB、PostgreSQL 约 100MB、Redis 约 20MB、JobService 约 80MB、Trivy 适配器再加一点空载合计在 400MB 到 600MB 之间。听起来 2GB 就够但实际推拉镜像时 Nginx 和 Registry 会有缓冲区开销JobService 做扫描时内存能冲到 500MB 以上加上 Docker 引擎自身、系统进程和页缓存4GB 是安全带。我用的就是 2 核 4GB跑起来比较从容如果你还要在这台机器上跑 CI runner直接上 4 核 8GB。磁盘要算两笔账。第一笔是镜像本体假设你打算放 100 个镜像标签平均压缩后 300MBHarbor 按层存储且层可共享实际占用约为总量的 60%也就是 100 × 300MB × 0.6 ≈ 18GB。第二笔是回收余量垃圾回收不会立刻释放空间而且回收前要把仓库切成只读中间可能堆积一批待清理对象建议预留 1.5 倍。18GB × 1.5 ≈ 27GB再加上日志、扫描数据库、系统独立数据盘开 100GB 是很舒服的配置学习场景下 40GB 也能跑但要盯着用。系统盘 40GB 足够但请务必确认 Harbor 的数据目录在数据盘上。默认安装会把data_volume放在安装目录下的data文件夹里如果安装目录在系统盘镜像很快就吃掉系统盘而系统盘写满会导致 SSH 都登不上这是最难受的一种故障。项目学习/单机小团队使用说明vCPU24编译、扫描占 CPU内存4GB8GB低于 2GB 易被 OOM系统盘40GB40GB仅装系统与镜像缓存数据盘100GB200GB 起存 Harbor 数据与 Docker 数据带宽按量 5Mbps固定带宽拉基础镜像时体现明显带宽这块补充一句学习环境用按量带宽、用完就停是最省的做法但要注意镜像拉取那一瞬间的峰值如果带宽只有 1Mbps拉几百兆的基础镜像会非常痛苦。我的做法是第一次把常用基础镜像拉完并推到私有仓库后面所有构建都走内网拉取外网带宽就只在少数时候打满。2.2 安全组与端口规划清单安全组是云上最容易配错的一层。原则很简单只开必须的其余全关。我这次开放的端口如下表注意 2375 这个 Docker 远程端口绝对不要对公网开放一旦暴露对方可以直接在你的机器上起容器做任何事这是常见的高危配置。端口协议来源用途22TCP你的固定出口 IP运维登录443TCP内网网段Harbor HTTPS8443TCP内网网段Harbor HTTP 跳转80TCP不开由 443 承接2375/2376TCP不开Docker 远程 API5432/6379TCP不开数据库与缓存仅容器内如果机器需要被其他网络的同事访问把 443 的来源改成对应网段不要图省事写0.0.0.0/0。另外很多云厂商还提供“安全组规则命中日志”配完跑一次docker login去日志里确认命中的是你预期的规则比盲猜快得多。2.3 数据盘挂载与目录体系拿到新实例第一件事是看盘。lsblk确认数据盘设备名通常系统盘是vda数据盘是vdb但不同镜像不一样稳妥做法是看容量和挂载点。lsblk # 假设输出中 vdb 没有挂载点容量符合你买的数据盘 mkfs.ext4 /dev/vdb mkdir -p /data blkid /dev/vdb # 记下输出的 UUID然后写/etc/fstab。这里一定要用 UUID 而不是/dev/vdb因为设备名在重启、扩容、换盘后可能变化用设备名会导致开机挂载失败进而进不了系统。写入一行UUID你的UUID值 /data ext4 defaults,nofail 0 2nofail这个选项很关键它保证盘挂不上时系统仍能启动方便你远程修复否则一个 fstab 写错就得走控制台的 VNC。写完执行mount -a验证再df -h看一眼。接着建目录体系我的划分是这样/data/harbor放 Harbor 数据卷/data/harbor-certs放证书也可以放/data/harbor/certs/data/docker给 Docker 引擎当># 以某主流发行版为例先装依赖 sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg # 添加软件源并安装源地址请替换为你环境可用的地址 sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin装完必须验证三件事docker version能看到 Client 和 Server 两段、docker compose version有输出、systemctl is-enabled docker返回enabled。第三点经常被忽略机器重启后 Docker 没自启Harbor 自然也就没起来第二天上班发现仓库挂了。Compose 这里有个版本差异要提醒Harbor 的不同版本对docker composev2 插件和docker-composev1 独立二进制的探测逻辑不同新版安装脚本优先用 v2。如果你机器上两个都装了偶尔会出现脚本调错的情况症状是install.sh报命令不存在但手动docker compose ps又是好的。最省事的做法是只保留一个我一般只留 v2 插件版。配置 Docker 守护进程这一步先做一半把>{ data-root: /data/docker, log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 }, live-restore: true }写到/etc/docker/daemon.jsonsystemctl daemon-reload systemctl restart docker。日志轮转这两个参数千万不要省容器日志默认不限制大小某天一个异常容器反复报错几十 GB 日志能把盘写满这个场景我遇到过两次都是深夜。3. Harbor 部署的完整实操链路环境就绪进入正题。这一节我会把配置文件逐项拆开讲因为 Harbor 的配置文件里几乎每一个改动都对应一个具体的后果抄作业可以但知道为什么抄更重要。3.1 离线包获取与解压从官方 release 页面下载离线安装包文件名形如harbor-offline-installer-v2.10.x.tgz注意选 offline 而不是 online离线包里面已经包含所有镜像 tar 包。下载后校验一下哈希然后在数据盘解压cd /data wget 离线安装包地址/harbor-offline-installer-v2.10.2.tgz tar -xzf harbor-offline-installer-v2.10.2.tgz cd harbor ls -l目录里应该有harbor.yml.tmpl、install.sh、prepare、harbor.v2.10.2.tar.gz。第一件事是把模板复制成正式配置cp harbor.yml.tmpl harbor.yml。不要直接改 tmpl因为升级时新包会覆盖模板你的配置就丢了而且排查问题时也分不清是默认值还是你的改动。3.2 harbor.yml 逐项拆解与参数计算配置文件不长但每一段都有坑。我按顺序讲关键项。hostname: harbor.internal http: port: 8443 https: port: 443 certificate: /data/harbor-certs/harbor.crt private_key: /data/harbor-certs/harbor.key harbor_admin_password: 换成你自己的强密码 data_volume: /data/harbor database: password: 换成独立强密码 max_idle_conns: 50 max_open_conns: 100 jobservice: max_job_workers: 10 trivy: ignore_unfixed: true skip_update: true insecure: false log: level: info local: rotate_count: 50 rotate_size: 200M location: /data/harbor/loghostname前面说过纯域名不带端口而且要和证书的 CN/SAN 完全一致否则客户端会报证书不匹配。http.port我设成 8443是给那些还在用明文访问的老脚本留一条跳转路实际使用时 Nginx 会把 HTTP 请求 301 到 HTTPS。https.port用 443这样客户端docker push的时候不用特殊写端口省掉很多麻烦。data_volume指向数据盘这一行决定了几十 GB 镜像落在哪。database.password单独设不要和harbor_admin_password一样虽然学习环境无所谓但养成习惯很重要——数据库密码泄露意味着整库凭据握在别人手里而管理员密码泄露最多是仓库被改。trivy.skip_update: true在离线或带宽有限的环境下很有用否则 Trivy 每次启动都去拉漏洞库拉不动就一直重试日志刷屏。jobservice.max_job_workers控制异步任务并发10 是个平衡值调到 30 以上在小机器上会把 CPU 打满导致 Web 界面卡顿。log.local的轮转参数是保命的rotate_count: 50配合rotate_size: 200M理论最大 10GB实际很少到但比不限制安全得多。如果你不用漏洞扫描可以在安装时加--without-trivy能省 300MB 左右内存和一点磁盘。学习阶段我建议保留扫一次看看报告长什么样挺有意思。3.3 自签证书生成SAN 是重点自签证书最容易出问题的地方不是加密强度而是Subject Alternative Name。现在所有主流 TLS 实现都只看 SAN不看 CN只写 CN 的证书在浏览器和 Docker 客户端里会被判定为无效。生成命令mkdir -p /data/harbor-certs cd /data/harbor-certs openssl req -x509 -nodes -newkey rsa:4096 -days 3650 \ -keyout harbor.key \ -out harbor.crt \ -subj /OInternal/OULearning/CNharbor.internal \ -addext subjectAltNameDNS:harbor.internal,IP:10.0.0.12这里几个参数值得说-days 3650是十年学习环境够用生产环境建议 90 到 365 天并配自动续期-addext需要 OpenSSL 1.1.1 以上如果你的系统版本老得改用写配置文件的方式SAN 里同时写 DNS 和 IP是因为我偶尔会用 IP 直连调试不写 IP 的话那条路就报证书错误。生成完检查一下openssl x509 -in harbor.crt -noout -text | grep -A2 Subject Alternative Name确认输出里有你写的两个值。这一步花十秒钟能省掉后面半小时的x509: certificate signed by unknown authority排查。顺手把证书文件的权限收紧chmod 600 harbor.key。私钥文件在容器挂载时会被读取权限太松虽然不直接报错但不符合最小暴露原则。3.4 执行 install.sh 与组件状态验证配置写完执行cd /data/harbor ./install.sh --with-trivy脚本会依次做几件事检查 Docker 和 Compose 可用性、调用prepare渲染出docker-compose.yml、加载离线镜像包、拉起全部容器、等待健康检查。整个过程两三分钟中途输出会滚得很快注意看有没有红色的 ERROR。安装成功的标志是最后一屏打印出访问地址和默认账号提示。然后验证docker compose -f docker-compose.yml ps你应该看到harbor-core、harbor-db、harbor-jobservice、harbor-log、harbor-portal、nginx、redis、registry、registryctl、trivy-adapter全部是running或healthy。healthy状态是核心服务的健康检查探针刚起来时可能是starting等 30 秒再看。如果某个容器反复重启先看日志docker logs --tail 100 harbor-core。日志目录在容器内是/var/log/harbor宿主机上通过harbor-log容器集中收集位置是data_volume下的log目录。这个设计挺巧妙所有组件日志统一落盘排查时不用一个个进容器。常用运维命令我记成三行出问题先敲这三条docker compose -f docker-compose.yml ps # 看状态 docker compose -f docker-compose.yml logs -f core # 看核心服务日志 tail -f /data/harbor/log/core/core.log # 看落盘日志3.5 首次登录与基础初始化动作浏览器打开https://harbor.internal自签证书会有安全警告点继续访问即可这也是为什么自签只适合内部环境。用默认管理员账号登录密码必须是Harbor12345这是官方默认值第一次登录会强制要求改密码。新密码的规则是长度至少 8 位、包含大小写字母和数字比如Lx2024Harbor。改完密码做几个初始化动作新建一个项目比如dev勾选私有这样匿名用户拉不了。默认的library项目是公开的很多人第一次push就推到这里结果所有人都能拉很不合适。在系统管理 - 用户管理里建几个普通用户分配给不同同事。进项目dev在成员里加成员角色选开发者可推可拉或访客只拉。Harbor 的角色模型是三层系统管理员、项目成员、匿名用户权限从项目设置里继承。在配置管理里把项目创建权限关掉避免任意用户随便建项目把磁盘塞满。这些动作不复杂但做了之后整个仓库才像个“能给别人用”的东西而不只是你自己玩的沙盒。4. 客户端接入从 login 到 push 的完整闭环服务端起得来只算完成一半。真正让人卡住的往往是客户端这一侧证书信任、域名解析、守护进程配置随便一个不对docker push就报一堆看不懂的错。这一节我把客户端要改的东西全部列清楚。4.1 Docker 守护进程的配置与证书信任客户端有两条路走要么让 Docker 信任你的自签证书要么把仓库标记为“非安全仓库”。生产环境必须走前者学习环境图省事可以用后者但要知道它的含义——insecure-registries会让 Docker 在推拉时跳过 TLS 校验中间人只要在你和仓库之间就能看到镜像内容。先讲后者快{ data-root: /data/docker, insecure-registries: [harbor.internal:443], insecure-registries: [harbor.internal], log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 } }注意上面这段我故意写了两遍同名 key只是想说明格式实际只能留一个正确写法是数组里放harbor.internal不带端口。如果仓库启用了 HTTPS 且用 443insecure-registries里写主机名即可如果是自定义端口才需要带端口。再讲正规做法让客户端信任证书sudo mkdir -p /etc/docker/certs.d/harbor.internal sudo cp harbor.crt /etc/docker/certs.d/harbor.internal/ca.crt sudo systemctl restart docker目录名必须是hostname:port格式如果端口是 443 可以省略端口部分但有些版本比较严格我建议统一写成harbor.internal:443和harbor.internal各建一个目录两边都放上证书避免猜版本行为。文件名同样是固定的自签根证书叫ca.crt客户端证书叫client.cert客户端私钥叫client.key名字错了不生效且不报错只是继续报证书不受信任非常迷惑。改完 daemon.json 一定要systemctl daemon-reload再重启直接 restart 有时不读新配置。验证方式是docker info看输出里Insecure Registries一栏有没有你的域名。4.2 域名解析的三种兜底方式harbor.internal这类内部域名在公网 DNS 上是不存在的。客户端报dial tcp: lookup harbor.internal: no such host就是解析没通。三种解法从轻到重第一种改 hosts。每台客户端机器加一行10.0.0.12 harbor.internal最简单机器多了难维护学习环境够用。第二种内网 DNS 加一条 A 记录。如果你们本来就有内网 DNS加一条记录所有机器自动生效这是最干净的方式。第三种用云厂商的私有 DNS 服务。在 VPC 内建私有解析区把域名指向内网 IP新机器加入 VPC 就自动生效。这个适合有一定规模的团队配置略复杂。不管用哪种配完在客户端执行getent hosts harbor.internal或nslookup确认能拿到正确的内网 IP不要用ping判断ICMP 可能被安全组拦掉但 TCP 443 是通的。4.3 项目、用户与机器人账号的权限模型Harbor 的权限模型刚接触会觉得绕讲清楚其实就三层。项目是最小隔离单位镜像地址的格式是hostname/项目名/镜像名:tag那个中间段就是项目。成员是用户和项目的绑定关系同一个用户在不同项目可以有不同角色。机器人账号是给自动化系统用的凭据名字自动带robot$前缀可以精确限定到某个项目、某类操作。为什么要有机器人账号因为 CI 系统里存明文密码风险太大机器人账号的特点是只能操作指定项目、可以设置过期时间、权限只给推或只给拉。实践中的配法是CI 用一个只有推送权限的机器人账号部署节点用一个只有拉取权限的机器人账号人类用户用自己账号三条线分离出事能定位。创建路径是进入项目 → 机器人账户 → 新建 → 勾选权限推送、拉取、删除等→ 设置过期天数 → 保存。生成的令牌只显示一次关掉页面就再也看不到必须当场复制到 CI 的密钥管理里。这个设计我第一次用的时候差点吃亏复制晚了只能删了重建。4.4 一次完整的推送与拉取演练链路走一遍心里才踏实。假设本地有个镜像叫myapp:1.0要推到dev项目。# 1. 登录回车后输入密码或令牌 docker login harbor.internal -u robot\$devci # 2. 打标签格式必须是 主机/项目/镜像:标签 docker tag myapp:1.0 harbor.internal/dev/myapp:1.0 # 3. 推送 docker push harbor.internal/dev/myapp:1.0 # 4. 换台机器拉取 docker pull harbor.internal/dev/myapp:1.0几个细节值得强调。第一docker login的-u参数里如果有$在命令行里要转义或加单引号否则 shell 会当成变量展开结果是用户名变成空字符串报unauthorized。第二镜像地址的斜杠结构是强制的harbor.internal/myapp:1.0这种少一段的写法会被 Docker 解释成 Docker Hub 上的仓库推不上去。第三标签里的 tag 不要用latest做唯一版本Harbor 支持不可变标签规则可以在项目配置里开启防止同名标签被覆盖导致线上拉到的镜像悄悄变了。推上去之后回 Web 界面看dev项目里应该出现myapp仓库点进去能看到标签、大小、拉取次数、漏洞扫描结果。第一次看到扫描报告可能会有不少条目别慌大部分是基础镜像里的历史组件勾选“仅显示可修复”会清爽很多。4.5 配额、垃圾回收与容量治理这一小节是很多人忽略、但迟早会撞上的问题。删除镜像不等于释放磁盘。Harbor 的存储是分层共享的删掉一个 tag 只是删了引用底层的 Blob 还在必须跑垃圾回收才会真正释放。流程是这样的先在配置管理里把仓库设为只读Repository read only打开然后在系统管理 - 垃圾回收里新建一次回收任务勾选“删除未被引用的 Blob”等它跑完再把只读关掉。为什么必须只读因为 GC 在扫描时如果有新镜像正在推送可能把正在写的层判定为无引用而删掉后果是镜像损坏。这个顺序我吃过一次教训当时没开只读回收完有个镜像拉下来manifest unknown只能重新推一遍。配额可以按项目设在项目配置里填存储上限比如 20GB超了之后推送会直接报denied: requested access to the resource is denied这个报错信息起得不太好配额和权限共用同一个错误码排查时容易走偏。配额的好处是防止某个人把整盘塞满导致所有人都用不了。容量监控靠两件事一看df -h /data的剩余空间二看 Harbor 首页的存储统计。我在机器上加了一条定时任务磁盘超过 80% 就发通知这个简单动作能避免绝大多数“仓库突然挂了”的事故。5. 踩坑实录与排查速查表这一节是我认为整篇最有价值的部分。前面所有步骤理论上都能一次成功但实际动手时总有各种偏差下面这些错我基本都亲手撞过一遍。5.1 证书与协议类报错最常见的两条。第一条是http: server gave HTTP response to HTTPS client意思是客户端用 HTTPS 去连服务端返回的是 HTTP。原因通常是 Harbor 只启了 HTTP 没启 HTTPS而你客户端按默认 HTTPS 发起。解决办法要么给 Harbor 配上证书要么把insecure-registries加上。不要尝试把仓库地址改成http://前缀Docker 不认这个写法docker push http://xxx会直接把地址当成镜像名的一部分。第二条是x509: certificate signed by unknown authority。这说明证书链没被信任检查三件事/etc/docker/certs.d/hostname/ca.crt是否存在且内容正确、目录名里的主机名和端口是否完全匹配、改完有没有重启 Docker。还有一种隐藏情况你配的证书 SAN 里只有harbor.internal但客户端用的是 IP 访问那自然不匹配补齐 SAN 或者改了访问方式。第三条是浏览器能打开但 curl 报证书错误这多半是浏览器点了“继续访问”的例外而命令行没有这个例外机制本质上还是证书没进信任链。5.2 认证与权限类报错unauthorized: authentication required就是没登录或者登录失效。Docker 的凭据存在~/.docker/config.json有时候换了账号但凭据还是旧的docker logout再login能解决大半。另一个高频原因是密码里带特殊字符在命令行里被 shell 吃掉了建议用--password-stdin从标准输入传echo 你的密码 | docker login harbor.internal -u youruser --password-stdindenied: requested access to the resource is denied有两个完全不同的原因一是项目不存在或者你没有该项目权限二是镜像地址的项目段写错了。第二种特别隐蔽因为报错一样。我当时推一个镜像报这个错折腾半天最后发现项目名叫dev我写成了developHarbor 不会自动建项目就直接拒了。还有一种情况是用了机器人账号但权限没勾全比如只给了拉取没给推送推的时候报同样的错。排查方式是去 Web 界面看该账号的权限列表一目了然。5.3 存储与性能类报错no space left on device是硬故障处理顺序是先df -h确认是哪个分区满再看/data/docker和/data/harbor占用清掉无用镜像和容器跑一次 GC。预防手段前面说过日志轮转和磁盘告警。context deadline exceeded出现在推送大镜像时通常是网络抖动或者 Nginx 缓冲区配置问题。Harbor 的 Nginx 模板里对 registry 路径已经关掉了proxy_buffering一般不会有问题但如果中间又套了一层反向代理那层代理的client_max_body_size和超时时间就要单独调大。默认client_max_body_size 1m是 Nginx 的出厂值它会把大层上传直接掐断症状是推送到一半失败、日志里是 413。还有一个性能问题值得提Harbor 的数据库在小机器上并发高了会慢表现是 Web 界面点击后转圈很久。这时候看max_open_conns和机器内存往往内存吃紧导致页缓存不足PostgreSQL 频繁读盘。加内存或者把max_job_workers调小都能缓解。5.4 一页纸速查表现象最可能原因处理动作server gave HTTP response to HTTPS client客户端用 HTTPS 连 HTTP 服务配证书或加 insecure-registriesx509: certificate signed by unknown authority证书未进信任链或 SAN 不匹配放 ca.crt 到 certs.d 并核对 SANlookup harbor.internal: no such host域名无法解析改 hosts 或配内网 DNSunauthorized: authentication required未登录或凭据过期重新 login用 stdin 传密码denied: requested access to the resource项目名错、无权限、超配额核对地址、成员角色、项目配额no space left on device磁盘满清空间后执行 GC413 请求实体过大前置代理体积限制调大 client_max_body_size页面 502后端服务未就绪或崩溃看 core 与 nginx 日志、看内存推送成功但列表看不到推到了别的项目或标签错看 Web 界面全量搜索镜像名删除镜像后空间没释放未执行垃圾回收开只读 → 跑 GC → 关只读6. 学习环境之外的加固思路与备份恢复单机能跑之后接下来做什么取决于这套东西会不会真的承载业务。如果只是学习这一节可以挑着看如果已经有同事在往里推镜像了下面几件事越早做越省心。6.1 证书续期与自动签发自签证书十年的有效期在内部环境够用但有一个隐患换机器、改 IP、加域名的时候要重新签发而所有客户端都得同步更新。机器一多这件事会变成负担。更稳的做法是走自动签发用一个能访问的 ACME 服务把 80 端口暴露给验证流程让工具自动签发和续期证书放固定路径Harbor 配置里指向它续期后重启 Nginx 容器即可生效。如果不想引入外部依赖也可以自建一个内部签发机构用它签服务端证书然后把根证书分发到所有客户端的certs.d目录。好处是内部证书链条可控换服务端证书不用动客户端。不管哪种方式都要有一个到期提醒。我见过最尴尬的情况是某天早上所有人都推不了镜像查了半天是证书过期而前一天还没有任何异常。加一条定时检查证书剩余天数的脚本少于 30 天就发通知成本极低。6.2 备份与恢复演练Harbor 的备份只需要四样东西harbor.yml、证书目录、数据卷主要是 registry 的存储目录、数据库导出。数据库导出用容器里的 pg_dumpdocker exec harbor-db pg_dump -U postgres -d registry /data/backup/registry_$(date %F).sql数据卷可以直接用tar打包但要注意体积几十 GB 的镜像打包会很慢。更聪明的方式是只备份元数据和配置镜像本身在极端情况下可以重新推送——如果你有 CI重新构建推一遍就行。所以我的策略是数据库每天导数据卷每周打一次配置文件随改随备份。恢复流程也要演练一次不然真出事手忙脚乱。大致步骤是新机器装 Docker、恢复配置文件与证书、把数据卷挂回去、导入数据库、执行./install.sh。整个过程我实际跑过一次从零到服务可用大概二十分钟前提是备份文件都在。6.3 反向代理与端口收敛Harbor 自带 Nginx已经够用但有时候你需要在一个公网入口后面挂多个服务那就得在前面再加一层反向代理。这时候要注意几个配置proxy_set_header Host $host必须带上否则 Core 服务生成的跳转地址会错client_max_body_size要调到至少 1Gproxy_request_buffering off让 Docker 的分块上传直通后端避免大镜像先落盘再转发。端口收敛的意思是对外只开 443把 8443 关掉。Harbor 的 HTTP 端口本来是跳转用的但它在某些配置下会直接暴露 API如果安全组没管好就是个意外入口。收敛之后客户端行为也更统一。6.4 资源监控与告警的最小配置不需要上完整监控体系先把三个指标盯住就够磁盘使用率、容器重启次数、接口响应时间。磁盘用系统自带的定时任务加脚本容器重启次数用docker inspect里的RestartCount定期采样响应时间用一次curl -o /dev/null -s -w %{time_total}记日志。再进一步可以给 Harbor 自身的/api/v2.0/health接口做探活返回 200 说明核心服务正常。这个接口不需要认证适合放在外部拨测里。我配了一个每五分钟跑一次的脚本连续三次失败就告警比等用户反馈快得多。7. 一些踩过之后的个人体会整套东西跑通之后我最大的感受是私有镜像仓库的难点根本不在“装起来”装起来只要半小时真正的功夫在“让它稳定地被别人用”。我第一次部署完兴奋了半天结果第二天同事来推镜像一个证书问题卡了两小时因为他机器上的certs.d目录名带了端口我没有。这类问题的共同点是配置项的名字看起来都差不多但差一个字就不生效而且不生效的时候往往不报错只是行为不对。第二个体会是关于“学习环境”的边界。一开始我什么都图省事自签证书、单机、不开备份觉得反正坏了重装。但当我真的往里推了几十个镜像之后心态就变了重装意味着重新推一遍代价不小。所以哪怕是学习环境我也建议从第一天就把data_volume和数据盘规划对把备份脚本写好这两件事的成本很低收益是把“随便折腾”变成“折腾不心疼”。第三个是给准备复现这套流程的人一个提醒遇到报错先看日志不要猜。Harbor 的日志集中在data_volume/log下core、jobservice、registry 各有文件Nginx 的在nginx目录里。我踩过的坑里有一半是因为我根据错误信息想象了一个原因结果改了半天配置实际问题在另一层。养成先tail日志再动手的习惯能省掉大量时间。最后分享一个我常用的排查套路当docker push失败时先用curl -k https://harbor.internal/api/v2.0/systeminfo确认服务端到底通不通再docker login确认认证最后才看推送。三步走下来问题落在哪一层非常明确比一上来就翻 Docker 日志高效得多。