3.2 数据库服务模板


3.2 数据库服务模板

本节摘要:数据库服务模板给出 MySQL、PostgreSQL、MongoDB 三份可直接抄的 compose 配置,覆盖环境变量初始化用户与库、数据卷持久化、健康检查、初始化脚本挂载、字符集与时区五项能力,并讨论数据库端口为何不该对外暴露、主从变体怎么写。读完后你能在五分钟内起一个带账号、带健康检查、数据不会丢的数据库服务。

阅读收获

  1. 能写出 MySQL、PostgreSQL、MongoDB 三份完整 compose 配置
  2. 能说清三类初始化环境变量的各自职责与首次生效条件
  3. 能配置 pg_isready、mysqladmin ping、mongosh 三种健康检查
  4. 能挂载 /docker-entrypoint-initdb.d 初始化脚本并理解执行时机
  5. 能解释数据库端口不对外暴露的理由并落地内部网络方案

一、镜像选型与版本策略

数据库镜像优先用官方镜像。官方镜像经过安全审查,行为也最接近原生发行版。版本号要写具体而不是 latest:mysql:8.0postgres:14mongo:5.0redis:7 都比 latest 可靠,因为 latest 会在你不知情时升级大版本,主版本升级往往伴随不兼容变更,哪天重启后连不上库,排查成本远高于当初选版本的成本。

选型还要看业务形态:MySQL 生态成熟、运维资料多,是默认选择;PostgreSQL 在复杂查询、JSON 字段、窗口函数上更强;MongoDB 适合文档型数据模型,字段结构频繁变化的业务最省心。三份模板结构几乎一样,差异只在环境变量名和数据目录。

二、MySQL 模板

services: db: image: mysql:8.0 container_name: mysql_db restart: always environment: MYSQL_ROOT_PASSWORD: root_password MYSQL_DATABASE: my_database MYSQL_USER: my_user MYSQL_PASSWORD: my_password ports: - "3306:3306" volumes: - db_data:/var/lib/mysql - ./initdb:/docker-entrypoint-initdb.d:ro healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"] timeout: 20s retries: 3 start_period: 5s volumes: db_data:

关键行逐个说:

  • MYSQL_ROOT_PASSWORD:root 账号密码,首次初始化数据目录时生效并写入配置。
  • MYSQL_DATABASEMYSQL_USERMYSQL_PASSWORD:初始化时自动建库、建用户、授权,业务连接串直接用它,不要用 root 跑业务。
  • db_data:/var/lib/mysql:数据目录挂命名卷。MySQL 8 的数据目录结构在首次启动时生成,卷里一旦有数据,环境变量就不再生效——这是理解数据库容器的一把钥匙:初始化只发生在空卷上
  • ./initdb:/docker-entrypoint-initdb.d:ro:把宿主机的 SQL 脚本目录挂进去,首次初始化时按文件名顺序自动执行。
  • 健康检查用 mysqladmin ping 探测本机,-p${MYSQL_ROOT_PASSWORD} 引用 compose 同目录 .env 里的变量,避免把密码写死在 yml 里。start_period: 5s 表示启动后 5 秒内不计数失败,给首次初始化留时间;retries: 3 是连续失败三次才判定不健康。

字符集与时区:MySQL 8 默认字符集是 utf8mb4,表情符号等四字节字符没问题,但排序规则默认是 latin1 系的旧习惯可能要改。稳妥做法是在配置里显式声明:

command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci environment: TZ: Asia/Shanghai

command 追加 mysqld 启动参数;TZ 环境变量让容器内日志时间与业务时区对齐。写入的数据和时区是两回事,时区只影响 NOW 这类函数的取值,连接串里也可以单独指定,但容器层先对齐能少很多迷惑。

连接串写法:应用容器里主机名直接写服务名 db,Python 的 SQLAlchemy 连接串形如 mysql+pymysql://my_user:my_password@db:3306/my_database,端口是容器内部端口 3306,与宿主机映射无关。

三、PostgreSQL 模板

