k8s之StatefulSet详解

本篇是基于k8s-v1.15.0版本。

1、介绍

RC、Deployment、DaemonSet都是面向无状态的服务,它们所管理的Pod的IP、名字,启停顺序等都是随机的,而StatefulSet是什么?顾名思义,有状态的集合,管理所有有状态的服务,比如MySQL、MongoDB集群等。
StatefulSet本质上是Deployment的一种变体,在v1.9版本中已成为GA版本,它为了解决有状态服务的问题,它所管理的Pod拥有固定的Pod名称,启停顺序,在StatefulSet中,Pod名字称为网络标识(hostname),还必须要用到共享存储。
在Deployment中,与之对应的服务是service,而在StatefulSet中与之对应的headless service,headless service,即无头服务,与service的区别就是它没有Cluster IP,解析它的名称时将返回该Headless Service对应的全部Pod的Endpoint列表。
除此之外,StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod副本创建了一个DNS域名,这个域名的格式为:
$(podname).(headless server name)
FQDN:$(podname).(headless server name).namespace.svc.cluster.local

2、特点

Pod一致性:包含次序(启动、停止次序)、网络一致性。此一致性与Pod相关,与被调度到哪个node节点无关;
稳定的次序:对于N个副本的StatefulSet,每个Pod都在[0,N)的范围内分配一个数字序号,且是唯一的;
稳定的网络:Pod的hostname模式为 ( s t a t e f u l s e t 名 称 ) − (statefulset名称)- (statefulset)(序号);
稳定的存储:通过VolumeClaimTemplate为每个Pod创建一个PV。删除、减少副本,不会删除相关的卷。

3、组成部分

Headless Service:用来定义Pod网络标识( DNS domain);
volumeClaimTemplates :存储卷申请模板,创建PVC,指定pvc名称大小,将自动创建pvc,且pvc必须由存储类供应;
StatefulSet :定义具体应用,名为Nginx,有三个Pod副本,并为每个Pod定义了一个域名部署statefulset。

为什么需要 headless service 无头服务?
在用Deployment时,每一个Pod名称是没有顺序的,是随机字符串,因此是Pod名称是无序的,但是在statefulset中要求必须是有序 ,每一个pod不能被随意取代,pod重建后pod名称还是一样的。而pod IP是变化的,所以是以Pod名称来识别。pod名称是pod唯一性的标识符,必须持久稳定有效。这时候要用到无头服务,它可以给每个Pod一个唯一的名称 。

为什么需要volumeClaimTemplate?
对于有状态的副本集都会用到持久存储,对于分布式系统来讲,它的最大特点是数据是不一样的,所以各个节点不能使用同一存储卷,每个节点有自已的专用存储,但是如果在Deployment中的Pod template里定义的存储卷,是所有副本集共用一个存储卷,数据是相同的,因为是基于模板来的 ,而statefulset中每个Pod都要自已的专有存储卷,所以statefulset的存储卷就不能再用Pod模板来创建了,于是statefulSet使用volumeClaimTemplate,称为卷申请模板,它会为每个Pod生成不同的pvc,并绑定pv,从而实现各pod有专用存储。这就是为什么要用volumeClaimTemplate的原因。

4、StatefulSet详解

kubectl explain sts.spec :主要字段解释
replicas :副本数
selector:那个pod是由自己管理的
serviceName:必须关联到一个无头服务商
template:定义pod模板(其中定义关联那个存储卷)
volumeClaimTemplates :生成PVC

5、部署一个statefulset服务

本教程假设你的集群被配置为动态的提供 PersistentVolume,动态PV参考ks8的数据管理—动态配置StorageClass;如果没有这样配置,在开始本教程之前,你需要手动准备存储卷。
如果集群中没有StorageClass的动态供应PVC的机制,也可以提前手动创建多个PV、PVC,手动创建的PVC名称必须符合之后创建的StatefulSet命名规则:(volumeClaimTemplates.name)-(pod_name)

