Kubernetes权威指南——深入掌握Pod(使用StatefulSet搭建MongoDB集群)

深入掌握Pod

13、使用StatefulSet搭建MongoDB集群

本节以MongoDB为例,使用StatefulSet完成MongoDB集群的创建,为每个MongoDB实例在共享存储中(这里采用GlusterFS)都申请一片存储空间,以实现一个无单点故障、高可用、可动态扩展的MongoDB集群。部署架构如图所示。

13.1、前提条件

在创建StatefulSet之前,需要确保在Kubernetes集群中管理员已经创建好共享存储,并能够与StorageClass对接,以实现动态存储供应的模式。本节的示例将使用GlusterFS作为共享存储(GlusterFS的部署方法参见8.6节的说明)​。

13.2、创建StatefulSet

为了完成MongoDB集群的搭建,需要创建如下三个资源对象。

  • 一个StorageClass,用于StatefulSet自动为各个应用Pod申请PVC。
  • 一个Headless Service,用于维护MongoDB集群的状态。
  • 一个StatefulSet。

首先,创建一个StorageClass对象。

storageclass-fast.yaml文件的内容如下:

bash 复制代码
apiVersion: storage.k8s.io/v1
kind: StorageClass               # 资源类型:StorageClass(存储类,用于动态供应 PersistentVolume)
metadata:
  name: fast                     # 存储类的名称(用户在 PVC 中可以通过 storageClassName: fast 来指定它)
provisioner: kubernetes.io/glusterfs  # 动态供应器(注意:该内置供应器在现代 Kubernetes v1.25+ 中已被移除)
parameters:
  resturl: "http://<heketi-rest-url>"  # GlusterFS/Heketi 的 REST API 地址,用于自动化创建卷

执行kubectl create命令创建该StorageClass:

bash 复制代码
# kubectl create -f storageclass-fast.yaml
storageclass "fast" created

接下来,创建对应的Headless Service。

mongo-sidecar作为MongoDB集群的管理者,将使用此Headless Service来维护各个MongoDB实例之间的集群关系,以及集群规模变化时的自动更新。

mongo-headless-service.yaml文件的内容如下:

yaml 复制代码
apiVersion: v1
kind: Service                   # 资源类型:Kubernetes Service
metadata:
  name: mongo                   # Service 的名称
  labels:
    name: mongo
spec:
  ports:
  - port: 27017                 # Service 对外暴露的端口
    targetPort: 27017           # 转发到后端 MongoDB 容器的端口
  clusterIP: None               # Headless Service(无头服务):不分配虚拟 IP,常用于有状态集群(如 StatefulSet)中让客户端直接通过 DNS 解析到各个 Pod 的 IP
  selector:
    role: mongo                 # 标签选择器:将流量路由到带有 role: mongo 标签的 Pod

使用kubectl create命令创建该StorageClass:

bash 复制代码
# kubectl create -f mongo-headless-service.yaml
service "mongo" created

最后,创建MongoDB StatefulSet。

statefulset-mongo.yaml文件的内容如下:

yaml 复制代码
apiVersion: apps/v1
kind: StatefulSet               # 资源类型:有状态副本集(StatefulSet),适用于数据库等需要稳定网络标识和持久化存储的场景
metadata:
  name: mongo                   # StatefulSet 的名称
