在 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上进行讨论。

阅读更多