深入掌握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中各应用实例在创建和运行的过程中,都具有固定的身份标识和独立的后端存储;还支持在运行时对集群规模进行扩容、保障集群的高可用等非常重要的功能。