Kubernetes 部署 MySQL:StatefulSet、存储与服务访问
从单实例 MySQL 的角度讲清 Kubernetes 上数据库部署的基本做法,包括 StatefulSet、持久卷和 Service 访问。
用一个单实例 MySQL 做练习,是把 Kubernetes 存储、Service 和有状态工作负载串起来的最快方式之一。它也会很快让你意识到:数据库不是“另一个普通容器”。只要牵涉到数据、初始化、探针和恢复,它就和无状态服务不是一个难度层级了。
这篇文章真正想帮你解决什么
这篇主要适合拿来理解:
- 数据库为什么一定会碰到 PVC
- 为什么单实例也需要稳定访问入口
- 为什么备份、恢复和 readiness 从第一天就值得考虑
它也是从简单模式过渡到更完整状态模型的中间一步。
一个比较现实的心智模型
单实例 MySQL 在 Kubernetes 里,最小有用组合通常是:
- 一个 MySQL 工作负载
- 一个 PVC 提供持久化数据
- 一个 Service 提供集群内稳定访问
- 一个 Secret 保存密码
所以这篇天然应该和下面几篇连着看:
kubernetes-quickstart-pv-pvc.mdkubernetes-quickstart-storageclass.mdkubernetes-quickstart-statefulset.mdkubernetes-quickstart-service.md
你会创建什么
- 一个数据库 namespace
- 一个凭据 Secret
- 一个持久卷申请
- 一个单实例 MySQL 工作负载
- 一个 ClusterIP Service
创建命名空间
kubectl create namespace db
把数据库和业务分开,后面做权限、网络策略和清理都会更清楚。
用 Secret 存密码
kubectl -n db create secret generic mysql-auth \
--from-literal=MYSQL_ROOT_PASSWORD='ChangeMe123!'
真实场景里,应用最好不要直接用 root 账号。root 更适合运维用途,而不是业务默认账号。
最小资源组合
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
namespace: db
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
namespace: db
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.0
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-auth
key: MYSQL_ROOT_PASSWORD
ports:
- containerPort: 3306
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumes:
- name: data
persistentVolumeClaim:
claimName: mysql-pvc
---
apiVersion: v1
kind: Service
metadata:
name: mysql
namespace: db
spec:
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306
应用它:
kubectl apply -f mysql.yaml
为什么这里还用 Deployment
对于单实例学习来说,Deployment 还算能接受,因为它能让示例保持足够短。
但这更像一块过渡踏板,而不是长期推荐。只要你开始关心身份稳定、重启顺序或副本扩展,StatefulSet 就会更合适。
验证 Pod、PVC 和 Service
kubectl get pods -n db
kubectl get pvc -n db
kubectl get svc -n db
如果 Pod 一直 Pending,优先检查 PVC 和 StorageClass 这条链,不要先怀疑 MySQL 本身。
用一个临时客户端验证
kubectl run -it --rm mysql-client \
--namespace db \
--image=mysql:8.0 \
--command -- bash
进入后:
mysql -h mysql.db.svc.cluster.local -u root -p
然后写一点测试数据:
CREATE DATABASE IF NOT EXISTS demo;
USE demo;
CREATE TABLE items (id INT PRIMARY KEY, name VARCHAR(64));
INSERT INTO items VALUES (1, 'hello');
这一步能同时验证存储、服务发现和凭据是否都接上了。
数据库也非常依赖 readiness
MySQL 初始化和恢复都可能比普通 Web 服务慢。如果 readiness 太激进,客户端就会在数据库“进程活着但并没准备好”时开始打流量。
livenessProbe:
exec:
command: ["mysqladmin", "ping", "-h", "127.0.0.1"]
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command: ["mysqladmin", "ping", "-h", "127.0.0.1"]
initialDelaySeconds: 10
periodSeconds: 5
这和 kubernetes-quickstart-probes.md 是直接相关的:数据库是最容易让你体会到“Running 不等于 Ready”的工作负载之一。
存储选型会很快开始影响体验
如果集群里有多个 StorageClass,PVC 最好显式指定:
spec:
storageClassName: fast-ssd
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
先确认有哪些类:
kubectl get storageclass
如果你想把这条链路看完整,建议接着读:
kubernetes-quickstart-pv-pvc.mdkubernetes-quickstart-storageclass.md
备份不是附加题
即便是单实例 MySQL,也应该尽早明确备份方式:
kubectl exec -n db deploy/mysql -- \
mysqldump -u root -p demo > backup.sql
然后一定要做一次恢复验证:
kubectl exec -n db deploy/mysql -- \
mysql -u root -p demo < backup.sql
PVC 解决的是“数据还在”,备份解决的是“坏了以后能回来”。这两件事别混在一起。
什么时候该切到 StatefulSet
如果出现这些需求,就不该再停留在这个简单模式里:
- 需要每个副本稳定身份
- 需要按副本自动创建 PVC
- 需要更稳妥的重启和顺序控制
- 开始做复制拓扑
这时下一步就应该去看:
kubernetes-quickstart-statefulset.mdkubernetes-quickstart-mysql-replication.md
FAQ
Q: 单实例 MySQL + Deployment 能直接算生产方案吗? A: 一般不建议。它更适合学习或轻量测试,不适合严肃的高可用诉求。
Q: 为什么 MySQL 看起来比普通 Web 服务难这么多? A: 因为它不仅要跑起来,还要考虑持久化、恢复、初始化顺序、探针、磁盘 IO 和密码管理。
Q: 如果 MySQL 连接不上,第一步先查什么? A: 先看 Pod 是否 Ready,再看 Service endpoints,然后看密码 Secret 和日志,最后再怀疑更深层网络问题。
下一步阅读
- 接着读
kubernetes-quickstart-statefulset.md - 如果你想继续走向主从复制,读
kubernetes-quickstart-mysql-replication.md - 如果你还想把存储链看清楚,回头读
kubernetes-quickstart-pv-pvc.md和kubernetes-quickstart-storageclass.md
收个尾
这个例子的价值就在于,它把数据库问题缩到足够小:一个 Pod、一个 PVC、一个 Service、一个 Secret。就这么点东西,已经足够让你体会到为什么有状态工作负载天然比无状态服务更需要认真对待。