ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

registry.tar.gz 离线交付实战:先分镜像包与数据快照,再谈 docker load 与迁移验证

registry.tar.gz 离线交付实战:先分镜像包与数据快照,再谈 docker load 与迁移验证 简介registry.tar.gz是一个面向离线环境的Docker私有仓库镜像文件适用于无法连接外网、需要在内网搭建镜像仓库的运维或开发场景。它解决了内网环境难以直接拉取官方镜像的问题加载到本地Docker环境后即可快速启动Registry服务。整个包共18个文件大小25.15MB结构包括json配置文件、tar格式的镜像层数据、version版本标识与repositories仓库索引文件能直观反映Docker镜像存储的分层组织方式。目前已有436人学习下载适合需要构建离线镜像分发体系或希望研究镜像内部结构的读者。借助这份资源可以省去手动导出导入的步骤直接获得可运行的Registry镜像同时还能通过预览文件看清镜像清单、分层数据与仓库索引之间的关联为搭建企业私有镜像服务或开展容器教学实验提供清晰参照。1. registry.tar.gz 到底是什么先分清镜像包与数据快照再动手registry.tar.gz这个文件名我在离线交付项目里接过不下十次。它可能是docker save registry:2导出的一份镜像压缩包也可能是私有仓库数据目录/var/lib/registry的整体快照。前者用于在一台连不上公网镜像站的机器上把 registry 服务跑起来后者用于把整个内网仓库原样搬到新环境。两者表面上都是「一个 tar.gz」落地路径却完全相反先docker load还是先解压到数据目录搞错一步后面全盘白干。下面按实际场景拆开讲从分型、落地到验证和踩坑照着做能少走很多弯路。2. 遇包先分型识别镜像包与数据快照的三招避免全程白忙很多人拿到registry.tar.gz的第一反应是解压出来看这个动作没错但前提是先想清楚一件事这个包是被谁、用什么方式制造出来的。同样是registry.tar.gzdocker save和tar czf /var/lib/registry打出来的内容结构、解压目标、后续操作完全是两套逻辑。镜像包解开后看到的是 manifest、config、layer 文件它要喂给docker load数据快照解开后是docker/registry/v2存储目录它要放在 registry 服务的数据卷路径下。把镜像包当快照解压到数据目录registry 起得来但仓库列表是空的把快照包当镜像docker loadload 会直接报错。所以分型是第一步这一章讲三个可落地的判断动作。2.1 特征一看命名与来源先猜个七八分registry.tar.gz这个命名本身没有统一规则但制造方的意图通常有迹可循。如果对方说「我把我们仓库打包给你」那大概率是数据快照如果对方说「我导出了 registry 镜像」那基本是docker save。还有一种常见情况是 registry 服务本身跑在容器里交付方图省事在容器运行状态下直接把整个数据卷目录打包这种也算数据快照只是包内会混入运行中的临时文件恢复后要多做一步清理。从包大小也能辅助判断。registry:2这个服务镜像本身只有几十到两百多 MB如果包只有这个量级它基本上是镜像包。数据快照的大小与仓库里实际存放的镜像总量正相关从几百 MB 到几十 GB 都正常。但要注意交付方可能把一堆业务镜像也一起docker save进去单包也能到几 GB所以大小只能帮你排除极端情况不能下定论。真正可靠的判断必须看包内结构见 2.2。2.2 特征二列压缩包内容一行 tar 看清顶层目录不用解压tar tzf只列出归档内的文件名不会真正展开数据tar tzf registry.tar.gz | head -n 5这一步会直接暴露包的真实结构。以下是三种最典型的输出var/lib/registry/docker/registry/v2/repositories/... # 数据快照 manifest.json # docker save 镜像包 etc/docker/registry/config.yml # registry 源码包或配置包head -n 5取前五行就够了。如果第一层目录就是var/lib/registry或者docker/registry/v2基本可以确定是仓库数据快照如果在归档根下直接看到manifest.json那是docker save的标准产物里面各 layer 的文件名是随机哈希不是按仓库组织的。还有一种极少见的情况解出来是cmd/、docs/、Makefile那是 registry 的源码 tar 包用途是编译打包不在本文的落地范围内。为了看得更完整可以用一条 awk 列出所有顶层目录的集合tar tzf registry.tar.gz | awk -F/ {print $1} | sort -u这条命令的输出通常只有一两个目录名比 head 更可靠。顶层目录越少越说明这份归档是单一用途的产物。2.3 特征三看 _manifests 与 blobs确认快照类型和完整性数据快照真正决定能不能用的是docker/registry/v2下的两块内容blobs与repositories。blobs/sha256按内容寻址存放所有镜像层和 manifest 的原始数据子目录按 sha256 哈希前两位分桶repositories下按仓库名和 tag 记录引用关系其中_manifests存 tag 到 digest 的索引_layers存该仓库引用的层链。恢复时这两块必须同时完整存在。用 grep 过滤一下确认索引和层文件都在tar tzf registry.tar.gz | grep -E /(_manifests|_layers|blobs/sha256)/ | head -n 10如果能看到_manifests/tags/latest/index/sha256/...这类路径说明 tag 索引在能看到blobs/sha256/前两位/完整哈希说明层文件在。只有索引没有对应 blob恢复后就会遇到 manifest unknown。这个命令顺带还能帮你发现打包时是否有人在推送镜像如果看到_uploads目录说明快照里混入了未完成的上传残留恢复后这些内容不影响已有镜像但会在 GC 时被清理。如果是老环境出来的包还要注意 schema 版本。registry 2.3 之前某些仓库用 schema 1 写 manifest新版本客户端默认要求 schema 2。恢复后拉取报 manifest invalid 时优先怀疑这个处理办法是不要用太老的 registry 镜像去恢复数据新环境直接用当前稳定版registry:2拉起老数据里的兼容问题交给 registry 自身处理必要时对个别镜像重新推送覆盖。提示判断数据快照的层数只有一个标准——解压后/var/lib/registry下第一级目录必须是docker再往下是registry/v2。任何多套出来的层级都会导致仓库列表为空。3. 场景 A用离线镜像包拉起私有 Docker Registry最小配置与批量推送分型结果是镜像包时目标很明确在目标服务器上以registry:2镜像把私有仓库服务跑起来让内网机器能往里推镜像。操作顺序是先docker load再docker run顺序别反。最常见的翻车是把 load 出来的 registry 镜像当成仓库直接开始 push结果发现 registry 服务根本没启动。3.1 docker load 直接吃 gzip镜像包的正确打开方式docker load -i registry.tar.gz docker images | grep registrydocker load能自动识别 gzip 压缩格式不需要先gunzip再加载。如果这个包是用docker save registry:2导出的docker images里会出现registry:2如果交付方把业务镜像也一起 save 进来了列表里会多出一批应用镜像。load 完成后检查一下 REPOSITORY 列有时包里的镜像 tag 是none这是因为 save 时源镜像本身没有 tag。这种镜像虽然能 load 成功但后续 push 和 pull 都找不到引用名需要手动补一个docker tag 镜像ID registry:2load 只是把镜像文件放进了本机镜像层光有这个还不够。registry 是一个服务镜像它自己不持有仓库数据仓库数据全部落在容器挂载的数据卷里所以下一步是拉起容器。3.2 拉起 registry 容器数据卷、端口与两个必配的环境变量mkdir -p /data/registry docker run -d --name registry \ -p 5000:5000 \ -v /data/registry:/var/lib/registry \ -e REGISTRY_STORAGE_DELETE_ENABLEDtrue \ -e REGISTRY_HTTP_SECRET$(openssl rand -hex 32) \ --restartalways \ registry:2-v /data/registry:/var/lib/registry把宿主机目录挂给容器内 registry 的默认存储根目录这样容器删了重建镜像数据不会丢。REGISTRY_STORAGE_DELETE_ENABLEDtrue对应 registry 配置文件里的storage.delete.enabled不打开它通过 registry API 删除 manifest 或 tag 会返回 405后面没法用脚本清理仓库。REGISTRY_HTTP_SECRET用于对上传下载会话做签名容器重建后只要这个值不变客户端已有的连接状态就不会失效这里用openssl rand -hex 32生成随机值生产环境应该固定写进配置而不是每次生成。--restartalways保证 docker 服务重启后容器自动回来离线环境里服务器不常重启但这个参数仍然值得写。跑起来后用docker ps看状态如果容器不断 restart直接docker logs registry --tail 50看日志定位。最常见的两类原因数据目录权限不对或者 5000 端口被占用。3.3 内网推送前的一步insecure-registries 配置registry 服务已经监听 5000 端口本机访问没有问题但内网其他机器向它推送时绝大多数会卡在 TLS 校验上。Docker daemon 对非 localhost 的 registry 地址默认走 HTTPS而我们提供的是裸 HTTP 服务客户端会直接拒绝握手。解决办法是让所有需要访问这台仓库的机器把该地址加入 insecure-registries 列表cat /etc/docker/daemon.json EOF { insecure-registries: [192.168.1.100:5000] } EOF systemctl restart dockerdaemon.json里写的是内网仓库的 IP 加端口有域名也可以写域名。改完必须重启 docker 才生效systemctl restart docker会影响宿主机上所有容器--restartalways的容器会自动回来普通容器不会所以要挑发布窗口操作。这里有个细节insecure-registries不只是允许 HTTP也允许该地址使用自签证书对纯内网环境这是最小成本方案如果公司有统一 CA也可以给 registry 配 HTTPS 证书走正规 TLS那就完全不用改客户端的 insecure 配置。3.4 批量推镜像与仓库自检命令内网机器能访问仓库地址后先把 load 出来的业务镜像批量推到新仓库。下面这段循环适用于镜像和目标仓库在可互通环境下的情况for img in $(docker images --format {{.Repository}}:{{.Tag}} | grep -v ^registry:2$); do new_name192.168.1.100:5000/${img} docker tag ${img} ${new_name} docker push ${new_name} done循环里先给每个镜像打上带仓库地址前缀的新 tag再 push。grep -v ^registry:2$排除 registry 服务镜像本身避免它混进业务镜像列表。如果目标仓库按项目分命名空间push 之前先在仓库里建好对应目录不然会在权限和路径规范上多折腾一轮。推送结束做一次自检curl -s http://192.168.1.100:5000/v2/_catalog | jq .repositories curl -s http://192.168.1.100:5000/v2/library/myapp/tags/list | jq .tags/v2/_catalog返回仓库里所有镜像名/v2/name/tags/list返回某个镜像的 tag 列表。看到非空输出说明镜像包这条链路已经走通。4. 场景 B数据快照迁移把 /var/lib/registry 原样搬进新环境并验证数据快照的场景通常发生在仓库整体迁机房、换服务器。目标不是重新推送镜像而是让新环境里的仓库内容、tag、digest 与原来完全一致。这里有个前提源仓库的存储结构是 v2 标准布局也就是/var/lib/registry/docker/registry/v2这套目录。打包快照时最好先把源 registry 容器停下来或者挑一个确定没人推送的窗口否则_uploads目录里会残留半截上传缓存恢复后虽然不影响已有镜像但会干扰后续 GC。4.1 先看顶层路径再决定 strip 参数别把目录套两层数据快照解压最常翻车的就是路径层级问题。打包的人在源机器上执行tar czf registry.tar.gz /var/lib/registry包内会保留var/lib/registry这层前缀如果他在/var/lib下执行tar czf registry.tar.gz registry包内顶层是registry如果直接进了 v2 目录再打包顶层就是docker。差异很大所以解压前先看tar tzf registry.tar.gz | head -n 3 tar tzf registry.tar.gz | awk -F/ {print $1} | sort -u第二条命令列出包内所有顶层目录比 head 更完整。判断逻辑只有一条解压后/var/lib/registry下面必须出现docker/registry/v2。对应关系如下包内顶层 var/lib/registry/docker/registry/v2 - strip-components3 包内顶层 docker/registry/v2 - strip-components0 包内顶层 registry/docker/registry/v2 - strip-components1实际操作mkdir -p /var/lib/registry tar xzf registry.tar.gz -C /var/lib/registry --strip-components3--strip-components3去掉var/lib/registry三级目录包内剩下的docker/registry/v2正好落在/var/lib/registry下。层级数不对时目录会整体多套一层或少套一层。多套一层的典型现象是registry 容器启动后仓库列表为空但du -sh显示磁盘占用很大因为真实数据在/var/lib/registry/var/lib/registry/docker/registry/v2这个状态最容易让人误判成数据损坏。4.2 恢复数据卷属主并拉起 registry 容器解压完成后检查属主ls -ld /var/lib/registry chown -R root:root /var/lib/registryregistry 官方镜像内的进程以 root 身份运行数据目录属主设为 root 没有问题。但很多交付包是普通用户打包的tar 解包会保留原 uid/gid如果解出来属主不是 root容器内写_uploads、_manifests时会报 permission denied。不用纠结 uid 是否一致统一chown -R root:root是最省事的做法。blobs是内容寻址的只读文件改属主不破坏内容。然后拉起容器命令与场景 A 基本一致docker run -d --name registry \ -p 5000:5000 \ -v /var/lib/registry:/var/lib/registry \ -e REGISTRY_STORAGE_DELETE_ENABLEDtrue \ --restartalways \ registry:2数据卷挂载路径是/var/lib/registry容器内存储根目录也是/var/lib/registry同名纯属正常。拉起后如果容器异常退出先看docker logs registry --tail 50权限错误和配置错误都会直接写在日志里。4.3 迁移后的连通性自检catalog、tags 与 docker pull恢复完不能只看容器起来了。先查仓库列表再查具体镜像的 tag最后实际拉一个镜像。三步从外到内确认索引、manifest、blob 三条链路都没有断curl -s http://127.0.0.1:5000/v2/_catalog | jq .repositories curl -s http://127.0.0.1:5000/v2/myapp/tags/list | jq .tags docker pull 127.0.0.1:5000/myapp:latest这里故意用127.0.0.1而不是内网 IP是因为 Docker 对127.0.0.1默认走 HTTP不依赖 insecure-registries 配置能少排除一个变量。如果_catalog为空但磁盘占用很大先怀疑 strip 层级不对回到 4.1 检查目录结构如果 tags 为空但 catalog 里有仓库名说明 tag 索引在迁移中丢了需要回到源端重新打包。内网机器用192.168.1.100:5000远端拉取失败时优先检查那台机器的 daemon.json而不是回来折腾 registry。4.4 顺手跑一次 GC dry-run看看有没有悬空层数据快照是从运行中仓库打的包里面大概率混着历史遗留的悬空 blob某个 tag 早就被删了但对应的 blob 层文件一直留在磁盘上。恢复后先做一次 dry-run GC在不删文件的前提下看 registry 会认为哪些内容可以清理docker exec registry registry garbage-collect \ -m /etc/docker/registry/config.yml-m是 dry-run 模式GC 命令会遍历 repositories 的引用把没有 tag 引用的 blob 标记为 eligible for deletion但这一步不实际删文件。输出里会出现大量blob eligible for deletion的行这属于正常现象不代表迁移失败。确认输出形态符合预期后再决定要不要去掉-m真正执行。注意GC 只能在仓库空闲时执行运行中的推送任务会被中断。刚恢复的仓库建议先正常跑几天确认没有人依赖旧 tag 再清理。5. 避坑registry.tar.gz 落地路上的 5 个典型坑与排查顺序上面两条链路把包用起来了但实际交付踩过的坑比主流程还多。单独列一节每条按「现象 → 原因 → 解决」的结构写按我遇到的频率排序。5.1 内网 push 报 HTTPS 错位先看 daemon.json 再看仓库配置内网机器执行docker push 192.168.1.100:5000/myapp:v1返回Error response from daemon: Get https://192.168.1.100:5000/v2/: http: server gave HTTP response to HTTPS client。原因不在仓库而在发起推送的客户端。Docker daemon 把所有非 localhost registry 地址默认当 HTTPS而这个仓库提供的是 HTTP 明文服务。报错关键词是server gave HTTP response to HTTPS client意思是服务端回的是 HTTP 响应客户端却拿着 HTTPS 握手协议在等两边说不到一块去。解决方式是在所有需要访问仓库的机器上改/etc/docker/daemon.json把192.168.1.100:5000加入insecure-registries数组重启 docker。初次接触的人经常只改一台然后用 curl 测通了就以为全部通了curl 不走 daemon 的 TLS 校验逻辑docker push 才会真实暴露问题。5.2 解压到一半报 gzip: invalid compressed data先验哈希再重传tar xzf registry.tar.gz -C /var/lib/registry --strip-components3解到一半终端弹出gzip: stdout: Invalid compressed>gzip -t registry.tar.gz echo OK file registry.tar.gzgzip -t检查 gzip 完整性file看真实格式。如果file显示POSIX tar archive说明这包根本不是 gzip直接tar xf registry.tar.gz即可。如果是真 gzip 但gzip -t失败请源端重新计算并给出sha256sum两边哈希不一致就重传不要试第三次。对几个 GB 的包传输前后各算一次哈希是基本素养。5.3 pull 报 manifest unknowntag 索引在、blob 不在迁移后docker pull 127.0.0.1:5000/myapp:v1报manifest unknown但/v2/_catalog里能看到 myapp 这个仓库名。原因是数据快照里 repositories 下的 tag 索引指向某个 manifest digest但blobs/sha256里没有对应的 blob 文件。最常见的是打包时仓库还在运行同时有人删镜像或跑 GC快照抓到一半状态也可能是交付前手动清理了 blob 但没有同步重建索引。先确认是哪些 tag 受影响curl -s http://127.0.0.1:5000/v2/myapp/tags/list | jq .tags再查问题 tag 的 digestcurl -sI http://127.0.0.1:5000/v2/myapp/manifests/v1 \ -H Accept: application/vnd.docker.distribution.manifest.v2json \ | grep Docker-Content-Digest如果 blobs 目录里对应哈希确实缺失这个 tag 只能从源仓库重新 push 覆盖手动往_manifests里补是不可行的。预防比修复重要打包快照前把源仓库切成只读或者直接停容器再打包。5.4 删了 tag 磁盘不下降GC 永远不会自动跑在管理端删掉一批旧镜像 tagdu -sh /var/lib/registry几乎没有变化新机器磁盘还是很快被占满。原因在于 registry 的 delete 语义是删除引用不是删除文件。blob 层文件只有在 registry 执行 garbage-collect 时才会被真正移除而 registry 默认没有任何计划任务触发 GC。解决方式是手动执行docker exec registry registry garbage-collect /etc/docker/registry/config.yml执行前确认没有推送任务在跑GC 过程中仓库会短暂出现 500。另外registry 2.7 之前版本的 GC 有个玄学问题GC 后紧接着 push 的新镜像部分 blob 会被误判为悬空。所以我一般建议直接上当前稳定版 registryGC 完成后再把所有 tag 拉一遍确认引用关系没有被误删。5.5 容器秒退加 permission deniedSELinux 和属主一起查数据快照恢复后docker run的 registry 容器不断 restartdocker logs registry --tail 50里全是permission denied但查文件属主又是正常的 root。在 RHEL 系系统上这是 SELinux 强制模式导致的。挂载到容器里的宿主机目录没有container_file_t上下文容器进程即使 uid 对了SELinux 层面的访问也被拒绝。解决方式是挂载时加上:Z或:zdocker run -d --name registry \ -p 5000:5000 \ -v /var/lib/registry:/var/lib/registry:Z \ --restartalways \ registry:2:Z给目录打上私有的容器上下文适合单个容器独占:z让目录可以被多个容器共享。如果之前用普通方式跑过容器目录上下文已经被改乱先执行restorecon -Rv /var/lib/registry恢复再重新创建容器。这个坑在 Ubuntu 上几乎不会出现用 CentOS 系做迁移的同学要格外留意。6. 进阶用哈希比对做迁移验证再顺手清掉悬空 blob前面的验证回答的是「能不能用」严格一点还要回答「是不是完全一致」。数据快照的blobs是按内容寻址存放的这给我们留了一个便宜的验证手段把包在临时目录解出来和恢复目录里的文件逐个算 sha256任何差异都会在 diff 里暴露。下面这段假设包内顶层是var/lib/registry其他顶层结构把 cd 的路径换成 4.1 里实际看到的那一层即可mkdir -p /tmp/registry-check tar xzf registry.tar.gz -C /tmp/registry-check cd /tmp/registry-check/var/lib/registry find . -type f -exec sha256sum {} \; | sort /tmp/before.sha cd /var/lib/registry find . -type f -exec sha256sum {} \; | sort /tmp/after.sha diff /tmp/before.sha /tmp/after.shadiff 无输出说明这份快照里的每个文件都和源包一致有输出就要看差异落在哪一行。同一相对路径下哈希不同说明文件写入过程中被改坏路径缺失说明解压或 strip 层级有问题。注意顺序哈希比对要在跑真实 GC 之前做GC 会删掉悬空 blob导致前后文件数不一致干扰比对结论。GC 的收益可以用一个命令量化du -sh /var/lib/registry在迁移完、确认没有人在用旧 tag 之后把 5.4 的 GC 命令去掉-m真正执行一次再跑一遍du -sh长期运行的旧仓库经常能掉下来几个 GB。这是迁移后最值得做的收尾。说句血泪经验我最初交付这类包拿到手第一件事就是解压结果有一次把 docker save 包误当数据快照registry 起了一个多小时仓库列表始终是空的全部时间浪费在错误方向上。现在我的固定流程是三步先tar tzf看结构再sha256sum对哈希最后才动手。迁移类的包尤其别急着删源包等目标端稳定运行一个发布周期确认没有回退需求再清理。希望帮到你。本文还有配套的精品资源点击获取
返回列表