kubernete编排技术三:StatefulSet

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
.cn 域名,1个 12个月
简介: kubernete编排技术三:StatefulSet

微信图片_20221212122522.jpg上一篇文章中,我们讲了deployment的编排技术,也提到了这种编排技术只能编排无状态的pod。但是在我们实际生产环境中,系统复杂很多。比如分布式系统,pod之间往往有依赖关系。再比如mysql数据库,主从节点需要通过binlog同步数据,读写请可能要求发送到不同节点上。对这种有状态的应用,kubernete的解决方案是StatefulSet。


StatefulSet的解决方案是把有状态应用的状态抽象成2种状态,拓扑状态和存储状态,把这些状态记录下来,pod重新创建后,帮助新pod恢复出这些状态。拓扑状态主要是就是有类似主从关系的应用,在启动或者升级发布的时候,pod的启动顺序都要固定,比如主节点先启动,从节点后启动。存储状态就是指不同应用的存储数据要固定,比如多个pod的服务重启后各自读取到的存储数据跟重启之前一样。


拓扑状态


在之前的文章《kubernete架构体系介绍》中提到过,kubernete为每一个pod绑定一个service服务,service服务作为pod的代理访问入口,配置的IP等地址信息是固定不变的,这样即使pod重启后IP地址发生了变化,只要service的ip地址不变,可以不用关心。

外部通过service代理访问pod,必须先访问service,这无非就是2种方式,直接访问service的ip地址或通过域名访问。如下图所示:

微信图片_20221212122549.png

