
1. 为什么会在Docker Registry和Harbor之间纠结1.1 为什么说Docker Registry只是“能用”先交代一下背景。我所在的团队负责维护一套自建的容器化交付链路镜像仓库最开始部署的就是纯Docker Registry部署很简单拉起来一个容器挂个数据卷暴露5000端口再配个Nginx反代就算上线了。早期镜像不多团队也就几个人用起来确实没什么大问题。但要是把这个阶段叫做“好用”那就有点勉强了。我个人的评价是Docker Registry做到了“能用”离“好用”还有明显距离。这种差距不是某个瞬间突然暴露出来的而是随着镜像数量、团队成员和发布频率增长一点点显现的。比如Registry原生不带Web界面你想看一下仓库里有哪些镜像、哪个tag是什么时候推送的只能通过docker registry API手动去翻或者装一个第三方的浏览器工具。再比如权限模型基本是“全有或全无”要么匿名可拉要么配一个简单的HTTP Basic认证做完认证之后所有人共享一套账号推了删了都不好追溯。还有一点很隐蔽就是垃圾回收机制。Registry的删除操作默认只是把manifest标记成不可用实际的blob数据还躺在磁盘上需要手动执行garbage-collect命令才会真正释放空间。如果没人管几版镜像迭代下来磁盘占用会涨得让你怀疑人生。所以对一个小团队来说Registry是一块很合适的垫脚石但一旦你的镜像仓库开始承担真实业务的交付任务它就很容易成为那个“看起来在正常工作实际上处处要人盯”的环节。1.2 什么时候其实不用换Harbor别被“升级”绑架这里要先泼一盆冷水不是所有团队都需要从Registry升级到Harbor。我自己见过不少案例团队只有三五个人镜像总量几百个没有严格的权限隔离需求也没有跨机房同步的要求结果看了一堆文章就把Harbor拉起来最后发现组件好几个、资源吃了一大截但日常用法和之前几乎没有区别。这属于典型的被“升级”两个字绑架。我理解的判断标准大概是这样的如果以下几个条件里你中了至少两个那Harbor才值得纳入考虑。第一团队内存在多个项目、多个角色需要区分谁能推谁能拉第二有安全合规要求需要记录谁在什么时间推送或删除了哪个镜像第三镜像是跨环境或跨机房租用的需要复制同步或批量迁移第四你的CI/CD链路开始需要程序化地管理镜像仓库比如自动创建项目、自动清理旧版本。如果这些一个都不沾用Registry完全没问题省掉Harbor那些组件运维负担小很多。不过如果你的情况和我当时类似——镜像仓库从“个人玩具”变成了“团队基础设施”那接下来这篇文章就值得你花十分钟看完了。我下面会按照我实际的踩坑顺序把从Docker Registry切换到Harbor的过程中那些真正需要关注的东西讲一遍。2. Docker Registry用起来不痛不痒那是问题还没攒够2.1 一个被“能用”掩盖的核心问题没有权限边界先说权限。Docker Registry本身支持两种认证方式一种叫htpasswd基础认证一种是通过token认证接入更上层的鉴权服务。很多小团队用的都是htpasswd轻量、好配但缺点非常明显账号要么全读全写要么根本没账号。你不能只给A项目的人推A项目的镜像B项目的镜像他们碰不了。所有人共用一个账号密码一旦泄露整个仓库就是裸奔状态。我们当时就是这样运维一个账号开发一个账号密码写在团队文档里。某次有同事离职为了安全起见得改密码改完之后所有构建机上配置的docker login信息全要同步更新漏掉一台那台机器就立刻推不上去。这件事重复了两三次之后我意识到用户名密码共享这种模式在镜像仓库这种更新频繁的系统里非常折磨人。Harbor这边权限模型是项目级别的RBAC一个项目可以划分访客、开发者、维护者、管理员等不同角色每个角色的权限边界很清楚。你不用再为了“谁能推谁不能推”去折腾一堆复杂配置。2.2 Registry的“假删除”和它带来的磁盘焦虑再聊垃圾回收。Docker Registry的GC是一个让很多人栽过跟头的地方。我在网上看过不少讨论说Registry删除镜像之后磁盘不释放一些比较老的文章还会引导你去手动删文件目录这其实是比较危险的做法Registry存储层的目录结构不是给用户直接改的手动删很容易把仓库弄坏。正确的做法是用registry自带的garbage-collect命令而且最好在read-only模式下操作避免并发写入造成数据错乱。但即便是正确使用GC也还是有个令人难受的点GC是一个全量扫描和清理的过程仓库越大耗时越长而且执行期间仓库会变得很脆弱。如果你的仓库数据很重要还要考虑先备份再GC这对一个“只是普通基础设施”的组件来说运维成本明显偏高了。我之前在一个镜像数量破千的仓库上跑过一次GC整个过程战战兢兢生怕某个blob还在被引用就被清了。事后想想与其在这种地方反复操心不如在系统设计层面直接给镜像生命周期上个“保险”Harbor的回收策略就是在仓库层面自动处理的设置了保留最近多少个tag这种规则之后老镜像会被自动清理不需要手工介入。2.3 没有可视化界面等于少了一双眼睛这个点听起来没那么“技术”但对日常使用体验影响很大。Docker Registry没有UI意味着你每次要确认“这个镜像到底有哪些tag”“昨天构建的那个测试版本还在不在”都得去调API。确实有像docker-registry-web这样的开源小工具可以补一下但这类工具功能普遍比较简单也就是能看列表交互体验和Harbor这种完整产品完全不在一个量级。Harbor自带一个比较完整的Web界面可以查看项目、仓库、镜像、tag、构建历史还能直接看到每个镜像的漏洞扫描结果。这种“可视性”在排查问题的时候特别有用。比如一个镜像在测试环境能跑、生产环境起不来你可以直接在Harbor页面上看这个tag的推送时间、扫描状态、相关元数据快速定位是不是拉错版本了。做过线上故障排查的人应该能明白这种直观的信息很多时候比命令行快很多。3. Harbor到底带来了什么从“能存镜像”到“能管镜像”3.1 架构上多出来的那些组件都有什么用Harbor的部署形态看起来比Registry重不少它依赖PostgreSQL、Redis还包含core、jobservice、registry、portal等多组组件实际上它的registry组件也是基于Docker Registry的只是在外面包了完整的管理能力。我第一次部署Harbor的时候也很困惑觉得“不就是一个镜像仓库吗为什么要拆这么多东西”。但现在回头看这些组件对应的是不同层次的能力core提供核心业务逻辑jobservice处理复制和清理这类异步任务portal是界面层registry负责真正的镜像存储Trivy则负责漏洞扫描。如果你使用的是官方docker-compose方式部署跑起来之后可以用docker compose ps看到十几个容器第一次看到确实会吓一跳。但这些组件大多可以认为是“内部服务”并不需要你逐个去维护。日常运维主要关注的还是core、registry、数据库和存储目录。所以在资源评估上Harbor建议至少给2核4G如果你的并发构建量比较大建议4核8G。这个量级对于大多数中小团队来说是完全可以接受的不算很大的运维负担。我在部署和使用Harbor的过程中体会到最核心的一点是Registry帮我们把“镜像内容”存下来了而Harbor是在这之上把“镜像的管理诉求”补全了。你需要的是一个存储节点还是一个具备管理能力的基础设施这决定了你在两者之间怎么选。3.2 权限、复制、扫描、回收四个能力解决四个真实痛点Harbor真正让我觉得“换上是对的”主要是四个能力。第一个是权限控制。在前面已经提到过RBAC这里不再展开。实际带来的好处是给不同项目配不同角色以后开发和运维之间的协作协调会顺很多不会再因为一个共享账号反复折腾。第二个是镜像复制。Harbor可以配置“复制规则”把镜像从一个实例同步到另一个实例或者从远端某个仓库拉取到本地。这个能力很多团队在跨机房、多环境部署时刚需。我之前用Registry的时候要做到跨机房同步只能自己写脚本调API拉镜像再打tag再推送非常麻烦还容易漏tag。Harbor的复制是策略化的Pull模型、Push模型都支持还可以设定同步模式每到一个新tag自动同步过去这比手工操作可靠得多。第三个是漏洞扫描。Harbor内置了Trivy扫描器可以在镜像推送后自动扫描已知漏洞。这个能力在相对规范的团队里几乎是一个合规需求你很难想象每次发版之前都手工去CVE库里查一遍。虽然Trivy的扫描结果不代表绝对安全但它能把“这个基础镜像该不该升级”这种问题变成有数据支撑的判断这对大部分研发团队来说都是很有价值的。第四个是回收策略。Harbor允许对仓库或项目设置回收策略比如“保留最近多少个tag”或者“保留最近几天的镜像”。这个功能不单单是节省存储空间更重要的是控制镜像总数的膨胀降低仓库的噪声。你如果经历过磁盘被镜像撑爆的事故会对这个功能格外有好感。3.3 自己部署Harbor时我建议你这样配Harbor的安装本身不算复杂但有几个配置点值得留意。我以常见的在线安装方式为例简单梳理一遍实际操作步骤。首先到Harbor的官方发布页下载对应版本的离线安装包解压后会看到harbor.yml文件。这个文件是Harbor的核心配置包含主机名、端口、管理后台密码、数据库密码等。需要注意harbor.yml里面默认会有http和https两种配置示例生产环境建议直接启用HTTPS证书配置不要用HTTP裸奔。如果你确实只是内网测试那可以暂时用HTTP但也要在配置里明确关闭https端口避免端口冲突。配置完成后运行./install.sh。如果你需要启用漏洞扫描能力记得在安装命令中加上--with-trivy参数。如果还要用Harbor管理Helm Chart或提供NFS存储可以带上--with-chartmuseum和--with-notary等参数。第一次安装的时候我建议只加Trivy其他按需再加组件越多初始排查问题的复杂度也越高。安装完成后访问https://你的主机名默认管理员账号是admin密码在harbor.yml里配置。登录之后第一件事是创建一个新的项目然后在这个项目下创建机器人账号。机器人账号是给CI/CD用的它的权限是受限的比如只允许推送不允许删除。这一步很多人会忽略直接用admin账号去配置Drone或Jenkins后面的安全风险可想而知。实际用下来机器人账号配合Harbor API是自动化链路里最顺手的一种鉴权方式。另一件值得做的事是配置存储目录。harbor的镜像数据默认保存在/data目录下如果你的系统盘不大一定要提前把/data挂载到独立的数据盘或网络存储上。我见过有人在默认配置下用了很久直到系统盘写满才意识到问题。数据目录的迁移在Harbor里不是不能做但涉及一堆容器编排的挂载点修改属于能避免就不要碰的运维操作。4. 用Harbor API把镜像仓库真正“自动化”起来4.1 先搞清楚Harbor API在CI/CD里到底承担什么角色最近关于Harbor的讨论里经常能看到一句话可以不通过Harbor去发布代码。这个说法本身没有错但有很多人产生了一个误解觉得用了Harbor之后代码发布就要依赖它。实际上Harbor在整个CI/CD链路里的定位非常清晰它是一个制品仓库负责存镜像、管镜像但是代码的拉取、构建、部署这些动作依然由你的CI平台和服务器执行器来完成。举个例子。如果用我自己常用的Gitea加Drone加Harbor这套组合实际的工作流是代码推送到GiteaGitea触发Drone构建任务Drone在构建机中完成编译和镜像打包然后通过docker login把自己登录到Harbor实例再把镜像推送到Harbor的某个项目下。部署时目标服务器从Harbor上拉取指定tag的镜像并启动容器。Harbor在这里只是“存和取”的角色代码发布这个动作仍然是通过CI平台的部署步骤去执行的不需要也不应该由Harbor来调度。明白了这一点你就不会把Harbor理解成发布系统也不会在架构设计上非要把Harbor往部署编排里塞。它跟Gitea、Drone、Nginx各司其职反而链路更清晰。4.2 用API自动创建项目并获取推送凭证很多团队在用Harbor的时候都是管理员在界面里手工创建项目、手工加成员。一开始项目少还行等团队规模上来了每个新项目都走一遍手工流程就很低效。Harbor API这时就派上用场了。你可以用一条curl命令创建项目再把准备阶段和自动化链路打通。比如创建一个名为backend的新项目可以用下面这样的请求curl -u admin:你的密码 -X POST https://harbor.example.com/api/v2.0/projects \ -H Content-Type: application/json \ -d {project_name:backend,public:false}这里public字段控制是否允许匿名拉取默认建议false强制走认证。-u参数是用管理员账号做Basic认证如果你希望更安全可以改用API Key或管理员预先生成的机器人账号token。创建完项目之后你还可以用API把某个用户或者机器人账号添加到项目里赋予它developer或maintainer角色。curl -u admin:你的密码 -X POST https://harbor.example.com/api/v2.0/projects/backend/members \ -H Content-Type: application/json \ -d {role_id:2,member_user:{username:ci-builder}}这里的role_id在Harbor API里一般是有限定值的比如1对应管理员、2对应开发者等具体可以查阅当前版本的接口文档。实际使用中我会把这类请求封装成一个小脚本在Gitea创建仓库时通过Webhook同步触发这样新项目发布基础设施的初始化就能自动完成不用人肉点半天。4.3 用API做镜像清理和制品查询Harbor的Web界面提供了回收策略但如果你需要在某个流程节点上动态清理镜像API也是一个很实用的口子。比如你想列出某个仓库下的所有tag可以用curl -u 用户名:密码 https://harbor.example.com/api/v2.0/projects/backend/repositories/backend/app/artifacts?with_tagtrue这条请求会返回对应镜像的制品列表包含tag名称、推送时间、大小、元数据等。你可以通过脚本对tag做按时间排序找出“超过30天且不是latest”的tag然后调用删除接口把它们清掉。删除制品时需要注意Harbor是支持按tag删除的删除之后配合回收策略存储空间才会真正释放。用API清理镜像的好处是它与你的发布节奏强绑定。比如你的CI在每次合并到主干后都会构建并推送一个新tag同时希望保留最近20个版本即可。你可以在Drone的pipeline里增加一个步骤在推送完镜像后调用Harbor API做一次过期清理。这样可以确保仓库里的镜像数量维持在一个可控范围而不是等着某一天磁盘报警再上去人工处理。5. 如果不想上Harbor有没有“轻量但好用”的替代方案5.1 Gitea加Registry的轻量组合适合哪些场景不是所有团队都愿意接受Harbor那些额外的组件毕竟PostgreSQL、Redis、Trivy这些都意味着K8s或容器环境里多了不少要监控的对象。如果你对“好用”的定义没有到Harbor那个程度但同样不想完全用Registry裸奔可以考虑一个折中组合Gitea加Docker Registry再加上Nginx的统一入口。Gitea本身不是镜像仓库但它具备完善的用户体系和权限管理。你可以在Gitea上创建账号、组织、团队然后通过Registry的token认证接入Gitea的OAuth2来验证推送拉取请求。这样你既保留了Registry的轻量又获得了比较统一的账号权限入口。不过这个方案的维护成本并不低因为你需要自己写一些认证中间件来让registry去验证Gitea的token涉及到token服务、配置JWT密钥等。实现上不能说复杂但比Harbor开箱即用要费一些事。另一种更省心的方式是直接用Nginx做一层鉴权反代在Nginx里配置简单的认证规则比如只有内网IP段能访问仓库推送操作需要额外的token。这种方案能挡住不少乱用但和真正的项目级权限控制还是两回事。5.2 Gitea加Harbor加Drone加Docker加Nginx的经典布局在真实项目里我见过比较顺手的组合就是Gitea负责代码托管Drone负责CI流水线Harbor负责镜像管理Nginx负责统一的外部入口。这套组合不像K8s加GitOps那样重但对一个中小团队来说已经能支撑一个比较完整的迭代发布流程了。部署层面最简单的方式是全部使用Docker Compose把这些组件作为一个整体编排起来。Harbor本身也自带docker-compose所以整体结构可以拆成两部分一部分是Gitea和Drone另一部分是HarborNginx挂在最前面把不同域名或路径转发到对应的服务。这样做的好处是每个组件都能独立升级、独立排查问题不会因为一个组件挂掉把整条链路拖死。Drone的配置里需要用到Harbor的地址、项目名、机器人账号密码。由于Drone的pipeline是声明式的直接在.drone.yml里加docker镜像构建和推送的步骤就行。最需要注意的一点是Drone构建镜像时要避免把宿主机的docker socket直接挂进去除非你非常信任你的构建脚本。更安全的做法是在Drone里配置DinD服务让构建任务在独立的Docker Daemon里跑这样镜像构建隔离性更好也不会污染宿主机。用DinD会带来一些网络上的小问题比如容器之间访问Harbor的地址要写成容器网络内的服务名这些踩坑经验后续可以单独聊。5.3 到底选Harbor还是轻量方案我给一个决策清单我个人的排序是如果团队规模小于5人、镜像总量不大、没有安全合规压力Gitea加Registry加Nginx完全够用如果团队处于成长阶段项目在变得复杂有多个环境、多个角色同时希望镜像仓库有界面可看、有扫描能力那么Harbor是最稳妥的选择。两者之间的“中间方案”也就是Gitea加Registry加自己的认证服务只适合你明确知道自己要什么、有足够精力维护额外认证逻辑的情况。从成本角度算Harbor的自带组件看着多但大多数都是封装好的容器日常要关注的核心其实就那两三个。反而自定义认证方案代码是你自己写的出问题排查范围更广。在“能用”和“好用”之间真正值得投入的是那一条清晰的、不用反复人工干预的自动化链路而不是某个组件的数量多少。6. 实际切换过程与常见故障排错实录6.1 从Registry迁移到Harbor我用的是这种方式如果已经有一批镜像在旧Registry里迁移到Harbor可以不动底层数据直接用Harbor里的复制功能。做法是先在Harbor里创建一个项目然后在“复制管理”里添加一条拉取规则源地址填旧Registry的地址带上认证信息目标项目填Harbor对应项目。这样Harbor会把旧仓库里的镜像批量拉取到本地。这个方法看起来简单但有一个前置条件旧Registry必须支持API访问且目标项目名称要对得上。如果你的旧Registry里有个镜像叫old.registry.com/team/app:v1在Harbor里新建的项目最好也叫team/app这样复制时路径映射比较自然。复制规则可以按需触发也可以设成定时同步。实际测试中几百个tag的仓库同步大概需要几分钟到十几分钟取决于镜像体积和网络带宽。如果你不想依赖Harbor的复制功能也可以用Skopeo这类命令行工具做镜像搬运。Skopeo的好处是不需要docker daemon可以在脚本里逐镜像同步适合做迁移或批处理skopeo copy --src-creds olduser:oldpass \ docker://old.registry.com/team/app:v1 \ --dest-creds newuser:newpass \ docker://harbor.example.com/team/app:v1跑之前建议先拉取一份仓库清单再写循环脚本逐个搬运同时记录搬运失败的tag方便后续重试。迁移过程中如果遇到某个tag在源端已经被标记但还在使用的情况Skopeo通常会报manifest unknow的错这时候检查源端仓库状态再重试就行。6.2 从Harbor拉取镜像时提示“unauthorized”最常见的3个原因Harbor投入使用后用户问得最多的一类问题就是“我docker login成功了但pull时报unauthorized”。这个问题挺典型排查时按顺序看三个地方基本能定位。第一是不是项目权限不对。Harbor的访问控制是按项目细分的你的账号如果只属于A项目那拉取B项目镜像肯定报unauthorized哪怕你登录成功了也一样。检查账号在项目里的角色是否至少拥有访客权限。第二是不是机器人账号的权限边界问题。机器人账号在创建时就要指定项目且只能访问该项目。如果Drone或脚本里配的机器人账号只给了backend的权限却去拉frontend的镜像照样会被拒绝。第三是不是镜像路径写错了。Harbor的镜像路径是/项目名/仓库名:tag很多人习惯性写服务器IP/仓库名:tag少了一个项目层级Harbor就会认为你要访问一个不存在的项目进而返回unauthorized注意是项目名和仓库名都要体现在路径里。这几个原因都排查完还没解决再看一下Harbor所在的服务器时间和构建机时间差异。如果时间偏差过大token验证会直接失败报错信息类似401或unauthorized这个坑比较隐蔽。6.3 构建机推镜像很慢或超时问题可能不在Harbor还有一个高频问题push镜像时特别慢甚至超时。很多人的第一反应是Harbor性能不行但其实大多数时候问题出在Nginx或网络链路。Harbor默认是走HTTPS的如果你用了自签名证书构建机上的docker daemon必须信任这个证书否则docker login的时候就会提示证书错误如果强制跳过验证后能连接但每次传输都很慢那就要看看Nginx的client_max_body_size和proxy_buffer配置是不是把比较大的分块给拦下来了。另一个常见瓶颈是存储IO。Harbor的镜像数据存在磁盘上push操作涉及大量写操作如果你用的是普通机械盘或者网络存储且带宽有限那么并发推送大量镜像时性能会急剧下降。我碰到过一次Harbor虚机CPU使用率不高但磁盘IO一直100%排查发现存储盘是从另一台NAS挂载过来的网络抖动导致写入延迟特别高。后来把数据目录迁移到本地SSD问题立刻缓解了。所以遇到推送慢不要只盯着应用日志先看底层的磁盘和网络指标。关于镜像清理后的空间释放我最后想多说一句。Harbor的UI上虽然能执行清理但清理动作是标记删除真正释放空间还是要等垃圾回收任务跑完。如果你发现Harbor的存储目录占满了要在界面的“垃圾回收”里触发一次回收确认回收任务成功后再看磁盘用量。否则即使你删了一堆tag磁盘可能依然稳如泰山。6.4 常见问题速查表现象可能原因解决思路docker login成功但pull提示unauthorized项目权限不足、镜像路径少了项目层级、机器人账号权限不对检查账号在项目中的角色核对镜像路径格式确认机器人账号覆盖目标项目push镜像超时或很慢Nginx参数限制、存储IO瓶颈、网络链路问题检查Nginx body大小和buffer配置查看磁盘IO和网络指标必要时迁移数据目录到高性能存储删除镜像后磁盘不释放没有触发垃圾回收或回收任务还没跑完在Harbor界面手动触发垃圾回收并查看任务执行状态Harbor服务全部正常但页面无法访问Nginx配置或证书过期查看Nginx日志和certbot续期状态确认证书有效期从Harbor复制到远端一直失败远端认证信息不对、目标项目不存在、网络不通逐项检查复制规则里的地址、账号、项目名再在jobservice日志里看具体报错排查问题时习惯是先去jobservice的日志里找task执行记录再去core日志里找API请求相关的报错大多数情况下这两处就能把问题定位清楚。Harbor日志本身信息量比较大我一般会先按时间范围过滤再针对错误级别检索效率高很多。从Docker Registry切换到Harbor这套动作我前前后后折腾了一两个星期踩过不少坑。最深的体会是工具切换本身不难难的是你清楚自己为什么要换。Registry能解决“把镜像存起来”的问题Harbor解决的是“把镜像管起来”的问题两者定位不同适合的阶段也不同。如果你正处在仓库还在“能用”阶段、但已经隐约觉得哪里都不太顺手的状态不妨按我上面列的这几点逐一对照看看哪些是实际痛点再决定要不要动手升级。