很久之前在本地开发一个工程需要用到数据库,因为对 MySQL 比较熟悉就随手用 Docker 开了一个 MySQL 5.7 的容器。之后生产环境跑了一段时间后需要把数据拉取下来本地做一些分析,当时就直接用 Navicat 在远程数据库和本地数据库直接做了数据传输。再后来生产环境因为一些原因用不上了,数据库删了,但本地的数据库一直还在。

最近又因为一些原因忽然要用到当时的数据,于是就想着本地的 Docker 容器里的数据应该还在。结果用 Docker 启动那个 MySQL 容器的时候,却怎么都启动不了。不仅如此,而且观察到启动容器的一瞬间,系统内存占用会直接升到 99%,然后回落的正常水平。此时容器报错退出。

使用 docker logs 查看日志:

1
2
3
4
2023-08-29 12:58:24+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 5.7.36-1debian10 started.
2023-08-29 12:58:28+00:00 [ERROR] [Entrypoint]: mysqld failed while attempting to check config
command was: mysqld --verbose --help --log-bin-index=/tmp/tmp.nKEgP9gvfv

通过复制日志在谷歌上进行了一番搜索,找到了这样的 Issue:https://github.com/containerd/containerd/issues/6707

简单来说就是新版本的 containerd 跟 MySQL 5.7 不太兼容,导致了内存泄露,而且目前仍然还没有修复。

这时的我在想,虽然容器启动不了,那我数据总归还是在磁盘里的,只要想办法从文件里把数据导出来然后拷贝到另一个数据库应该就行了吧。

使用 docker inspect 查看容器文件系统在本地磁盘的具体位置,然后查看数据文件。在数据目录下,有 ibdata1 文件以及另外几个数据库对应的文件夹,里面是一些跟表有关的 ibd 文件和 frm 文件。

根据网上的说法,只要把这些文件拷贝的一个新数据库的数据目录下就可以实现恢复了。然而经过我的一番研究,发现这种恢复方法非常的繁琐,不仅要手动创建所有的表字段,每次还只能恢复一张表的数据。我的数据库里有十几张表都需要恢复,如果这么操作的话就太麻烦了。于是,我开始寻找其他解决办法。

在翻了一大堆的 GitHub Issues 后,我在评论区中找到了一个临时的解决办法:修改系统的 ulimit。然而问题是,这个方法是对于新建的容器的。我现在要修改已有容器的 ulimit,可是连容器都启动不了,该怎么修改呢?

这时,我无意中翻到了一份 docker-compose 的模板,忽然灵机一动:只要我在启动新实例的时候修改 ulimit,同时把旧容器的数据目录挂载到新容器上,不久可以了吗?

于是立马开始写配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
version: '3'

services:
mysqldb:
image: mysql:5.7
container_name: mysql_test
restart: always
ports:
- 3306:3306
ulimits:
nproc: 65535
nofile:
soft: 20000
hard: 40000
volumes:
- "旧容器的数据目录:/var/lib/mysql"

然后使用 docker-compose up -d 命令启动容器,成功在新的数据库里恢复了旧容器的数据。