>

适用场景:把 PostgreSQL / MySQL / Redis / MongoDB / ClickHouse 跑进 Docker,却担心「数据丢、高可用、备份、主从、升级、性能」——这篇给生产级答案
> 核心结论:数据库可以容器化,但必须:持久化卷 + 固定网络 + 资源限制 + 定时备份(含 PITR)+ 监控 + 明确升级策略
> 组件:CloudNativePG(PG Operator)/ Percona(MySQL)/ Bitnami Chart / Wal-G / PBM / 连接池(PgBouncer / ProxySQL)


0. 先回答灵魂拷问:数据库到底该不该容器化?

维度容器化裸机/系统包结论
部署速度秒级 docker run分钟级手动✅ 容器快
版本隔离/多实例多容器多版本并存需编译或虚拟化✅ 容器强
数据安全⚠️ 需正确挂卷+备份直接落盘⚠️ 裸机省心
高可用/集群✅ Operator 一键手动搭✅ 容器强(K8s)
性能损耗1-3%(host 网络/裸盘挂载)无≈ 持平
升级/回滚改 tag 重跑(有风险)包管理⚠️ 各有风险
团队技能需懂 Docker/K8s传统 DBA⚠️ 看团队
>

建议:生产单机/小规模 → Docker Compose + 固定卷 + 定时备份;需要 HA/自愈/多租户 → K8s Operator(CloudNativePG / Percona)。纯裸机跑唯一数据库且不折腾 → 系统包也行。


1. PostgreSQL 容器化(CloudNativePG Operator,生产首选)

1.1 为什么选 CloudNativePG(而非普通 Deployment)

特性普通 docker runCloudNativePG
主从复制(流复制)手动配置Operator 全自动
自动故障转移❌ 手搓✅ 自动 promote
备份/恢复(PITR)手动 pg_dump✅ Wal-G + S3 + 时间点恢复
滚动升级停机 or 手动✅ Operator 调度
监控指标手动✅ 内置 cnpg exporter

1.2 安装 Operator + 部署集群

# 1. 安装 CloudNativePG Operator(k3s 集群内)
kubectl apply -f https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/release-1.24/releases/cnpg-1.24.0.yaml

# 2. 创建 PostgreSQL 集群(3 实例 + 自动备份到 S3/MinIO)
kubectl apply -f - <<'EOF'
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: pg-cluster
spec:
  instances: 3
  imageName: ghcr.io/cloudnative-pg/postgresql:16.3
  storage:
    size: 100Gi
    storageClass: longhorn
  postgresql:
    parameters:
      max_connections: "200"
      shared_buffers: "2GB"
      effective_cache_size: "6GB"
      work_mem: "16MB"
      maintenance_work_mem: "512MB"
      random_page_cost: "1.1"
      effective_io_concurrency: "200"
  bootstrap:
    initdb:
      database: app
      owner: app
      secret:
        name: pg-secret
  backup:
    barmanObjectStore:
      destinationPath: s3://pg-backups/
      endpointURL: http://minio.local:9000
      s3Credentials:
        accessKeyId:
          name: minio-secret
          key: access-key
        secretAccessKey:
          name: minio-secret
          key: secret-key
      wal:
        compression: gzip
    retentionPolicy: "30d"
  monitoring:
    enablePodMonitor: true
EOF

1.3 连接与验证

# 获取连接信息
kubectl get secret pg-cluster-app -o jsonpath='{.data.password}' | base64 -d

# 主从状态
kubectl get cluster pg-cluster
kubectl get pods -l cnpg.io/cluster=pg-cluster

# 故障转移演练:删主 Pod → Operator 自动 promote 从库
kubectl delete pod pg-cluster-1
kubectl get pods -w   # 观察自动切换

1.4 PITR(时间点恢复)

# 恢复到某时间点(如误删数据前 1 小时)
kubectl apply -f - <<'EOF'
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: pg-cluster-restore
spec:
  instances: 1
  bootstrap:
    recovery:
      source: pg-cluster
      recoveryTarget:
        targetTime: "2026-09-13T10:00:00Z"
  storage:
    size: 100Gi
EOF

2. 单机 Docker Compose 数据库(家庭/小型生产)

2.1 PostgreSQL 单实例 + PgBouncer 连接池

