一、Statefulset控制器:概念、原理解读

StatefulSet是为了管理有状态服务的问题而设计的。用于管理有状态应用程序的部署。与无状态应用程序不同,有状态应用程序在运行时通常要求稳定的网络标识和持久性存储。

1.1、有状态服务?

StatefulSet是有状态的集合,管理有状态的服务,它所管理的Pod的名称不能随意变化。数据持久化的目录也是不一样,每一个Pod都有自己独有的数据持久化存储目录。比如MySQL主从、redis集群等。

1.2、无状态服务?

RC、Deployment、DaemonSet都是管理无状态的服务,它们所管理的Pod的IP、名字,启停顺序等都是随机的。个体对整体无影响,所有pod都是共用一个数据卷的,部署的tomcat就是无状态的服务,tomcat被删除,在启动一个新的tomcat,加入到集群即可,跟tomcat的名字无关。

二、Statefulset资源清单文件编写技巧

2.1、查看定义Statefulset资源需要的字段

帮助命令:kubectl explain statefulset

root@k8s-master:~/K8sStudy/Chapter2-12# kubectl explain statefulset |grep '<*>'
  apiVersion    <string>	#定义statefulset资源需要使用的api版本
  kind  <string>	#定义的资源类型
  metadata      <ObjectMeta>	#元数据
  spec  <StatefulSetSpec>	#定义容器相关的信息
  status        <StatefulSetStatus>	

2.2、查看statefulset.spec字段如何定义?

帮助命令:kubectl explain statefulset.spec

root@k8s-master:~/K8sStudy/Chapter2-12# kubectl explain statefulset.spec |grep '<*>'
FIELD: spec <StatefulSetSpec>
  minReadySeconds       <integer>
  ordinals      <StatefulSetOrdinals>
  persistentVolumeClaimRetentionPolicy  <StatefulSetPersistentVolumeClaimRetentionPolicy>
  podManagementPolicy   <string>	#pod管理策略
  replicas      <integer>	#副本数
  revisionHistoryLimit  <integer>	#保留的历史版本
  selector      <LabelSelector> -required-	#标签选择器,选择它所关联的pod
  serviceName   <string> -required-	#headless service的名字
  template      <PodTemplateSpec> -required-	#生成pod的模板
  updateStrategy        <StatefulSetUpdateStrategy>	#更新策略
  volumeClaimTemplates  <[]PersistentVolumeClaim>	#存储卷申请模板

2.3、查看statefulset的spec.template字段如何定义?

对于template而言,其内部定义的就是pod,pod模板是一个独立的对象

帮助命令:kubectl explain statefulset.spec.template

root@k8s-master:~/K8sStudy/Chapter2-12# kubectl explain statefulset.spec.template |grep '<*>'
FIELD: template <PodTemplateSpec>
  metadata      <ObjectMeta>
  spec  <PodSpec>	#定义容器属性的

通过上面可以看到,statefulset资源中有两个spec字段。第一个spec声明的是statefulset定义多少个Pod副本(默认将仅部署1个Pod)、匹配Pod标签的选择器、创建pod的模板、存储卷申请模板,第二个spec是spec.template.spec:主要用于Pod里的容器属性等配置。

.spec.template里的内容是声明Pod对象时要定义的各种属性,所以这部分也叫做PodTemplate(Pod模板)。还有一个值得注意的地方是:在.spec.selector中定义的标签选择器必须能够匹配到spec.template.metadata.labels里定义的Pod标签,否则Kubernetes将不允许创建statefulset。

三、Statefulset使用案例:部署web站点

创建statefulset资源:

查看yaml资源清单文件:Eg-StatefulSet.yaml

root@k8s-master:~/K8sStudy/Chapter2-12# cat Eg-StatefulSet.yaml 
apiVersion: v1	#定义api版本
kind: Service	#定义要创建的资源:service
metadata: 
  name: nginx	#定义service的名字
  labels:
     app: nginx	#service的标签
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None	#创建一个没有ip的service
  selector:
    app: nginx	#选择拥有app=nginx标签的pod
---
apiVersion: apps/v1
kind: StatefulSet
metadata: 
  name: web
spec:
  selector:
    matchLabels:
      app: nginx
  serviceName: "nginx"	#headless service的名字
  replicas: 2	#副本数
  template:		#定义pod的模板
    metadata: 
     labels:
       app: nginx
    spec: 
      containers:
      - name: nginx
        image: nginx:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:	#存储卷申请模板
  - metadata:
      name: www
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: nfs-storageclass	#指定从哪个存储类申请pv
      resources:
        requests: 
          storage: 20M	#需要20M的pvc,会自动跟符合条件的pv绑定

应用/更新资源清单文件:Eg-StatefulSet.yaml

root@k8s-master:~/K8sStudy/Chapter2-12# kubectl apply -f Eg-StatefulSet.yaml 
service/nginx created
statefulset.apps/web created
root@k8s-master:~/K8sStudy/Chapter2-12#

查看statefulset是否创建成功

k8s控制器Statefulset_StatefulSet

查看pod