PostgreSQL 与 MySQL 的模板只差三个环境变量名和一个数据目录:

services: db: image: postgres:14 container_name: postgres_db restart: always environment: POSTGRES_USER: my_user POSTGRES_PASSWORD: my_password POSTGRES_DB: my_database TZ: Asia/Shanghai ports: - "5432:5432" volumes: - db_data:/var/lib/postgresql/data - ./initdb:/docker-entrypoint-initdb.d:ro healthcheck: test: ["CMD-SHELL", "pg_isready -U my_user -d my_database"] timeout: 20s retries: 3 start_period: 5s volumes: db_data:
  • POSTGRES_USER 同时创建超级用户与同名数据库,POSTGRES_DB 再补一个业务库。注意这里没有独立的 root 概念,默认用户就是 POSTGRES_USER,密码即 POSTGRES_PASSWORD。
  • 数据目录 /var/lib/postgresql/data 挂命名卷。不要挂成绑定目录加子路径,比如把宿主目录直接挂到该路径,PostgreSQL 会因为目录权限或属主不一致拒绝启动。
  • 健康检查用 pg_isready -U my_user -d my_database,它只回答"服务器能不能接受连接",不验证密码,正合适做探活。
  • 初始化脚本目录同名 /docker-entrypoint-initdb.d,MySQL、PostgreSQL、MongoDB 三家的镜像共用这套约定:只放 .sql 或 .sh 文件,首次空卷初始化时按字典序执行。

时区写法不同:PostgreSQL 里容器 TZ 只影响日志,数据库内部时区要用 command: ["postgres", "-c", "timezone=Asia/Shanghai"] 或建库时指定。实践里更常见的做法是应用层统一用 UTC 存储、展示层转换,避免每个环境改时区。

⚠️ PostgreSQL 的 UID 是 999:官方镜像以 uid 999 运行。如果做绑定挂载,宿主目录要先 chown 999:999,否则容器没有写权限,日志里会出现 permission denied。用命名卷则没有这个问题,卷由 Docker 管理属主。

四、MongoDB 模板

MongoDB 默认不鉴权,直接映射端口等于裸奔,模板里第一步就是开启账号:

services: db: image: mongo:5.0 container_name: mongo_db restart: always environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: root_password ports: - "27017:27017" volumes: - db_data:/data/db - ./initdb:/docker-entrypoint-initdb.d:ro healthcheck: test: ["CMD", "mongosh", "--eval", "db.runCommand({ping:1})"] timeout: 20s retries: 3 start_period: 5s volumes: db_data:
  • MONGO_INITDB_ROOT_USERNAMEMONGO_INITDB_ROOT_PASSWORD 创建 root 管理员;MONGO_INITDB_DATABASE 可选,指定后把初始化脚本应用到该库。
  • 数据目录是 /data/db。MongoDB 4.4 起官方镜像不再支持 --auth 参数,鉴权完全由环境变量控制,所以这两个变量是必填项而不是可选项。
  • 健康检查用 mongosh --eval db.runCommand({ping:1}),返回 ok 即存活。老版本镜像里没有 mongosh 只有 mongo shell,命令要换成 mongo --eval "db.runCommand({ping:1})",镜像版本换了检查命令也要跟着换。
  • 初始化脚本里可以用 mongosh 创建业务库和业务用户,脚本同样只在首次空卷初始化时执行。

五、初始化脚本机制详解

三家数据库镜像都支持 /docker-entrypoint-initdb.d,但执行规则要记准,这是线上事故的高发区:

  • 只在数据目录为空时执行。卷里已有数据的库,重启容器不会执行任何脚本。想改表结构?改脚本没用,得用迁移工具或手动执行。
  • 按文件名顺序执行,习惯用 01_init.sql02_seed.sql 这样的编号控制顺序。
  • .sql 文件按顺序导入;.sh 文件需要可执行权限,且要自己处理连接参数,比如 mysql -u root -p"$MYSQL_ROOT_PASSWORD" < /docker-entrypoint-initdb.d/data.sql
  • 初始化失败会导致容器启动失败。脚本里写错语法、引用了不存在的库,容器会反复重启,docker compose logs db 能看到具体报错行。
  • 初始化阶段没有健康检查,容器状态是 starting,依赖它的服务要等 healthy 之后才能连。