spec:
  serviceName: "mongo"          # 关联的 Headless Service 名称(用于为每个 Pod 生成稳定的 DNS 记录)
  replicas: 3                   # 副本数:启动 3 个 MongoDB 实例以构建副本集
  selector:
    matchLabels:
      role: mongo
      environment: test
  template:
    metadata:
      labels:
        role: mongo             # Pod 标签(用于 Service 路由和 Sidecar 自动发现)
        environment: test
    spec:
      terminationGracePeriodSeconds: 10  # 优雅终结等待时间(秒)
      containers:
      - name: mongo             # 主容器:MongoDB 数据库
        image: mongo
        command:
        - mongod
        - "--replSet"
        - rs0                   # 启用 MongoDB 副本集(Replica Set),名称为 rs0
        - "--smallfiles"
        - "--noprealloc"
        ports:
        - containerPort: 27017  # MongoDB 默认监听端口
        volumeMounts:
        - name: mongo-persistent-storage
          mountPath: /data/db   # 将 PVC 挂载到 MongoDB 数据目录
      - name: mongo-sidecar     # 辅助容器(Sidecar):用于自动初始化 MongoDB 副本集
        image: cvallance/mongo-k8s-sidecar
        env:
        - name: MONGO_SIDECAR_POD_LABELS
          value: "role=mongo,environment=test"
        - name: KUBERNETES_MONGO_SERVICE_NAME
          value: "mongo"
  volumeClaimTemplates:         # 持久卷模板:为每个 Pod 自动创建独立的 PVC
  - metadata:
      name: mongo-persistent-storage
      annotations:
        volume.beta.kubernetes.io/storage-class: "fast"  # 指定存储类(对应前面定义的名为 fast 的 StorageClass)
    spec:
      accessModes: [ "ReadWriteOnce" ]  # 访问模式:单节点读写
      resources:
        requests:
          storage: 100Gi        # 每个 Pod 分配 100Gi 的持久化存储空间

其中的主要配置说明如下。

  • (1)在该StatefulSet的定义中包括两个容器:mongo和mongo-sidecar。mongo是主服务程序,mongo-sidecar是将多个mongo实例进行集群设置的工具。mongo-sidecar中的环境变量如下。
    • MONGO_SIDECAR_POD_LABELS:设置为mongo容器的标签,用于sidecar查询它所要管理的MongoDB集群实例。
    • KUBERNETES_MONGO_SERVICE_NAME:它的值为mongo,表示sidecar将使用mongo这个服务名来完成MongoDB集群的设置。
  • (2)replicas=3表示这个MongoDB集群由3个mongo实例组成。
  • (3)volumeClaimTemplates是StatefulSet最重要的存储设置。在annotations段设置volume.beta.kubernetes.io/storage-class="fast"表示使用名为fast的StorageClass自动为每个mongo Pod实例分配后端存储。resources.requests.storage=100Gi表示为每个mongo实例都分配100GiB的磁盘空间。

使用kubectl create命令创建这个StatefulSet:

bash 复制代码
# kubectl create -f statefulset-mongo.yaml
statefulset "mongo" created

最终可以看到StatefulSet依次创建并启动了3个mongoPod实例,它们的名字依次为mongo-0、mongo-1、mongo-2:

bash 复制代码
# kubectl get pods -l role=mongo
NAME      READY     STATUS    RESTARTS   AGE
mongo-0   2/2       Running   0          4m
mongo-1   2/2       Running   0          3m
mongo-2   2/2       Running   0          2m

StatefulSet会用volumeClaimTemplates中的定义为每个Pod副本都创建一个PVC实例,每个PVC的名称由StatefulSet定义中volumeClaimTemplates的名称和Pod副本的名称组合而成,查看系统中的PVC,可以验证这一点:

bash 复制代码
# kubectl get pvc
NAME                               STATUS    VOLUME
CAPACITY   ACCESSMODES   STORAGECLASS     AGE
mongo-persistent-storage-mongo-0  Bound
pvc-7d963fef-42b3-11e7-b4ca-000c291bc5fc   100Gi        RWO           fast  4m
mongo-persistent-storage-mongo-1  Bound
pvc-8953f856-42b3-11e7-b4ca-000c291bc5fc   100Gi        RWO           fast  3m
mongo-persistent-storage-mongo-2  Bound
pvc-a0fdc059-42b3-11e7-b4ca-000c291bc5fc   100Gi        RWO           fast  3m

下面是mongo-0这个Pod中的Volume设置,可以看到系统自动为其挂载了对应的PVC:

yaml 复制代码
# kubectl get pod mongo-0-o yaml
apiVersion: v1
kind: Pod
metadata:
  name: mongo-0
