DBmotion全量迁移容器集合:Docker Compose部署实战指南

发布时间:2026/10/12 17:47:56

DBmotion全量迁移容器集合:Docker Compose部署实战指南 简介面向部署数据库迁移工具的开发与运维人员这份容器镜像集合将迁移引擎、代理服务、监控告警、可视化界面等组件打包为可一键编排的部署方案。包内共十一个文件包含十个gz格式的容器镜像包和一个YAML编排文件整体大小约七百三十八兆。镜像覆盖数据库、消息代理、监控、可视化等核心服务配合编排文件执行单条命令即可批量启动所有依赖组件省去手工配置网络、卷挂载与环境变量的繁琐步骤。对于需要快速搭建数据库同步或迁移环境的技术团队该资源能显著缩短环境准备时间并借助监控与可视化组件获得清晰的运行状态视图。目前已有四十八人学习下载适合具备基础容器使用经验、希望直接复用现成编排方案的读者。1. 为什么说 DBmotion 的全量迁移先从一份容器集合开始做数据迁移的人应该都有这种体会真正耗时间的往往不是迁移任务本身而是把源端、采集端、传输通道、目标端这一整套依赖环境凑齐。DBmotion 这类迁移工具虽然把同步逻辑封装得很好但它的全量迁移链路里ZooKeeper、Kafka、etcd、采集与写入服务一个都少不了。手动一个个装、一个个调半天过去了还没开始迁移数据。所谓“DBmotion 全量所需要容器集合包含可执行的 docker-compose.yaml”就是把这条链路里的每个环节都做成容器用一份 docker-compose 编排文件一次性拉起来。适合两种人一是想快速评估 DBmotion 迁移效果、不想在环境搭建上花太多时间的开发者二是要给客户或内部环境交付迁移方案需要一套可复现、可移植的部署模板。这篇文章按我实际搭建这套环境的顺序把容器集合怎么设计、compose 文件怎么写、参数怎么调、哪些坑必踩一次讲清楚。2. 拆开 DBmotion 的容器集合每个容器解决迁移链路里的哪个环节2.1 一条全量迁移链路里哪些组件是不能省的DBmotion 做全量迁移简单说就是“读源库 → 传输 → 写目标库”。但中间为了保证数据不丢、顺序不乱、断点能续引入了一组支撑组件。我最早做模拟项目X的时候就想着精简只起源端和目标端两个容器结果根本跑不通。后来把链路理清楚才发现下面这些角色各有各的活谁也替不了谁。源端数据库容器承担“数据从哪来”的角色。全量迁移要验证数据一致性没有真实的源库实例后面所有环节都是空转。我一般用 MySQL 或 PostgreSQL 的官方镜像直接挂初始化脚本造数据。采集端容器是 DBmotion 里干累活的。它连上源库把全量数据按表读出来转成统一的消息格式发到 Kafka。这个角色通常对应 DBmotion 的采集服务如果只是做功能验证可以用轻量模拟程序代替但正式评估时必须用真实的采集节点。Kafka 是核心传输通道。全量数据量大采集端不可能直接往目标库灌那样源库和目标库会互相拖垮。Kafka 在这里相当于一个高速缓冲带采集端只管往里面写写入端只管从里面读两者解耦。Kafka 本身依赖 ZooKeeper 做元数据管理所以 ZooKeeper 容器成了隐藏的必需项。etcd 管的是迁移任务的元数据。哪些表迁移完了、迁移到哪个偏移量了、任务当前什么状态这些都存在 etcd 里。没有它断点续传就是空话。最后是写入端容器它消费 Kafka 里的数据把全量数据按目标库的方言写入同时往 etcd 上报进度。这么一拆就很清楚了源端、采集端、Kafka、ZooKeeper、etcd、写入端、目标端七个角色缺一个链路就断。我见过有人想把 ZooKeeper 省掉直接连 Kafka只能说那是给自己挖坑。2.2 为什么用 ZooKeeper Kafka etcd 三件套而不是单机版很多人第一次看到这套组合会问都是存元数据为什么 Kafka 要配 ZooKeeperDBmotion 自己又要一个 etcd这俩不能合并吗答案是职责不同。Kafka 用 ZooKeeper 存的是 broker 列表、分区分配、消费者组偏移量这些“消息系统自己的状态”。Kafka 的历史版本把 ZooKeeper 当成硬依赖虽然新版本在做 KRaft 模式去 ZooKeeper但 DBmotion 官方链路默认还是走 ZooKeeper 的方式实盘验证时扯皮最少。我自己试过 KRaft 模式不是说不行而是排查问题时社区资料少一旦出状况很难判断是 DBmotion 的问题还是 Kafka 自身的问题。etcd 管的是迁移任务级别的元数据比如哪个表迁移到哪条记录了。这不是 Kafka 的分区偏移量是业务数据在迁移管道里的进度。如果硬塞进 Kafka 的 topic 里任务一旦重建历史偏移量会污染新任务。所以这套“三件套”不是重复造轮子而是各自守住各自的边界。ZooKeeper 守消息系统etcd 守迁移任务。明白了这一点编排容器时就能理解为什么每个服务都要独立部署而不是图省事塞进同一个容器里。进程混在一起日志、重启、扩容全都互相干扰全量迁移跑到一半某个组件挂了排查起来想死的心都有。2.3 容器集合的拓扑与端口规划我把整套容器集合的拓扑固定成了一张表每次搭新环境直接照着抄。拓扑上分为三层数据层、传输层、任务层。数据层是源端和目标端数据库传输层是 Kafka 加 ZooKeeper任务层是采集端、写入端和 etcd。DBmotion 本身的管理端如果有放在任务层最上面负责下发任务和查看状态。服务镜像类型默认端口数据持久化职责source-dbMySQL/PostgreSQL 官方镜像3306/5432是初始化脚本挂载全量数据来源target-db目标数据库官方镜像3306/5432是全量数据写入目标zookeeperZooKeeper 官方镜像2181是dataDir 挂载Kafka 元数据存储kafkaKafka 官方镜像9092是log.dirs 挂载全量数据传输通道etcdetcd 官方镜像2379, 2380是data 目录挂载迁移任务进度/状态dbmotion-collectorDBmotion 采集端镜像无外部端口否读源库写 Kafkadbmotion-writerDBmotion 写入端镜像无外部端口否读 Kafka写目标库端口规划有个原则只有需要被外部访问的服务才映射到宿主机服务间通信用 Docker 内部网络。Kafka 比较特殊因为客户端采集端、写入端跑在别的容器里Kafka 自己的 advertised.listeners 必须配成容器网络内的地址否则会踩“容器里连不上 Kafka”的经典大坑。这一点后面专门讲。etcd 的 2380 是集群内部 peer 通信用的单节点部署不需要映射到宿主机2379 看需要再决定要不要暴露。3. 写一份可执行的 docker-compose.yaml部署模型与关键参数3.1 先从 compose 文件结构入手网络、卷、服务三段式一份可执行的 compose 文件结构上分三块networks 定义容器间通信网络volumes 定义持久化存储services 定义每个容器。全量迁移的场景里容器之间都是“服务发现”的关系——采集端通过 etcd 找任务通过 Kafka 发数据写入端从 Kafka 拉数据。所以网络必须用自定义 bridge 网络而不是默认的 default 网络这样才能用服务名互相访问。下面是我整理出来的精简版文件框架。注意 source-db 和 target-db 的初始化脚本我用的是挂载目录而不是镜像内嵌这样换表结构、换数据都不用重新 build 镜像。version: 3.8 networks: dbmotion-net: driver: bridge ipam: config: - subnet: 172.28.0.0/16 volumes: zk-data: kafka-data: etcd-data: source-db-data: target-db-data: services: zookeeper: image: zookeeper:3.8 container_name: dbmotion-zookeeper networks: dbmotion-net: ipv4_address: 172.28.0.10 ports: - 2181:2181 volumes: - zk-data:/data - ./zk-conf:/conf environment: ZOO_MY_ID: 1 ZOO_SERVERS: server.1zookeeper:2888:3888;2181 healthcheck: test: [CMD, zkServer.sh, status] interval: 10s timeout: 5s retries: 5network 指定了固定子网和固定 IP。这个做法在 compose 里不是必须的但配合 healthcheck 和 depends_on 的 condition 时服务名解析稳定排查网络问题也少一层干扰。volumes 全部用 named volume而不是 bind mount 到宿主机目录原因是迁移环境经常要在不同机器之间搬named volume 随 compose 项目走不会被宿主机路径差异绊倒。zookeeper 这里指定了 ZOO_MY_ID 和 ZOO_SERVERS。单节点时 ZOO_MY_ID 写 1 就行ZOO_SERVERS 的格式是 server.1主机名:2888:3888;21812888 是 Leader 选举端口3888 是集群内通信端口。单节点不涉及选举但配全了以后想扩成三节点集群只需再追加 server.2 和 server.3不用改其他配置。healthcheck 用的是 zkServer.sh status这条命令在容器里返回 0 表示服务健康。3.2 中间件服务的参数陷阱Kafka 的 advertised.listeners 与健康检查Kafka 是这套容器集合里最容易翻车的服务没有之一。官方镜像默认的 listener 配置只对宿主机访问友好容器内其他服务通过服务名访问时Kafka 返回的 broker 地址是容器 ID客户端拿着这个地址去连必然超时。这就是业界说的“Kafka 容器网络黑匣子”问题。解决办法是在 environment 里显式指定三组 listenerPLAINTEXT 负责容器内通信EXTERNAL 负责宿主机访问CONTROLLER 是给新版本 Kafka 用的。advertised.listeners 必须写成服务名 kafka:9092而不是 localhost 或宿主机 IP。如果写 127.0.0.1采集容器里的客户端能连上 broker但 broker 返回的元数据里写的地址还是 127.0.0.1客户端拿这个地址根本路由不到 Kafka 容器。kafka: image: bitnami/kafka:3.4 container_name: dbmotion-kafka networks: dbmotion-net: ipv4_address: 172.28.0.11 ports: - 9092:9092 volumes: - kafka-data:/bitnami/kafka environment: KAFKA_CFG_NODE_ID: 1 KAFKA_CFG_PROCESS_ROLES: broker,controller KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093 KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1kafka:9093 KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: true KAFKA_CFG_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 KAFKA_CFG_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 KAFKA_CFG_TRANSACTION_STATE_LOG_MIN_ISR: 1 ALLOW_PLAINTEXT_LISTENER: yes KAFKA_HEAP_OPTS: -Xmx512m -Xms256m healthcheck: test: [CMD, kafka-topics.sh, --bootstrap-server, localhost:9092, --list] interval: 15s timeout: 10s retries: 5 start_period: 30s这里用的是 bitnami 镜像环境变量前缀 KAFKA_CFG_ 是 bitnami 的封装规范和 Apache 官方镜像的 KAFKA_ 前缀不一样。选 bitnami 的原因是其健康检查脚本更完整kafka-topics.sh --list 能真实反映 broker 是否对外可用而 Apache 镜像默认的 healthcheck 经常只是检查进程在不在。KAFKA_CFG_PROCESS_ROLES 写成 broker,controller 是让单节点同时承担 broker 和 controller 职责。全量迁移的最小环境不需要独立的 KRaft controller 节点。副本因子全部设成 1因为单节点 Kafka 上不允许配大于 1 的副本因子这个参数不影响迁移正确性但能避免一堆 “Replication factor is larger than available brokers” 的报错刷屏。KAFKA_HEAP_OPTS 限制 JVM 内存后面避坑章节专门讲内存问题。etcd 的配置相对清爽主要注意 client 端口和 peer 端口的区分。etcd: image: quay.io/coreos/etcd:v3.5.5 container_name: dbmotion-etcd networks: dbmotion-net: ipv4_address: 172.28.0.12 ports: - 2379:2379 volumes: - etcd-data:/etcd-data command: - /usr/local/bin/etcd - --nameetcd0 - --data-dir/etcd-data - --advertise-client-urlshttp://etcd:2379 - --listen-client-urlshttp://0.0.0.0:2379 - --listen-peer-urlshttp://0.0.0.0:2380 - --initial-advertise-peer-urlshttp://etcd:2380 - --initial-clusteretcd0http://etcd:2380 - --initial-cluster-statenew healthcheck: test: [CMD, etcdctl, endpoint, health] interval: 10s timeout: 5s retries: 5advertise-client-urls 写容器服务名 etcd 而不是 IP这样 DBmotion 写入端的容器不管调度到哪个网络位置都能通过服务名稳定访问。initial-cluster-state 必须写 new如果写 existingetcd 会去找旧集群状态单节点场景下会一直卡在等待集群就绪。3.3 DBmotion 相关服务的启动顺序与 depends_on 的正确用法容器编排里启动顺序是个老生常谈但永远有人踩坑的问题。DBmotion 采集端启动时必须能连上 etcd 和 Kafka写入端必须能连上 Kafka 和 etcd而 Kafka 又要等 ZooKeeper 就绪。这些依赖如果用默认的 depends_ondocker compose 只保证“先启动”不保证“真就绪”采集端大概率在 Kafka 还在初始化时就尝试连接然后崩溃退出。容器退出后 compose 不会自动重启整套环境就瘫在那里。正确做法是 depends_on 配 condition: service_healthy再配合每个中间件的 healthcheck。compose 会先启动依赖服务等 healthcheck 通过后才启动下游服务。dbmotion-collector: image: dbmotion-collector:latest container_name: dbmotion-collector networks: dbmotion-net: ipv4_address: 172.28.0.20 depends_on: kafka: condition: service_healthy etcd: condition: service_healthy environment: DBMOTION_ROLE: collector DBMOTION_ETCD_ENDPOINTS: http://etcd:2379 DBMOTION_KAFKA_BOOTSTRAP: kafka:9092 DBMOTION_SOURCE_DSN: root:sourcepasstcp(source-db:3306)/migration_db?charsetutf8mb4 command: [/app/dbmotion-collector, --config, /etc/dbmotion/collector.yaml] dbmotion-writer: image: dbmotion-writer:latest container_name: dbmotion-writer networks: dbmotion-net: ipv4_address: 172.28.0.21 depends_on: kafka: condition: service_healthy etcd: condition: service_healthy environment: DBMOTION_ROLE: writer DBMOTION_ETCD_ENDPOINTS: http://etcd:2379 DBMOTION_KAFKA_BOOTSTRAP: kafka:9092 DBMOTION_TARGET_DSN: root:targetpasstcp(target-db:3306)/migration_target?charsetutf8mb4 command: [/app/dbmotion-writer, --config, /etc/dbmotion/writer.yaml]DBMOTION_SOURCE_DSN 和 DBMOTION_TARGET_DSN 里的主机名全部写服务名tcp(source-db:3306) 这种格式是 Go 语言数据库驱动的标准写法。这里有个容易忽略的点source-db 和 target-db 这两个容器本身也要配 healthcheck否则 depends_on 里如果加了它们condition 会检查不到健康状态而一直等待。MySQL 镜像的 healthcheck 建议用 mysqladmin pingPostgreSQL 用 pg_isready。启动顺序变长了以后整套环境冷启动可能要等 1 到 2 分钟这不是卡死是中间件在做初始化。后面加了一个 start_period 参数专门解决“启动慢被 healthcheck 误杀”的问题。采集端和写入端的镜像这里用 dbmotion-collector:latest 指代实际使用时换成你自己的镜像名。4. 把容器集合跑起来从 docker compose up 到第一个迁移任务4.1 启动前检查端口冲突、内存预算、镜像源启动之前有三次检查能帮你省掉后面至少两个小时的排查时间。第一次检查端口冲突。2181、9092、2379 这三个端口是中间件默认端口本机如果已经跑着别的 ZooKeeper、Kafka 或 etcdcompose 起服务时会直接报 bind: address already in use。检查命令很简单lsof -i :2181 -i :9092 -i :2379有输出说明端口被占了。解决方式有两个关掉宿主机的旧服务或者在 compose 文件里改 ports 映射的宿主机侧端口比如把 Kafka 改成 19092:9092。要注意改宿主机侧端口不影响容器间通过服务名访问因为容器间走的是 9092 这个容器内端口。第二次检查内存预算。Kafka 默认的 JVM 堆上限是 1G加上 ZooKeeper、etcd、两个数据库实例整套环境冷启动大概需要 3 到 4G 可用内存。如果机器只有 2GKafka 会频繁 Full GC然后被 healthcheck 判死compose 就会反复重启它日志里全是内存溢出。建议在 compose 里给 Kafka 显式限制 KAFKA_HEAP_OPTS给 MySQL/PostgreSQL 用 shm_size 控制共享内存。第三次检查镜像源。DBmotion 相关镜像如果是私有仓库docker compose 不会自动登录需要先手动 docker login。ZooKeeper、Kafka、etcd 这几个中间件镜像建议提前 docker pull 到本地免得 compose up 时才开始拉一旦网络不稳定整个环境起得断断续续。4.2 启动整套环境与验证每个容器状态检查做完就可以启动了。我习惯用后台模式启动然后把日志输出到文件方便后面排查。docker compose up -d这个命令会按依赖顺序依次启动所有容器。等第一次执行完别急着配迁移任务先花 30 秒确认每个容器都进入了 healthy 状态docker compose ps输出里每个服务对应一行STATUS 列应该有 healthy 字样。这一步非常重要因为采集端和写入端的启动依赖于 Kafka 和 etcd 健康如果中间件还处于 starting下游容器会卡在等待状态。看到 unhealthy 的容器立即看它的日志docker compose logs -f kafka docker compose logs -f etcd日志出现的常见关键字也有套路。ZooKeeper 日志里出现 bind to port 2181 说明监听成功Kafka 日志里出现 [KafkaRaftServer nodeId1] Started 说明 KRaft 模式启动完成etcd 日志出现 serving insecure client requests on 0.0.0.0:2379 说明客户端接口可用。如果等了两分钟这些关键字还没出现基本可以断定配置有问题而不是启动慢。所有容器都 healthy 之后再验证一下容器间的网络连通性。我一般会进入采集端容器里用内置工具测试到 Kafka 和 etcd 的连通性docker exec -it dbmotion-collector bash # 在容器内执行 curl http://etcd:2379/health # 期望输出 {health:true} kafka-topics.sh --bootstrap-server kafka:9092 --list # 期望输出不报错哪怕没有 topic这一步验证的是服务名解析和端口连通。curl 输出 health true 说明 etcd 正常kafka-topics.sh 能连上说明 Kafka 的 advertised.listeners 配置生效了。如果这两步有一处失败不要继续往下配任务先回头改配置。4.3 跑通一个最小全量迁移任务环境就绪后迁移任务本身怎么下发DBmotion 的做法是通过 etcd 写入任务配置采集端和写入端监听 etcd 的变化然后开始干活。所以我需要往 etcd 里写入一个最小的迁移任务配置。任务配置的核心内容包括源库连接信息、目标库连接信息、要迁移的表列表、Kafka topic 名称。docker exec -it dbmotion-etcd etcdctl put /dbmotion/tasks/task001 { name: full_migration_demo, source: root:sourcepasstcp(source-db:3306)/migration_db, target: root:targetpasstcp(target-db:3306)/migration_target, tables: [users, orders], kafka_topic: dbmotion-full-001, mode: full, batch_size: 1000 }这段配置我一般在源库和目标库里都先建好同名表再填到 etcd。batch_size 控制每个批次写入的记录数设 1000 是保守值主要考虑目标库写入性能和 Kafka 消息大小。模式写成 full表示只做全量迁移不做增量同步。字段名和结构按实际 DBmotion 版本可能略有差异但核心信息就是这四类两端连接、表清单、通道、批大小。任务写进去之后采集端监听到配置变化会去源库读数据然后发到 Kafka写入端从 Kafka 拉数据写入目标库。整个过程中可以通过 etcd 查看任务进度docker exec -it dbmotion-etcd etcdctl get /dbmotion/tasks/task001/status看到 status 字段变成 finished 或 done说明全量迁移跑完了。这时进目标库查一下数据量docker exec -it dbmotion-target-db mysql -uroot -ptargetpass -e SELECT COUNT(*) FROM migration_target.users;如果和目标库源表行数一致第一个全量迁移任务就算通了。第一次跑通的意义不在于迁移了多大数据量而在于验证了从容器集合到任务下发的整条链路是通的后续换真实库、加表、调并发都在这条链路上迭代。5. 容器集合落地避坑五个最容易翻车的点5.1 Kafka 容器起来了客户端却连不上现象docker compose ps 显示 Kafka 是 healthy但采集端容器日志里反复报 connection refused 或者 timeoutKafka 的 topic 列表也拉取失败。原因绝大多数情况是 advertised.listeners 配置不对。Kafka 容器对外广播的地址写成了 localhost 或 127.0.0.1容器内的采集端拿到这个地址后尝试连自己自然连不上。我见过有人排查了一下午最后发现是 KAFKA_CFG_ADVERTISED_LISTENERS 漏了 KAFKA_CFG_ 前缀导致配置根本没生效。 解决把 advertised.listeners 显式写成 PLAINTEXT://kafka:9092注意主机名必须和 compose 服务名一致。改完之后重启 Kafka 容器然后进入采集端容器里执行 kafka-topics.sh --bootstrap-server kafka:9092 --list 验证。改完配置不重启、只看 docker inspect 里的 Environment 字段是不会生效的。5.2 depends_on 直接写服务名下游容器还是崩溃退出现象dbmotion-collector 容器启动后立即退出日志显示连接 etcd 超时或 Kafka 未就绪但 Kafka 容器明明是 running。原因depends_on 只保证服务启动先后顺序不保证服务内部就绪。Kafka 容器进程起来了但 broker 还在加载磁盘里的日志、注册 controller这个窗口可能持续几十秒。采集端这个时候去连必然失败。容器退出之后 compose 不会自动拉起它所以整个环境就静默地“半死”了。 解决depends_on 里加上 condition: service_healthy然后给 Kafka 和 etcd 都配上真正的 healthcheck。注意 Kafka 的 healthcheck 不要用 telnet 端口要用 kafka-topics.sh --list因为端口通了不代表 broker 完成注册。这是一个很典型的“玄学问题”其实不是玄学是就绪语义没搞清。5.3 etcd 容器启动失败名叫“permission denied”现象docker compose up 时 etcd 容器反复重启日志里出现 mkdir /etcd-data: permission denied。原因etcd 官方镜像里的进程默认以非 root 用户运行而挂载进来的 named volume 初始所有权是 root。容器用户没有写权限etcd 建不了数据目录。这个坑在大部分 Linux 发行版上都会踩macOS 上因为 Docker Desktop 的特殊权限模型可能不触发反而增加了迷惑性。 解决最简单的方式是在 compose 的 etcd 服务里加 user: 0:0 强制以 root 运行或者用初始化容器预先 chown 数据目录。生产环境建议用初始化容器本地评估可以直接加 user。改完记得 docker compose down -v 把旧的卷删掉重建否则权限修复不会套用到已创建的卷上。5.4 整套环境跑着跑着某个容器被系统杀掉现象迁移任务跑到一半Kafka 或两个数据库容器突然消失docker compose ps 里看不到系统日志里出现 oom-kill 记录。原因宿主机内存被占满。Kafka 默认的 JVM 堆内存上限是 1G加上 ZooKeeper、etcd、两个数据库实例再算上 Docker 本身的开销2G 内存的机器根本扛不住。系统 OOM killer 会优先杀掉内存占用最大的进程Kafka 首当其冲。 解决在 compose 里给每个服务加 mem_limitKafka 设 512mZooKeeper 设 256metcd 设 256m数据库各自设 512m。同时给 Kafka 设置 KAFKA_HEAP_OPTS 为 -Xmx512m -Xms256m让 JVM 在容器内存限制之内运行。压内存之前先用 docker stats 看实时占用确定是谁在吃内存。5.5 数据“丢了”重启后源库或目标库是空的现象整套环境重启之后迁移任务的任务还在但目标库里的数据没了有时候源库的初始化数据也没了。原因数据库容器虽然挂了 volumes但挂的是匿名卷或没挂卷。compose down 的时候如果没加 -v匿名卷会保留但 docker compose down -v 会把匿名卷一起删掉。更隐蔽的场景是镜像自带的数据目录路径和你在 volumes 里挂载的容器内路径不一致比如 MySQL 镜像的数据目录是 /var/lib/mysql你挂成了 /var/lib/mysql/data卷里存了文件但 MySQL 根本不认识启动时生成一个空的数据目录看起来就是“数据丢了”。 解决先确认镜像文档里的数据目录路径。MySQL 官方镜像是 /var/lib/mysqlPostgreSQL 是 /var/lib/postgresql/dataetcd 的路径通过 --data-dir 参数自己指定。compose 里 volumes 的容器侧路径必须和实际数据目录完全一致。养成一个好习惯改动环境之前先 docker compose down不要直接 docker compose down -v-v 是删除数据卷的操作没有后悔药。6. 让这套容器集合更顺手参数化模板与多环境复用容器集合能跑通只是第一步第二步是让它能复用。我最常用的是 .env 配合 compose 变量替换。把端口、密码、数据目录这些环境相关的值抽到 .env 文件里compose 文件本身保持一份不动。# .env 示例 SOURCE_DB_PORT3306 TARGET_DB_PORT3307 MYSQL_ROOT_PASSWORDsourcepass TARGET_MYSQL_ROOT_PASSWORDtargetpass KAFKA_PORT9092compose 文件里用 ${MYSQL_ROOT_PASSWORD} 引用这些变量。这样同一份 compose 文件开发环境、测试环境、客户现场只要改 .env 就能迁移不用动 YAML 本身。变量替换是 compose 原生的能力不需要额外工具。数据校验是另一个值得养成的习惯。全量迁移结束只对比行数远远不够。行数一样但内容对不上比如字符集编码问题、时区转换问题、浮点数精度问题都会让迁移“成功但错误”。我一般用一个简单的校验脚本把源表和目标表按主键排序后做哈希对比docker exec source-db mysql -e SELECT CRC32(GROUP_CONCAT(col1, col2, col3 ORDER BY id)) FROM users; docker exec target-db mysql -e SELECT CRC32(GROUP_CONCAT(col1, col2, col3 ORDER BY id)) FROM users;两边的 CRC32 值一致说明这批数据内容层面也对上了。不一致时把排序后的主键列表导出做 diff就能定位到具体哪一行差异。这个校验方法简单直接不用引入额外的工具链。这套容器集合做到现在这个程度我最大的教训是不要因为追求“环境小”而阉割依赖组件。最早我为了省内存把 ZooKeeper 换成嵌入式模式把 etcd 塞进采集端进程里结果每次排查问题都要先怀疑自己改过的地方反而浪费更多时间。按标准链路把容器集合完整拉起来虽然占资源多一点但每一步行为和线上行为一致验证出来的结论才作数。希望这篇笔记能帮你把环境搭建这一步真正省下来把精力留给迁移任务本身。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/12 17:47:56