services:
  postgres:
    image: postgres:16-alpine
    container_name: postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${PG_PASSWORD}
      POSTGRES_DB: app
      TZ: Asia/Shanghai
    volumes:
      - /data/postgres/data:/var/lib/postgresql/data
      - /data/postgres/backup:/backup
    # 连接池 + 避免 OOM
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: "2"
    command:
      - "postgres"
      - "-c", "max_connections=200"
      - "-c", "shared_buffers=512MB"
      - "-c", "effective_cache_size=1.5GB"
      - "-c", "work_mem=16MB"
      - "-c", "maintenance_work_mem=256MB"
      - "-c", "wal_level=replica"
      - "-c", "archive_mode=on"
      - "-c", "archive_command=test ! -f /backup/archive/%f && cp %p /backup/archive/%f"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 10s
      timeout: 5s
      retries: 5

  pgbouncer:
    image: edoburu/pgbouncer:latest
    container_name: pgbouncer
    restart: unless-stopped
    environment:
      DB_USER: app
      DB_PASSWORD: ${PG_PASSWORD}
      DB_HOST: postgres
      DB_NAME: app
      POOL_MODE: transaction
      MAX_CLIENT_CONN: 500
      DEFAULT_POOL_SIZE: 50
    ports:
      - "6432:5432"
    depends_on:
      postgres:
        condition: service_healthy

2.2 MySQL 单实例 + 主从(Bitnami Charts 或原生)

services:
  mysql:
    image: mysql:8.4
    container_name: mysql
    restart: unless-stopped
    command:
      - --character-set-server=utf8mb4
      - --collation-server=utf8mb4_unicode_ci
      - --default-authentication-plugin=mysql_native_password
      - --max_connections=300
      - --innodb-buffer-pool-size=1G
      - --innodb-flush-log-at-trx-commit=1
      - --binlog-format=ROW
      - --gtid-mode=ON
      - --enforce-gtid-consistency=ON
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: app
      MYSQL_USER: app
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      TZ: Asia/Shanghai
    volumes:
      - /data/mysql/data:/var/lib/mysql
      - /data/mysql/backup:/backup
      - /data/mysql/conf:/etc/mysql/conf.d
    deploy:
      resources:
        limits: {memory: 2G, cpus: "2"}
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5

3. Redis 容器化(主从 + 哨兵 / Cluster)

3.1 单实例

services:
  redis:
    image: redis:7-alpine
    container_name: redis
    restart: unless-stopped
    command: [
      "redis-server",
      "--appendonly", "yes",
      "--appendfsync", "everysec",
      "--maxmemory", "1gb",
      "--maxmemory-policy", "allkeys-lru",
      "--save", "900", "1", "300", "10", "60", "10000"
    ]
    volumes:
      - /data/redis/data:/data
    deploy:
      resources:
        limits: {memory: 1.2G, cpus: "1"}

3.2 主从 + 哨兵(高可用)

services:
  redis-master:
    image: redis:7-alpine
    command: ["redis-server", "--appendonly", "yes", "--requirepass", "${REDIS_PASSWORD}"]
    volumes: [/data/redis/master:/data]

  redis-replica:
    image: redis:7-alpine
    command: ["redis-server", "--replicaof", "redis-master", "6379", "--masterauth", "${REDIS_PASSWORD}", "--appendonly", "yes"]
    volumes: [/data/redis/replica:/data]
    depends_on: [redis-master]

  sentinel-1:
    image: redis:7-alpine
    command: ["redis-sentinel", "/etc/redis/sentinel.conf"]
    volumes:
      - /data/redis/sentinel1:/data
      - ./sentinel.conf:/etc/redis/sentinel.conf
  sentinel-2:
    image: redis:7-alpine
    command: ["redis-sentinel", "/etc/redis/sentinel.conf"]
    volumes: [/data/redis/sentinel2:/data, ./sentinel.conf:/etc/redis/sentinel.conf]
  sentinel-3:
    image: redis:7-alpine
    command: ["redis-sentinel", "/etc/redis/sentinel.conf"]
    volumes: [/data/redis/sentinel3:/data, ./sentinel.conf:/etc/redis/sentinel.conf]
# sentinel.conf
port 26379
sentinel monitor mymaster redis-master 6379 2
sentinel auth-pass mymaster ${REDIS_PASSWORD}
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel parallel-syncs mymaster 1

4. MongoDB 容器化(副本集)