StatefulSet在编排上的一个创新是外部应用访问pod的时候,不用在通过访问service的ip地址或者域名,而是直接访问pod的域名来访问pod,而service的名字绑定在这个pod中对pod的启动顺序进行控制。域名的格式如下:

    <pod-name>.<svc-name>.<namespace>.svc.cluster.local

    这种不提供域名或ip地址的service被称作Headless Service,本质就是yaml中定义的clusterIP是None,yaml文件(HeadlessService.yaml)如下:

    apiVersion: v1
    kind: Service
    metadata:
      name: bootservice
      labels:
        app: bootservice
    spec:
      ports:
      - port: 80
        name: web
      clusterIP: None
      selector:
        app: springboot

    上面的service就会控制selector选择出的携带标签app: springboot的pod。而我们重新改写之前基于deployment的yaml文件,改为基于StatefulSet的文件,文件名springboot-mybatis-statefulset.yaml内容如下:

    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: bootstatefulset
    spec:
      serviceName: "bootservice"
      selector:
        matchLabels:
          app: springboot
      replicas: 2
      template:
        metadata:
          labels:
            app: springboot
        spec:
          containers:
          - name: spingboot-mybatis
            imagePullPolicy: IfNotPresent
            image: zjj2006forever/springboot-mybatis:1.3
            ports:
            - containerPort: 8300

    相比于之前基于deployment的文件,除了修改了kind为StatefulSet和pod名称之外,它多了一个serviceName字段,就是指定代理自己的headless service的名字。


    我们首先执行下面命令创建这个service


    kubectl create -f HeadlessService.yaml

    创建成功后可以查看这个service,CLUSTER-IP为None

    [root@master k8s]# kubectl get service
    NAME          TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
    bootservice   ClusterIP   None         <none>        80/TCP    11s
    kubernetes    ClusterIP   10.96.0.1    <none>        443/TCP   14d

    接着我们创建StatefulSet,命令如下:


    kubectl create -f springboot-mybatis-statefulset.yaml

    成功后我们查看刚刚创建的StatefulSet

    [root@master k8s]# kubectl get StatefulSet
    NAME              READY   AGE
    bootstatefulset   2/2     7s

    注意,创建StatefulSet的时候,我们可以执行下面的命令查看pod创建过程,从输出可以看到,StatefulSet会先创建出一个pod并且编号为0,启动成功后才会开始创建第二个pod,编号为1,这样就固定了pod的创建顺序

    [root@master k8s]# kubectl get pods -w -l app=springboot
    NAME                READY   STATUS    RESTARTS   AGE
    bootstatefulset-0   0/1     Pending   0          0s
    bootstatefulset-0   0/1     Pending   0          0s
    bootstatefulset-0   0/1     ContainerCreating   0          0s
    bootstatefulset-0   1/1     Running             0          3s
    bootstatefulset-1   0/1     Pending             0          0s
    bootstatefulset-1   0/1     Pending             0          0s
    bootstatefulset-1   0/1     ContainerCreating   0          0s
    bootstatefulset-1   1/1     Running             0          2s

    我们进入pod中查看hostname可以发现跟pod名字一样

    [root@master k8s]# kubectl exec -it bootstatefulset-0 -- /bin/sh
    / # hostname
    bootstatefulset-0

    我们ping上面提到的域名,输出正是容器中的IP地址

    / # ping bootstatefulset-0.bootservice.default.svc.cluster.local
    PING bootstatefulset-0.bootservice.default.svc.cluster.local (10.244.1.41): 56 data bytes
    64 bytes from 10.244.1.41: seq=0 ttl=64 time=0.419 ms
    64 bytes from 10.244.1.41: seq=1 ttl=64 time=0.065 ms
    64 bytes from 10.244.1.41: seq=2 ttl=64 time=0.071 ms

    nslookup看一下,解析正常,结果如下:

    / # nslookup  bootstatefulset-0.bootservice.default.svc.cluster.local
    Name:      bootstatefulset-0.bootservice.default.svc.cluster.local
    Address 1: 10.244.1.41 bootstatefulset-0.bootservice.default.svc.cluster.local

    从上面的输出可以看出,stateful为pod生成的域名生效了。之后如果我们删除第一个bootstatefulset-0会发生什么呢?

    执行下面删除命令后查看pod变化状态


    kubectl delete pod bootstatefulset-0

    可以看到bootstatefulset-0这个pod被删除后重新创建出来,并且编号没有变化。

    [root@master kubernetes]# kubectl get pods -w -l app=springboot
    NAME                READY   STATUS    RESTARTS   AGE
    bootstatefulset-0   1/1     Running   0          44m
    bootstatefulset-1   1/1     Running   0          44m
    bootstatefulset-0   1/1     Terminating   0          44m
    bootstatefulset-0   0/1     Terminating   0          44m
    bootstatefulset-0   0/1     Terminating   0          44m
    bootstatefulset-0   0/1     Terminating   0          44m
    bootstatefulset-0   0/1     Pending       0          0s
    bootstatefulset-0   0/1     Pending       0          0s
    bootstatefulset-0   0/1     ContainerCreating   0          0s
    bootstatefulset-0   1/1     Running             0          1s

    再次进入bootstatefulset-0中ping域名,发现pod的ip地址发生了变化,但是域名正常访问,这也说明访问pod是必须使用域名而不能直接用ip地址访问

    [root@master k8s]# kubectl exec -it bootstatefulset-0 -- /bin/sh
    / # ping bootstatefulset-0.bootservice.default.svc.cluster.local
    PING bootstatefulset-0.bootservice.default.svc.cluster.local (10.244.1.46): 56 data bytes
    64 bytes from 10.244.1.46: seq=0 ttl=64 time=0.158 ms
    64 bytes from 10.244.1.46: seq=1 ttl=64 time=0.543 ms

    这样StatefulSet就用pod name+编号的方式为pod的启动和升级发布固定了顺序,在主从关系情况下也能保证主节点先启动,从节点后启动。同时节点暴露的域名是固定的,外部服务需要通过域名访问。


    存储状态


    StatefulSet对存储的解决方案,是引入了Persistent Volume Claim和Persistent Volume,简称PVC和PV。

    下面就是一个PVC的定义,这个PVC定义了一个Volume大小占用256M,挂载方式是可以读写。

    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:
      name: pv-claim
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 256Mi

    注:下面是来自官网的accessModes

    ReadWriteOnce -- the volume can be mounted as read-write by a single node
    ReadOnlyMany -- the volume can be mounted read-only by many nodes
    ReadWriteMany -- the volume can be mounted as read-write by many nodes

    我们可以在之前的StatefulSet声明中,可以使用这个PVC,如下:

    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: bootstatefulset
    spec:
      serviceName: "bootservice"
      selector:
        matchLabels:
          app: springboot
      replicas: 2
      template:
        metadata:
          labels:
            app: springboot
        spec:
          containers:
          - name: spingboot-mybatis
            imagePullPolicy: IfNotPresent
            image: zjj2006forever/springboot-mybatis:1.3
            ports:
            - containerPort: 8300
        volumeMounts:
        - mountPath: "/usr/share/"
          name: pvstorage
          volumeClaimTemplates:
        - metadata:
            name: pvstorage
        spec:
          accessModes:
          - ReadWriteOnce
          resources:
           requests:
            storage: 256Mi

    像上面这样,我们只需要在模板中声明一个PVC的名字,就可以使用这个存储了。这需要kubernete为这个PVC绑定一个pv,用这个这个pv来声明volume,下面我们看一个PV的定义:

    kind: PersistentVolume
    apiVersion: v1
    metadata:
      name: pv-volume
      labels:
        type: local
    spec:
      capacity:
        storage: 512Mi
      accessModes:
        - ReadWriteOnce
      rbd:
        monitors:
        - '10.244.1.158:6789'
        - '10.244.1.159:6789'
        pool: kube
        image: foo
        fsType: ext4
        readOnly: true
        user: admin
        keyring: /etc/ceph/keyring

    注:上面monitors是使用kubectl get pods -n rook-ceph -o wide看到的rook-ceph-mon-开头ip地址


    接着创建上面的StatefulSet后,就会生成2个pvc,名字格式是PVC名字-StatefulSet名字-编号

    执行创建命令


    kubectl create -f springboot-mybatis-statefulset.yaml

    查看pod

    [root@master k8s]# kubectl get pods
    NAME                READY   STATUS    RESTARTS   AGE
    bootstatefulset-0   1/1     Running   0          2m4s
    bootstatefulset-1   1/1     Running   0          2m1s

    查看pvc

    kubectl get pvc -l app=springboot
    NAME                          STATUS    VOLUME                                     CAPACITY   ACCESSMODES   AGE
    pvstorage-bootstatefulset-0   Bound     pvc-12c125c7-b507-11e6-932f-5210a500005   256Mi      RWO           29s                            
    pvstorage-bootstatefulset-1   Bound     pvc-12c136c7-b507-11e6-932f-5210a500005   256Mi      RWO           29s   

    上面创建了这个带编号的pvc后,pod会按照编号来绑定pvc,如上bootstatefulset-0会使用pvstorage-bootstatefulset-0这个pvc,我们在每个pod中创建一个文件,然后删除pod后等待重新创建,文件依然存在。


    这是因为pod被删除后,pv和pvc并没有被删除,而pod被创建出来后,因为StatefulSet的控制,pod会严格按照之前的编号顺序创建出来,而它们会重新绑定相同编号的pvc,从而绑定pvc对应的pv来获取volume里面的数据。


    总结


    StatefulSet也是一种Deployment,只是它的每一个pod都携带了一个唯一并且固定的编号。这个编号非常重要,因为这个编号固定了pod的拓扑关系(比如主从),固定了pod的DNS记录,有了这个序号,当pod重建时,就不会丢失之前的状态了。pvc则固定了pod的存储状态,它与pv进行绑定从而使用pv中声明的volume存储。这样pod重启后数据就不会丢失了。

    相关实践学习
    通过Ingress进行灰度发布
    本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
    容器应用与集群管理
    欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
    相关文章
    |
    存储 应用服务中间件 nginx
    kubernetes Statefulset控制器
    kubernetes Statefulset控制器
    |
    存储 Kubernetes 负载均衡
    【Kubernetes系统原理、核心资源、Pod原理与创建及生命周期管理、Job、Cronjob、Statefulset、Service负载均衡Ingress】
    【Kubernetes系统原理、核心资源、Pod原理与创建及生命周期管理、Job、Cronjob、Statefulset、Service负载均衡Ingress】
    390 2
    |
    存储 Kubernetes 数据库
    剖析 Kubernetes 控制器:Deployment、ReplicaSet 和 StatefulSet 的功能与应用场景
    剖析 Kubernetes 控制器:Deployment、ReplicaSet 和 StatefulSet 的功能与应用场景
    652 0
    |
    6月前
    |
    存储 Kubernetes Cloud Native
    云原生|kubernetes| StateFulSets控制器详解
    云原生|kubernetes| StateFulSets控制器详解
    132 0
    |
    弹性计算 Kubernetes 固态存储
    StatefulSet应用部署
    本场景带您体验如何使用k8s的原生命令kubectl部署一个StatefulSet应用的镜像到k8s集群中,并对该StatefulSet应用进行伸缩操作。
    |
    存储 运维 Kubernetes
    【探索 Kubernetes|作业管理篇 系列 14】StatefulSet 存储状态
    【探索 Kubernetes|作业管理篇 系列 14】StatefulSet 存储状态
    85 0
    |
    存储 域名解析 Kubernetes
    【探索 Kubernetes|作业管理篇 系列 13】StatefulSet 拓扑状态
    【探索 Kubernetes|作业管理篇 系列 13】StatefulSet 拓扑状态
    128 0
    |
    存储 Kubernetes 网络协议
    【云原生 | 从零开始学Kubernetes】二十三、Kubernetes控制器Statefulset
    StatefulSet 是为了管理有状态服务的问题而设计的
    163 0
    【云原生 | 从零开始学Kubernetes】二十三、Kubernetes控制器Statefulset
    |
    Kubernetes NoSQL 测试技术
    Kubernetes StatefulSets有状态应用
    Kubernetes StatefulSets有状态应用
    |
    存储 Kubernetes Cloud Native
    云原生|kubernetes| StateFulSets控制器详解(二)
    云原生|kubernetes| StateFulSets控制器详解
    128 0