5.1、 基于动态sc创建一个pv

step1:创建statefueset的命名空间

cat << EOF > nginx-ns.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: nginx-ss
EOF

step2、基于sc创建动态存储

cat << EOF > nginx-sc.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nginx-nfs-storage
provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
  archiveOnDelete: "false" # # When set to "false" your PVs will not be archived
                           # by the provisioner upon deletion of the PVC.
EOF
kubectl apply -f nginx-sc.yaml

step3、创建statefulset

cat << EOF > nginx-ss.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
  namespace: nginx-ss
spec:
  selector:
    matchLabels:
      app: nginx #必须匹配 .spec.template.metadata.labels
  serviceName: "nginx"  #声明它属于哪个Headless Service.
  replicas: 3 #副本数
  template:
    metadata:
      labels:
        app: nginx # 必须配置 .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: www.my.com/web/nginx:v1
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: nginx-pvc
          mountPath: /usr/share/nginx/html

  volumeClaimTemplates:   #可看作pvc的模板
  - metadata:
      name: nginx-pvc
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "nginx-nfs-storage"  #存储类名,改为集群中已存在的
      resources:
        requests:
          storage: 1Gi
EOF
kubectl apply -f nginx-ss.yaml

step4、观察pod的创建,会发现是有序创建

kubectl get pod -n nginx-ss

在这里插入图片描述

step5、创建svc

cat << EOF > nginx-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: nginx-ss
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
EOF
kubectl apply -f nginx-svc.yaml

step6、查看sc,pv,pvc,pod,statefulset,svc

在这里插入图片描述

5.2、验证解析

每个 Pod 都拥有一个基于其顺序索引的稳定的主机名
在这里插入图片描述
使用 kubectl run 运行一个提供 nslookup 命令的容器,该命令来自于 dnsutils 包。通过对 Pod 的主机名执行 nslookup,你可以检查他们在集群内部的 DNS 地址
kubectl run -i --tty --image www.my.com/k8s/busybox:1.27 -n nginx-ss dns-test --restart=Never --rm
nslookup web-0.nginx
在这里插入图片描述
可以发现headless service 的 CNAME 指向 SRV 记录(记录每个 Running 和 Ready 状态的 Pod)。SRV 记录指向一个包含 Pod IP 地址的记录表项。
重启pod会发现,pod中的ip已经发生变化,但是pod的名称并没有发生变化;这就是为什么不要在其他应用中使用 StatefulSet 中的 Pod 的 IP 地址进行连接,这点很重要
在这里插入图片描述
Pod 的序号、主机名、SRV 条目和记录名称没有改变,但和 Pod 相关联的 IP 地址可能发生了改变
如果你需要查找并连接一个 StatefulSet 的活动成员,你应该查询 Headless Service 的 CNAME。和 CNAME 相关联的 SRV 记录只会包含 StatefulSet 中处于 Running 和 Ready 状态的 Pod。

扫描二维码关注公众号,回复: 12342267 查看本文章

如果你的应用已经实现了用于测试 liveness 和 readiness 的连接逻辑,你可以使用 Pod 的 SRV 记录(web-0.nginx.nginx-ss.svc.cluster.local, web-1.nginx.nginx-ss.svc.cluster.local,web-2.nginx.nginx-ss.svc.cluster.local)。因为他们是稳定的,并且当你的 Pod 的状态变为 Running 和 Ready 时,你的应用就能够发现它们的地址。

5.3、写入稳定的存储

将 Pod 的主机名写入它们的index.html文件并验证 NGINX web 服务器使用该主机名提供服务

kubectl exec -it web-0 -n nginx-ss /bin/bash
echo $(hostname) > /usr/share/nginx/html/index.html
kubectl exec -it web-1 -n nginx-ss /bin/bash
echo $(hostname) > /usr/share/nginx/html/index.html
kubectl exec -it web-2 -n nginx-ss /bin/bash
echo $(hostname) > /usr/share/nginx/html/index.html