k8s控制器Statefulset_StatefulSet_02

通过上面可以看到创建的pod是有序的.

查看headless service

k8s控制器Statefulset_StatefulSet_03

查看pvc

k8s控制器Statefulset_StatefulSet_04

查看pv

k8s控制器Statefulset_StatefulSet_05

备注:

StatefulSet由以下几个部分组成:

1. Headless Service:用来定义pod网路标识,生成可解析的DNS记录

2. volumeClaimTemplates:存储卷申请模板,创建pvc,指定pvc名称大小,自动创建pvc,且pvc由存储类供应。

3. StatefulSet:管理pod的

扩展:什么是Headless service?

Headless service不分配clusterIP,headless service可以通过解析service的DNS,返回所有Pod的dns和ip地址,普通的service,只能通过解析service的DNS返回service的ClusterIP。

1、headless service会为service分配一个域名:<service name>.$<namespace name>.svc.cluster.local

K8s中资源的全局FQDN格式:  Service_NAME.NameSpace_NAME.Domain.LTD.  Domain.LTD.=svc.cluster.local.     #这是默认k8s集群的域名。

FQDN 全称 Fully Qualified Domain Name,即全限定域名:同时带有主机名和域名的名称

FQDN = Hostname + DomainName

如 主机名是rshine

域名是:lucky.com

FQDN= rshine.lucky.com

2、StatefulSet会为关联的Pod保持一个不变的Pod Name;statefulset中Pod的名字格式为$(StatefulSet name)-$(pod序号)

3、StatefulSet会为关联的Pod分配一个dnsName;$<Pod Name>.$<service name>.$<namespace name>.svc.cluster.local

为什么要用volumeClaimTemplate?

对于有状态应用都会用到持久化存储,比如mysql主从,由于主从数据库的数据是不能存放在一个目录下的,每个mysql节点都需要有自己独立的存储空间。而在deployment中创建的存储卷是一个共享的存储卷,多个pod使用同一个存储卷,它们数据是同步的,而statefulset定义中的每一个pod都不能使用同一个存储卷,这就需要使用volumeClainTemplate,当在使用statefulset创建pod时,volumeClainTemplate会自动生成一个PVC,从而请求绑定一个PV,每一个pod都有自己专用的存储卷。

验证域名:

使用kubectl run运行一个提供nslookup命令的容器的,这个命令来自于dnsutils包,通过对pod主机名执行nslookup,可以检查pod和service在集群内部的DNS地址:kubectl run busybox --image docker.io/library/busybox:1.28 --image-pull-policy=IfNotPresent --restart=Never --rm -it busybox -- /bin/sh

这个命令之后会进入到容器中,我们可以通过nslookup命令验证域名。

1、验证statefulset创建的pod是有域名的。

k8s控制器Statefulset_StatefulSet_06

可以看到pod的域名web-0.nginx.default.svc.cluster.local是由pod名称.service名称.名称空间名称.svc.cluster.local。通过nslookup命令pod域名各自解析成了各自的pod的ip地址。

2、验证service的域名

k8s控制器Statefulset_StatefulSet_07

可以看到service的域名nginx.default.svc.cluster.local是由service名称.名称空间名称.svc.cluster.local。因为本示例service没有配置ip地址,通过nslookup命令service域名解析到了所有关联的pod的ip地址。如果创建的service有ip,那对这个service做dns解析,会解析到service本身ip。

Statefulset总结:

1、Statefulset管理的pod,pod名字是有序的,由statefulset的名字-0、1、2这种格式组成

2、创建statefulset资源的时候,必须事先创建好一个service,如果创建的service没有ip,那对这个service做dns解析,会找到它所关联的pod ip,如果创建的service有ip,那对这个service做dns解析,会解析到service本身ip。

3、statefulset管理的pod,删除pod,新创建的pod名字跟删除的pod名字是一样的

4、statefulset具有volumeclaimtemplate这个字段,这个是卷申请模板,会自动创建pv,pvc也会自动生成,跟pv进行绑定,那如果创建的statefulset使用了volumeclaimtemplate这个字段,那创建pod,数据目录是独享的

5、ststefulset创建的pod,是有域名的(域名组成:pod-name.svc-name.svc-namespace.svc.cluster.local)


四、Statefulset管理pod:扩容、缩容、更新

4.1、Statefulset实现pod的动态扩容

如果我们觉得两个副本太少了,想要增加,只需要修改配置文件Eg-StatefulSet.yaml里的replicas的值即可,原来replicas: 3,现在变成replicaset: 2,修改之后,执行命令kubectl apply -f Eg-StatefulSet.yaml更新

root@k8s-master:~/K8sStudy/Chapter2-12# grep replicas Eg-StatefulSet.yaml 
  replicas: 2
root@k8s-master:~/K8sStudy/Chapter2-12# vim Eg-StatefulSet.yaml
root@k8s-master:~/K8sStudy/Chapter2-12# 
root@k8s-master:~/K8sStudy/Chapter2-12# 
root@k8s-master:~/K8sStudy/Chapter2-12# grep replicas Eg-StatefulSet.yaml 
  replicas: 3