services:
  mongo1:
    image: mongo:7.0
    container_name: mongo1
    restart: unless-stopped
    command: ["mongod", "--replSet", "rs0", "--bind_ip_all", "--wiredTigerCacheSizeGB", "1"]
    volumes: [/data/mongo1:/data/db]
    healthcheck:
      test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
      interval: 10s
      timeout: 5s
      retries: 5
  mongo2:
    image: mongo:7.0
    command: ["mongod", "--replSet", "rs0", "--bind_ip_all"]
    volumes: [/data/mongo2:/data/db]
  mongo3:
    image: mongo:7.0
    command: ["mongod", "--replSet", "rs0", "--bind_ip_all"]
    volumes: [/data/mongo3:/data/db]
# 初始化副本集
docker exec -it mongo1 mongosh --eval '
rs.initiate({_id:"rs0", members:[
  {_id:0, host:"mongo1:27017"},
  {_id:1, host:"mongo2:27017"},
  {_id:2, host:"mongo3:27017"}
]})'

5. ClickHouse / OLAP 容器化(可选)

services:
  clickhouse:
    image: clickhouse/clickhouse-server:24.6
    container_name: clickhouse
    restart: unless-stopped
    ulimits:
      nofile: {soft: 262144, hard: 262144}
    ports:
      - "8123:8123"   # HTTP
      - "9000:9000"   # native
    volumes:
      - /data/clickhouse/data:/var/lib/clickhouse
      - /data/clickhouse/log:/var/log/clickhouse-server
    environment:
      CLICKHOUSE_DB: analytics
      CLICKHOUSE_USER: app
      CLICKHOUSE_PASSWORD: ${CH_PASSWORD}

6. 备份策略(比数据库本身更重要)

6.1 PostgreSQL:逻辑 + 物理(PITR)

# 逻辑备份(每日全量,快速恢复单库表)
docker exec postgres pg_dump -U app -Fc app > /backup/app_$(date +%F).dump

# 物理备份(WAL 归档 + 基础备份,支持任意时间点恢复)
docker exec postgres pg_basebackup -U postgres -D /backup/base_$(date +%F) -Ft -z -X fetch

# WAL 归档(配 archive_command,见上文 compose)
# 恢复:先恢复基础备份,再 replay WAL 到目标时间点
docker run --rm -v /backup:/backup -v /restore:/restore postgres:16-alpine \
  pg_restore -U app -d app /restore/app_2026-09-13.dump

6.2 MySQL:mysqldump + binlog(PITR)

# 全量
docker exec mysql mysqldump -uroot -p${MYSQL_ROOT_PASSWORD} --single-transaction --master-data=2 --all-databases > /backup/mysql_$(date +%F).sql

# binlog 持续归档(配 expire_logs_days、binlog_expire_logs_seconds)
# PITR:恢复全量 → mysqlbinlog 回放 binlog 到目标时间点

6.3 Redis:RDB + AOF 双写

# 配置里 combined: appendonly yes + save 定期 RDB
# 备份:直接拷贝 dump.rdb + appendonly.aof
docker exec redis redis-cli BGSAVE
cp /data/redis/data/dump.rdb /backup/redis_dump_$(date +%F).rdb

6.4 MongoDB:mongodump + oplog

docker exec mongo1 mongodump --host rs0/mongo1:27017,mongo2:27017,mongo3:27017 --oplog --out /backup/mongo_$(date +%F)

6.5 集中备份调度(统一脚本 + systemd timer / cron)

#!/usr/bin/env bash
# /usr/local/bin/backup-all-db.sh
set -euo pipefail
BACKUP_DIR="/data/backup/db"
RETENTION_DAYS=30

echo "=== 数据库备份 $(date -Is) ==="
mkdir -p "$BACKUP_DIR"/{postgres,mysql,redis,mongo}

# PostgreSQL
docker exec postgres pg_dump -U app -Fc app | gzip > "$BACKUP_DIR/postgres/app_$(date +%F).dump.gz"

# MySQL
docker exec mysql mysqldump -uroot -p"${MYSQL_ROOT_PASSWORD}" --all-databases --single-transaction | gzip > "$BACKUP_DIR/mysql/all_$(date +%F).sql.gz"

# Redis
docker exec redis redis-cli BGSAVE
cp /data/redis/data/dump.rdb "$BACKUP_DIR/redis/dump_$(date +%F).rdb"

