zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Kubernetes 一文详解StatefulSet 控制器

Kubernetes 详解 一文 控制器 StatefulSet
2023-09-14 09:01:47 时间

无状态与有状态


Deployment控制器设计原则:管理的所有Pod一模一样,提供同一个服务,也不考虑在哪台Node运行,可随意扩容和缩容。这种应用称为“无状态”,例如Web服务

在实际的场景中,并不能满足所有应用,尤其是分布式应用,会部署多个实例,这些实例之间往往有依赖关系,例如主从关系、主备关系,这种应用称为“有状态”,例如MySQL主从、Etcd集群

 

StatefulSet 控制器概述


StatefulSet控制器用于部署有状态应用,满足一些有状态应

用的需求:

• Pod有序的部署、扩容、删除和停止

• Pod分配一个稳定的且唯一的网络标识

• Pod分配一个独享的存储

Deployment数据卷是共享的,当创建3个pod都是用的同一个数据卷,对外提供统一的服务。

 

StatefulSet 控制器:主机名


稳定的网络标识:使用Headless Service(相比普通Service只是将spec.clusterIP定义为None)来维护Pod网络身份,会为每个Pod分配一个数字编号并且按照编号顺序部署。还需要在StatefulSet添加serviceName: “nginx”字段指定StatefulSet控制器要使用这个Headless Service。

稳定主要体现在主机名和Pod A记录:

• 主机名:<statefulset名称>-<编号>

• Pod DNS A记录:<statefulset名称-编号>.<service-name> .<namespace>.svc.cluster.local

之前的service会自动的帮你填充cluster ip,这里将字段设置为none,不会分配,和deployment不同,deployment都是提供相同的统一的入口。有状态的都是提供独立的访问。

[root@k8s-master ~]# cat service.yaml 
apiVersion: v1
kind: Service
metadata:
  name: headless-web
  namespace: default
spec:
  clusterIP: None
  ports:
  - port: 80       
    protocol: TCP  
    targetPort: 80 
  selector:        
    app: nginx  
  type: ClusterIP
[root@k8s-master ~]# kubectl apply -f service.yaml 
service/headless-web created
[root@k8s-master ~]# kubectl get svc
NAME           TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
headless-web   ClusterIP   None         <none>        80/TCP         9s
kubernetes     ClusterIP   10.96.0.1    <none>        443/TCP        76d

 可以看到没有为其分配cluster ip

稳定的网络标识怎么体现的,为每个pod分配固定的主机名,并且会创建dns记录

[root@k8s-master ~]# cat statefulset-web.yml 
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "headless-web"
  replicas: 3 
  selector:
    matchLabels:
      app: nginx 
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx 
        ports:
        - containerPort: 80
          name: web
[root@k8s-master ~]# kubectl get pod
NAME                                     READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-d565bb89b-8d9hp   1/1     Running   4          26h
web-0                                    1/1     Running   0          99s
web-1                                    1/1     Running   0          39s
web-2                                    1/1     Running   0          22s

这个主机名是固定的,即使pod被删除掉了,重新启动也是这个名称上面体现了稳定的主机名

[root@k8s-master ~]# kubectl exec web-0 -- hostname
web-0

[root@k8s-master ~]# kubectl exec web-1 -- hostname
web-1

[root@k8s-master ~]# kubectl exec web-2 -- hostname
web-2

可见,对于一个拥有 N 个副本的 StatefulSet 来说,Pod 在部署时按照 {0 …… N-1} 的序号顺序创建的,而删除的时候按照逆序逐个删除,这便是我想说的第一个特性。StatefulSet 创建出来的 Pod 都具有固定的、且确切的主机名。

 

 

StatefulSet 控制器:网络标识


可以看到,每个 Pod 都有一个对应的 A 记录。

[root@k8s-master ~]# kubectl run -i -t dns-test --image busybox:1.28.4 /bin/sh
If you don't see a command prompt, try pressing enter.
/ # nslookup headless-web.default.svc.cluster.local
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      headless-web.default.svc.cluster.local
Address 1: 10.244.169.147 web-2.headless-web.default.svc.cluster.local
Address 2: 10.244.169.146 web-1.headless-web.default.svc.cluster.local
Address 3: 10.244.36.81 web-0.headless-web.default.svc.cluster.local


/ # ping web-0.headless-web.default.svc.cluster.local
PING web-0.headless-web.default.svc.cluster.local (10.244.36.81): 56 data bytes
64 bytes from 10.244.36.81: seq=0 ttl=62 time=1.105 ms
64 bytes from 10.244.36.81: seq=1 ttl=62 time=2.990 ms
64 bytes from 10.244.36.81: seq=2 ttl=62 time=1.408 ms
^C

这里解析出来了三条,任意访问其中的一条可以访问具体的pod,即使pod重建ip发生变化,也能够通过这个记录解析到新的Ip上面

有头服务只会解析出一条记录

上面固定的dns名称和主机名就是网络标识的体现

 删除一下这些 Pod,看看会有什么变化:

[root@k8s-master ~]# kubectl get pod
NAME                                     READY   STATUS    RESTARTS   AGE
dns-test                                 1/1     Running   0          10m
nfs-client-provisioner-d565bb89b-8d9hp   1/1     Running   4          26h
web-0                                    1/1     Running   0          14m
web-1                                    1/1     Running   0          13m
web-2                                    1/1     Running   0          13m
[root@k8s-master ~]# kubectl delete pod web-0
pod "web-0" deleted


