
做桌面运维和云上部署这么多年我越来越觉得镜像拉取这事属于那种“平时没啥感觉、一断起来真要命”的隐形瓶颈。群里天天有人喊 docker pull 超时、卡在 layer 下载、公共加速器时好时坏——尤其一帮人在国内服务器上搞持续集成一到发布窗口就集体便秘。折腾了几天我终于在阿里云 ECS 上把 Harbor 私有镜像仓库部署了起来从根上解决了“拉起镜像”这个老大难。这篇就把整个部署过程、踩过的坑、以及如何真正把镜像稳定地拉起来一次说清楚。这篇东西适合谁看一个是和我一样被镜像拉取折磨过的开发运维另一个是想自己搭内网镜像仓库、但没系统梳理过 Harbor 配置细节的新手。我会把从云资源准备、Harbor 安装、到客户端拉镜像的全链路拆开讲每个关键参数都说明白为什么这么配而不是扔给你一串 yml 让你照抄。1. 镜像拉不起来到底卡在哪里1.1 表面上看到的是超时实际是链路问题先别急着上手搭仓库得先弄明白“拉起镜像问题”的根子在哪。Docker 默认从 Docker Hub 拉镜像这个服务部署在境外国内服务器访问它要经过跨境链路。直连的话延迟高、丢包多拉个大镜像经常出现EOF、net/http: TLS handshake timeout、下载一半进度条卡死这类现象。很多人以为是 Docker 配置出了问题其实链路本身就不稳定。公共加速器是大家最先想到的方案。但加速器本质上是别人搭的缓存节点高峰期排队、仓库限流、节点动不动挂掉都无法由我们自己控制。我实测过同一时间用不同加速器拉同一个镜像一个 5 分钟拉完另一个直接握手超时体验完全看运气。在关键的生产环境这种不确定性是不可接受的。所以解决方案就清晰了在离我们服务器最近的地方放一个自建镜像仓库。阿里云 ECS 是国内节点服务器走内网带宽就能访问我们自己的仓库延迟低、速度快、稳定性完全掌握在自己手里。Harbor 在这个方案里不仅是存储中间层还可以做代理缓存和镜像复制一下把“拉镜像”的链路由不可控变成可控。1.2 为什么是 Harbor不是别的GitHub 上registry这个项目就能搭一个最小镜像仓库两行命令起来听起来很香。但它只有存储和拉取的功能没有 Web 界面、没有权限管理、没有垃圾回收的管理界面更别说多租户隔离和漏洞扫描。日常个人用没问题Teams 一起用或者生产环境用就太薄弱了。Nexus 也能当镜像仓库功能也很强但它的定位是通用制品仓库jar、npm、pypi 什么都能存Docker 只是其中一个模块配置相对重、上手成本高。用 Nexus 管镜像总觉得是杀鸡用牛刀而且对 Docker Registry HTTP API 的支持细节没有 Harbor 那么专注。Harbor 是专门面向容器镜像场景设计的企业级仓库我选择它最主要的原因有三条能力解决了什么问题多租户 RBAC不同团队分配不同项目权限互不干扰Web 管理界面项目、镜像、复制、垃圾回收都在页面上操作代理缓存和复制外网仓库的镜像可以“借用”和缓存既提升速度又避免重复拉取还有一个很实在的点Harbor 自带完整的审计日志。谁在什么时候 push 了什么镜像、谁删除了哪个 tag都能查到团队协作和线上故障排查的时候特别有用。纯 registry 完全没有这个能力。2. 部署前的三个准备云资源、基础软件、网络2.1 云资源配置建议买 ECS 的时候大多数人第一反应是 CPU 和内存怎么配。Harbor 本身对 CPU 要求不高但是它是一个 Docker 容器集需要常驻很多组件——核心服务、数据库、Redis、门户、日志收集器全套一起跑起来内存就不太够看了。我的实测经验是2C4G 起步4C8G 舒服。低于 2G 内存的机器装完 Harbor 以后很容易出现 OOM某个容器反复被杀又重启表现得很诡异。带宽我在第一次部署时吃过大亏。当时买了 1 Mbps 的按流量计费小鸡结果往仓库里推一个几百 MB 的镜像速度以 KB 为单位推了半小时推不完。后来把带宽换成 5 Mbps 起步推大镜像才能控制在分钟级。如果你们团队经常推比较大的业务镜像建议直接上 10 Mbps。用弹性公网 IP 的朋友记得带宽跟着 ECS 升云盘类型选 SSD 而不是高效云盘——镜像随机读写的场景多机械盘的寻道延迟在高并发拉取时会暴露出来。磁盘更是个关键点。镜像仓库是典型的“只增不减”存储镜像越堆越多磁盘只会越来越紧张。我给系统盘和数据盘做了分离系统盘 40G 只放 OS数据盘单独买了一块 100G 的云盘挂载到/data专门给 Harbor 存镜像。后面垃圾回收、清理历史版本数据都在这块盘上动手脚不至于把系统盘挤爆。2.2 安装 Docker 与 ComposeHarbor 依赖 Docker 环境所以先把 Docker 装起来。我的服务器是 CentOS 7.9内核镜像本身没问题直接利用阿里云镜像源来加快依赖下载速度# 配置阿里云 yum 源替换为阿里云镜像站提供的仓库配置 sudo yum install -y yum-utils sudo yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo sudo sed -i sdownload.docker.commirrors.aliyun.com/docker-ce /etc/yum.repos.d/docker-ce.repo sudo yum install -y docker-ce docker-ce-cli containerd.io # 启动并设置开机自启 sudo systemctl start docker sudo systemctl enable docker docker version安装完成后建议顺手配一下 Docker 的守护进程参数。注意这里我不建议一上来就配置加速器——既然已经要走自建 Harbor 路线docker daemon 的registry-mirrors字段可以暂时留空把重心放在后面 Harbor 代理缓存的落地。不过如果你现阶段还是想保留公共加速器作为兜底也是可以理解的两个并不冲突。Harbor 的安装还需要 docker-compose。这里有个版本差异要说明一下老教程里基本都是让你安装 Python 版的docker-compose独立二进制但在 2024 年之后新版 Docker 已经内置了docker compose插件带横杠。两种写法功能几乎一样但是命令不同。下面是配套安装# Docker Compose 插件版推荐 sudo yum install -y docker-compose-plugin docker compose version用插件版的好处是它和 Docker 一起升级不会出现二进制版本和 Docker 版本不匹配导致的兼容性报错。如果你的环境里没有这个插件包也可以用官方 GitHub release 下载独立二进制放到/usr/local/bin/docker-compose并赋可执行权限这仍然是最常见的做法。2.3 数据盘与目录规划部署之前先把磁盘规划定下来免得后面数据没地方存。给数据盘做分区、格式化、挂载每一步都要明确这块盘将来是 Harbor 的“仓库本体”目录不能乱。我之前用的操作是# 假设数据盘设备名为 /dev/vdb sudo fdisk /dev/vdb sudo mkfs.ext4 /dev/vdb1 sudo mkdir /data sudo mount /dev/vdb1 /data echo /dev/vdb1 /data ext4 defaults 0 0 | sudo tee -a /etc/fstabHarbor 默认的数据目录是宿主机的/data。如果你后边修改过harbor.yml里的data_volume字段那就要保证那个路径所在的分区足够大。我习惯把数据盘挂到/data直接贴合 Harbor 的默认行为少一个变量。还有一个小经验把 Harbor 的数据目录放在独立盘上以后万一系统盘损坏需要重装系统数据盘卸载下来挂到新机器上Harbor 数据还在。云厂商的快照功能对系统盘做快照也行但对/data这种大容量盘做快照会很慢对数据盘单独做快照更轻量、恢复也更精准。重装不丢数据这在运维层面是很重要的安全感。3. Harbor 安装配置实操3.1 下载安装包与配置 harbor.yml从 GitHub 下载 Harbor 离线安装包时建议直接选最新的稳定 v2.x 系列。曾经有过 Harbor 1.x 和 2.x 在配置项上比较大的差异新项目直接用 2.x 就好社区迭代和新特性都在 2.x 上。下载命令cd /opt wget https://github.com/goharbor/harbor/releases/download/v2.11.1/harbor-offline-installer-v2.11.1.tgz tar xzvf harbor-offline-installer-v2.11.1.tgz cd harbor cp harbor.yml.tmpl harbor.yml这一步关键是编辑harbor.yml。我贴一个能用的精简版本再逐行解释核心字段hostname: harbor.example.com http: port: 80 harbor_admin_password: Harbor12345 data_volume: /data storage: ca_bundle: database: password: root123 redis: max_idle_conns: 100 idletimeout_secs: 5hostname是重中之重。它不只是展示用的名字还直接关系到访问地址和 TLS 证书匹配。比如你设成harbor.example.com那后面所有客户端都必须用这个地址访问不能用 IP 或者别的域名。如果你暂时没有域名可以直接填 ECS 的公网 IP但是后面要用 HTTPS 证书的话就麻烦一些——证书本身得和 IP 绑定。所以我建议如果有域名先解析一个二级域名给 Harbor走 HTTPS 更顺。http.port我留了 80 端口。如果你已经用这个端口跑别的服务就要改成其他端口比如 8080。这个端口将会影响所有客户端使用时的访问地址以及 docker daemon 对仓库的insecure-registries配置。harbor_admin_password是安装后管理员账号的初始密码。一定要在安装之前就把它改成一个强密码安装脚本会在安装完成时写入配置。如果安装之后再改需要重新执行配置生成比较麻烦。data_volume按前面说的填/data。另外注意database.password这是 Harbor 内部数据库 root 的密码默认值也建议改掉不为别的就为这台机器将来暴露到公网时少一点被脱库的风险。3.2 执行 install.sh 启动配置确认没问题后运行安装脚本sudo ./install.sh脚本会检查环境依赖拉取 Harbor 各组件镜像然后通过内置的 docker-compose 模板把容器全部起起来。首次执行因为要下载镜像可能需要几分钟耐心等。装完以后用docker ps看看容器状态。正常情况你能看到这些主要容器容器名作用nginx入口网关统一接收外部请求harbor-core核心业务逻辑处理 API 请求harbor-registry镜像存储层基于 Docker Registryharbor-dbPostgreSQL 数据库存元数据harbor-redis缓存和任务队列harbor-jobservice异步任务复制、垃圾回收等harbor-portalWeb 管理界面如果某个容器反复 restart优先怀疑内存不足。我在第一次部署的时候碰到harbor-db一直崩溃重启查日志发现内核 OOM killer 把 postgres 进程杀了。把 ECS 内存升到 4G 之后就好了这个问题在后续运维中容易再次出现特别是同一台机器上还跑着其他容器的服务。启动之后访问http://harbor.example.com能看到登录页。用admin和你在harbor.yml里设置的初始密码登录就算完成基础部署了。如果页面打不开先看服务器安全组有没有放行 80/443 端口——这是新手最容易遗漏的。阿里云 ECS 的安全组规则在实例详情页的网络和安全组选项卡里单独一条入方向规则放行 TCP 80 和 TCP 443操作要明确具体端口不要图省事全开。3.3 HTTPS 证书选型我在部署初期用的是 HTTP 访问图省事。后来发现不行现代 Docker 客户端默认所有仓库都要走 HTTPS 连接你的 Harbor 只开 HTTP 的话客户端会报http: server gave HTTP response to HTTPS client。解决这个问题有两个方向第一用自签名证书开 HTTPS。需要在harbor.yml的https段下配置证书路径和密钥路径。自签名证书没有权威 CA 签署所以所有使用方都要在配置里设置insecure-registries或者把证书加到系统信任列表里客户端和 Harbor 之间像“互相认识但对暗号”不会轻易被中间人信心误判。第二给域名申请一张免费证书。阿里云有免费 SSL 证书的申请入口只要域名解析在阿里云就能签下来。签名证书受系统默认信任客户端不需要额外配置就能访问 Harbor唯一的成本是证书有有效期快到期时要续期替换。我的建议是公司内部小团队自签名 insecure-registries最省事对外提供稳定服务优先考虑免费 SSL 证书省掉客户端一大堆配置问题。两者之间没有绝对的优劣关键是看你的用户是谁、他们对安全的要求是什么。无论用哪种证书记得把harbor.yml里 https 段的certificate和private_key路径写对证书文件权限不能太开放否则 nginx 容器启动会报权限错误。这个细节我在真实部署中遇到过花了不少时间排查。4. 真正解决“拉起镜像”两种落地姿势4.1 姿势一常用镜像主动搬运Harbor 部署完成是开始但目标是让“拉起镜像”变得又快又稳。最直白的方案是把团队日常用到的镜像打上标签推到自己的 Harbor之后所有服务器都改从 Harbor 拉取。操作思路是三步从外网仓库把镜像拉下来、重新打 tag、推到 Harbor。因为推送到自己的仓库走的是阿里云内网或者公网都能访问速度比直接从外网仓库拉快得多。以大热的开源应用 Redis 为例# 1. 通过外网仓库拉取镜像可先在任意可用的节点完成 docker pull redis:7.0 # 2. 给镜像重新打标签指向 Harbor docker tag redis:7.0 harbor.example.com/library/redis:7.0 # 3. 推送镜像到 Harbor docker push harbor.example.com/library/redis:7.0如果是团队自研业务镜像推送思路完全一样无非 tag 名字里带上了项目名。这个“搬砖”工作可以用脚本批量做。举个例子把镜像列表写在文件里循环处理#!/bin/bash HARBOR_URLharbor.example.com while read IMAGE; do docker pull $IMAGE docker tag $IMAGE $HARBOR_URL/library/$(basename $IMAGE) docker push $HARBOR_URL/library/$(basename $IMAGE) done images.txt注意这里有个容易踩坑的地方Harbor 项目默认有公开项目的概念。如果library项目设为公开那么仓库里的镜像不需要登录就能拉取这对集群节点很友好如果是私有项目每台机器都要先docker login不然拉取会报unauthorized。我在实际落地时把library设成公开其他业务项目单独建私有仓库这是最常见的最佳实践。这种方案适合那种“镜像列表很固定、变更频率不高”的场景。比如今天需要 Redis 7.0、MySQL 8.0、Nginx明天还是这套那就主动搬运一次以后全部走本地速度基本是内网满速。4.2 姿势二开 Harbor 代理缓存直连官方仓库主动搬运的缺点是新镜像一个都没提前搬遇到临时要拉一个比较冷门的镜像调度效率立刻打回原形。Harbor 2.x 提供了“代理缓存”功能这个设计很灵活。它能让你在拉取一个不存在的镜像时由 Harbor 去上游仓库拉取并缓存下来同时客户端感知不到后端是谁。操作路径是在 Harbor Web 管理界面的“仓库管理”里新建一个“代理缓存”类型的目标上游填 Docker Hub 的地址。然后建一个对应的项目项目类型选择“代理缓存”绑定这个上游目标。建好之后你往这个代理缓存的仓库地址拉镜像Harbor 会先去 Docker Hub 拉一份到本地再返回给你。我被这个功能拯救过一次。当时生产环境半夜要拉一个新版本的服务镜像外部仓库突然变得非常慢久拉不下来而且那台服务器上还没配置任何加速器。我把地址改成 Harbor 代理缓存项目的地址Harbor 那边虽然也是走外网去拉但至少它有重试和缓存机制第一次慢一点第二次开始直接命中本地缓存速度和本地仓库一样快。命中了几次以后整个团队对这个镜像的拉取都稳定了。所以在落地时我把代理缓存和主动搬运结合固定镜像主动搬运冷门镜像走代理缓存拉过一次以后自动变稳定。这样的组合拳才能覆盖绝大多数“拉起镜像”问题。4.3 客户端侧配置与登录验证仓库建好镜像也推上去了剩下最关键的一步让所有客户端知道该去 Harbor 拉镜像并且能够正常认证。如果是 HTTP 仓库比如上文提到的默认配置客户端必须显式声明它是可信任的非 TLS 仓库否则 docker 会默认尝试 HTTPS 连接然后失败。在每台需要拉取镜像的服务器上编辑/etc/docker/daemon.json{ insecure-registries: [harbor.example.com] }然后重启 Docker 守护进程sudo systemctl restart docker这个配置意味着客户端认为该仓库虽然不支持 HTTPS但信任它明文 HTTP 的传输也被允许。对于企业内部网络这个方案完全可接受但对公网访问来说尽量还是用 HTTPS 更安心一点。登录认证方面docker login harbor.example.com如果library项目设为公开拉公开镜像不需要登录push 镜像、访问私有项目必须登录。登录信息会存在~/.docker/config.json像 CI 机器可以在流水线里通过docker login -u xxx -p xxx预先认证。验证一下拉取是否真的走本地仓库docker pull harbor.example.com/library/redis:7.0第一次如果代理缓存没有命中Harbor 会去上游拉取所以可能还是有一定等待第二次拉同样的镜像速度就非常快了。这种“速度的感知差异”其实就验证了整个链路打通了。5. 运维阶段磁盘、GC、备份与升级5.1 磁盘膨胀与垃圾回收机制Harbor 跑久了磁盘占用会像个无底洞。表面上你删掉了一些镜像和 tag但磁盘空间不一定会立刻释放这跟 Docker Registry 的存储机制有关——镜像层实际是以哈希目录的形式存放在文件系统里的删除 tag 只是移除了元数据引用底层 blob 还在。明白这个机制之后你就能理解为什么需要定期做垃圾回收。Harbor 在执行完清理策略之后需要手动在 Web 界面里“系统管理 — 垃圾清理”跑一次 GC磁盘才会真正回收空间。我建议每个月跑一次并且在执行 GC 之前做好备份磁盘 I/O 在 GC 期间会有一定压力尽量选择业务低峰期执行。提示不要下意识以为 Harbor 的“清理”按钮点一下就万事大吉GC 是独立的步骤略过它你会持续被“镜像删了但磁盘不减”困扰。5.2 备份策略Harbor 的数据分两部分数据库里的元数据和/data下的镜像存储文件。只备份数据库而丢掉镜像文件恢复后看到的镜像列表是空的只备份存储文件而没有数据库Harbor 无法感知到这些 blob 属于哪些项目。稳妥备份方案是三件套备份对象方式Harbor 配置harbor.yml单独备份按环境归档PostgreSQL 数据库容器内pg_dump导出 SQL/data目录镜像存储云盘快照或 rsync 到备份机数据库导出命令示例docker exec -it harbor-db pg_dump -U postgres -d registry harbor_db_backup.sql恢复的时候先把/data还原再导入数据库最后重启 Harbor 全套容器。这套流程在故障演练环境验证过恢复时间取决于数据量但整体是可控的。5.3 升级注意事项Harbor 升级不能跳版本莽着来。官方支持从旧大版本升级但最好看准 release note 里的升级路径。升级步骤一般是用新版离线包里的migrate工具先迁移数据库再执行install.sh。整个过程要保证数据盘有足够空间因为迁移中间态可能会占用额外磁盘。我踩过一次升级坑直接替换二进制跳了两个大版本数据库迁移执行到一半报错回滚没做好前后花了两个小时才恢复。后来学乖了升级前一定先打快照升级路径按官方文档逐版本推进不做测试机的生产环境绝对不冒险升级。6. 常见问题排查速查表我在部署和使用过程中积累了一些高频问题整理成表你们可以对照快速定位现象根因处理方法http: server gave HTTP response to HTTPS client客户端默认走 HTTPSHarbor 只开了 HTTP配置insecure-registries或启用 HTTPSx509: certificate signed by unknown authority自签名证书未被系统信任配置insecure-registries或安装证书到系统信任链容器反复重启内存不足 / OOM扩大 ECS 内存检查单个容器内存限制Harbor 页面打不开安全组没放行端口在阿里云安全组增加入方向 80/443 规则登录成功后 push 报 401项目私有未授权在 Harbor 项目成员里添加用户并赋角色删了镜像磁盘没变空未执行垃圾回收系统管理里执行 GC 操作docker pull 偶尔 EOF上游外网链路抖动使用代理缓存/提前推送镜像避免直连外部仓库这些排查点每一条都是真实的“操作现场”不是从文档里抄出来的。排查问题时先按现象归类很快就能缩小范围。在镜像同步的时候还有个小技巧值得提一下如果你手头有多个地域的阿里云 ECS不需要每个地域都各推一遍镜像。Harbor 有“复制”功能可以设置一个主仓库自动把镜像同步到其他地域的 Harbor 或阿里云容器镜像服务个人版实例。跨地域同步时按需拉取不用每个节点单独做“搬运工”能省下不少带宽和操作时间。还有一个容易忽视的点是demo用的初始密码。安装起来之后第一件事就应该是改掉harbor_admin_password默认密码太容易被猜到。改完后再把管理员账号绑定到一个你日常使用的邮箱万一忘记密码至少有找回路径。最后再分享一点个人体会Harbor 部署本身不难难的是让它真正融进你的发布链路。我在这个项目里做的最满意的一步不是把 Harbor 跑起来而是把 docker daemon 配置、Harbor 代理缓存、镜像主动同步这三件事全部打通让“拉镜像”变成一件不需要人为干预的默认行为。如果你现在正在被镜像拉取折磨照着上面这套组合拳去搭应该能感受到那种“拉镜像不再看脸色”的稳定感。