整套启动逻辑用状态图可以一眼看穿,排查"为什么我的脚本没执行"时先对照这张图:

六、端口不要对外暴露的实践

模板里 ports 映射是为了本地调试方便,但数据库端口暴露到宿主机意味着:任何能访问宿主机的人都能直接连数据库,绕过应用层的鉴权和限流。生产部署我们通常这么做:

services: db: image: postgres:14 environment: POSTGRES_USER: my_user POSTGRES_PASSWORD: my_password POSTGRES_DB: my_database volumes: - db_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U my_user -d my_database"] app: build: ./app depends_on: db: condition: service_healthy environment: DATABASE_URL: postgresql://my_user:my_password@db:5432/my_database

删掉 ports 段,应用容器与服务名 db 同网络,直接走内部端口 5432。需要临时管理数据库时,用 docker compose exec db psql -U my_user -d my_database 进容器操作,或者只把管理端口映射到回环地址 "127.0.0.1:5432:5432",宿主机以外的机器连不进来。

💡 备份也走内部网络:备份容器与数据库同网络,定时任务里用服务名连接。备份策略的完整做法见第 4 章,这里只提醒一点:先验证备份文件能恢复,再谈备份周期。

连接池与连接数上限。数据库容器默认的连接上限是镜像出厂值,MySQL 是 151,PostgreSQL 是 100。多个应用容器共用一个库时,每个应用连接池的默认大小乘容器数量,很容易撞上限,症状是间歇性的 connection refused。两类解法配合用:应用侧把连接池 max 压到合理值,比如 10 到 20;数据库侧按需调大,MySQL 在 command 里加 --max_connections=300,PostgreSQL 用 command: ["postgres", "-c", "max_connections=200"]。改上限前先想清楚:每个连接都有内存开销,盲开一千个连接不如让应用复用连接。

账号权限最小化。初始化环境变量只造了"一个用户一个库"的粗粒度权限。要再细分,初始化脚本里补授权语句:给只读报表应用建 GRANT SELECT 账号,给运维建带备份权限的专用账号,业务账号只拥有自己库的 DML 权限。授权语句写进 initdb 脚本的好处是随环境重建自动生效,不用每次手动敲。权限最小化在单机开发时看不出价值,但库一旦暴露在办公网,一个万能账号就是全网数据库的后门。

七、主从变体

单库扛不住读写分离需求时,可以先用 compose 拉一个演示级主从。以 MySQL 为例,主库和两个从库各挂各的卷:

services: master: image: mysql:8.0 restart: always environment: MYSQL_ROOT_PASSWORD: root_password MYSQL_REPLICATION_PASSWORD: replication_password ports: - "3306:3306" volumes: - master_data:/var/lib/mysql slave1: image: mysql:8.0 restart: always environment: MYSQL_REPLICATION_PASSWORD: replication_password MYSQL_REPLICATION_HOST: master volumes: - slave1_data:/var/lib/mysql depends_on: - master slave2: image: mysql:8.0 restart: always environment: MYSQL_REPLICATION_PASSWORD: replication_password MYSQL_REPLICATION_HOST: master volumes: - slave2_data:/var/lib/mysql depends_on: - master volumes: master_data: slave1_data: slave2_data:

MYSQL_REPLICATION_HOST 指向主库的服务名 master,从库靠它找到复制源;depends_on 保证主库先启动。这套配置能跑通主从同步,但离生产还有距离:没有 GTID 校验、没有故障自动切换、从库也不能自愈。真要上生产,要么用云厂商托管,要么上专门的复制编排工具,compose 只负责把拓扑搭出来。

图 3-3 数据库主从与持久化拓扑图

图 3-3 数据库主从与持久化拓扑图

八、备份与恢复的容器化做法

数据库模板配完只是开始,真正决定数据安全的是备份链路。compose 里加一个备份容器是常见套路,以 PostgreSQL 为例:

backup: image: postgres:14 restart: "no" depends_on: db: condition: service_healthy environment: PGPASSWORD: my_password volumes: - ./backups:/backups command: > sh -c "pg_dump -h db -U my_user -d my_database -F c -f /backups/db_$(date +%F_%H%M).dump && ls -t /backups/*.dump | tail -n +8 | xargs -r rm"
  • 备份容器复用 postgres 镜像,pg_dump 是镜像自带的,不需要额外安装客户端。
  • -F c 输出自定义格式,恢复用 pg_restore,比纯 SQL 更灵活,可以只恢复部分表。
  • 文件名带时间戳,配合 ls -t | tail -n +8 | xargs rm 只保留最近 7 份,防止备份目录无限膨胀。
  • restart: "no":备份是一次性任务,跑完即退出,不该被 restart 策略反复拉起。定时执行交给宿主 cron 或 3.6 节的 Airflow。
  • MySQL 对应 mysqldump -u my_user -p密码 -h db my_database > 文件.sql,MongoDB 对应 mongodump --uri,三条命令的结构完全一致:进容器、连服务名、导出到挂载目录。

恢复演练比备份本身重要。备份文件躺了三个月没人碰,等到事故现场才发现格式不对、权限不对,那备份等于没有。建议每季度做一次恢复演练:起一个全新 compose 项目,把 dump 恢复进去,抽查几条关键数据比对。MySQL 的恢复要小心:mysqldump 默认不含建库语句时,要先手动 CREATE DATABASE 再导入,漏了这步会报 Unknown database。

卷级备份是兜底。逻辑备份(dump)之外,数据卷本身也要有副本:停库状态下把卷内容复制出来,或者用 docker run --rm -v db_data:/data -v 宿主机目录:/backup alpine tar czf /backup/db_data.tar.gz -C /data . 打一个卷的 tar 包。卷级备份恢复快,但只适合同版本数据库;逻辑备份可跨版本迁移,两者互补,别只做一种。

资源限制:数据库容器建议直接给足资源上限,避免与其他服务抢内存。compose 里加 mem_limit: 1gcpus: "2.0",MySQL 的 buffer pool、PostgreSQL 的 shared_buffers 都按这个上限调参。数据库是内存敏感型应用,配额给少了性能跳水,给多了挤垮邻居,这个度要靠压测找。

数据库模板参数对照

项目 MySQL PostgreSQL MongoDB
镜像 mysql:8.0 postgres:14 mongo:5.0
超级用户变量 MYSQL_ROOT_PASSWORD POSTGRES_USER MONGO_INITDB_ROOT_USERNAME
业务库变量 MYSQL_DATABASE POSTGRES_DB MONGO_INITDB_DATABASE
数据目录 /var/lib/mysql /var/lib/postgresql/data /data/db
健康检查 mysqladmin ping pg_isready mongosh ping
初始化目录 docker-entrypoint-initdb.d docker-entrypoint-initdb.d docker-entrypoint-initdb.d
容器内 UID 默认 root 运行 999 默认 root 运行

要点串联

  1. 初始化只发生在空卷上:数据目录有内容后,环境变量和初始化脚本都不再生效
  2. 业务账号与超级账号分离:业务连接用 MYSQL_USER 或 POSTGRES_USER,别拿 root 跑业务
  3. 健康检查是组合的入场券:应用容器用 service_healthy 等数据库就绪
  4. 字符集与时区显式声明:MySQL 补 command 参数,PG 用 timezone 配置,别指望默认值
  5. 生产库不暴露端口:内部网络访问,临时管理用 exec 或回环地址映射
  6. 绑定挂载要注意 UID:PostgreSQL 是 999,目录属主不对直接拒绝启动
  7. 主从变体只是演示级:能同步不代表能容灾,生产高可用要另选方案
  8. 备份先验证恢复:能恢复的备份才有意义,这条比备份本身更重要

下一节给数据库加上加速器与解耦层——消息队列与缓存模板,四种组件各有各的性格。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U