1. 自建私有镜像仓库这件事到底值不值得做先说结论如果你只是想把本地 build 出来的镜像推到一个自己能管的地方让几台机器都能拉那自建私有 docker 仓库这件事一个下午就能跑通但如果你想让它在半年后还稳定可用、磁盘不被撑爆、同事能安全接入那就得把认证、证书、存储回收、备份这几件事一起想明白。我见过太多人卡在“能 push 了”这一步就收工结果三个月后docker push开始报 500登录服务器一看磁盘 100%清理时还发现镜像删了空间没回来。私有 docker 仓库Docker Registry本质上就是一个实现了 OCI Distribution 规范的 HTTP 服务它负责两件事接收镜像层blob和清单manifest并落盘存储以及按请求把对应的层和清单吐回去。它不负责构建、不负责编排甚至连用户体系都很简陋——这也是为什么很多人一开始觉得它“简陋得不像个正经服务”。但正是这种简单让它在学习和小团队场景里性价比极高一个容器、一个配置文件、一个数据目录剩下的全是标准 HTTP 行为出问题的时候排查路径非常短。这篇文章面向三类人刚接触容器、想搞明白镜像到底存在哪儿的初学者手上有几台云主机、需要一个内部镜像中转站的小团队以及已经跑起来但被 GC、证书、413 之类问题折磨过、想系统梳理一遍的运维同学。我会从一个空白的云主机开始把选型、容量估算、配置拆解、证书方案、存储回收、故障排查完整走一遍中间会把我踩过的坑和判断依据都写清楚。你按顺序看下来基本可以照着抄如果你只想解决某一个具体报错直接跳到第 8 节的速查表也行。2. 方案选型Registry、Harbor、Nexus 各自适合谁2.1 三个方案的定位差异很多教程一上来就推 Harbor理由是“功能全”。功能全是对的但对学习场景来说Harbor 会一次性把 PostgreSQL、Redis、Nginx、Core、Jobservice、Registry 六个容器塞给你你还没搞明白 manifest 和 blob 的关系就先陷进了容器编排和依赖排查里。我的建议是先分清这三个东西各自解决什么问题方案核心定位部署复杂度资源占用适合谁Distribution (Registry v2)纯粹的镜像存储服务OCI 规范的参考实现低单容器200MB 内存起学习原理、单团队内部中转Harbor在 Registry 之上加认证、RBAC、UI、复制、扫描、保留策略中高需编排2GB 内存起多项目多租户、需要审计和 UINexus / 其他制品库统一管理镜像、Maven、npm、PyPI 等多种制品中1GB 内存起已有制品管理需求镜像只是其中一类选择的关键判断点是你要的是“一个能放镜像的地方”还是“一个镜像治理平台”。前者用 Registry后者用 Harbor。别为了一个团队的十几个镜像去上 Harbor运维成本会远超收益反过来如果公司有十几个项目组、需要按项目隔离权限、需要知道谁在什么时候推了什么那 Registry 的 basic auth 完全不够用硬撑下去就是给自己挖坑。还有一个容易被忽略的点Harbor 本身也是通过内置的 Registry 组件来存镜像的你学明白 Registry 的存储布局和 GC 逻辑将来上 Harbor 遇到磁盘不释放的问题一样能定位。所以从 Registry 入手不是“退而求其次”而是打地基。2.2 学习场景的选型结论与版本锁定我自己的选择是 Distribution 官方镜像。理由有三条第一它的配置文件就是一个 YAML每个字段在干什么一查就知道非常适合搞清楚“认证在哪一层、TLS 在哪一层、存储驱动怎么选”第二它的错误信息虽然简陋但足够真实你能看到manifest unknown、blob unknown这种原始语义而不是被上层包装过的提示第三它的数据目录结构是标准的docker/registry/v2/你ls进去就能看到blobs/sha256/和repositories/的对应关系这种直观感受是任何文档都给不了的。版本方面建议锁定具体的小版本号而不是用latest。Registry 的 2.x 系列迭代很慢但latest意味着你某天重启容器时可能悄悄换了个版本行为变了你还以为是配置写错了。我在测试环境就因为latest从 2.8.2 漂到 2.8.3--delete-untagged的默认行为变化导致一次清理多删了东西虽然不致命但在生产环境这就是事故。所以启动命令里我写死registry:2.8.3。再补一句存储驱动的选择Registry 支持 filesystem、s3、azure、gcs 等多种后端。云主机本地盘用 filesystem 最省事但要清楚它的短板——扩容只能换盘或者迁移而且本地盘一旦损坏数据就没了。如果后面要上多副本或者担心数据可靠性就该切到对象存储后端配置里换成s3段再填 endpoint、bucket、accesskey 即可镜像本身的组织方式不变。学习阶段先用 filesystem把逻辑跑通别一开始就引入对象存储的鉴权和网络问题。3. 云主机与环境准备的几个关键决定3.1 规格与磁盘先算一笔容量账很多人开云主机是拍脑袋选配置2 核 4G 随手一点。Registry 本身很轻但它的负载特征比较特殊push 的时候是持续的大流量写入pull 的时候是大量的并发读而内存占用主要取决于并发连接数而不是镜像大小。我的经验值是单实例 2 核 2G 足以支撑几十人的团队日常使用但如果你的 CI 流水线会并行 build 十几个镜像同时推那 2 核就容易被 IO 和网络软中断吃满建议 4 核 4G 起步。磁盘才是真正要算的。这里给一个可以直接套的估算方法先统计镜像的基础体积。一个精简的 alpine 基础镜像约 7MB一个带 JDK 的运行时镜像 200~400MB一个带完整构建链的镜像可能 1GB 以上。再乘上 tag 数量。注意 Docker 的分层是共享的同一个仓库的不同 tag 如果基于同一个基础层实际只存一份。所以不能简单用“镜像数 × 体积”而要看“独立层数 × 层体积”。加上历史残留。这是最容易被低估的部分频繁构建会产生大量悬空dangling的中间层如果你不主动清理一个月能堆出几十 GB。一个比较务实的算法是预估活跃镜像实际体积为 V磁盘至少给 2.5V。多出来的 1.5V 里1V 是历史残留和未 GC 的删除对象0.5V 是 GC 过程中的临时占用和系统盘余量。举例来说你预计常年保留的有效镜像加起来 30GB那磁盘就开 80GB别开 40GB 然后每周去救火。另外强烈建议数据目录单独挂一块云盘不要和系统盘混在一起。好处有两个——扩容时可以直接在线扩容云盘再resize2fs不用重建实例出问题时可以卸载云盘挂到另一台机器上抢救数据。系统盘做这个操作会麻烦得多。3.2 网络、安全组和域名的落地网络这块的核心原则只有一条仓库端口不要直接对公网开放。Docker Registry 默认走 HTTP 无加密basic auth 的密码在网络上基本等同于明文公网暴露等于把镜像和凭据一起送出去。正确做法是把仓库放在私有网络里只对本机的内网网段或者内网 IP 开放端口。具体到安全组规则我一般这样配入方向只允许内网网段访问 443或者你映射的其他端口源地址写具体网段不要图省事写0.0.0.0/0。入方向SSH 端口只对自己常用的出口 IP 开放别全开。出方向默认全通方便拉基础镜像和更新证书。如果确实需要公网访问比如异地办公、外部 CI 拉镜像那就必须配好 HTTPS并且考虑加一层反向代理做限流和访问日志。这部分我在第 5 节详细讲。域名这块学习阶段完全可以用内网 DNS 或者直接改 hosts但我还是建议走正常 DNS 解析给自己一个像registry.example.com这样的域名。原因很实际Docker 对仓库地址的处理是以域名为单位的用 IP 访问虽然能跑但证书的 CN/SAN 要对 IP 做特殊处理镜像路径里写着 IP 也不好看将来换机器还要改所有客户端的 tag 和配置。域名 内网解析 自签证书是学习阶段最省心又最接近真实环境的组合。4. 把 Registry 跑起来配置逐项拆解4.1 目录规划与 config.yml 每一段在管什么先建目录这一步看着琐碎但很关键因为 Registry 容器是无状态的所有需要持久化的东西都必须挂出来mkdir -p /data/registry/{config,certs,auth,data}四个目录的职责分别是config放config.ymlcerts放证书和私钥auth放 htpasswd 文件data放镜像数据。分开的好处是权限可以分别收紧——certs里的私钥设成 600auth设成 640data让它自己写。然后写配置文件。下面这份是我实际在用的每一段我都标注了意图version: 0.1 log: level: info fields: service: registry environment: internal storage: filesystem: rootdirectory: /var/lib/registry delete: enabled: true cache: blobdescriptor: inmemory maintenance: uploadpurging: enabled: true age: 168h interval: 24h dryrun: false http: addr: :5000 headers: X-Content-Type-Options: [nosniff] tls: certificate: /etc/registry/certs/registry.crt key: /etc/registry/certs/registry.key auth: htpasswd: realm: basic-realm path: /etc/registry/auth/htpasswd health: storagedriver: enabled: true interval: 10s threshold: 3几个字段值得单独说明。storage.delete.enabled: true是必须打开的否则你调用删除接口会直接返回 405很多人第一次遇到“删不掉”就是这个原因。但要明确它只允许你标记删除 manifest不会真的释放磁盘真正的回收要靠后面的 GC这两件事经常被混为一谈。maintenance.uploadpurging是很多人漏配的一项。它的作用是清理那些“传了一半就中断”的分片上传残留——比如网络抖动导致 push 失败一堆临时文件会留在_uploads目录里正常情况下不显眼日积月累能占掉几 GB。age: 168h意思是超过 7 天没动静的残留才清interval: 24h是每天扫一次。这个值别设太激进如果你的 CI 里有大镜像上传能持续几十分钟age设太小会把正在传的任务也干掉。http.tls段只要配了证书Registry 就自动启用 HTTPS服务端还会把 HTTP 请求重定向到 HTTPS所以端口映射上只需要暴露一个。health.storagedriver是健康检查如果你要用反向代理做存活探测这个端点会返回存储驱动的实际状态比单纯ping端口可靠得多。4.2 启动命令与参数意图先用官方 httpd 镜像生成 htpasswd。注意这里要用-B走 bcrypt别用默认的 MD5——Registry 对 bcrypt 支持良好而 MD5 在现在的算力下基本等于没加密docker run --rm --entrypoint htpasswd httpd:2.4 \ -Bbn deploy YourStrongPassw0rd /data/registry/auth/htpasswd如果你有多个账号重复执行并追加即可最后cat一眼确认格式是用户名:哈希。接着生成自签证书。这里最关键的是subjectAltName现代客户端早就不看 CN 了只认 SAN。如果你只写 CN 不写 SANdocker login会报x509: certificate relies on legacy Common Name field这个错会让很多人以为是证书本身有问题其实是缺扩展字段openssl req -x509 -nodes -newkey rsa:4096 -days 3650 \ -keyout /data/registry/certs/registry.key \ -out /data/registry/certs/registry.crt \ -subj /CNregistry.example.com \ -addext subjectAltNameDNS:registry.example.com,IP:10.0.0.12私钥权限收一下chmod 600 /data/registry/certs/registry.key。启动容器docker run -d --name registry \ --restartalways \ -p 443:5000 \ -v /data/registry/config/config.yml:/etc/registry/config.yml:ro \ -v /data/registry/certs:/etc/registry/certs:ro \ -v /data/registry/auth:/etc/registry/auth:ro \ -v /data/registry/data:/var/lib/registry \ registry:2.8.3这里有几个细节。第一配置文件用:ro挂只读防止容器内被意外改写。第二宿主机的 443 端口我没有额外再起 Nginx 反代学习阶段少一层少一个问题如果你后面要加限流和日志再上反代。第三端口映射用了 443 而不是 5000因为客户端的证书校验是跟域名绑定的如果对外暴露 5000 但证书里只有域名地址写成registry.example.com:5000就要求 SAN 里带端口实际上 SAN 不支持端口会绕进一些没必要的麻烦里直接映射到标准端口最省事。启动后先自检curl -k https://registry.example.com/v2/返回{}就说明服务活着。返回 401 也正常——那是认证生效了等你带上账号再试。4.3 第一次 push/pull 的完整验证链客户端这边先把证书信任配好。自签证书的场景下把registry.crt复制到客户端的/etc/docker/certs.d/registry.example.com/ca.crtmkdir -p /etc/docker/certs.d/registry.example.com cp registry.crt /etc/docker/certs.d/registry.example.com/ca.crt systemctl restart docker这一步很多人会奇怪“为什么把服务端证书当 CA 用”。原因是自签证书本身就是自签发的它自己既是叶子证书也是签发者所以放进certs.d作为受信根是可行的。如果你是用内部 CA 签发的那这里放的应该是 CA 的证书而不是叶子证书。然后走完整链路docker login registry.example.com -u deploy docker pull nginx:1.25-alpine docker tag nginx:1.25-alpine registry.example.com/demo/nginx:1.25 docker push registry.example.com/demo/nginx:1.25push 成功后去宿主机上看数据目录你会看到这样的结构/data/registry/data/docker/registry/v2/ ├── blobs/sha256/ # 所有实际数据按内容寻址 └── repositories/demo/nginx/ ├── _layers/ # 层到 blob 的软链接 ├── _manifests/ │ ├── revisions/sha256/... │ └── tags/1.25/current/link看清楚这个结构你就明白两件事一是镜像层是内容寻址的同一个层被多少个 tag 引用都只存一份二是 tag 只是一个指向 manifest digest 的指针文件。这就解释了一个常见困惑——为什么你改了 tag 再推磁盘涨得不多层复用了也解释了为什么删 tag 不等于删数据只是把指针去掉了。最后在另一台机器上验证拉取把证书同样配好docker pull registry.example.com/demo/nginx:1.25成功整条链路才算闭环。别只在自己本机验证跨机器验证能顺带测出网络、DNS、证书分发的问题。5. 认证与 HTTPS从能用到敢用5.1 用 htpasswd 做基本认证的最小实现Registry 自带的认证机制非常克制只支持三种htpasswd、token、silly仅测试用。silly顾名思义就是谁都能进绝对不能上生产token需要你自己实现一个认证服务复杂度陡增所以九成场景下htpasswd就是标准答案。它的工作方式是Registry 收到请求后检查Authorization: Basic头解密出用户名密码去htpasswd文件里比对哈希。这意味着密码在传输层以外是安全的有 TLS 的前提下但权限只有“能进/不能进”两档没有读和写的区分。所有账号都是全权限能 push 也能 delete。如果你需要更细的权限有两个方向。一个是上 Harbor它把权限模型做在 Registry 前面一层按项目做读写隔离。另一个是自己加反向代理在 Nginx 里按路径和方法做限制比如只允许GET/HEAD给某些来源其余方法只对内网开放。后一种做法我在小团队环境用过配置不复杂location /v2/ { limit_except GET HEAD { allow 10.0.0.0/8; deny all; } proxy_pass https://127.0.0.1:5000; client_max_body_size 0; proxy_request_buffering off; }client_max_body_size 0和proxy_request_buffering off这两个是必须的。前者关掉请求体大小限制后者关掉缓冲——否则大镜像 push 时 Nginx 会先把整个请求体收下来再转发既费内存又慢还容易触发超时。我一开始没关缓冲推一个 800MB 的镜像每次都在 60 秒左右断掉报 504查了半天才定位到这儿。5.2 证书的两条路线和客户端信任处理证书方案无非两条路自签和公签。自签的好处是零成本、内网完全够用坏处是每个客户端都要手动分发证书机器一多就是体力活。如果你有配置管理工具可以在初始化脚本里统一拉取如果没有就老老实实写一份操作文档把mkdir、cp、restart三步写清楚别指望别人自己会。公签的好处是客户端零配置坏处是需要一个真实可解析的域名而且证书续期要有自动化。用 acme 客户端做自动续期的时候要注意一个坑续期后必须让 Registry 重新加载证书。Registry 本身不会监听文件变化你换了证书文件它还用老的直到容器重启。所以续期脚本的最后一步应该是docker restart registry或者发信号让它重载否则你会遇到“证书明明更新了但浏览器还是提示过期”的诡异现象。还有一个细节值得说如果你在 Registry 前面加了反向代理那证书可以只配在代理上Registry 走内网 HTTP 就行。这时候config.yml里的http.tls段可以删掉端口回到 5000 并且只监听 127.0.0.1。这样做的好处是证书管理集中在一处缺点是少了一层就少一个排查点——出问题时你得分清是代理的证书问题还是 Registry 的问题。我的建议是学习阶段先直连把 Registry 的 TLS 行为搞明白再考虑上代理。另外提醒一句insecure-registries这个配置项能不用就不用。它的作用是让 Docker 跳过证书校验很多人图省事直接加进去结果就是中间人攻击面完全敞开。如果只是临时验证用完记得从/etc/docker/daemon.json里删掉并重启 Docker别让它长期留在配置里。6. 存储回收镜像删了空间为什么没回来6.1 delete API 与 garbage-collect 的配合这是 Registry 使用者最容易困惑的地方我把它拆成三步说清楚。第一步拿到 manifest 的 digest。注意不能直接用 tag 删除必须先查 digestcurl -k -u deploy:pass -I \ -H Accept: application/vnd.docker.distribution.manifest.v2json \ https://registry.example.com/v2/demo/nginx/manifests/1.25返回头里的Docker-Content-Digest就是你要的值。这里的Accept头不能省因为 Registry 支持多种 manifest 格式schema1、schema2、OCI不带Accept它可能返回一个你不期望的格式拿到的 digest 和实际存的对不上删除就会 404。第二步调用删除接口curl -k -u deploy:pass -X DELETE \ -H Accept: application/vnd.docker.distribution.manifest.v2json \ https://registry.example.com/v2/demo/nginx/manifests/sha256:abc123...返回 202 就表示标记成功。这时候你去看数据目录_manifests/revisions下的对应文件已经没了但blobs/sha256/里的数据一个字节都没少。这就是关键删除只是断开了引用数据还在。第三步跑 GCdocker exec registry /bin/registry garbage-collect \ --delete-untaggedtrue /etc/registry/config.ymlGC 的逻辑是遍历所有还在被引用的 manifest把它们指向的 blob 标记为“存活”然后扫描整个 blobs 目录把没有标记的删掉。所以它能回收的不只是你主动删的还包括那些因为反复构建产生的悬空层。6.2 GC 的操作窗口与风险控制GC 有几个必须知道的特性不注意就会踩坑。第一GC 期间不要有写入。官方文档明确说了GC 和 push 并发执行可能导致数据损坏——因为 GC 扫描的时候如果有新的 blob 正在上传它可能被误判为无引用而删掉结果就是镜像推上去了但拉不下来报blob unknown。所以我的做法是GC 放在低峰期并且先用--dry-run跑一遍看要删多少确认量级合理再真正执行。docker exec registry /bin/registry garbage-collect \ --dry-run --delete-untaggedtrue /etc/registry/config.yml第二GC 是一个全量扫描操作很吃 IO。数据量到几百 GB 的时候一次 GC 可能要跑十几分钟期间磁盘 IO 打满pull 会明显变慢。如果你的仓库是 CI 的关键路径建议把 GC 做成定期任务而不是临时手动跑并且提前通知。第三--delete-untagged要慎用。它的作用是删除所有没有 tag 指向的 manifest。这在“清理历史”的场景下很有用但如果你有“先推一个临时 tag 做验证验证完删 tag 保留 digest 引用”的工作流这个参数会把你的东西一起清掉。我第一次用的时候就是这么丢了一个用于回滚的镜像版本好在只是测试环境。为了避免手滑我给自己的规矩是GC 前先做一次数据目录快照或者备份。云盘快照是最省事的几十 GB 的盘做一次增量快照几分钟就完事出问题回滚一下比事后复盘强得多。这个成本很低但能救命。7. 日常运维备份、监控与流水线接入7.1 备份策略与恢复演练镜像仓库的备份有个特点它不是数据库没有事务一致性要求但文件数量巨大。一个中等规模的仓库可能有几十万个小文件用tar打包会非常慢而且恢复时解压出几万个 inode 也很痛苦。我的做法分两层。第一层是云盘快照按天做增量保留 7 天。这是最省心的方案恢复时直接回滚云盘分钟级完成。第二层是定期把整个data目录同步到对象存储用增量同步工具只传变化的文件。这一层是为了防“云盘所在可用区出问题”这种极端情况虽然概率低但配置一次就能一直用。有一件事必须做恢复演练。我自己踩过的坑是备份脚本跑了半年某次真要恢复的时候发现同步任务的排除规则写错了把blobs/sha256下的一个大子目录给排除了恢复出来是个残缺的仓库。所以每隔一两个月我会拿一份备份恢复到临时实例上随便 pull 一个镜像验证。这个动作花不了半小时但能确认你的备份是真备份而不是心理安慰。7.2 监控指标与告警阈值Registry 暴露的指标不多但有几个足够用。如果你开了 debug 端口配置里加debug: addr: :5001可以拿到 Prometheus 格式的 metrics。我最关注的三个是指标含义建议告警阈值磁盘使用率数据目录所在分区使用率超过 75% 预警85% 紧急registry_http_requests_total5xx 比例服务端错误率5 分钟内超过 1%容器重启次数进程稳定性1 小时内超过 2 次磁盘是最重要的。我给自己的规则是 75% 就动手因为从 75% 到 100% 可能只需要一次大镜像的批量推送时间而磁盘满之后 Registry 的行为很难看——它会返回 500但日志里可能只有一句no space left on device如果你没配日志收集还得进去翻容器日志。日志方面至少把容器日志接到一个能长期留存的地方。Registry 的 info 级别日志会记录每次请求的方法、路径、状态码和耗时排查“谁在什么时候推了个 2GB 的镜像把盘塞满了”这类问题时非常有用。如果不想上重型方案用 Docker 的--log-opt max-size50m --log-opt max-file5做本地轮转也比默认的无限增长强。8. 踩坑实录报错速查与排查路径8.1 连接与证书类报错这一类占了新手问题的七成以上基本都是 TLS 和地址解析的问题报错信息根因处理方式x509: certificate signed by unknown authority客户端不信任服务端证书把证书放到/etc/docker/certs.d/域名/ca.crt后重启 Dockerx509: certificate relies on legacy Common Name field证书缺 SAN 扩展重新签发加subjectAltNamehttp: server gave HTTP response to HTTPS client服务端没启 TLS客户端走 HTTPS配好证书或临时加insecure-registriesdial tcp: lookup registry.example.com: no such hostDNS 或 hosts 未配检查/etc/hosts和 DNS 配置connection refused端口映射或安全组问题先curl -k本机测再查安全组排查顺序我固定成这样先在服务端本机curl -k https://127.0.0.1:5000/v2/通了说明服务没问题再从客户端curl -kv https://registry.example.com/v2/看握手过程和证书链最后才试docker login。这个顺序能把“服务端问题 / 网络问题 / 客户端信任问题”三层快速切开比一上来就翻 Docker 日志高效得多。8.2 权限、容量与性能类报错这一类更隐蔽因为报错信息往往不指向真正的原因现象根因处理方式401 Unauthorized密码确认没输错htpasswd 文件路径不对或容器内读不到检查挂载路径和文件权限进容器cat一次405 Method Not Allowed删除时未开启storage.delete.enabled配置里打开并重启push 报413 Request Entity Too Large反向代理的 body 限制设client_max_body_size 0push 到一半中断、报超时代理开启了请求缓冲设proxy_request_buffering off删除成功但磁盘不降只标记未 GC执行 garbage-collect注意避开写入pull 报blob unknownGC 与 push 并发导致数据被误删重新 push并规范 GC 时间窗口push 极慢磁盘 IO 打满或网络带宽限制查iostat考虑换云盘类型最后补一个我自己遇到过、但在网上不容易搜到的现象某次 push 大镜像时速度从 50MB/s 突然掉到 1MB/s日志里没有任何报错最终是超时失败。查了半天发现是云盘的IOPS 配额被其他进程占满了——那台机器上同时跑了个定时任务在做全盘扫描。教训是Registry 这种 IO 密集型服务尽量不要和其他重 IO 的进程共用一台机器如果非共用不可至少用 cgroup 给容器做 IO 限制把资源边界划清楚。9. 几件我踩过之后才明白的事关于证书的自动续期我现在的做法是在续期脚本里显式加一步容器重启并且重启后立刻curl一次验证新证书的notAfter。看起来多余但有两次就是因为证书更新了服务没重载导致半夜告警。这种“看起来不会错”的环节恰恰是最容易出问题的环节。关于账号管理我建议从一开始就用“一个用途一个账号”而不是所有人共用deploy。虽然 Registry 的权限模型只有全权限一档但至少从日志里你能看出是哪个账号在推镜像。如果哪天某个账号的凭据泄露你可以直接把它从 htpasswd 里删掉而不用通知所有人改密码。关于那些“够用就行”的判断我的体会是学习阶段最忌讳一上来就搭一个生产级架构。先把 Registry 单实例跑通把 push、pull、删除、GC 这条链路完整走一遍把每个报错都实际遇到一次并解决掉你脑子里就有了一张清晰的图。等到真需要多租户和审计了再迁移到 Harbor你会发现之前积累的这些理解全部能直接复用而且遇到问题的时候你能判断是上层的问题还是底层 Registry 的问题不至于被一堆中间件绕晕。最后分享一个小习惯我每搭完一个环境会写一份“重建手册”把从空白机器到可用的所有命令按顺序记下来包括每一步的验证方式。这份东西的价值不在于下次搭建有快照就够了而在于半年后出问题时能快速定位是哪一环变了。这个过程花的时间比事后排查少得多。
阅读完成 · 觉得有帮助?