在 K8s 中创建有状态集 MariaDB 应用
在上一篇博客中,我们创建了一个无状态应用,使用 K8s 资源 Deployment 进行部署,这允许复制应用,但在 Pods 重启时数据会丢失,这意味着没有数据一致性。在同一篇博客中,我们使用了 PersistentVolumeClaim 进行 PersistentVolume 的动态配置,但我们使用了用于无状态应用的 Deployment,这种方式对于有状态应用(其中每个副本应拥有自己的持久卷)是Create statefulset MariaDB application in K8s*不推荐*的。实现这一目标的正确方法是通过 Statefulset 资源,本文将介绍这一点。
在 K8s 中可以创建有状态应用,例如数据库应用,它需要将数据保存到持久磁盘存储中供服务器/客户端/其他应用使用,以跟踪其状态,并能够在分布式系统中进行复制和使用。有状态应用是使用名为 StatefulSet 的 K8s 资源进行部署的。
StatefulSet 像 Deployment 一样根据容器规范部署 Pods,但为每个 Pod 维护一个固定的身份。Pods 由相同的规范创建,但不可互换,并且在重新调度后仍具有持久的标识符,这意味着当一个 Pod 死亡时,它会被一个新的 Pod 替换,但保留其身份。
有状态集示例
让我们看看配置文件可能是什么样的(可在 GitHub 上找到)。
apiVersion: v1
kind: Service
metadata:
name: mariadb-service
labels:
app: mariadb
spec:
ports:
- port: 3306
name: mariadb-port
clusterIP: None
selector:
app: mariadb
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mariadb-sts
spec:
serviceName: "mariadb-service"
replicas: 3
selector:
matchLabels:
app: mariadb
template:
metadata:
labels:
app: mariadb
spec:
containers:
- name: mariadb
image: mariadb
ports:
- containerPort: 3306
name: mariadb-port
env:
# Using Secrets
- name: MARIADB_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mariadb-secret
key: mariadb-root-password
volumeMounts:
- name: datadir
mountPath: /var/lib/mysql/
volumeClaimTemplates:
- metadata:
name: datadir
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 300M
如上所示,首先我们创建了 Service,具体来说是 Headless 类型(Cluster IP 等于 None
)的服务。StatefulSet 需要此服务来负责 Pods 的网络身份,但我们需要创建它。它用于 MariaDB Pods 和集群内客户端之间的 DNS 查询。
VolumeClaimTemplates 是 Pods 被允许引用的声明列表。此列表中的每个声明必须在模板中的一个容器中至少有一个匹配(按名称)的 volumeMount。此列表中的声明优先于模板中任何同名卷。因此,我们创建了 datadir
PersistentVolumeClaim,动态配置后挂载到容器路径,即默认的数据目录。对于 StatefulSet 中定义的每个 VolumeClaimTemplate 条目,每个 Pod 会收到一个 PersistentVolumeClaim。在上面的示例中,每个 Pod 会收到一个 StorageClass 为默认(标准)且配置了 300 MB 存储空间的单个 PersistentVolume。Pod 的名称将作为前缀添加到挂载卷的名称中(例如 datadir-mariadb-sts-0
)。
应用配置文件并验证
首先创建 Secret,然后部署上面的 manifest,从而部署 Statefulset。
# Create the Secret
$ kubectl apply -f mariadb-secret.yaml
secret/mariadb-secret created
# Create service/sts
$ kubectl apply -f mariadb-sts.yaml
service/mariadb-service created
statefulset.apps/mariadb-sts created
# Verify sts
$ kubectl get sts
NAME READY AGE
mariadb-sts 1/3 8s
# Use wide option
$ kubectl get statefulset mariadb-sts -o wide
NAME READY AGE CONTAINERS IMAGES
mariadb-sts 3/3 19m mariadb mariadb
# Verify pods (wait until all are in running state)
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
mariadb-sts-0 1/1 Running 0 2m29s
mariadb-sts-1 1/1 Running 0 2m23s
mariadb-sts-2 1/1 Running 0 2m18s
# Verify service
$ kubectl get svc -l app=mariadb
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mariadb-service ClusterIP None <none> 3306/TCP 22s
现在我们已经部署了一个有状态的 MariaDB 应用。请注意,创建的 Pods 是按数字前缀排序的,而不是按随机哈希值。
测试有状态应用
每个 Pod 都具有基于其序数索引的稳定主机名
$ for i in 0 1 2; do kubectl exec "mariadb-sts-$i" -- bash -c "hostname"; done
mariadb-sts-0
mariadb-sts-1
mariadb-sts-2
要获取 StatefulSet 中每个 Pod 的完全限定域名(FQDN),请使用以下命令
$ for i in 0 1 ; do kubectl exec "mariadb-sts-$i" -- hostname -f; done
mariadb-sts-0.mariadb-service.default.svc.cluster.local
mariadb-sts-1.mariadb-service.default.svc.cluster.local
mariadb-service
Service 为所有 Pod 创建了一个域名,即 mariadb-service.default.svc.cluster.local
。
现在,让我们通过缩放副本数量来缩减和扩容应用,并观察结果
# Scale down
$ kubectl scale sts mariadb-sts --replicas=2
# Watch the Pods
$ kubectl get pods -w
NAME READY STATUS RESTARTS AGE
mariadb-sts-0 1/1 Running 0 4m27s
mariadb-sts-1 1/1 Running 0 4m21s
mariadb-sts-2 1/1 Running 0 4m16s
mariadb-sts-2 1/1 Terminating 0 4m38s
# Scale up
$ kubectl scale sts mariadb-sts --replicas=4
# Watch the Pods
$ kubectl get pods -w
NAME READY STATUS RESTARTS AGE
mariadb-sts-0 1/1 Running 0 7m19s
mariadb-sts-1 1/1 Running 0 7m13s
mariadb-sts-2 0/1 Pending 0 0s
mariadb-sts-2 0/1 Pending 0 0s
mariadb-sts-2 0/1 ContainerCreating 0 0s
mariadb-sts-2 1/1 Running 0 5s
mariadb-sts-3 0/1 Pending 0 0s
mariadb-sts-3 0/1 Pending 0 0s
mariadb-sts-3 0/1 Pending 0 1s
mariadb-sts-3 0/1 ContainerCreating 0 1s
mariadb-sts-3 1/1 Running 0 4s
# Alternatively here we can use the kubectl edit command to change the number of replicas
我们可以得出结论,缩容时,最后一个副本会被终止;而扩容时,数字前缀会按序增加,并且在前一个 Pod 处于“运行”状态后才会创建新的 Pod(比较 Pod 2 和 3)。
Pod 可以使用 kubectl delete 命令删除,并且将以相同的序数前缀重新创建
# Since Pods retain their sticky identity
# let's remove Pod with ordinal index 0
$ kubectl delete pod mariadb-sts-0
pod "mariadb-sts-0" deleted
# Watch the Pods during deletion
$ kubectl get pods -w
NAME READY STATUS RESTARTS AGE
mariadb-sts-0 1/1 Running 0 31m
mariadb-sts-1 1/1 Running 0 31m
mariadb-sts-2 1/1 Running 0 24m
mariadb-sts-3 1/1 Running 0 24m
mariadb-sts-0 1/1 Terminating 0 31m
mariadb-sts-0 0/1 Terminating 0 31m
mariadb-sts-0 0/1 Terminating 0 31m
mariadb-sts-0 0/1 Terminating 0 31m
mariadb-sts-0 0/1 Pending 0 0s
mariadb-sts-0 0/1 Pending 0 0s
mariadb-sts-0 0/1 ContainerCreating 0 0s
mariadb-sts-0 1/1 Running 0 4s
由于 PersistentVolumeClaims 是动态配置的,我们也可以查看它们
$ kubectl get pvc -l app=mariadb
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
datadir-mariadb-sts-0 Bound pvc-bdc2be42-f3b6-467e-a3b2-ad5bf882c556 300M RWO standard 33m
datadir-mariadb-sts-1 Bound pvc-f05d44c6-0bfe-4ba4-a6c4-8312341b8367 300M RWO standard 33m
datadir-mariadb-sts-2 Bound pvc-6573cd4d-8e83-4273-8764-33fb2bce6633 300M RWO standard 33m
每个 Pod 都有自己创建的持久卷,所以让我们测试一下。在第一个 Pod 中创建示例数据
$ kubectl exec -it mariadb-sts-0 -- mariadb -uroot -psecret -e "create database if not exists mytest0; use mytest0; create table t(t int); insert into t values (1),(2); select * from t;"
+------+
| t |
+------+
| 1 |
| 2 |
+------+
现在我们将能够从该 Pod 中获取数据,但无法从其他 Pod 中获取。为了获取与第一个 Pod 相同的数据,可以使用 MariaDB 的复制功能。
$ kubectl exec mariadb-sts-0 -- mariadb -uroot -psecret -e "show databases like '%test%'; use mytest0; select * from t;"
Database (%test%)
mytest0
t
1
2
$ kubectl exec mariadb-sts-1 -- mariadb -uroot -psecret -e "show databases like '%test%'; use mytest0; select * from t;"
ERROR 1049 (42000) at line 1: Unknown database 'mytest0'
command terminated with exit code 1
删除 Pod 时,作为家庭作业,尝试验证持久卷是否会保留。
要删除有状态集,请使用 kubectl delete statefulset
命令。
结论
这篇博客展示了如何创建一个 MariaDB Statefulset 应用以及如何使用它。
通过这篇博客,我们结束了关于 MariaDB & K8s 的小型系列博客。
欢迎在 Zulip上进行讨论。
这是一篇有趣且写得很好的文章。
也许值得指出的是,对于 StatefulSet,合适的用法是声明 volumeClaimTemplates 而不是 persistentVolumeClaim。否则,对于 RWO 卷,缩放 StatefulSet 将永远无法工作,而且您会希望每个副本都有自己的持久卷。
你好 Rene,
感谢你的查阅。
我在 yaml 文件中使用了 volumeClaimTemplates。