root@k8s-master:~/K8sStudy/Chapter2-12# kubectl apply -f Eg-StatefulSet.yaml 
service/nginx unchanged
statefulset.apps/web configured
root@k8s-master:~/K8sStudy/Chapter2-12# kubectl get pod -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          25m
web-1   1/1     Running   0          25m
web-2   1/1     Running   0          20s
root@k8s-master:~/K8sStudy/Chapter2-12# 

也可以直接编辑控制器实现扩容: kubectl edit statefulset web

k8s控制器Statefulset_StatefulSet_08

把上面的spec下的replicas 后面的值改成4,保存退出.

查看更新后的statefulset的pod状态:kubectl get pod -l app=nginx

k8s控制器Statefulset_StatefulSet_09

可以看到pod的数量扩容到4个了,两种方法都可行,一般建议使用修改yaml文件的方式扩容。

4.2、Statefulset实现pod的动态缩容

如果我们觉得4个Pod副本太多了,想要减少,只需要修改配置文件Eg-StatefulSet.yaml里的replicas的值即可,把replicaset:4变成replicas: 2,修改之后,执行命令kubectl apply -f Eg-StatefulSet.yaml更新

root@k8s-master:~/K8sStudy/Chapter2-12# grep replicas Eg-StatefulSet.yaml 
  replicas: 3
root@k8s-master:~/K8sStudy/Chapter2-12# vim Eg-StatefulSet.yaml
root@k8s-master:~/K8sStudy/Chapter2-12# 
root@k8s-master:~/K8sStudy/Chapter2-12# 
root@k8s-master:~/K8sStudy/Chapter2-12# grep replicas Eg-StatefulSet.yaml 
  replicas: 2
root@k8s-master:~/K8sStudy/Chapter2-12# kubectl apply -f Eg-StatefulSet.yaml
service/nginx unchanged
statefulset.apps/web configured
root@k8s-master:~/K8sStudy/Chapter2-12# kubectl get pod -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          36m
web-1   1/1     Running   0          36m
root@k8s-master:~/K8sStudy/Chapter2-12#

4.3、Statefulset实现pod的更新

4.3.1、查看Statefulset更新策略sts.spec.updateStrategy有哪些字段?

帮助命令:kubectl explain sts.spec.updateStrategy

root@k8s-master:~/K8sStudy/Chapter2-12# kubectl explain sts.spec.updateStrategy |grep '<*>'
FIELD: updateStrategy <StatefulSetUpdateStrategy>
  rollingUpdate <RollingUpdateStatefulSetStrategy>
  type  <string>

rollingUpdate: 滚动更新,默认更新策略

type:更新方式,默认值为rollingUpdate,可选值:rollingUpdate、OnDelete

4.3.2、查看Statefulset滚动更新策略.spec.updateStrategy.rollingUpdate有哪些字段?

帮助命令:kubectl explain sts.spec.updateStrategy.rollingUpdate

root@k8s-master:~/K8sStudy/Chapter2-12# kubectl explain sts.spec.updateStrategy.rollingUpdate |grep '<*>'
FIELD: rollingUpdate <RollingUpdateStatefulSetStrategy>
  maxUnavailable        <IntOrString>	# 最多有多少个pod不可用
  partition     <integer>		# 将大于这个数字的pod序号的pod删除

修改Eg-StatefulSet.yaml,添加更新策略,和修改image镜像,如下:

查看yaml资源清单文件:Eg-StatefulSet.yaml

root@k8s-master:~/K8sStudy/Chapter2-12# cat Eg-StatefulSet.yaml 
apiVersion: v1
kind: Service
metadata: 
  name: nginx
  labels:
     app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata: 
  name: web
spec:
  # 添加更新策略
  updateStrategy:
    rollingUpdate:
      maxUnavailable: 0
      partition: 1
  selector:
    matchLabels:
      app: nginx
  serviceName: "nginx"
  replicas: 2
  template:
    metadata: 
     labels:
       app: nginx
    spec: 
      containers:
      - name: nginx
      # 修改镜像由nginx改为tomcat
        image: tomcat:latest
        imagePullPolicy: IfNotPresent
        ports:
        # 修改容器内部端口80改为8080
        - containerPort: 8080
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: nfs-storageclass
      resources:
        requests: 
          storage: 20M

另起一个Master终端动态查看pod:kubectl get pods -l app=nginx -w

应用/更新yaml资源清单文件:Eg-StatefulSet.yaml

root@k8s-master:~/K8sStudy/Chapter2-12# kubectl apply -f Eg-StatefulSet.yaml 
service/nginx unchanged
statefulset.apps/web configured

另一个终端动态查看pod结果如下:

k8s控制器Statefulset_StatefulSet_10

从上面结果可以看出来,pod在更新的时候,只是更新了web-1这个pod, partition: 1表示更新的时候会把pod序号大于等于1的进行更新,如果想全部都更新,就把这个值设置为0。

如果更新策略是OnDelete,那不会自动更新pod,需要手动删除,重新常见的pod才会实现更新