# MongoDB
docker exec mongo1 mongodump --oplog --out /tmp/mongo_dump
tar -czf "$BACKUP_DIR/mongo/mongo_$(date +%F).tar.gz" -C /tmp mongo_dump

# 清理 30 天前
find "$BACKUP_DIR" -type f -mtime +$RETENTION_DAYS -delete

# 同步到远程(S3/另一台 NAS)
rclone sync "$BACKUP_DIR" remote-s3:db-backups --retries 3
echo "=== 完成 ==="
echo "0 3 * * * /usr/local/bin/backup-all-db.sh" >> /etc/crontabs/root
/etc/init.d/cron restart

7. 监控与告警(数据库必须盯的指标)

指标说明告警阈值参考
连接数pg_stat_activity / Threads_connected> 80% max_connections
慢查询log_min_duration_statement / slow_query_log> 1s 或按业务
缓冲池命中率pg_buffercache / Innodb_buffer_pool_read_requests< 95%
WAL/Binlog 落后pg_wal_lsn_diff / Seconds_Behind_Master> 60s
磁盘使用率数据目录 + WAL + 备份> 80%
锁等待pg_locks / innodb_lock_waits> 10s
复制延迟主从 lag> 30s
死锁/回滚率pg_stat_database.deadlocks / Innodb_row_lock_waits增长趋势
# postgres_exporter + mysqld_exporter + redis_exporter(Prometheus)
services:
  postgres-exporter:
    image: prometheuscommunity/postgres-exporter:latest
    environment:
      DATA_SOURCE_URI: "postgres:5432/app?sslmode=disable"
      DATA_SOURCE_USER: app
      DATA_SOURCE_PASS: ${PG_PASSWORD}
    ports: ["9187:9187"]

  mysqld-exporter:
    image: prom/mysqld-exporter:latest
    environment:
      DATA_SOURCE_NAME: "app:${MYSQL_PASSWORD}@(mysql:3306)/"
    ports: ["9104:9104"]

  redis-exporter:
    image: oliver006/redis_exporter:latest
    environment:
      REDIS_ADDR: "redis://redis:6379"
      REDIS_PASSWORD: ${REDIS_PASSWORD}
    ports: ["9121:9121"]

8. 升级策略(最容易翻车的环节)

数据库安全升级路径危险操作
PostgreSQLpg_dumpall → 新版本容器 → 导入,或 pg_upgrade直接改镜像 tag 原地升级(大版本)
MySQLmysqldump → 新版本,或 mysql_upgrade跳过 2+ 个大版本
Redis先 BGSAVE,改 tag 重启(小版本),RDB 兼容混用不同版本 AOF
MongoDB逐版本升级(5→6→7),cannot skip直接 5→7
# PostgreSQL 大版本升级(16 → 17)安全流程
# 1. 全量备份
docker exec postgres pg_dumpall -U postgres > /backup/all_$(date +%F).sql
# 2. 起新版本容器(新数据目录)
docker run -d --name postgres17 -v /data/postgres17:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=xxx postgres:17-alpine
# 3. 导入
docker exec -i postgres17 psql -U postgres < /backup/all_$(date +%F).sql
# 4. 验证后切换
docker stop postgres && docker rename postgres postgres16
docker rename postgres17 postgres

9. 常见坑 & 避坑指南

现象原因修正
容器重启后数据丢失没挂卷,数据在容器层必须 volumes: 挂到宿主机/命名卷
数据库 OOM 被杀无内存限制,max_connections 过大设 deploy.resources.limits.memory,调参 shared_buffers/innodb_buffer_pool
主从延迟高备库性能差 / 网络抖动提高备库配置、synchronous_commit=remote_apply(数据零丢失)
PITR 恢复失败WAL/Binlog 未归档或过期配 archive_command/binlog_expire,定期演练恢复
容器 /var/lib/mysql 权限错挂载空目录 SELinux 上下文不对:Z/:z 挂载或 chcon -Rt svirt_sandbox_file_t
连接数打满没上连接池PgBouncer / ProxySQL,max_connections 调到 200,池化到 1000+
备份文件损坏未校验定期 pg_restore --list / mysql -e "CHECK TABLE" 验证

10. 资源 & 参考


(完)

— 感谢阅读 —

一起交流

分享你的思考,让讨论更进一步。