在容器化落地走到一定规模之后,几乎每个团队都会遇到一个绕不开的基础设施问题:镜像仓库。项目标题就四个字“docker镜像仓库”,但真正动手自建过的人都知道,这四个字背后藏着选型、存储、安全、性能、运维一长串的决策链。这篇就结合我自己的实操经历,把自建镜像仓库从选型到落地的完整路径拆开讲透,包括那些文档里不会写、只有踩过坑才懂的细节。
1. 内容整体设计与思路拆解
很多东西都是“用的时候觉得简单,自己搭的时候才发现全是细节”。镜像仓库就是典型。开发的时候docker pull拉个镜像、docker push推个包,感觉就是一个“上传下载工具”。但你一旦要自己在内网搭一套,或者要把镜像分发链路管起来,立刻就会面对一堆之前根本不会想的问题:Registry和Harbor到底选哪个?存储用本地目录还是对象存储?HTTPS证书怎么配才能让所有节点信任?镜像越堆越多,磁盘空间怎么管?多个开发同时推送,仓库会不会成瓶颈?
这一篇我把整个设计思路拆成几个层次来讲。
先明确镜像仓库在整个容器化体系里的定位。官方对镜像仓库的定义是“用于存储和分发容器镜像的服务”,听起来很简单,但它在实际链路里承担的是“制品资产中心”的角色。你的应用最终以镜像形式运行,镜像的版本、签名、漏洞状态、分发速度,全部由仓库这一层决定。它不直接跑业务,但一旦它出问题,整个CI/CD流水线全部卡死,线上扩容也做不了。所以自建仓库的第一原则不是“能用”,而是“稳”。
其次要理解镜像仓库的访问模型。Docker引擎通过docker pull和docker push与仓库交互,底层走的是Registry HTTP API V2协议。这个协议规定了镜像层(layer)的上传下载、清单(manifest)的校验、以及认证授权机制。理解这个模型很重要,因为很多配置问题——比如推送失败报denied、拉取时报manifest unknown——本质上都是没有吃透协议细节。
然后就是影响范围的分析。一套镜像仓库覆盖的不只是“存储镜像”这一个功能点,它会关联到:
- 开发环境:开发人员需要拉取基础镜像和中间件镜像;
- CI/CD流水线:构建产物要推送进仓库,部署时要从仓库拉取;
- 生产环境:集群节点扩容时需要从仓库拉取应用镜像;
- 安全合规:镜像需要做漏洞扫描、签名校验、访问审计;
- 容灾备份:镜像仓库的数据量通常很大,备份策略、异地同步都要考虑。
所以我在做方案的时候,习惯先画一条“镜像流转链路”:构建机 → 仓库 → 运行节点,然后把每个环节的需求和风险点标出来。仓库本身是链路的核心,但真正决定它好不好用的,是它周边的这些配套能力。
基于这样的思路,下面的章节按“选型对比 → 部署实操 → 安全加固 → 性能调优 → 故障排查”的顺序展开。每个部分都尽量写清楚“为什么这么做”,而不是只丢命令给你抄。
2. 镜像仓库方案选型与核心机制解析
选型是自建仓库的第一道坑。网上教程一搜一大把,但很多只告诉你“用Harbor,功能全”,不讲背后的取舍逻辑。我在实际项目里两种方案都用过,把它们放在一起对比,可以帮助你更清楚地知道自己需要什么。
2.1 Registry与Harbor怎么选:两种方案的适用场景差异
先说结论:如果只是个人开发机、小团队内部试用,或者只需要一个“能push能pull”的仓库,那么官方Registry就足够;如果仓库要服务多条业务线、有多个项目团队、有权限管控和审计需求,那么Harbor是更稳妥的选择。
用一张表可以看得很清楚:
| 对比维度 | 官方Registry | Harbor |
|---|---|---|
| 部署复杂度 | 单容器即可,配置简单 | 依赖较多,需要数据库和对象存储(或PVC) |
| 镜像管理UI | 无 | 有完整的Web界面 |
| 多租户能力 | 弱,主要靠路径区分 | 项目(Project)级别的隔离 |
| 访问控制 | 基于简单认证 | 基于角色的访问控制(RBAC) |
| 镜像复制 | 需自行配置 | 内置多向复制策略 |
| 漏洞扫描 | 无 | 内置(基于Trivy等引擎) |
| 适合场景 | 轻量使用、单一团队 | 企业级交付、多团队协作 |
这里有一个容易被忽略的点:Registry并不是没有项目概念,而是非常弱。Registry默认通过镜像名称的前缀来区分“命名空间”,比如192.168.1.10:5000/team-a/app和192.168.1.10:5000/team-b/app,它们在物理上共用一个存储后端,没有权限隔离的天然边界。只要你有推送权限,理论上可以推到任何一个命名空间下。而Harbor的Project是真正独立的,每个项目有独立的成员、配额、策略,镜像仓库之间互不可见,这正好符合企业里“多个项目团队共用一套仓库”的需求。
再考虑运维成本。Harbor虽然功能全,但生产部署通常要挂PostgreSQL数据库和Redis,镜像数据要落到对象存储或者足够大的持久化卷上。这意味着你除了维护仓库容器本身,还要维护它的依赖组件。而Registry单容器加一个数据目录就能跑起来,升级迁移都简单很多。所以“功能全”的背面是“依赖多”,这个权衡一定要想清楚再做决定。
提示:如果团队规模在10人以内,仓库只服务一两条产品线,我建议直接用Registry,把精力省下来去做镜像构建和部署流程的优化。功能选型不是越重越好,够用、稳定、好维护才是自建仓库的核心原则。
2.2 镜像分层存储的原理:为什么删除镜像不一定释放空间
这是很多人第一次接触仓库存储时懵掉的地方。在Registry里,镜像不是“一个文件”,而是一个清单文件加一堆层文件。docker pull的时候,Docker引擎会先获取manifest,然后根据manifest里的层信息,逐层从仓库拉取数据。多个镜像之间可以共享层,比如两个服务的基础镜像都是同一个版本的某个基础系统镜像,那么这两个镜像在仓库里存储时,重复的基础层只会保存一份。
这个设计的存储模型有个直接后果:当你通过UI或者API删除一个镜像时,如果某些层还被其他镜像引用,这些层不会真的被删除。只有当一个层不再被任何镜像引用时,它才变成“悬空层”,等待垃圾回收机制来清理。这就是“删了镜像但磁盘空间没降”的根本原因。
Registry的垃圾回收需要手动触发,而且官方文档明确警告过:GC必须在停止Registry服务的状态下执行,否则可能导致数据损坏。我在实际维护中遇到过这种情况:仓库存储占用到了90%,我在线删了几十个旧镜像tag,结果空间一点没释放,后来查了文档才发现GC的触发条件。如果你用的是Harbor,它在删除镜像时会帮你在后台处理垃圾回收,体验会好很多,但底层逻辑是一样的。
理解这个原理对容量规划非常重要。自建仓库时,不要以为“删了旧镜像就腾出空间了”,一定要在存储容量上提前留出余量,并且建立周期性的镜像清理和GC机制。
2.3 认证与授权机制:从裸奔到Token认证
默认启动的Registry没有任何认证机制,任何人只要能访问你的端口,就能push和pull。这在生产环境是不可接受的。Registry支持的认证方案是接入一个认证服务,由认证服务发放Bearer Token,Docker引擎拿到Token后用它访问仓库API。
流程大致是这样的:
- Docker引擎对仓库发起操作请求;
- 仓库返回
401 Unauthorized,并在响应头里带上WWW-Authenticate: Bearer realm="https://auth.example.com/token",service="registry.example.com"; - Docker引擎拿着你配置的账号密码去认证服务换取Token;
- 认证服务返回Token,同时附带该用户对哪些仓库路径有访问权限;
- Docker引擎再带着Token重新请求仓库,仓库校验Token通过后放行。
这个设计把“认证”和“授权”分开了:认证服务负责确认“你是谁”,仓库根据Token里的权限声明决定“你能干什么”。我在自建方案里采用的是nginx做反向代理加基础认证,配合Registry的Token认证配置。如果团队规模再大一点,直接用Harbor自带的认证体系会更省心,它内部已经把这个流程完整实现了。
注意:无论是Registry还是Harbor,只要走HTTP协议,账号密码和Token都是明文传输的。生产环境必须启用HTTPS,否则相当于把仓库的访问凭证裸奔在网络上,被别人扫到端口后可以直接拖走你的镜像数据。
3. 环境准备与部署实操记录
理论讲清楚之后,实际操作部分就直接上干货。下面这套部署流程是基于我实际搭建的一套私有仓库环境整理的:两台Linux主机,一台部署仓库服务,一台作为验证客户端。操作系统是Ubuntu Server,Docker引擎版本和Registry版本都保持更新到当前稳定版。
3.1 部署前的基础环境准备
部署前先确认三件事:域名、证书、存储。
域名我建议不要用IP地址。虽然Docker允许你在配置文件里把某个IP加为“不安全仓库”来绕过HTTPS,但这样做的安全风险很高,而且一旦涉及多台机器、多个环境,IP直连的维护成本会越来越大。正确做法是准备一个内部域名,比如registry.example.internal,然后在所有需要访问仓库的机器上做好域名解析。
证书方面,如果有企业内部的CA服务,就直接申请内部CA签发的证书;如果没有内部CA,也可以用自签名证书,但所有客户端节点都需要信任这个根证书。我在部署时曾经偷懒用自签名证书又不想让每台机器都配信任,结果客户端拉镜像时报的是x509: certificate signed by unknown authority,排查了很久才意识到问题根源。
存储方面,我先用本地磁盘做了第一版部署,验证整个流程通了之后再迁移到对象存储。如果你一开始就打算用对象存储,需要在Registry的配置里指定storage为s3类型(或者其他兼容S3的存储),并配置好相关的Bucket和访问密钥。本地存储做起来方便,但后续容量扩展要迁移数据,所以条件允许的话推荐一步到位上对象存储。
3.2 Registry单节点部署与HTTPS配置
Registry本身部署很简单,核心在于配置文件的编写。下面是我用的配置文件,里面涵盖了存储路径、HTTP配置、以及认证相关的基本项。
version: 0.1 log: level: info storage: filesystem: rootdirectory: /var/lib/registry http: addr: :5000 headers: X-Content-Type-Options: [nosniff] tls: certificate: /certs/registry.example.internal.crt key: /certs/registry.example.internal.key auth: token: realm: https://auth.example.internal/token service: registry.example.internal issuer: registry-token-issuer rootcertbundle: /certs/auth-server-cert.pem health: storagedriver: enabled: true interval: 10s threshold: 3配置文件里有几个关键点要说明。
log.level在生产环境我建议设为info而不是debug。debug日志量非常大,排查问题时会刷屏,正常情况下没必要开。只有出现协议层面的疑难问题,我才临时切到debug定位。
storage.filesystem.rootdirectory就是镜像数据的存放根目录。前面讲过,镜像在这里不是简单的一个文件一个镜像,而是一堆blob文件加manifest文件,所以这个目录的容量规划要按镜像总体积的1.5-2倍预留。
http.tls配置了证书和私钥路径。证书和私钥文件要挂载进容器,我习惯放在宿主机的一个固定目录下统一管理。这里有一个坑:证书文件在第一次启动时会读取,如果之后你更换了证书但没有重启容器,Registry会继续用旧证书,导致客户端连接异常。所以换证书后一定要记得重启服务。
启动命令如下:
docker run -d \ --name registry \ --restart=always \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ -v /certs:/certs \ -v /etc/registry/config.yml:/etc/docker/registry/config.yml \ registry:2挂载三个目录分别对应数据、证书、配置文件。--restart=always保证宿主机重启后仓库能自动拉起。生产环境我不会忘记配日志轮转,否则Registry的访问日志会无限增长。可以将容器日志交给Docker的log driver管理,也可以直接把日志挂到宿主机用logrotate处理,根据自己的习惯来。
部署完成后,先在宿主机本机做自检:
curl -k https://registry.example.internal:5000/v2/返回{}(空JSON对象)说明Registry服务基本正常。如果返回的是401,也属于正常现象,因为配置了认证后,未带Token的访问会被拒绝。/v2/这个端点本身就是探测用的,能返回任何响应都说明服务在运行。
3.3 客户端配置与镜像推送拉取验证
客户端配置的核心是让Docker引擎信任仓库的证书,并完成登录操作。
如果你用的是内部CA签发的证书,需要在客户端机器上把这个内部CA根证书添加到系统信任列表。Debian/Ubuntu系列的命令是:
sudo cp ca.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates如果你为了省事用了自签名证书,那就需要把这个自签名证书本身加到信任列表,效果一样。但这里要注意:证书信任配置完成后,需要重启Docker引擎才能生效:
sudo systemctl restart docker不重启的话,Docker引擎的HTTP客户端可能还在使用旧的CA证书列表,你拉镜像时照样会报证书错误。我遇到过好几次这样的问题,刚配好证书时拉取失败,一查才知道是没重启Docker。
然后执行登录:
docker login registry.example.internal:5000输入账号密码。登录成功之后,Docker引擎会把凭证保存在配置目录下。接下来做一次镜像推送验证:
docker tag nginx:latest registry.example.internal:5000/demo/nginx:v1 docker push registry.example.internal:5000/demo/nginx:v1推送成功后,在另一台机器上执行拉取:
docker pull registry.example.internal:5000/demo/nginx:v1这一套流程走通,说明Registry的部署、证书、认证都正常。如果走到这里就结束,那么这个仓库已经可以服务日常开发了。
4. 安全加固与访问控制实践
仓库能用和仓库好用之间,隔着安全加固这一层。镜像仓库存放的是你的应用制品,一旦被篡改,线上跑的可能就是别人塞进来的恶意代码。下面这几项是我认为自建仓库必须做的加固措施。
4.1 网络访问控制与端口暴露策略
第一步就是别把仓库直接暴露到公网。Registry默认监听5000端口,这个端口在公网上是扫描器的重点目标。我见过的安全事件里,有不少就是开发图方便把仓库端口映射到了公网,结果被扫描到后直接匿名pull走了所有镜像,甚至被匿名push了恶意镜像。
正确的网络方案是:仓库服务只在内网或私有网络环境内暴露,通过防火墙规则限制来源IP,只允许构建机和运行节点所在的网段访问。如果有跨地域分发的需求,优先用仓库内置的复制功能做异地同步,而不是直接开放公网访问。
注意:即使有HTTPS加密传输,也不能完全依赖加密来保护仓库。加密防的是“窃听”和“篡改”,防不了“未授权访问”。访问控制必须在认证层和应用层同时做,加密只是保护传输过程。
4.2 HTTPS证书管理细节与常见坑
证书管理是安全加固里最容易被搞砸的部分。我总结几个实际踩过的坑:
第一,证书的Common Name或Subject Alternative Name必须与仓库域名一致。Docker客户端在建立TLS连接时会校验主机名,如果证书里没有这个域名,就算证书受信任也会报错。自签名证书生成时,很多人忘记加subjectAltName,导致客户端报x509: cannot validate certificate for x.x.x.x because it doesn't contain any IP SANs。
第二,证书有效期要纳入运维提醒。内部CA签发的证书通常1年或者2年有效,过期之后客户端会直接拒绝连接。我建议设置一个证书到期提醒,一般是提前30天提醒,避免在业务高峰期突然发现仓库不可用。
第三,证书更新后要重启所有依赖仓库的客户端和服务端。Registry容器要重启以加载新证书,各台机器上的Docker引擎如果做的是双向TLS校验,也要确保本地信任库更新。
4.3 访问控制策略设计:从弱口令到角色权限
用Registry加Token认证的方案,可以实现基于用户的权限控制。Token认证服务会返回一个JWT格式的Token,里面包含用户对仓库路径的访问权限声明。比如一个用户只有registry.example.internal:5000/project-a/*的pull权限,那么它无法push到这个项目下,也看不到其他项目的内容。
这套方案在权限粒度上足够用,但管理多个用户和权限时没有UI,全靠维护配置文件,对运维人员来说体验一般。如果团队规模大、项目多,我建议直接上Harbor,用它的RBAC体系来管理。Harbor里可以建多个项目,每个项目下有多个成员角色,从“访客”到“管理员”分了多个等级,权限配置在Web界面点几下就能完成。
无论用哪种方案,账号密码的管理都建议接入统一的认证源(比如企业的LDAP或统一认证系统),避免在多个系统里维护不同的密码。否则你又要面临“开发离职了但是他的仓库账号没删”这类遗留风险。
5. 性能调优与容量管理实战
仓库跑起来容易,跑得又快又稳才是本事。镜像仓库的性能调优涉及存储后端选择、GC策略、网络传输、缓存等多个维度。
5.1 存储后端的选型:本地盘、网络盘还是对象存储
存储后端的选择很大程度上决定了仓库的性能上限和运维模式。
本地磁盘方案(filesystem)性能最好,部署最简单。但它的问题在于“单机绑定”——数据落在特定机器的磁盘上,迁移、扩容、备份都比较麻烦。如果这台机器宕机,仓库数据可能就没了。
网络存储方案(NFS或者类似网络文件系统)把数据从单机剥离出来,多台机器可以共享同一份数据,实现高可用。但网络存储的延迟比本地盘高,高并发时可能成为瓶颈。我在实际测试中,纯本地盘方案在频繁push大量小层文件时依然能保持不错的吞吐,NFS方案在高并发场景下则会出现延迟抖动。
对象存储方案(S3或者兼容S3的对象存储)是目前生产环境我最推荐的方案。它是水平扩展的,容量几乎没有上限,数据可靠性由对象存储服务保证,而且对接CDN转发后能大幅提升跨区域拉取速度。代价是引入额外的依赖成本,配置也稍微复杂。
| 存储方案 | 性能 | 容量扩展 | 运维复杂度 | 推荐场景 |
|---|---|---|---|---|
| 本地磁盘 | 高 | 需要人工迁移 | 低 | 单机、测试环境 |
| 网络文件系统 | 中 | 依赖存储设备 | 中 | 小规模高可用 |
| 对象存储 | 中高 | 近乎无限 | 中 | 生产环境推荐 |
一个容易被忽略的点:Registry在读取层文件时的IO模型是“小文件随机读”。一个镜像常常有几十个层,每个层可能只有几MB甚至几百KB,在高并发拉取时,存储系统对随机读小文件的性能要求很高。所以如果你的对象存储服务在小对象读写的性能上表现一般,可以考虑加一层缓存,把大镜像的“热点层”缓存到本地的SSD上。
5.2 垃圾回收与存储空间治理策略
前面讲过,删除镜像不释放空间是正常现象,因为可能还有共享层。所以存储空间的治理必须建立一套相对完整的策略。
最基础的是周期性GC。Registry的官方GC是在服务停止状态下执行的,命令如下:
docker stop registry docker run --rm \ -v /data/registry:/var/lib/registry \ registry:2 garbage-collect /etc/docker/registry/config.yml docker start registry要容忍GC期间仓库短暂不可用,而且GC过程本身比较耗时,镜像多了可能要跑很久。所以GC一般安排在业务低峰期执行,同时要评估“冗余层”产生的速度。
在用Harbor的时候,GC是可以在Web界面里触发的,Harbor会在后台执行GC而不需要停服务。但即便如此,我也建议定期检查GC日志,确认空间确实被释放,而不是GC失败后默默堆积。
如果镜像流失控,直接扩容存储也不是长久之计。我会在仓库上层加一个镜像清理策略:规定每个镜像最多保留多少个版本tag,或者只保留最近N天的构建产物。这个策略可以在CI/CD流水线里做,也可以在Harbor里配置清理规则。定期清理“陈旧tag”结合GC,才能让存储容量保持在一个合理水位。
提示:我在某次容量告警排查中,发现仓库里存了超过300个几乎相同的构建tag,每次CI构建都会生成新的镜像,但没有清理机制,最终把一整块500GB的数据盘吃满了。这类问题不是存储性能问题,而是流程治理问题——镜像仓库也要有“生命周期管理”。
5.3 大镜像加速:分层下载与并发拉取优化
大镜像的拉取速度差,很多时候不是网络带宽的问题,而是层文件下载的并发与顺序问题。其实Registry协议本身是支持并发拉取多个层的,Docker引擎在pull时也会并行下载。但有两个环节会成为瓶颈。
第一是TLS握手和连接复用。如果仓库前面没有加负载均衡,高并发拉取时单节点Redis的握手开销、文件句柄数会拖慢整体速度。我建议在仓库前面加一层反向代理(nginx或者HAProxy),开启长连接复用,减少握手次数。
第二是存储随机读的性能瓶颈。如果在高并发拉取时大量读层文件,本地盘可能需要换成更高IOPS的SSD,或者干脆走对象存储加缓存。反过来讲,如果团队使用的镜像普遍较大,可以考虑把CI/CD的构建流程优化成“小镜像策略”——使用更精简的基础镜像、合并RUN层、清理构建缓存,从源头上减小镜像体积。层数越少、层大小越小,镜像分发的压力就越小,仓库的并发能力自然就上来了。
5.4 高可用与异地复制:从单点到多活
生产环境的仓库如果是单点,一旦机器挂掉,整个发布链路都会停摆。所以高可用方案是一定要做的。
最简单的高可用模式是“主备双机共享存储”。两台机器同时部署Registry,前面加一个负载均衡,后端共享同一份存储(对象存储或者网络存储)。一台机器挂掉,负载均衡会把流量切到另一台,用户无感知。这种模式下,Registry本身是无状态的,所有状态都在存储层。
跨地域的场景则需要镜像复制。Harbor内置了复制规则,可以把一个项目的镜像自动复制到另一个地域的仓库。Registry官方也有相关的复制方案,但配置相对复杂。我做异地分发时用的是Harbor的复制功能:源仓库把镜像推送到目标仓库,支持同步模式和异步模式,异步模式的抗故障能力更强。
高可用方案的代价是架构复杂度的上升。并不是所有场景都需要多活,如果团队只有一套测试环境,单台Registry加定期备份可能已经足够。做高可用之前,先明确宕机的真实影响范围和发生概率,再决定投入多少成本。
6. 常见问题与排查技巧实录
最后这部分是“踩坑实录”。我把实际维护中遇到过的、有代表性的一些问题整理成速查表,并附上排查思路。这些问题在网上文档里基本找不到现成的答案,都是要靠日志和协议细节一个个推出来的。
6.1 常见错误速查表
| 错误现象 | 报错信息 | 根因方向 | 解决动作 |
|---|---|---|---|
| 拉取时报证书错误 | x509: certificate signed by unknown authority | 客户端不信任仓库证书 | 导入根证书并重启Docker |
| 推送报权限拒绝 | denied: requested access to the resource is denied | Token权限不足或未登录 | 检查账号在目标项目下的权限 |
| 拉取报manifest未知 | manifest unknown | 镜像tag不存在或已被清理 | 列出仓库tag确认镜像实际存在 |
| 删除镜像后空间未释放 | 无报错 | 层文件被其他镜像引用 | 执行GC或使用Harbor清理规则 |
| 登录成功但push失败 | authentication required | 认证服务与仓库配置不一致 | 检查Token服务的realm和service配置 |
| 大镜像拉取超时 | net/http: TLS handshake timeout | 网络不稳定或仓库并发过高 | 调整仓库连接数、优化网络质量、启用缓存 |
6.2 排查流程与日志分析实战
遇到仓库问题,我的排查习惯是按“客户端 → 网络 → 仓库 → 存储”的链路逐层定位。
先在客户端复现报错。docker pull的报错信息往往包含了最关键的线索,比如TLS错误、HTTP状态码、manifest错误等。然后用curl直接请求仓库API来绕过Docker引擎的干扰:
curl -k https://registry.example.internal:5000/v2/demo/nginx/tags/list这一步能判断仓库服务本身是否正常。如果curl也报错,说明问题在网络层或者服务层;如果curl正常但docker不行,说明问题在Docker客户端的配置。
再查日志。Registry的日志输出到stdout,用docker logs查看:
docker logs registry --tail 200重点看HTTP状态码和错误信息。500错误通常是仓库后端存储异常;401错误是认证问题;404错误可能是镜像路径写错了,也可能是manifest在存储中丢失。
最后如果怀疑存储层有问题,检查存储挂载的容量、权限和文件完整性。比如对象存储Bucket的访问密钥失效、本地磁盘被写满、目录权限被改动,都会导致仓库读写异常。
6.3 背靠背的经验:从GC失误到数据修复
有次我在掌握不够的前提下,直接在服务运行状态下执行了Registry的GC操作,结果把仓库搞到不可用。停止服务后发现镜像列表还在、但部分镜像的层文件对应关系已经被破坏,有镜像无法pull。最后是删除了出问题的镜像tag,用CI流水线重新构建镜像才恢复。这件事给我的教训是:GC这类破坏性操作,一定要先备份元数据,再在完全停服的状态下执行,不要抱侥幸心理。
还有一次遇到的情况是push小镜像成功,但大镜像推送持续失败。排查发现是nginx的client_max_body_size默认限制是1MB,需要调大,但镜像层文件动辄几百MB。所有的大文件上传都撞上了这个限制。后来我在反向代理的配置里加了client_max_body_size 0;(表示不限制大小),问题立刻解决。
注意:如果你在镜像仓库前面挂了nginx或其它反向代理,一定要检查body大小限制和proxy超时时间。默认配置对镜像仓库来说往往不够,尤其是大镜像的推送场景,nginx默认的60秒超时很可能直接掐掉上传连接。
写在最后的实操体会
这一套自建镜像仓库的流程,前前后后我反复部署过多次。不同阶段的方案选型会随着团队规模、镜像体量、安全要求的变化而不断调整。最开始可能是一个单容器Registry,跑在开发服务器上;后来慢慢有了正式环境,有了多项目团队的协作,才逐步过渡到Harbor加对象存储的架构。
我个人最大的体会是:镜像仓库的本质是一个“制品资产管理平台”,它的价值不在仓库本身,而在于它能把镜像的构建、分发、使用、回收这一整条链路管理起来。很多人只关注部署,忽略镜像生命周期治理,结果仓库越用越乱,最后沦为一堆没人知道是什么的tag堆。
另外一个值得重视的点是备份。镜像仓库的数据量通常很大,全量备份成本很高。但至少要对元数据(哪些镜像存在、哪些tag指向哪些manifest、权限配置)做定期备份,这样即使存储发生故障,也能快速重建镜像列表和权限体系,再配合重新构建或从上游仓库同步,把损失控制在可接受的范围内。
希望这篇基于实操的记录,能帮正在自建或计划自建镜像仓库的朋友少踩一些坑。如果你在部署过程中遇到本文没覆盖到的报错,不妨先从HTTP状态码和Registry日志入手,十有八九能定位到方向。