97、数据库容器化:Docker部署、Kubernetes编排、持久化存储、弹性伸缩
说实话,数据库容器化这个话题,我犹豫了很久才决定放进这100例里。
为什么犹豫?因为很多老派的DBA一听“数据库上容器”就摇头。我自己早年也踩过不少坑——容器一重启,数据丢了;Pod一漂移,连不上了;流量一上来,磁盘不够了……
但这两年,随着Kubernetes生态成熟,加上StatefulSet、CSI、Operator这些技术的完善,数据库容器化已经不是“能不能做”的问题,而是“怎么做才稳”的问题。今天我就把实战中沉淀下来的一套方法,掰开了讲给你听。
一、先画张图:容器化数据库的整体架构
动手之前,脑子里得有个全局图。我习惯把数据库容器化拆成四个层次:
你看,从下往上,每一层解决一个核心问题:基础设施层提供计算资源,存储层保证数据不丢,编排层管理生命周期,应用层负责接入。嗯,这个分层思路,我建议你记下来,后面所有操作都围绕它展开。
二、Docker部署:先让数据库跑起来
容器化第一步,就是把数据库装进Docker镜像里。别小看这一步,我见过有人直接把官方镜像拉下来就跑,结果字符集不对、时区不对、数据目录没挂载……
我个人习惯,写一个干净的Dockerfile:
# Dockerfile - 以MySQL 8.0为例
FROM mysql:8.0.33
# 设置时区
ENV TZ=Asia/Shanghai
# 复制自定义配置文件
COPY my.cnf /etc/mysql/conf.d/my.cnf
# 初始化脚本(可选)
COPY init.sql /docker-entrypoint-initdb.d/
EXPOSE 3306
VOLUME ["/var/lib/mysql"]
对应的my.cnf,我一般会加上这几行:
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
max_connections=500
innodb_buffer_pool_size=2G
启动命令很简单:
docker run -d \
--name mysql-prod \
-e MYSQL_ROOT_PASSWORD=MyStr0ng!Pass \
-v /data/mysql:/var/lib/mysql \
-p 3306:3306 \
mysql:8.0.33
-v /data/mysql:/var/lib/mysql 是持久化的关键。我曾经有一次忘记挂载卷,容器重启后数据库直接变空——那感觉,就像写了一天代码没保存。所以,生产环境必须挂载宿主机目录或使用命名卷。
三、Kubernetes编排:用StatefulSet管理有状态服务
单机Docker跑数据库,只能算“尝鲜”。真正上生产,得用Kubernetes编排。这里有个关键点:数据库是有状态的,不能用Deployment,得用StatefulSet。
为什么?Deployment的Pod是无状态的,重启后名字、IP都会变。而StatefulSet保证每个Pod有稳定的网络标识和持久化存储。我当年第一次用Deployment部署MySQL,Pod一重启,从库找不到主库了……后来换成StatefulSet,世界清净了。
来看一个精简的StatefulSet定义:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql-headless
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0.33
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 50Gi
volumeClaimTemplates 这个字段。它会为每个Pod自动创建独立的PVC。Pod-0对应PVC-data-mysql-0,Pod-1对应PVC-data-mysql-1。这样每个实例的数据是隔离的,不会互相干扰。
配合一个Headless Service,让Pod之间通过DNS直连:
apiVersion: v1
kind: Service
metadata:
name: mysql-headless
spec:
clusterIP: None
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
这样一来,mysql-0.mysql-headless.default.svc.cluster.local 就能稳定访问到第一个实例。主从复制配置里,直接写这个地址就行。
四、持久化存储:数据不能丢
容器化数据库最大的痛点,就是存储。Pod可以漂,但数据必须原地不动。
Kubernetes里,持久化存储靠PVC和PV。我建议你用动态存储供应,而不是手动创建PV。比如用阿里云的NAS、AWS的EBS,或者自建的Rook/Ceph。
下面是一个StorageClass的例子:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp3
fsType: ext4
reclaimPolicy: Retain
🔑 关键配置: reclaimPolicy: Retain 表示PVC删除后,PV保留。这样即使误删了StatefulSet,数据还在。我建议生产环境都用Retain,别用Delete——别问我怎么知道的,说多了都是泪。
另外,备份策略一定要提前规划。我常用的做法是:
- 每天凌晨用
mysqldump全量备份,存到对象存储 - 每6小时做一次binlog增量备份
- 备份脚本以CronJob形式跑在K8s集群里
一个简单的备份CronJob:
apiVersion: batch/v1
kind: CronJob
metadata:
name: mysql-backup
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: mysql:8.0.33
command:
- /bin/sh
- -c
- |
mysqldump -h mysql-0.mysql-headless \
-u root -p$MYSQL_ROOT_PASSWORD \
--all-databases > /backup/mysql-$(date +%Y%m%d).sql
restartPolicy: OnFailure
五、弹性伸缩:流量大了怎么办?
数据库不像Web服务,不能随便加Pod。加个只读副本,还得同步数据。但Kubernetes的HPA(Horizontal Pod Autoscaler)配合StatefulSet,还是能实现一定程度的弹性。
我一般这样设计:
- 主库:不参与伸缩,保持单点写入
- 从库:根据CPU或连接数自动扩缩
- 读写分离:应用层通过Proxy(如ProxySQL)路由
HPA配置示例:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: mysql-read-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: StatefulSet
name: mysql-read
minReplicas: 2
maxReplicas: 8
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
readinessProbe,等同步完成后再加入服务。另外,不要对主库做HPA,主库扩缩容易引发数据一致性问题。
六、避坑指南:我踩过的几个坑
最后,分享几个实战中遇到的坑,希望能帮你省点时间:
- 坑一:容器内文件权限。MySQL镜像默认用mysql用户运行,但挂载的宿主机目录可能是root权限。解决办法:在Dockerfile里用
chown,或者在宿主机上chown -R 999:999 /data/mysql。 - 坑二:Pod重启后IP变了。主从复制配置里如果写死了IP,Pod重启就废了。一定要用StatefulSet的DNS名称,比如
mysql-0.mysql-headless。 - 坑三:磁盘空间不足。数据库日志增长很快,尤其是binlog。我建议在StatefulSet里加上
resources.limits.ephemeral-storage,并配置日志自动清理。 - 坑四:网络延迟。跨AZ部署时,主从同步延迟可能飙升。如果业务对一致性要求高,建议把主从放在同一个可用区。
好了,数据库容器化这条路,说难不难,说简单也不简单。核心就三句话:存储要持久,网络要稳定,扩缩要谨慎。把这三点拿捏住,你的数据库在容器里跑个三五年没问题。
公众号:蓝海资料掘金营,微信deep3321