......
  volumes:
  - name: mongo-persistent-storage
    persistentVolumeClaim:
      claimName: mongo-persistent-storage-mongo-0
......

至此,一个由3个实例组成的MongoDB集群就创建完成了,其中每个实例都拥有稳定的名称和独立的存储空间。

13.3、查看MongoDB集群的状态

登录任意一个mongo Pod,在mongo命令行界面用rs.status()命令查看MongoDB集群的状态,可以看到mongo集群已通过sidecar完成了创建。在集群中包含3个节点,每个节点的名称都是StatefulSet设置的DNS域名格式的网络标识名称:

  • mongo-0.mongo.default.svc.cluster.local
  • mongo-1.mongo.default.svc.cluster.local
  • mongo-2.mongo.default.svc.cluster.local

同时,可以看到3个mongo实例各自的角色(PRIMARY或SECONDARY)也都进行了正确的设置:

bash 复制代码
# kubectl exec -ti mongo-0-- mongo
MongoDB shell version v3.4.4
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.4
Welcome to the MongoDB shell.
......
rs0:PRIMARY>
rs0:PRIMARY> rs.status()
{
      "set" : "rs0",
      "date" : ISODate("2017-05-27T08:13:07.598Z"),
      "myState" : 2,
      "term" : NumberLong(1),
      "syncingTo" : "mongo-0.mongo.default.svc.cluster.local:27017",
      "heartbeatIntervalMillis" : NumberLong(2000),
      "optimes" : {
              "lastCommittedOpTime" : {
                    "ts" : Timestamp(1495872747, 1),
                    "t" : NumberLong(1)
              },
              "appliedOpTime" : {
                    "ts" : Timestamp(1495872747, 1),
                    "t" : NumberLong(1)
              },
              "durableOpTime" : {
                    "ts" : Timestamp(1495872747, 1),
                    "t" : NumberLong(1)
                }
          },
          "members" : [
                {
                        "_id" : 0,
                        "name" : "mongo-0.mongo.default.svc.cluster.local:27017",
                      "health" : 1,
                      "state" : 1,
                        "stateStr" : "PRIMARY",
                      "uptime" : 260,
                      "optime" : {
                              "ts" : Timestamp(1495872747, 1),
                              "t" : NumberLong(1)
                      },
                      "optimeDurable" : {
                              "ts" : Timestamp(1495872747, 1),
                              "t" : NumberLong(1)
                      },
                      "optimeDate" : ISODate("2017-05-27T08:12:27Z"),
                      "optimeDurableDate" : ISODate("2017-05-27T08:12:27Z"),
                      "lastHeartbeat" : ISODate("2017-05-27T08:13:05.777Z"),
                      "lastHeartbeatRecv" :
ISODate("2017-05-27T08:13:05.776Z"),
                      "pingMs" : NumberLong(0),
                      "electionTime" : Timestamp(1495872445, 1),
                      "electionDate" : ISODate("2017-05-27T08:07:25Z"),
                      "configVersion" : 9
                },
                {
                        "_id" : 1,
                        "name" : "mongo-1.mongo.default.svc.cluster.local:27017",
                      "health" : 1,
                      "state" : 2,
                        "stateStr" : "SECONDARY",
                      "uptime" : 291,
                      "optime" : {
                              "ts" : Timestamp(1495872747, 1),
                              "t" : NumberLong(1)
                      },
                      "optimeDate" : ISODate("2017-05-27T08:12:27Z"),
                      "syncingTo" : "mongo-0.mongo.default.svc.cluster.local:
27017",
                      "configVersion" : 9,
                      "self" : true
              },
              {
                      "_id" : 2,
                      "name" : "mongo-2.mongo.default.svc.cluster.local:27017",
                      "health" : 1,
                      "state" : 2,
                      "stateStr" : "SECONDARY",
                      "uptime" : 164,
                      "optime" : {
                            "ts" : Timestamp(1495872747, 1),
                            "t" : NumberLong(1)
                      },
                      "optimeDurable" : {
                            "ts" : Timestamp(1495872747, 1),
                            "t" : NumberLong(1)
                      },
                      "optimeDate" : ISODate("2017-05-27T08:12:27Z"),
                      "optimeDurableDate" : ISODate("2017-05-27T08:12:27Z"),
                      "lastHeartbeat" : ISODate("2017-05-27T08:13:06.369Z"),
                      "lastHeartbeatRecv" : ISODate("2017-05-27T08:13:06.
635Z"),
                      "pingMs" : NumberLong(0),
                      "syncingTo" :
"mongo-0.mongo.default.svc.cluster.local:27017",
                      "configVersion" : 9
              }
        ],
        "ok" : 1
}

对于需要访问这个mongo集群的Kubernetes集群内部客户端来说,可以通过Headless Service"mongo"获取后端的所有Endpoints列表,并组合为数据库链接串,例如"mongodb://mongo-0.mongo, mongo-1.mongo,mongo-2.mongo:27017/dbname_?"​。

13.4、StatefulSet的常见应用场景

下面对MongoDB集群常见的两种场景进行操作,说明StatefulSet对有状态应用的自动化管理功能。

13.4.1、MongoDB集群的扩容

假设在系统运行过程中,3个mongo实例不足以满足业务的要求,这时就需要对mongo集群进行扩容。仅需要通过对StatefulSet进行scale操作,就能实现在mongo集群中自动添加新的mongo节点。

使用kubectl scale命令将StatefulSet设置为4个实例:

bash 复制代码
# kubectl scale --replicas=4 statefulset mongo
statefulset "mongo" scaled

等待一会儿,看到第4个实例"mongo-3"创建成功:

bash 复制代码
# kubectl get po -l role=mongo
NAME      READY     STATUS    RESTARTS   AGE
mongo-0   2/2       Running   0          1h
mongo-1   2/2       Running   0          2h
mongo-2   2/2       Running   0          2h
mongo-3   2/2       Running   0          1m

进入某个实例查看mongo集群的状态,可以看到第4个节点已经加入:

bash 复制代码
# kubectl exec -ti mongo-0-- mongo
MongoDB shell version v3.4.4
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.4.4
Welcome to the MongoDB shell.
......
rs0:PRIMARY>
rs0:PRIMARY> rs.status()
{
......
      "members" : [
              {
                    "_id" : 0,
                    "name" : "mongo-0.mongo.default.svc.cluster.local:27017",
                    "health" : 1,
                    "state" : 1,
                    "stateStr" : "PRIMARY",
......
              {
                        "_id" : 4,
                        "name" : "mongo-3.mongo.default.svc.cluster.local:27017",
                        "health" : 1,
                        "state" : 2,
                        "stateStr" : "SECONDARY",
                        "uptime" : 102,
                        "optime" : {
                              "ts" : Timestamp(1495880578, 1),
                              "t" : NumberLong(4)
                        },
                        "optimeDurable" : {
                              "ts" : Timestamp(1495880578, 1),
                              "t" : NumberLong(4)
                        },
                        "optimeDate" : ISODate("2017-05-27T10:22:58Z"),
                        "optimeDurableDate" : ISODate("2017-05-27T10:22:58Z"),
                        "lastHeartbeat" : ISODate("2017-05-27T10:23:00.049Z"),
                        "lastHeartbeatRecv" :
ISODate("2017-05-27T10:23:00.049Z"),
                        "pingMs" : NumberLong(0),
                        "syncingTo" :
"mongo-1.mongo.default.svc.cluster.local:27017",
                        "configVersion" : 100097
                  }
          ],
          "ok" : 1
    }

同时,系统也为mongo-3分配了一个新的PVC用于保存数据,此处不再赘述,有兴趣的读者可自行查看系统为mongo-3绑定的Volume设置和后端GlusterFS共享存储的资源分配情况。

13.4.2、自动故障恢复(MongoDB集群的高可用)

假设在系统运行过程中,某个mongo实例或其所在主机发生故障,则StatefulSet将会自动重建该mongo实例,并保证其身份(ID)和使用的数据(PVC)不变。

以mongo-0实例发生故障为例,StatefulSet将会自动重建mongo-0实例,并为其挂载之前分配的PVC"mongo-persistent-storage-mongo-0"​。服务"mongo-0"在重新启动后,原数据库中的数据不会丢失,可继续使用。

bash 复制代码
# kubectl get po -l role=mongo
NAME      READY     STATUS              RESTARTS   AGE
mongo-0   0/2       ContainerCreating   0          2h
mongo-1   2/2       Running             0          2h
mongo-2   2/2       Running             0          3s

# kubectl get pod mongo-0-o yaml
apiVersion: v1
kind: Pod
metadata:
  name: mongo-0
......
  volumes:
  - name: mongo-persistent-storage
    persistentVolumeClaim:
      claimName: mongo-persistent-storage-mongo-0
......

进入某个实例查看mongo集群的状态,mongo-0发生故障前在集群中的角色为PRIMARY,在其脱离集群后,mongo集群会自动选出一个SECONDARY节点提升为PRIMARY节点(本例中为mongo-2)​。重启后的mongo-0则会成为一个新的SECONDARY节点:

bash 复制代码
# kubectl exec -ti mongo-0-- mongo
......
rs0:PRIMARY> rs.status()
{
......
      "members" : [
              {
                    "_id" : 1,
                    "name" : "mongo-1.mongo.default.svc.cluster.local:27017",
                    "health" : 1,
                    "state" : 2,
                    "stateStr" : "SECONDARY",
......
              {
                    "_id" : 2,
                    "name" : "mongo-2.mongo.default.svc.cluster.local:27017",
                    "health" : 1,
                    "state" : 1,
                          "stateStr" : "PRIMARY",
                          "uptime" : 6871,
      ......
                    {
                          "_id" : 3,
                          "name" : "mongo-0.mongo.default.svc.cluster.local:27017",
                          "health" : 1,
                          "state" : 2,
                          "stateStr" : "SECONDARY",
                          "uptime" : 6806,
      ......

从上面的例子中可以看出,Kubernetes使用StatefulSet来搭建有状态的应用集群(MongoDB、MySQL等)​,同部署无状态的应用一样简便。Kubernetes能够保证StatefulSet中各应用实例在创建和运行的过程中,都具有固定的身份标识和独立的后端存储;还支持在运行时对集群规模进行扩容、保障集群的高可用等非常重要的功能。

相关推荐
fb_123451 小时前
K8s动态卷供应实战_StorageClass与LocalPath_NFS双Provisioner
云原生·容器·kubernetes
大囚长1 小时前
Kubernetes 的资源开销、适用边界,以及游戏与有状态负载的工程现实
游戏·容器·kubernetes
RuoyiOffice1 小时前
Hyper-V+Kubernetes 搭建实战:保留 Windows,构建三节点 Linux 容器集群
linux·spring boot·kubernetes·containerd·hyper-v·微服务部署·ruoyi office
fb_123451 小时前
K8s StatefulSet五大有状态集群实战_Nginx_Etcd_Redis_Cassandra_MySQL
nginx·kubernetes·etcd
时间售卖铺2 小时前
Docker私有仓库搭建与实战指南
运维·docker·云原生·容器
mengge.cloud3 小时前
10-29-Kubernetes动态卷供应StatefulSet与ETCD
容器·kubernetes·etcd
小哈里3 小时前
【推理】NVIDIA DLI培训:大规模部署与优化 AI 推理(推理原理,K8S,网关层,pd分离,MoE,Dynamo)
人工智能·容器·kubernetes·nvidia·推理
Blockbuater_drug1 天前
Dify 1.18 本地部署实战:Windows + WSL2 + Docker,从零跑到本地可用
windows·docker·容器·dify·大模型应用平台
shangjian0071 天前
K8S学习-基础组件
学习·容器·kubernetes