删除pod后,即使pod的ip发生变化,但是依然不影响访问
在这里插入图片描述
虽然 web-0 、web-1 和web-2被重新调度了,但它们仍然继续监听各自的主机名,因为和它们的 PersistentVolumeClaim 相关联的 PersistentVolume 被重新挂载到了各自的 volumeMount 上。不管 web-0、web-1、web-3 被调度到了哪个节点上,它们的 PersistentVolumes 将会被挂载到合适的挂载点上

5.4、扩容/缩容 StatefulSet

扩容/缩容StatefulSet 指增加或减少它的副本数。这通过更新replicas字段完成。你可以使用kubectl scale 或者kubectl patch来扩容/缩容一个 StatefulSet。

kubectl scale sts web --replicas=4 -n nginx-ss   #扩容
kubectl scale sts web --replicas=2 -n nginx-ss   #缩容

或者

kubectl patch sts web -p '{"spec":{"replicas":4}}' -n nginx-ss  #扩容
kubectl patch sts web -p '{"spec":{"replicas":2}}' -n nginx-ss  #缩容

在这里插入图片描述
会发现,放扩容是,如果pv是动态的sc的话,pvc同样会增加,但当缩容时,pvc并不会自定的删除

6、案例(部署mysql)

step1:创建mysql的命名空间、sc

cat << EOF > mysql-ns.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: mysql-sts
EOF
cat << EOF > mysql-sc.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: mysql-nfs-storage
provisioner: fuseim.pri/ifs # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
  archiveOnDelete: "false" # # When set to "false" your PVs will not be archived
                           # by the provisioner upon deletion of the PVC.  
EOF
kubectl apply -f mysql-ns.yaml
kubectl apply -f mysql-sc.yaml

step2:部署mysql的statefulset

cat << EOF > mysql-ss.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
  namespace: mysql-sts
spec:
  selector:
    matchLabels:
      app: mysql #必须匹配 .spec.template.metadata.labels
  serviceName: "mysql"  #声明它属于哪个Headless Service.
  replicas: 3 #副本数
  template:
    metadata:
      labels:
        app: mysql # 必须配置 .spec.selector.matchLabels
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: mysql
        image: www.my.com/sys/mysql:5.7
        ports:
        - containerPort: 3306
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "123456"
        volumeMounts:
        - name: mysql-pvc
          mountPath: /var/lib/mysql

  volumeClaimTemplates:   #可看作pvc的模板
  - metadata:
      name: mysql-pvc
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "mysql-nfs-storage"  #存储类名,改为集群中已存在的
      resources:
        requests:
          storage: 1Gi
EOF
kubectl apply -f mysql-ss.yaml

step3:创建svc

cat << EOF > mysql-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql
  namespace: mysql-sts
  labels:
    app: mysql
spec:
  ports:
  - port: 3306
    name: mysql
  clusterIP: None
  selector:
    app: mysql
EOF
kubectl apply -f mysql-svc.yaml

step4:查看

在这里插入图片描述

step5:访问测试

在这里插入图片描述
且三个mysql的pod数据是相互独立的

step6:编写mysql-read的svc

cat << EOF > mysql-svc-port.yaml
apiVersion: v1
kind: Service
metadata:
  name: mysql-read
  namespace: mysql-sts
  labels:
    app: mysql-2
spec:
  ports:
  - port: 3306
    name: mysql
  selector:
    app: mysql-2
EOF   
kubectl apply -f mysql-svc-port.yaml

在这里插入图片描述
让mysql-read可以通过外部访问
使用NodePort作为访问方法
执行命令kubectl edit svc/mysql-read -n mysql-sts
进入修改界面,修改type为NodePort,同时给spec/ports中加入nodePort: 31988配置。
再次执行kubectl get svc -n mysql-sts
在这里插入图片描述
在本地电脑通过mysql客户端工具登陆即可
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44729138/article/details/106054025