ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

PostgreSQL 换机器后数据全丢:Docker 数据卷 uid 错位为何比 down -v 更隐蔽

PostgreSQL 换机器后数据全丢:Docker 数据卷 uid 错位为何比 down -v 更隐蔽 本文摘要换机迁移后数据卷 uid 错位旧库被判为不存在而静默重建。入口脚本以 PG_VERSION 可读性判断初始化权限阻断即误判。文件级迁移仅适合同版本大库跨版本应改走 pg_upgrade。一、问题与结论一套docker compose部署的 PostgreSQL 16 从旧机器搬到新机器docker compose up -d一切正常业务查询立刻报错ERROR: relation orders does not exist容器日志只有database system is ready to accept connections没有删除记录、没有告警。原因是 named volume 里的文件按数值 uid 落在宿主机上迁移后属主漂移成0:0新机器的 compose 多了一行user: 1000:1000官方镜像 entrypoint 的 root 分支被跳过$PGDATA/PG_VERSION对 uid 1000 不可读[ ! -s $PGDATA/PG_VERSION ]判定为空于是静默initdb建了一个空库。这比docker compose down -v更隐蔽down -v是显式命令下一次up就会发现数据没了uid 错位触发的重建没有任何提示新库照常接客等业务查不到表时可能已经在空库上写了半天新数据。二、排查与选择依据排查三步先看数据卷属主数值再看容器启动身份最后用docker compose logs postgres | grep -iE initdb|initdb: warning找第二次初始化的痕迹。dockervolume inspect pgdata--format{{ .Mountpoint }}sudols-ln/var/lib/docker/volumes/pgdata/_data|head-3dockerrun--rm-vpgdata:/data alpinels-ln/data/PG_VERSION /data/base判据两条base及其下文件的属主数值应与容器内postgres一致docker run --rm postgres:16-alpine id postgres可查官方镜像通常为uid999(gid999)PG_VERSION必须能被启动身份读到注意它的模式常为0600。dockercompose config|grep-A2userdockercomposeexecpostgresid只要user:的 uid 与postgres不同、而数据属主又是第三个数值权限错位就不会被 entrypoint 修复。initdb.d 不会重复执行补救要靠重放/docker-entrypoint-initdb.d只在数据目录初始化时执行一次。数据被重建后把脚本重新挂进去也不会再跑而为了让它重跑去清空PGDATA等于主动销毁数据。补救顺序从备份恢复数据目录 →chown -R 999:999修属主 → 手动psql -f重放 initdb.d 里的建表、扩展、账号脚本并逐条核对pg_extension、pg_roles、pg_hba.conf的手工改动。替代方案与取舍方案选择条件代价不该用的场景pg_dump/pg_restore中小库、可跨大版本、目标环境 uid 不可控导入耗时随数据量线性增长不保留物理布局上百 GB 且停机窗口以分钟计pg_upgrade同机跨大版本、停机窗口内完成需新旧 bin 目录并存扩展需逐个验证主从集群整体迁移、目标机版本不明pg_basebackup大库物理迁移、同版本要求源库在线并开放 replication 权限跨大版本、源库无法改pg_hba.conftar --numeric-owner同版本文件级搬运、追求最短窗口强依赖 uid 语义一致目标机启用userns-remap、自定义 entrypoint文件级拷贝只解决“搬运”不解决“版本”与“权限语义”只要目标环境 uid 语义或主版本不同就应换上面三种之一。三、关键原理官方镜像docker-entrypoint.sh的初始化判断是“文件是否存在且非空”而这个判断受读权限与父目录可遍历性影响if[!-s$PGDATA/PG_VERSION];thendocker_init_database_dirfiif[$(id-u)0];thenfind$PGDATA\!-userpostgres-execchownpostgres{}execgosu postgres$BASH_SOURCE$fi三层叠加才致命named volume 的文件以数值 uid/gid 落盘不绑定用户名tar/rsync迁移时若按用户名映射属主会在目标机漂移以非 root 启动时chown分支被跳过。最终PG_VERSION对启动身份不可读或不可判定为非空-s测试返回假entrypoint 把“读不到”当成“不存在”initdb静默重建。日志里看不到任何“数据丢失”字样。四、可运行示例环境Docker Engine 20.x 与 compose v2镜像postgres:16-alpinenamed volumepgdata。# docker-compose.ymlservices:postgres:image:postgres:16-alpineenvironment:POSTGRES_PASSWORD:demovolumes:-pgdata:/var/lib/postgresql/datavolumes:pgdata:dockercompose up-dsleep5dockercomposeexecpostgres psql-Upostgres-c\CREATE TABLE orders(id serial, amount int); INSERT INTO orders(amount) VALUES (100),(200);# 导出数据目录模拟迁移到新机器未加 --numeric-ownerdockerrun--rm-vpgdata:/data-v$PWD:/backup alpinetarczpf /backup/pgdata.tar.gz-C/data.dockercompose downdockervolumermpgdatadockervolume create pgdatadockerrun--rm-vpgdata:/data-v$PWD:/backup alpinetarxzf /backup/pgdata.tar.gz-C/data# 模拟迁移后的属主漂移dockerrun--rm-vpgdata:/data alpinechown-R0:0 /data复制docker-compose.yml为docker-compose-user.yml在services.postgres下加一行user: 1000:1000模拟新机器差异启动后验证dockercompose-fdocker-compose-user.yml up-dsleep5dockerrun--rm-vpgdata:/data alpinels-ln/data/PG_VERSIONdockercompose-fdocker-compose-user.ymlexecpostgres psql-Upostgres-cSELECT * FROM orders;预期输出ls -ln显示PG_VERSION属主999:999SELECT返回 2 行。实际输出属主为0:0SELECT报relation orders does not exist日志无任何异常。修复dockercompose downdockerrun--rm-vpgdata:/data alpinechown-R999:999 /datadockerrun--rm-vpgdata:/data alpinechmod700/datadockercompose up-d# 去掉 compose 中的 user: 字段以 root 启动让 entrypoint chown常见失败去掉user:后仍报FATAL: data directory /var/lib/postgresql/data has group or world access原因是迁移时目录权限被放宽到0755修复是chmod 700后再启动而不是放宽 PostgreSQL 的权限检查。五、验证结果与边界验证三条SELECT能查到迁移前的数据行ls -ln属主与id postgres一致容器日志中没有第二次initdb/initdb: warning痕迹。三条任一不满足都应视为数据未就位。单机场景停机窗口内二选一——同版本用文件级迁移或pg_basebackup跨大版本用pg_upgrade新旧 bin 目录并存扩展需逐个确认。主从场景不要把主库文件目录直接拷给从库从库一律pg_basebackup重建pg_upgrade只能对单节点离线执行升级完主库后重建 standby再恢复复制。边界条件目标机启用userns-remap时数值 uid 在宿主机侧含义改变--numeric-owner也不足以保证一致自定义 entrypoint 的镜像需自查判断逻辑上述复现在postgres:16-alpine上执行过13–17 主版本的判断逻辑表述一致但未在每个主版本逐一验证。参考资料docker-entrypoint.shdocker-library/postgres master 分支Docker Volumes 官方文档Docker Engine 20.x–27.xpg_upgrade — PostgreSQL 16 官方文档SQL Dump — PostgreSQL 16 官方文档pg_basebackup — PostgreSQL 16 官方文档
RELATED READING

延伸阅读

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