讲解
无状态服务(web、API)容器化是顺风局,有状态服务(数据库)则要额外回答三个问题:数据放哪(命名卷,绝不放容器里)、初始化怎么做(首次启动的环境变量与初始化脚本)、坏了怎么恢复(备份独立于容器生命周期)。MySQL 官方镜像把这套流程做得很规范,是学「有状态容器化」最好的教材。
官方镜像的初始化约定:MYSQL_ROOT_PASSWORD 必填(或用 MYSQL_RANDOM_ROOT_PASSWORD/MYSQL_ALLOW_EMPTY_PASSWORD 显式选择);MYSQL_DATABASE、MYSQL_USER、MYSQL_PASSWORD 会在首次启动(数据目录为空)时创建业务库和普通用户——业务连接用普通用户,root 留给运维;挂载到 /docker-entrypoint-initdb.d/ 的 .sql/.sh 文件会在初始化阶段按字母序执行(建表、灌种子数据的标准位置)。关键认知:这些只在数据目录为空的首启生效,之后改环境变量没用——要改配置走配置文件或 SQL。
备份用 mysqldump 在容器里执行、输出重定向到宿主机文件(或挂备份卷);恢复则反向管道。备份文件放宿主机/对象存储,与卷分开存放——卷丢了你还有 dump。最后强调立场:容器跑数据库适合开发、测试和中小规模生产;超大规模或强合规场景,托管数据库(RDS)通常更省心——容器化不是教条。
示例
起 MySQL 8:命名卷持久化、初始化业务库与普通用户、映射宿主 13306 便于本机验证:
docker run -d --name ohmydocs-verify-mysql \
-e MYSQL_ROOT_PASSWORD=tutorial-root \
-e MYSQL_DATABASE=shop \
-e MYSQL_USER=shopper -e MYSQL_PASSWORD=shopper-pass \
-v ohmydocs-verify-mysqldata:/var/lib/mysql \
-p 13306:3306 \
mysql:8.0
等它就绪(初始化需要几十秒;注意要用业务用户探测——mysqladmin ping 在引导阶段就会响应,此时业务库和用户可能还没建好):
ok=""
for i in $(seq 1 50); do
if docker exec ohmydocs-verify-mysql mysql -ushopper -pshopper-pass shop -e 'SELECT 1' >/dev/null 2>&1; then ok=1; echo "MySQL 就绪(业务用户与库已初始化)"; break; fi
sleep 3
done
[ -n "$ok" ] || { echo "等待超时, 看日志:"; docker logs --tail 10 ohmydocs-verify-mysql; exit 1; }
用业务用户建表、写入、查询,验证库可用:
docker exec -i ohmydocs-verify-mysql mysql -ushopper -pshopper-pass shop 2>/dev/null <<'SQL'
CREATE TABLE IF NOT EXISTS products (id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(64), price DECIMAL(10,2));
INSERT INTO products (name, price) VALUES ('机械键盘', 299.00), ('无线鼠标', 99.00);
SELECT * FROM products;
SQL
备份到宿主机文件(mysqldump 输出重定向出容器),并验证备份文件内容:
docker exec ohmydocs-verify-mysql sh -c 'mysqldump -uroot -ptutorial-root shop 2>/dev/null' > shop-backup.sql
grep -c "INSERT INTO" shop-backup.sql && head -3 shop-backup.sql | tail -1
删除容器再重建(卷复用),数据还在——这就是「容器可抛、数据长存」:
docker rm -f ohmydocs-verify-mysql
docker run -d --name ohmydocs-verify-mysql -e MYSQL_ROOT_PASSWORD=tutorial-root -v ohmydocs-verify-mysqldata:/var/lib/mysql mysql:8.0
ok=""
for i in $(seq 1 50); do
if docker exec ohmydocs-verify-mysql mysql -ushopper -pshopper-pass shop -e 'SELECT COUNT(*) FROM products' >/dev/null 2>&1; then ok=1; echo "重建后就绪(products 表可查询)"; break; fi
sleep 3
done
[ -n "$ok" ] || { echo "等待超时, 看日志:"; docker logs --tail 10 ohmydocs-verify-mysql; exit 1; }
sleep 2
n=""
for i in $(seq 1 10); do
n=$(docker exec ohmydocs-verify-mysql mysql -ushopper -pshopper-pass shop -e 'SELECT COUNT(*) AS product_count FROM products;' 2>/dev/null) && break
sleep 3
done
echo "$n"
[ -n "$n" ] || { echo "查询失败, 容器状态与日志:"; docker ps -a --filter name=ohmydocs-verify-mysql; docker logs --tail 15 ohmydocs-verify-mysql; exit 1; }
docker rm -f ohmydocs-verify-mysql
常见坑
- 不挂卷跑数据库:容器一删数据全没,数据库容器的 -v 是生死线。
- 改环境变量想改已有实例:初始化变量只在空数据目录首启生效;改配置要挂 my.cnf 或执行 SQL。
- 业务连接用 root:用 MYSQL_USER 创建的低权用户,权限最小化;root 密码别出现在应用配置里。
- 备份和卷放同一故障域:卷损坏 dump 也一起没;备份要离卷、离机、离线分层存放。
小结
有状态容器三问:数据进命名卷、初始化走首启变量与 initdb 脚本、备份独立于卷;容器可抛数据长存。最后一章收束全部经验:生产最佳实践。