ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

无网络环境下Docker复杂应用离线迁移完整指南:从镜像到数据

无网络环境下Docker复杂应用离线迁移完整指南:从镜像到数据 最近接手了一个挺棘手的活儿要在完全无网络、也没有私有镜像仓库的隔离环境里把一套十几台容器、涵盖数据库、缓存、消息队列、应用前后端、定时任务的多应用复杂 Docker 环境原封不动迁到另一台新机器上。很多人一听无网络、无镜像仓库第一反应都是docker save 打成 tar 包拿过去 load 不就行了。如果真这么简单我也不至于专门拿两天时间做方案预研还把过程整理成这篇东西。真正当你面对的是多应用复杂环境时镜只不过是最基础的一环后面还有数据卷、编排文件、自定义网络、依赖启动顺序、系统权限、内核兼容性……任何一个环节漏掉迁移后在目标环境里都是连环爆炸。这篇文章我会把完整的离线迁移方案拆开揉碎来讲涵盖盘点、导出、传输、导入、重建、验证的全流程包括我实际踩过的坑和排查思路。适合几类人看一是刚接手隔离环境迁移任务的运维或 DevOps 工程师二是需要用 Docker 做国产化替换、等保合规或者灾备演练的朋友三是自己搭了一套 Docker 服务想整体搬家的小白。不管你是哪种情况这篇文章的目标只有一个——让你在无网络、无镜像仓库的前提下把复杂 Docker 环境平平稳稳搬过去。1. 项目背景与需求拆解1.1 什么场景会碰到无网络 无镜像仓库的迁移需求先说说这种需求一般出现在哪。最常见的就是内外网物理隔离或逻辑隔离的环境比如政务内网、金融生产区、军工涉密区域、能源控制区这些地方别说外网连一个内网的镜像仓库Harbor、Registry都未必给你部署。还有一些是项目交付场景客户现场就是一个孤岛你没法把镜像推到远端仓库再拉下来。另外国产化替代和信创类项目这两年也越来越多要求你在完全离线的新服务器上把整套业务跑起来这种任务听着简单真做起来全是细节。还有一种容易被忽略的场景灾备演练和机房搬迁。机房物理搬迁时业务系统要整体换个环境很多时候新的生产环境还没有建设完整的镜像仓库体系只能靠最原始的文件搬运方式把 Docker 环境恢复出来。我有一次是被客户临时拉过去做数据迁移演练演练环境都是隔离的就是从这种需求里逼出来的经验。这类需求有一个共同特点环境敏感、操作窗口短、失败容忍度低。你不能在目标环境上连外网拉镜像也不能临时搭个 Registry 慢慢推必须用一次性搬运的思路把整个运行环境搬过去。这要求你在出发之前就把所有东西盘清楚而不是到了现场再想怎么补锅。1.2 为什么docker save load解决不了全部问题我见过不少人的误区以为离线迁移等于导出镜像 - 拷贝 - 导入镜像 - 启动。如果只有一两个容器比如单跑一个 Nginx 或者一个 MySQL这套流程确实够了。但只要应用一多麻烦就来了。镜像只是运行程序的代码真正让服务跑起来的还有环境变量、挂载的数据、自定义网络、启动顺序、健康检查、各种配置文件。举个最简单的例子一套用 docker compose 编排的应用里面有个 PostgreSQL 容器数据是存在匿名卷或者具名卷里的。你只导镜像过去load 之后创建容器数据库里空荡荡的业务起来直接连不上数据这就是镜像迁移成功、应用迁移失败的典型翻车现场。再比如多个容器之间有内部网络通信它们在源环境里用的是自定义 bridge 网络指定了静态 IP 和网段。如果你直接在新环境里默认创建一个网络IP 段变了配置文件里写死的 IP 就全部失效服务之间互相访问全部超时。这些层面的细节单纯 save/load 镜像根本覆盖不到。我把这个问题拆成三层来看迁移层级对应内容不处理的后果运行时层Docker 镜像、容器配置、运行参数容器创建不出来或镜像丢失数据层数据卷、bind mount 目录、文件权限数据丢失、权限错乱、服务无法启动编排层compose 文件、.env、自定义网络、依赖顺序服务启动失败、端口冲突、网络不通所以如果你要接手的是多应用复杂 Docker 环境千万别把目标只定在把镜像倒过去而是要从三层一起考虑。这一套组合拳才算真正意义上的环境迁移。1.3 迁移方案的整体设计思路我习惯用搬家来打比方镜像相当于家具数据卷相当于家里的东西compose 文件相当于房间的布局图自定义网络相当于小区里的道路规划。搬家绝不是只管把家具搬过去东西怎么打包、到新家怎么摆放、水电网怎么接通都得提前想好。整个方案的设计思路可以总结为四步盘点把源环境里的镜像、容器、卷、网络、compose 文件、.env 文件、宿主机层面的依赖全部列出来形成一份迁移清单。打包针对不同对象用不同的打包方式镜像用 docker save数据卷用 rsync 或 tar编排文件直接复制。传输通过离线介质或内网通道把备份包送到目标机器同时做校验确保包没损坏。重建与验证在目标环境上导入镜像、恢复数据卷、重建网络、启动容器并逐项验证服务状态。这套思路的核心是先规划后动手。前面每一步都有一个明确产出物到了目标环境就变成了递增式的恢复过程而不是东一榔头西一棒子。2. 迁移前的准备工作与信息盘点2.1 源环境的信息盘点用命令说话迁移前最重要的一件事就是在源环境上把家底全部摸清楚。别嫌麻烦这一步至少花半天时间。你盘点得越细致到目标环境后翻车的概率就越低。我通常会在源服务器上执行一串命令把关键信息都导出来存成文件# 1. 当前所有镜像包含中间层镜像导出成文本 docker images -a images_list.txt # 2. 所有容器含已停止的显示挂载、网络、端口等信息 docker ps -a containers_list.txt # 3. 所有数据卷 docker volume ls volumes_list.txt # 4. 所有网络 docker network ls networks_list.txt # 5. 查看系统与 Docker 版本 cat /etc/os-release docker version光看列表还不够对于关键容器还要深入 inspect。我比较关注这几个字段Mounts容器挂载了哪些卷类型是 volume 还是 bind宿主机路径是什么容器内路径是什么。NetworkSettings.Networks容器接入了哪个网络用的什么 IP 和别名。RestartPolicy容器的重启策略。PortBindings端口映射关系。Env环境变量这个是重灾区尤其是数据库密码、连接串、各种密钥。我把这些命令汇总写成一个脚本在源环境上一口气跑完把输出全部保存到 /backup/docker_info 目录下。后面做 compose 文件改写、恢复卷目录、重建网络时这些记录就是最可靠的地图。2.2 目标环境的检查与准备很多人一到目标环境就急着导镜像其实应该先看看目标环境到底准备好了没有。我一般按这个顺序检查Docker 是否已安装且可用执行docker info注意有没有报错。如果报permission denied while trying to connect to the docker api说明当前用户不在 docker 组里要么sudo usermod -aG docker $USER后重新登录要么命令前加 sudo。这个权限问题是个高频坑我放在后面常见问题里细讲。内核版本和 cgroup 版本uname -rDocker 20.10 以上版本对 cgroup v2 的支持比较成熟但如果你在 CentOS 7 这种老系统上内核版本和 Docker 版本之间有兼容矩阵先确认一下避免装完 Docker 起不来。特别是热搜词里提到的virtualization support not detected这类问题多半是虚拟化环境没开嵌套虚拟化或者 Docker Desktop 在 Windows 上的 Hyper-V/WSL2 特性没启用。磁盘空间df -h除了系统盘还要看 /var/lib/docker 所在分区的剩余空间。迁移的数据卷有时候比镜像大得多磁盘不够是硬伤。端口占用与防火墙ss -tlnp看常用端口是否被占用firewalld或ufw的状态也要确认。容器端口映射本身不受宿主机防火墙影响但如果你配置了 host 网络模式防火墙就拦得住了。SELinux 状态CentOS / RHEL 系默认开启 SELinux可能会拦截容器访问 bind mount 的目录。临时用setenforce 0验证一下是不是它的锅生产环境建议用chcon -Rt svirt_sandbox_file_t给目录打标签而不是简单关闭 SELinux。目标环境的检查最好做一张记录表每一项打勾。不要怕慢这一步能帮你省下后面大概率会遇到的十几次容器重启。2.3 离线介质与传输方式选型盘点完成之后就到了怎么把东西带过去这个环节。很多人首先想到 U 盘但我建议先考虑数据量级。一套复杂环境镜像加起来可能几十 GB 甚至上百 GB卷数据也不少。U 盘不是不能用但有很多限制比如默认 FAT32 格式的单文件不能超过 4GB你导出的镜像 tar 包很可能超限U 盘上根本拷不进去。建议把 U 盘或者移动硬盘格式化成 exFAT 或者 ext4如果是 Linux 环境。如果是大容量数据我宁可用移动硬盘加 rsync比 U 盘可靠得多。如果源环境和目标环境虽然不能上外网但内网之间是可达的那更推荐直接走 scp 或者 rsync# 传输镜像包到目标机器 scp /backup/images/*.tar usertarget:/data/docker_backup/images/ # 大数据量推荐 rsync支持断点续传网络抖动不怕 rsync -avP /data/volumes/ usertarget:/data/volumes/rsync 有个好处是支持断点续传大镜像文件传到一半断了不用从头再来。离线介质的话建议传输完成后做一次校验用 md5sum 或者 sha256sum 对每个文件算校验值两边对比一致了再往下走。这一步特别重要镜像 tar 包只要有一个字节损坏docker load 就可能报错而且报错信息往往很隐晦让你排查半天。3. 核心实操镜像、数据与编排的离线迁移3.1 镜像离线导出docker save 的正确用法镜像导出是整个迁移里的第一关。我的做法是先把镜像列表按业务需要的运行镜像和基础依赖镜像分开。注意docker images -a会列出中间层镜像但实际 compose 里用到的通常是带 tag 的镜像中间层镜像一般不需要单独导出因为 docker save 某个带 tag 的镜像时会把它的所有依赖图层一起打包进去。实操命令分两步先导出再压缩、校验。# 导出单个镜像 docker save -o /backup/images/nginx_1.24.tar nginx:1.24-alpine # 批量导出多个镜像到不同文件用 tag 做文件名 for img in nginx:1.24-alpine mysql:8.0.36 redis:7.2-alpine; do fname$(echo $img | tr /: __) docker save -o /backup/images/${fname}.tar $img done # 也可以一次性导出多个镜像为一个 tar 包 docker save -o /backup/images/all_apps.tar nginx:1.24-alpine mysql:8.0.36 redis:7.2-alpine批量导出之后统一做压缩tar 里镜像本身有很多层压缩率和普通 tar 差不多但体积能小不少离线介质传起来更快gzip /backup/images/*.tar这里有几个细节值得强调。第一docker save 时镜像的 tag 必须写全。如果你只写镜像 ID 或者不写 tagload 到目标环境后可能变成none:none而 compose 文件里指定的还是原来的 tag两者对不上容器根本起不来。第二导出后要把 tar 包的清单和校验值留底。我会生成一个 images_checksum.txt列出每个 tar 包对应的镜像、文件名、sha256。到目标环境加载完镜像后一条条比对 docker images 输出确保没有遗漏。第三如果环境里有镜像名称带私有仓库前缀比如registry.internal:5000/app:v1导出的 tar 包在目标环境 load 后镜像名仍然保留这个前缀。如果你的 compose 文件引用的也是这个前缀那没问题如果目标环境没有这个仓库域名启动时 compose 会尝试去连那个域名 pull 镜像直接失败。这种情况就要在导入后重新打 tag去掉仓库前缀或者改 compose 文件。热搜词里gogs迁移外部仓库那种场景本质上也类似仓库地址变了引用关系全得跟着改。3.2 数据与卷的迁移三种方式对比镜像搬过去了数据没搬过去一切都是白费。我在实际工作中遇到过三种主流做法各有适用场景方式一直接打包 /var/lib/docker/volumes/ 下的卷目录这种方式最直接。你可以在源机器上进入 /var/lib/docker/volumes/ 下找到对应卷的 _data 目录直接 tar 打包。优点是速度快不依赖容器状态缺点是要求你有宿主机 root 权限而且在容器运行状态下直接打包容易拿到不一致的数据文件正在写入快照不完整。# 停掉应用容器确保数据一致这一步很重要 docker stop app_container # 打包卷目录目录名来自 docker volume inspect tar -czf /backup/volumes/app_data.tar.gz -C /var/lib/docker/volumes/app_data/_data .到了目标环境先创建好同名卷或者直接手动创建目录再把 tar 包解压进去# 目标环境创建卷 docker volume create app_data # 解压数据到卷的 _data 目录 tar -xzf /backup/volumes/app_data.tar.gz -C /var/lib/docker/volumes/app_data/_data上面这种方式比较底层但可靠。唯一要注意的是数据卷名称到实际目录的对应关系务必用docker volume inspect 卷名查看 Mountpoint别搞错目录。方式二用 docker cp 从容器里拷贝docker cp 可以把运行中容器里的文件复制出来但我不太推荐用在大规模迁移上。原因很简单如果容器没停拷出来的数据可能不一致如果容器已经停了docker cp 在某些情况下效率不高而且你得为每个容器单独执行工作量大。方式三bind mount 目录直接 rsync如果你的 compose 文件用的是 bind mount即挂载宿主机某个路径到容器内比如/data/app:/app/data那迁移就简单了直接在源头 rsync 整个目录到目标机器即可连 docker cp 都用不上。这也是我建议新项目尽量多用命名的数据卷而不是 bind mount 的原因之一数据卷的目录层级在 docker 管理之下跨机器恢复时路径不容易错bind mount 则要额外处理好源机和目标机的路径差异。数据迁移的核心原则只有一条先停写再备份。对于数据库容器最稳妥的做法是把容器停掉确保没有任何写入然后做一次完整拷贝。如果业务窗口不允许停太久可以在不停容器的情况下先做一次全量拷贝再短暂停服处理增量但这个操作复杂度就上去了不在本文展开。3.3 compose 编排文件的移植与改写镜像和数据都过去之后真正决定环境能不能活的是编排层。我用的是 docker compose 作为编排工具所以 compose 文件、.env 文件、环境变量、网络定义这些全都要带上。这一节内容非常关键因为很多人在这一步踩坑。首先是.env 文件的传承。compose 文件里经常会引用${MYSQL_ROOT_PASSWORD}这类变量.env 文件里存的是变量的实际值。迁移时如果不带 .env或者带过去的 .env 里的密码和源环境不一致数据库容器启动后密码对不上业务连接直接失败。这是个非常隐蔽的坑因为报错信息往往五花八门比如应用侧报connection refused或者access denied你不会第一时间联想到 .env 没同步。其次是路径配置。源环境里的 bind mount 路径可能是/home/user/project/data到了目标环境如果项目目录不一样就必须改 compose 文件里的 host 路径否则容器起来后目录是空的应用读不到数据。这个在迁移前盘点时就应该记录下来然后在 compose 里统一替换。再是容器名字和 hostname。如果 compose 服务里显式设置了container_name比如container_name: mysql8那么在目标环境里这个容器名必须唯一。如果你之前迁移过另一套环境已经占了这个名字新容器会创建失败报Conflict. The container name /mysql8 is already in use。解决办法是迁移前规划好目标环境的命名空间或者把container_name去掉让 compose 自动生成。我一般会把源环境的 compose 文件原样带上然后在目标环境单独建一个目录把 compose 和 .env 放好先扫描一遍里面的路径、镜像名、端口确认没问题再启动。这个过程不需要改动太多但一定要逐项核对。# 以项目目录迁移为例把 compose 文件、.env 和一个启动脚本一起传过去 scp docker-compose.yml .env run.sh usertarget:/opt/myapp/3.4 自定义网络与依赖顺序的重建复杂 Docker 环境里网络配置往往比想象中复杂。源环境里可能有一个名为app_net的自定义 bridge 网络网段是172.22.0.0/16几个容器在这个网段里分配了静态 IP。你不把网络配置一起搬过去目标环境上 compose 会自动创建一个新网络网段大概率不一样而且容器之间如果通过配置文件里的静态 IP 互访直接全部失联。如果是 compose 文件里定义的网络那还好办因为 compose 启动时会自动创建。但要注意 subnet、gateway 这些参数要么在 compose 里显式声明要么让它自动分配。我比较建议在 compose 里显式声明网络的 IPAM 配置这样在任何环境里都能保证相同的网段networks: app_net: driver: bridge ipam: config: - subnet: 172.22.0.0/16 gateway: 172.22.0.1然后给每个服务配上静态 IPservices: backend: networks: app_net: ipv4_address: 172.22.0.5这里有个坑目标环境所在局域网如果本身就在 172.22.0.0/16 网段内就有可能出现路由冲突。到时候容器访问宿主机以外的网络会不通现象非常诡异。所以在迁移前一定要确认目标环境的局域网网段如果冲突可以换一个不与现有网络冲突的私有网段比如 172.28.0.0/16同时修改配置文件里所有对应的 IP。启动顺序问题也值得一提。多应用环境里数据库、缓存要先起来应用后起来否则应用启动时连不上依赖直接 crash 或者反复重启。compose 的depends_on只能保证先创建容器不能保证依赖服务已经就绪。我在 compose 里倾向于给关键服务配置 healthcheck然后在依赖方用depends_on的 condition 来控制services: mysql: image: mysql:8.0.36 healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 5s timeout: 3s retries: 10 backend: depends_on: mysql: condition: service_healthy这样 compose 会等 mysql 的 healthcheck 通过之后再启动 backend完美解决依赖还没就绪就启动的问题。这也是我在多次迁移实战里觉得最值得做重构的地方源环境如果是靠脚本 sleep 等待也可以改成 healthcheck 式更优雅。4. 迁移后的验证与问题排查4.1 启动验收清单不是 docker ps 变 UP 就完事很多人觉得容器状态变成 Up 就成功了其实这远远不够。容器 Up 只能说明容器进程没有退出不代表业务可用。我每次迁移完都按下面的清单逐项检查镜像完整性检查docker images逐一对照迁移前导出的镜像清单确认 tag 无误没有多余的none镜像占据空间。这一步是为后续故障切换预留线索。容器状态检查docker ps -a查看所有容器记录哪些是 Up、哪些是 Exited、哪些在 restarting。restarting 是重点排查对象多半是启动时依赖没就绪或者配置文件有问题。日志检查docker logs --tail 100 容器名先看有没有 ERROR、FATAL、panic 之类的关键字。数据库、消息队列这类中间件的日志信息量很大学会 grep 关键词能快速缩小范围。网络连通性验证进入容器内部或者从宿主机测试。比如docker exec backend curl http://172.22.0.10:8080/health或者docker exec backend ping 172.22.0.5。注意容器里不一定有 ping 命令可以用nc -zv代替。业务级验证这个才是最关键的一步。比如调用一下后端的接口看一下是否能正常返回连一下数据库确认表结构、数据量对不对查看消息队列中堆积量是否正常。这一步一定要做而且要在客户或者业务方在场的时候演示一遍最好把输出截图留存。我之前接过一个迁移任务容器全部 Up客户看了没说话结果到了当天下午业务侧来报数据不全。原因就是卷目录在迁移之前容器没停干净MySQL 的 redo log 和应用数据不一致很多数据没有刷盘。从那以后验证业务总在验证机制之前。4.2 常见问题速查表现象可能原因排查与解决docker load 报invalid tar header镜像 tar 包损坏重新导出先校验 sha256再 load镜像导入后变成nonesave 时未指定完整 tag或镜像名带仓库前缀重新打 tagdocker tag 镜像ID 完整tagcompose up 报镜像拉取失败引用了不存在的镜像或镜像 tag 未导入docker images核对看 compose 里的 image 字段容器启动后反复重启依赖服务未就绪、环境变量缺失、配置文件路径不对docker logs看退出原因用 healthcheck 控制依赖顺序容器间网络不通自定义网络未重建、网段变化、配置里写死旧 IP检查 compose networks 定义进入容器排查 IP 与互通数据库连接失败.env 未同步、密码不一致、数据卷未恢复对比环境变量确认数据卷路径与内容端口映射失效防火墙、端口占用、docker 网络冲突ss -tlnp查端口临时关防火墙验证permission denieddocker api当前用户不在 docker 组sudo usermod -aG docker $USER重新登录数据卷目录权限异常容器内用户 UID 与宿主机目录属主不一致调整目录属主chown -R 999:999 目录具体 UID 看镜像说明SELinux 拦截挂载目录访问容器无法读取 bind mount 目录chcon -Rt svirt_sandbox_file_t 目录或临时setenforce 0验证这张表是我每次迁移都会同步维护的一张避坑清单如果你之后也经常碰到类似环境迁移建议你把它抄到自己的笔记里遇到问题直接查现象定位原因。4.3 慢启动与依赖顺序的实战处理即使你已经用了 healthcheck 和 depends_on大型环境启动时仍可能出现假死现象。比如数据库初始化需要两三分钟Redis 加载持久化文件需要时间应用启动时探活失败就反复重启。我处理这类问题的两个小技巧可以分享。第一个技巧是手动编排启动顺序不依赖 compose 的一次性 up。先启动基础组件docker compose up -d mysql redis rabbitmq # 等待中间件就绪可以用 sleep 或循环探测 docker compose up -d backend frontend worker但要注意这种手动方式需要你非常清楚容器之间的依赖关系不要让应用在数据库还没初始化完成时就启动。第二个技巧是利用 compose 的 restart 策略兜底。给每个服务配置restart: unless-stopped哪怕应用启动时依赖没就绪导致进程退出Docker 也会自动重启它直到依赖可用。配合 healthcheck能在迁移后快速达到自愈状态。当然生产环境不建议无限循环重启要设置合理的max_retries。5. 踩坑实录与适用边界5.1 我实际踩过的几个典型坑写这篇东西不只是为了讲流程更想把那些平时文档里不会写的坑讲清楚。第一个坑来自FAT32 格式 U 盘。当时导出的镜像 tar 包单个超过 5GBU 盘怎么拷都报文件过大。后来才意识到是 U 盘格式的问题重新格式化为 exFAT 才解决。这个坑虽然不大但是现场环境下真的很尴尬尤其当客户在旁边看着的时候。第二个坑是数据库卷没停容器就打包。我第上次迁移时因为想尽量缩短业务停机时间容器没停就直接打包卷目录。结果数据文件在打包过程中处于活动写入状态tar 包里的文件快照不一致目标环境恢复后 MySQL 启动失败报ibdata1相关错误。最后只能回源环境重新停容器再导一次。那次之后我长记性了迁移数据库类容器必须走停止写入 - flush tables - 停容器 - 打包这个流程不可贪快。第三个坑是环境变量 .env 没带过去。那套环境里 MySQL 密码存在 .env 文件里compose 用${MYSQL_ROOT_PASSWORD}引用。我迁移时只复制了 compose 文件没带 .env结果 MySQL 用了新随机密码应用全部连接失败。这个错误不算复杂但排查起来很绕当时盯着应用日志看了快俩小时才想到是 .env 的问题。第四个坑是静态 IP 和宿主机网段冲突。源环境自定义网络用了 172.22.0.0/16目标环境的局域网恰好也是这个段。容器起来之后部分业务访问宿主机其他服务走的是容器网关路由全乱了。最后把 compose 里的 subnet 改了顺带把应用配置里的 IP 也改了折腾了大半天。第五个坑是防火墙拦截 host 网络模式。有个监控容器用的是 network_mode: host在源环境一切正常目标环境怎么都访问不了。查了半天是目标机器 firewall 拦了端口。这个确实容易漏因为在默认 bridge 模式下端口映射绕过了防火墙但 host 模式不绕。5.2 这套方案的适用范围与边界这套无网络、无镜像仓库的迁移流程最适合的是单机或者小型集群下的 Docker Compose 环境容器数量在几十个以内节点数一两个网络结构相对简单。如果你遇到的问题规模是 K8s 集群、Docker Swarm 或者多节点分布式环境那这套方案就不够用了。K8s 环境迁移还涉及镜像仓库策略、etcd 数据、StatefulSet 的有状态服务、PV/PVC 绑定等等那是另一套更复杂的工程方法论。另外这套方案也默认你有目标机器的 root 权限或者至少 docker 管理权限。如果目标环境是受管平台比如容器云、OpenShift 这种权限模型完全不一样操作手法也要换。如果后续业务环境允许部署私有镜像仓库我的建议是一次性把环境变成可复用的迁移方案。即在目标环境搭建一个离线 Registry把导出的镜像先 push 进去后续新增实例或者更新版本都能从本地 Registry 拉取相当于把一次性搬迁升级成了长期可复制的部署能力。这个方向值得做但不在这次文章的核心范围内。最后再分享一个实用小技巧迁移前在源环境里的所有操作都要有日志记录尤其是 docker save 和 tar 打包的命令建议写成脚本并保留输出。迁移结束后在目标环境把所有恢复操作也记录下来。两次日志对比就是你团队内部最好的运维资产。我每次这么做之后后面再遇到同类迁移任务基本都能照着老脚本快速推进省下的不是一点半点的时间。从我个人的实际体会来说迁移这类问题真正难的从来不是把文件搬过去而是搬过去之后能不能原样跑起来。镜像可以复制但数据一致性、网络规划、依赖关系、配置同步这些软性的东西才决定一次迁移是成功还是灾难。希望这篇内容能帮你把那些软性坑提前填上下次再碰到无网络环境下的 Docker 搬迁能比我第一次从容得多。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进