• Kubernetes(k8s)控制器(五):有状态应用StatefulSet


    一.系统环境

    本文主要基于Kubernetes1.21.9和Linux操作系统CentOS7.4。

    服务器版本 docker软件版本 Kubernetes(k8s)集群版本 CPU架构
    CentOS Linux release 7.4.1708 (Core) Docker version 20.10.12 v1.21.9 x86_64

    Kubernetes集群架构:k8scloude1作为master节点,k8scloude2,k8scloude3作为worker节点。

    服务器 操作系统版本 CPU架构 进程 功能描述
    k8scloude1/192.168.110.130 CentOS Linux release 7.4.1708 (Core) x86_64 docker,kube-apiserver,etcd,kube-scheduler,kube-controller-manager,kubelet,kube-proxy,coredns,calico k8s master节点
    k8scloude2/192.168.110.129 CentOS Linux release 7.4.1708 (Core) x86_64 docker,kubelet,kube-proxy,calico k8s worker节点
    k8scloude3/192.168.110.128 CentOS Linux release 7.4.1708 (Core) x86_64 docker,kubelet,kube-proxy,calico k8s worker节点

    二.前言

    在Kubernetes集群中部署和管理有状态应用是一项复杂的任务。有状态应用(Stateful Applications)通常要求持久化存储、唯一网络标识和有序的启动顺序。为了解决这些需求,Kubernetes提供了一个强大的控制器——StatefulSet。

    使用StatefulSet的前提是已经有一套可以正常运行的Kubernetes集群,关于Kubernetes(k8s)集群的安装部署,可以查看博客《Centos7 安装部署Kubernetes(k8s)集群》https://www.cnblogs.com/renshengdezheli/p/16686769.html。

    三.StatefulSet简介

    StatefulSet是Kubernetes的一个核心控制器,用于管理有状态应用的部署和伸缩。它为每个Pod分配一个唯一的标识符,确保有状态应用的稳定性和可扩展性。StatefulSet还可以自动处理Pod的创建、删除和更新,确保有状态应用的高可用性。

    相比于Deployment等控制器,StatefulSet提供了以下特性:

    • 稳定的网络标识符:每个Pod都会被分配一个稳定的主机名和网络标识符,使得有状态应用能够方便地进行服务发现和通信。
    • 有序的部署和扩展:StatefulSet确保有状态应用的Pod按照指定的顺序进行部署和扩展,保证有序启动和关闭。
    • 持久化存储:StatefulSet支持将持久化存储卷(Persistent Volume)与Pod绑定,使得有状态应用能够保留数据。

    StatefulSet简写为sts,deployment控制器创建出来的pod都是一样的,更适合用于对外提供服务的pod,但是有一些应用,只适用于公

    司内部,并不想对外提供服务,比如一个数据库有1个master节点和3个slave节点,master提供写,slave提供读,master通过日志把数

    据拷贝给slave,slave节点有自己的存储,slave1读slave1的存储,slave2读slave2的存储,以此类推,slave节点不共享存储,这种情况

    不适合使用deployment来创建pod,这种情况就需要使用statefulset控制器。

    StatefulSet常用的属性包括:

    • serviceName:指定与StatefulSet关联的Service的名称。这个Service将用于提供稳定的网络标识符,供其他应用访问StatefulSet中的Pod。
    • volumeClaimTemplates:定义了每个Pod所需的持久化存储卷(Claim)模板。可以在这里指定存储类、存储大小等参数,以满足有状态应用对持久化存储的需求。
    • podManagementPolicy:指定Pod管理策略,可以是OrderedReady或Parallel。OrderedReady表示按顺序启动和终止Pod,保证有序性和稳定性;Parallel表示可以并行启动和终止Pod,适用于无依赖关系的应用场景。

    四.有状态应用和无状态应用区别

    在Kubernetes中,应用可以分为有状态应用和无状态应用。有状态应用是指需要保留数据或状态的应用,而无状态应用则不需要。

    有状态应用与无状态应用之间的主要区别如下:

    • 网络标识符:有状态应用需要稳定的网络标识符,使得其他服务能够准确地访问它们。而无状态应用通常使用Service来提供负载均衡和服务发现,不直接依赖于特定的网络标识符。
    • 数据持久化:有状态应用需要将数据保存在持久化存储中,并且能够在Pod重新启动时恢复数据。无状态应用通常将数据保存在外部数据库或缓存中,不依赖于Pod的生命周期。
    • 有序启动和关闭:有状态应用通常需要按照特定的顺序启动和关闭,以确保数据的一致性。无状态应用则可以并行启动和关闭,不需要保证顺序。

    五.StatefulSet

    5.1 创建StatefulSet

    创建存放yaml文件的目录。

    [root@k8scloude1 ~]# mkdir statefulset
    
    [root@k8scloude1 ~]# cd statefulset/
    

    创建命名空间。

    [root@k8scloude1 statefulset]# kubectl create ns statefulset
    namespace/statefulset created
    

    切换命名空间。

    [root@k8scloude1 statefulset]# kubens statefulset
    Context "kubernetes-admin@kubernetes" modified.
    Active namespace is "statefulset".
    

    现在还没有pod。

    [root@k8scloude1 statefulset]# kubectl get pod
    No resources found in statefulset namespace.
    

    查看存储类storageclass,managed-nfs-storage是我们前面章节创建的storageclass,关于存储类storageclass的详细信息,请查看博客《Kubernetes(k8s)存储管理之数据卷volumes(五):动态制备-存储类StorageClass》。

    [root@k8scloude1 statefulset]# cd sts/
    
    [root@k8scloude1 sts]# kubectl get sc
    NAME                  PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
    managed-nfs-storage   fuseim.pri/ifs   Delete          Immediate           false                  71d
    

    查看managed-nfs-storage 的描述信息。

    [root@k8scloude1 sts]# kubectl describe sc managed-nfs-storage 
    Name:            managed-nfs-storage
    IsDefaultClass:  No
    Annotations:     kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{},"name":"managed-nfs-storage"},"parameters":{"archiveOnDelete":"false"},"provisioner":"fuseim.pri/ifs"}
    
    Provisioner:           fuseim.pri/ifs
    Parameters:            archiveOnDelete=false
    AllowVolumeExpansion:  <unset>
    MountOptions:          <none>
    ReclaimPolicy:         Delete
    VolumeBindingMode:     Immediate
    Events:                <none>
    

    StatefulSet的yaml文件如下,功能为:创建一个StatefulSet,其中包含一个Pod运行着Nginx容器,并且有一个挂载了持久化存储卷的文件系统。这个StatefulSet通过Service提供稳定的网络标识符,可以被其他应用访问。

    [root@k8scloude1 sts]# vim sts.yaml 
    
    [root@k8scloude1 sts]# cat sts.yaml 
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: web
    spec:
      selector:
        #matchLabels:设置了一个标签选择器,选择具有app: nginx标签的Pod。
        matchLabels:
          app: nginx 
      #serviceName: "nginx":指定了与StatefulSet关联的Service的名称为"nginx"。该Service将提供稳定的网络标识符供其他应用访问StatefulSet中的Pod。    
      serviceName: "nginx"
      #replicas: 1:定义了StatefulSet的副本数量为1,即只创建一个Pod。
      replicas: 1
      template:
        metadata:
          labels:
            app: nginx 
        spec:
          #当需要关闭容器时,立即杀死容器而不等待默认的30秒优雅停机时长。
          terminationGracePeriodSeconds: 0
          containers:
          - name: nginx
            image: nginx
            #imagePullPolicy: IfNotPresent:表示如果本地已经存在该镜像,则不重新下载;否则从远程 Docker Hub 下载该镜像
            imagePullPolicy: IfNotPresent
            #把www卷挂载到容器内的路径为"/usr/share/nginx/html"
            volumeMounts:
            - name: www
              mountPath: /usr/share/nginx/html
      #volumeClaimTemplates:定义了持久化存储卷(Claim)模板,用于创建Pod所需的持久化存储。        
      volumeClaimTemplates: 
      - metadata:
          #name: www:指定了卷模板的名称为"www"。
          name: www
        spec:
          #accessModes: [ "ReadWriteOnce" ]:设置了卷的访问模式为"ReadWriteOnce",即可以被一个节点以读写方式挂载。
          accessModes: [ "ReadWriteOnce" ]
          #storageClassName: "managed-nfs-storage":指定了存储类的名称为"managed-nfs-storage",用于创建持久化存储卷。
          storageClassName: "managed-nfs-storage" 
          resources:
            requests:
              #requests:设置了卷的请求资源,即要求1Gi的存储空间。
              storage: 1Gi
    

    现在还没有PVC。关于PV,PVC的详细内容,请查看博客《Kubernetes(k8s)存储管理之数据卷volumes(四):持久卷Persistent Volume 》。

    [root@k8scloude1 sts]# kubectl get pvc
    No resources found in statefulset namespace.
    

    创建statefulset。

    [root@k8scloude1 sts]# kubectl apply -f sts.yaml 
    statefulset.apps/web created
    

    创建statefulset之后,pod和PVC都有了。

    [root@k8scloude1 sts]# kubectl get pod
    NAME    READY   STATUS    RESTARTS   AGE
    web-0   1/1     Running   0          8s
    
    [root@k8scloude1 sts]# kubectl get pvc
    NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
    www-web-0   Bound    pvc-3b58a17b-acbd-4eb6-a1e4-a2bd2cf331ac   1Gi        RWO            managed-nfs-storage   37s
    

    查看statefulset。

    [root@k8scloude1 sts]# kubectl get statefulset -o wide
    NAME   READY   AGE    CONTAINERS   IMAGES
    web    1/1     111s   nginx        nginx
    
    [root@k8scloude1 sts]# kubectl get sts -o wide
    NAME   READY   AGE     CONTAINERS   IMAGES
    web    1/1     2m11s   nginx        nginx
    

    5.2 scale扩展副本数

    把statefulset的副本数扩展为2。

    [root@k8scloude1 sts]# kubectl scale sts web --replicas=2
    statefulset.apps/web scaled
    

    查看statefulset。

    [root@k8scloude1 sts]# kubectl get sts -o wide
    NAME   READY   AGE   CONTAINERS   IMAGES
    web    2/2     3m    nginx        nginx
    

    pod变为2个了。

    [root@k8scloude1 sts]# kubectl get pod -o wide
    NAME    READY   STATUS    RESTARTS   AGE     IP               NODE         NOMINATED NODE   READINESS GATES
    web-0   1/1     Running   0          3m27s   10.244.251.230   k8scloude3   <none>           <none>
    web-1   1/1     Running   0          37s     10.244.251.219   k8scloude3   <none>           <none>
    

    把statefulset的副本数扩展为3。

    [root@k8scloude1 sts]# kubectl scale sts web --replicas=3
    statefulset.apps/web scaled
    

    pod变为3个了。

    [root@k8scloude1 sts]# kubectl get pod
    NAME    READY   STATUS    RESTARTS   AGE
    web-0   1/1     Running   0          4m43s
    web-1   1/1     Running   0          113s
    web-2   1/1     Running   0          13s
    

    查看statefulset。

    [root@k8scloude1 sts]# kubectl get sts
    NAME   READY   AGE
    web    3/3     5m9s
    

    pvc也变为了3个。

    [root@k8scloude1 sts]# kubectl get pvc
    NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
    www-web-0   Bound    pvc-3b58a17b-acbd-4eb6-a1e4-a2bd2cf331ac   1Gi        RWO            managed-nfs-storage   5m54s
    www-web-1   Bound    pvc-689e6b63-9ca8-413b-b240-5e2fae467a12   1Gi        RWO            managed-nfs-storage   3m4s
    www-web-2   Bound    pvc-565f137f-9e4b-44a1-8953-2b71e393a409   1Gi        RWO            managed-nfs-storage   84s
    

    在每个pod里写入不同的内容。

    [root@k8scloude1 sts]# kubectl exec -it web-0 -- sh -c "echo web-0 > /usr/share/nginx/html/index.html"
    
    [root@k8scloude1 sts]# kubectl exec -it web-1 -- sh -c "echo web-1 > /usr/share/nginx/html/index.html"
    
    [root@k8scloude1 sts]# kubectl exec -it web-2 -- sh -c "echo web-2 > /usr/share/nginx/html/index.html"
    

    把statefulset的副本数扩展为0,所有pod都被删除了。

    [root@k8scloude1 sts]# kubectl scale sts web --replicas=0
    statefulset.apps/web scaled
    
    [root@k8scloude1 sts]# kubectl get pod
    No resources found in statefulset namespace.
    

    把statefulset的副本数扩展为3。

    [root@k8scloude1 sts]# kubectl scale sts web --replicas=3
    statefulset.apps/web scaled
    

    pod变为3个了。

    [root@k8scloude1 sts]# kubectl get pod
    NAME    READY   STATUS              RESTARTS   AGE
    web-0   1/1     Running             0          3s
    web-1   0/1     ContainerCreating   0          1s
    
    [root@k8scloude1 sts]# kubectl get pod
    NAME    READY   STATUS    RESTARTS   AGE
    web-0   1/1     Running   0          8s
    web-1   1/1     Running   0          6s
    web-2   1/1     Running   0          4s
    

    可以发现,把pod删除之后,又创建3个pod,新的3个pod名字没变,并且web-0的存储还是www-web-0,web-1的存储还是www-web-1,web-2的存储还是www-web-2,pod对应的存储不会错乱,每个pod里面的内容也没有变化。

    [root@k8scloude1 sts]# kubectl get pod -o wide
    NAME    READY   STATUS    RESTARTS   AGE     IP               NODE         NOMINATED NODE   READINESS GATES
    web-0   1/1     Running   0          5m41s   10.244.251.225   k8scloude3   <none>           <none>
    web-1   1/1     Running   0          5m39s   10.244.251.226   k8scloude3   <none>           <none>
    web-2   1/1     Running   0          5m37s   10.244.251.218   k8scloude3   <none>           <none>
    
    [root@k8scloude1 sts]# kubectl exec -it web-0 -- sh -c "cat /usr/share/nginx/html/index.html"
    web-0
    
    [root@k8scloude1 sts]# kubectl exec -it web-1 -- sh -c "cat /usr/share/nginx/html/index.html"
    web-1
    
    [root@k8scloude1 sts]# kubectl exec -it web-2 -- sh -c "cat /usr/share/nginx/html/index.html"
    web-2
    

    手动删除web-1这个pod。

    [root@k8scloude1 sts]# kubectl delete pod web-1
    pod "web-1" deleted
    

    statefulset自动创建了一个一模一样的web-1。

    [root@k8scloude1 sts]# kubectl get pod -o wide
    NAME    READY   STATUS    RESTARTS   AGE    IP               NODE         NOMINATED NODE   READINESS GATES
    web-0   1/1     Running   0          3h7m   10.244.251.225   k8scloude3   <none>           <none>
    web-1   1/1     Running   0          3s     10.244.251.221   k8scloude3   <none>           <none>
    web-2   1/1     Running   0          3h7m   10.244.251.218   k8scloude3   <none>           <none>
    

    5.3 创建无头服务headless service

    删除其中一个pod,statefulset很快又会生成一个同名的pod,但是IP地址发生变化,不适合直接使用pod的IP地址访问,使用svc访问更

    好,但是创建svc有一定问题,svc具有负载均衡的作用,你访问svc,可能给你后端转发到web-0,也可能后端转发到web-1,后端pod又

    都有自己专属的存储,所以svc也不合适,这时就需要无头服务(headless service)了。关于svc的详细内容,请查看博客《Kubernetes(k8s)服务service:service的发现和service的发布》。

    现在没有svc。

    [root@k8scloude1 sts]# kubectl get svc
    No resources found in statefulset namespace.
    

    如下是无头服务headless service的yaml文件,功能为:创建一个名为"nginx"的Service,它与具有app: nginx标签的Pod关联。该Service通过暴露在80端口上,并且没有Cluster IP地址。

    无头服务headless service和service的区别是:无头服务headless service的 clusterIP为None。

    [root@k8scloude1 sts]# cat svc.yaml 
    apiVersion: v1
    kind: Service
    metadata:
      name: nginx
      #设置了一个标签app: nginx,与其他相关资源进行关联。
      labels:
        app: nginx
    spec:
      ports:
      #port: 80:将Service的端口设置为80。
      - port: 80
        #name: web:为该端口指定名称为"web"。
        name: web
      #clusterIP: None:将Service的Cluster IP设置为"None",表示该Service是一个Headless Service,没有集群内部的虚拟IP地址。  
      clusterIP: None
      selector:
        #app: nginx:设置了一个标签选择器,选择具有app: nginx标签的Pod。
        app: nginx
    

    创建无头服务headless service。

    [root@k8scloude1 sts]# kubectl apply -f svc.yaml 
    service/nginx created
    

    查看无头服务headless service,CLUSTER-IP为None。

    [root@k8scloude1 sts]# kubectl get svc -o wide
    NAME    TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE   SELECTOR
    nginx   ClusterIP   None         <none>        80/TCP    12s   app=nginx
    

    5.4 通过无头服务headless service访问StatefulSet

    查看pod的标签。

    [root@k8scloude1 sts]# kubectl get pod -o wide --show-labels
    NAME    READY   STATUS    RESTARTS   AGE     IP               NODE         NOMINATED NODE   READINESS GATES   LABELS
    web-0   1/1     Running   0          3h20m   10.244.251.225   k8scloude3   <none>           <none>            app=nginx,controller-revision-hash=web-748c7c878b,statefulset.kubernetes.io/pod-name=web-0
    web-1   1/1     Running   0          13m     10.244.251.221   k8scloude3   <none>           <none>            app=nginx,controller-revision-hash=web-748c7c878b,statefulset.kubernetes.io/pod-name=web-1
    web-2   1/1     Running   0          3h20m   10.244.251.218   k8scloude3   <none>           <none>            app=nginx,controller-revision-hash=web-748c7c878b,statefulset.kubernetes.io/pod-name=web-2
    

    因为无头服务headless service的CLUSTER-IP为None,所以访问方式为:pod名.svc名 来访问pod,statefulset创建的pod,pod名字是不变的。

    [root@k8scloude1 sts]# curl web-0.nginx
    curl: (6) Could not resolve host: web-0.nginx; 未知的名称或服务
    

    容器外无法访问。

    [root@k8scloude1 sts]# curl http://web-0.nginx
    curl: (6) Could not resolve host: web-0.nginx; 未知的名称或服务
    

    创建一个临时容器podclient作为客户端,来访问pod,--rm表示创建临时容器。

    [root@k8scloude1 sts]# kubectl run -it podclient --image=yauritux/busybox-curl:latest --rm --image-pull-policy=IfNotPresent -- sh
    If you don't see a command prompt, try pressing enter.
    
    #访问成功
    /home # curl web-0.nginx
    web-0
    
    /home # curl web-1.nginx
    web-1
    
    /home # curl web-2.nginx
    web-2
    
    /home # exit
    Session ended, resume using 'kubectl attach podclient -c podclient -i -t' command when the pod is running
    pod "podclient" deleted
    

    可以发现,StatefulSet只适合内部访问,不适合外部访问。

    [root@k8scloude1 sts]# kubectl get sts
    NAME   READY   AGE
    web    3/3     3h43m
    

    删除StatefulSet。

    [root@k8scloude1 sts]# kubectl delete sts web
    statefulset.apps "web" deleted
    
    [root@k8scloude1 sts]# kubectl get sts
    No resources found in statefulset namespace.
    
    [root@k8scloude1 sts]# kubectl get pod
    No resources found in statefulset namespace.
    

    六.总结

    本文介绍了Kubernetes中的一个重要控制器——StatefulSet,用于管理有状态应用的部署和伸缩。通过为每个Pod分配唯一的标识符和稳定的网络标识,StatefulSet确保了有状态应用的稳定性、可扩展性和数据持久性。相比于无状态应用,有状态应用需要更多的关注持久化存储和有序性启动等问题。

    使用StatefulSet可以轻松地部署和管理有状态应用,使其能够充分发挥Kubernetes的优势。通过深入理解StatefulSet的概念和特性,我们可以更好地构建和管理复杂的有状态应用。

  • 相关阅读:
    Java调用操作系统命令的输出乱码问题解决
    2023华为OD统一考试(B卷)题库清单(持续收录中)以及考点说明
    AI应用开发之路-准备:发起第2个开源小项目 SemanticKernel.DashScope
    Python的get请求报错Error: Unexpected status code 400
    AI智能客服机器人是什么?对企业重要吗?
    传输层 TCP 拥塞控制(3):快速重传与快速恢复
    如何使用Python将PDF转为图片
    mapboxgl 中插值表达式的应用场景
    Leetcode周赛365补题(3 / 3)
    从苹果、SpaceX等高科技企业的产品发布会看企业产品战略和敏捷开发的关系
  • 原文地址:https://www.cnblogs.com/renshengdezheli/p/17530743.html