HALCON 22.11架构跃迁:从脚本到工业视觉中间件

1. 这不是“第12篇笔记”,而是HALCON工程落地的分水岭时刻很多人看到“HALCON学习笔记 12”这个标题,第一反应是:又一篇按序号堆砌的入门教程?翻到第12篇,大概率还在讲图像采集、阈值分割、blob分析这些基础操作——没…

2026/10/12 17:47:56

兰博基尼按下纯电暂停键:超跑电动化的现实瓶颈与混动过渡

兰博基尼把电动超跑按下暂停键这事儿,在圈子里讨论热度不低。纯电超跑Revuelto的纯电继任者原计划不出意外的话会按时落地,结果集团层面突然调整节奏,把纯电车型时间线往后推了好几年。这个动作背后不是简单的“不玩了”,而是整个…

2026/10/12 17:42:55

西门子S7-200 PLC八层电梯控制系统梯形图逻辑详解

做电梯控制这行当久了,你会慢慢发现一个道理:楼有多高、层有多少其实都是表象,真正决定项目难度的,永远是控制逻辑怎么组织。尤其是八层电梯,不上不下、尴尬得很——比六层多了两个外呼按钮,比十几层的高楼…

2026/10/12 18:53:26

Arduino-IRremote红外遥控编解码实战:从原理到避坑指南