/ # nslookup headless-web.default.svc.cluster.local
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      headless-web.default.svc.cluster.local
Address 1: 10.244.36.82 web-0.headless-web.default.svc.cluster.local
Address 2: 10.244.169.147 web-2.headless-web.default.svc.cluster.local
Address 3: 10.244.169.146 web-1.headless-web.default.svc.cluster.local

 可以看出,DNS 记录中 Pod 的域名没有发生变化,仅仅 IP 地址发生了更换。因此当 Pod 所在的节点发生故障导致 Pod 飘移到其他节点上,或者 Pod 因故障被删除重建,Pod 的 IP 都会发生变化,但是 Pod 的域名不会有任何变化,这也就意味着服务间可以通过不变的 Pod 域名来保障通信稳定,而不必依赖 Pod IP。

有了spec.serviceName这个字段,保证了 StatefulSet 关联的 Pod 可以有稳定的网络身份标识,即 Pod 的序号、主机名、DNS 记录名称等。

 

StatefulSet 控制器:独享存储


最后一个我想说的是,对于有状态的服务来说,每个副本都会用到持久化存储,且各自使用的数据是不一样的。

StatefulSet 通过 PersistentVolumeClaim(PVC)可以保证 Pod 的存储卷之间一一对应的绑定关系。同时,删除 StatefulSet 关联的 Pod 时,不会删除其关联的 PVC。

独享存储:StatefulSet的存储卷使用VolumeClaimTemplate创建,称为卷申请模板,当StatefulSet使用VolumeClaimTemplate创建一个PersistentVolume时,同样也会为每个Pod分配并创建一个编号的PVC,每个PVC绑定对应的PV,从而保证每个Pod都拥有独立的存储。

[root@k8s-master ~]# cat statefulset-web.yml 
apiVersion: apps/v1
kind: StatefulSet              
metadata:
  name: web
spec:
  serviceName: "headless-web"
  replicas: 3 
  selector:
    matchLabels:
      app: nginx 
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx 
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html

  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      storageClassName: "managed-nfs-storage"
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi

[root@k8s-master ~]# kubectl apply -f statefulset-web.yml 
statefulset.apps/web created

 volumeClaimTemplates: 这个字段delploy是不支持的,只要state支持,这个是通过动态供给完成的,实际上还是申请pvc pv ,www是申请模板里面的名字

[root@k8s-master ~]# kubectl get pvc,pv
NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
persistentvolumeclaim/www-web-0   Bound    pvc-7403fc64-998a-4d94-9bf7-00cde6f4e52d   1Gi        RWO            managed-nfs-storage   87s
persistentvolumeclaim/www-web-1   Bound    pvc-9c10db54-3a82-4374-ad24-b14b59d4819e   1Gi        RWO            managed-nfs-storage   66s
persistentvolumeclaim/www-web-2   Bound    pvc-d8ea2562-6137-404f-bef9-b0c9f700a040   1Gi        RWO            managed-nfs-storage   44s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS          REASON   AGE
persistentvolume/pvc-7403fc64-998a-4d94-9bf7-00cde6f4e52d   1Gi        RWO            Delete           Bound    default/www-web-0   managed-nfs-storage            86s
persistentvolume/pvc-9c10db54-3a82-4374-ad24-b14b59d4819e   1Gi        RWO            Delete           Bound    default/www-web-1   managed-nfs-storage            66s
persistentvolume/pvc-d8ea2562-6137-404f-bef9-b0c9f700a040   1Gi        RWO            Delete           Bound    default/www-web-2   managed-nfs-storage 

 Pvc和pv一一绑定的,可以看到每个独立的Pod都有独立的pv

[root@reg kubernetes]# pwd
/ifs/kubernetes
[root@reg kubernetes]# ls
default-www-web-0-pvc-7403fc64-998a-4d94-9bf7-00cde6f4e52d
default-www-web-1-pvc-9c10db54-3a82-4374-ad24-b14b59d4819e
default-www-web-2-pvc-d8ea2562-6137-404f-bef9-b0c9f700a040

当你删除pod的时候pv pvc不会被删除,并且删除重建之后还是可以读取之前的数据,当pod被重建之后,k8s还是根据pod编号找对应的pvc进行挂载

 

 

如何更新升级 StatefulSet


那么,如果想对一个 StatefulSet 进行升级,该怎么办呢?

在 StatefulSet 中,支持两种更新升级策略,即 RollingUpdate 和 OnDelete。

RollingUpdate策略是默认的更新策略。可以实现 Pod 的滚动升级,跟我们上一节课中 Deployment 介绍的RollingUpdate策略一样。比如我们这个时候做了镜像更新操作,那么整个的升级过程大致如下,先逆序删除所有的 Pod,然后依次用新镜像创建新的 Pod 出来。这里你可以通过kubectl get pod -n demo -w -l app=nginx来动手观察下。

 

 

总结


现在我们就总结下 StatefulSet 的特点:

  • 具备固定的网络标记,比如主机名,域名等;
  • 支持持久化存储,而且最好能够跟实例一一绑定;
  • 可以按照顺序来部署和扩展;
  • 可以按照顺序进行终止和删除操作;
  • 在进行滚动升级的时候,也会按照一定顺序。

借助 StatefulSet 的这些能力,我们就可以去部署一些有状态服务,比如 MySQL、ZooKeeper、MongoDB 等。你可以跟着这个教程在 Kubernetes 中搭建一个 ZooKeeper 集群。

同时使用 RollingUpdate 更新策略还支持通过 partition 参数来分段更新一个 StatefulSet。所有序号大于或者等于 partition 的Pod 都将被更新。你这里也可以手动更新 StatefulSet 的配置来实验下。

当你把更新策略设置为 OnDelete 时,我们就必须手动先删除 Pod,才能触发新的 Pod 更新。