Docker 外挂的数据库文件丢失之迷分析

线上服务器的数据库(MySQL、MongoDB、Redis)全部运行在 Docker 中,数据库文件是通过卷挂载到宿主机 /data 目录中实现持久存储的,配置如下:

mongo:
  image: mongo
  container_name: mongo
  restart: always
  logging: *default-logging
  ports:
    - 127.0.0.1:27017:27017
  volumes:
    - /data/mongo:/data

redis:
  image: redis
  container_name: redis
  restart: always
  logging: *default-logging
  ports:
    - 127.0.0.1:6379:6379
  volumes:
    - /data/redis:/data

今天弄项目环境时,一个误操作不小心让 MongoDB 和 Redis 的数据库文件消失了(/data/mongo 和 /data/redis 俩目录突然为空),吓得我以为生产环境中的数据库被我删了。

赶紧刷新了下项目系统页面,却还能正常显示数据。于是出现了很奇怪的一幕,执行下面命令看到在容器里有数据文件:

sudo docker exec -it mongo ls /data
sudo docker exec -it redis ls /data

但宿主机上 /data/mongo 和 /data/redis 目录却是空的。

回想发现问题前的最后一次操作是用 docker-compose 新启了个 MongoDB 和 Redis 的容器,检查了下 docker-compose.yaml,原来是把数据挂载到了 /data 目录,原因显而易见了,新的容器卷把 /data/mongo 和 /data/redis 给覆盖掉了,思考下,所谓的覆盖就是把这俩目录分配了新的 inode 号并指到新的数据块,而原来运行中的 Redis 和 MongoDB 容器之所以还能看到数据,是因为旧的数据块暂未释放,它们仍读写的旧的数据块。

我用运行的 ElasticSearch 容器来验证这个猜想:

$ sudo docker exec elasticsearch ls -i /usr/share/elasticsearch/data
44564482 nodes

$ ls -i /data/elasticsearch/
44564482 nodes

可以看到宿主机和容器里,数据文件都是同一个 inode 号;再看下 MongoDB 的:

$ sudo docker exec mongo ls -i /data/
813490 configdb
813492 db
$ sudo ls -i /data/mongo/
116129795 configdb
116129794 db

可见 inode 号不一致,所以我猜想是对的。这种情况,应当在数据还能读时,立即将给导出来做好备份,一旦重启 Docker 什么的导致资源释放,数据就没了。