1. 从一个“吃灰”的红外接收头说起如果你手头有一堆闲置的遥控器——电视的、机顶盒的、老式风扇的、甚至某个不知道什么设备的——然后你盯着它们想:能不能让我的开发板听懂这些按键在说什么?那这个项目就是为你准备的。Arduino-IRremote是一个在嵌入式…

2026/10/12 18:53:26

嵌入式开发必备:Cppcheck静态分析实战指南

刚开始接触嵌入式静态分析时,我一度觉得这类工具离实际项目很远:单片机代码量不大,出问题的地方翻来覆去就那么几个,跑一遍编译器告警也够用了,何必再折腾一个分析工具。直到有一次在某个电机控制项目里排查一个偶发复…

2026/10/12 18:53:26

AI协同开发STM32:五段式流程与实战指南

做嵌入式这些年,我写得最多的就是STM32的初始化代码:开时钟、配GPIO、对着数据手册核对寄存器值、一遍遍试外设配置。这些活儿技术含量不算高,但特别耗时间,每换一颗芯片、每接一个新外设,整套流程都要重走一遍。自从我…

2026/10/12 18:48:25

开放式代码审查:让橡皮图章式审查真正失效的实战方案

大约半年前,我们差点把一个严重缺陷带上生产环境。改动本身小得不能再小——某个配置项的超时时间从3秒改成10秒,PR里只有几行代码,当天就被审查者点了通过。上线前一天,一位刚入职的同事在整理其他需求时偶然看到这个改动&#x…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/12 12:01:26

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/12 12:54:07

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/12 0:04:22

绝缘子缺陷检测数据集清洗与工业级训练实战指南

简介:本资源是面向电力AI研发人员、工业视觉工程师及智能巡检系统开发者的绝缘子缺陷检测专用YOLO格式数据集,解决无人机航拍场景下绝缘子破损、污闪、积雪等9类典型缺陷的精准识别与定位难题。数据集共2139张真实巡检图像(含训练/验证/测试集…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