• kubernetes控制器之StatefulSet


    一、简介

    实例之间的不等关系以及实例对外数据有依赖关系的应用,就被称为"有状态应用"。
    所谓实例之间的不等关系即对分布式应用来说,各实例,各应用之间往往有比较大的依赖关系,比如某个应用必须先于其他应用启动,否则其他应用将不能启动等。
    对外数据有依赖关系的应用,最显著的就是数据库应用,对于数据库应用,我们是需要持久化保存其数据的,如果是无状态应用,在数据库重启数据和应用就失去了联系,这显然是违背我们的初衷,不能投入生产的。

    所以,为了解决Kubernetes中有状态应用的有效支持,Kubernetes使用StatefulSet来编排管理有状态应用。
    StatefulSet类似于ReplicaSet,不同之处在于它可以控制Pod的启动顺序,它为每个Pod设置唯一的标识。其具有一下功能:

    • 稳定的,唯一的网络标识符
    • 稳定的,持久化存储
    • 有序的,优雅部署和缩放
    • 有序的,自动滚动更新

    StatefulSet的设计很容易理解,它把现实世界抽象为以下两种情况:
    (1)、拓扑状态。这就意味着应用之间是不对等关系,应用要按某种顺序启动,即使应用重启,也必须按其规定的顺序重启,并且重启后其网络标识必须和原来的一样,这样才能保证原访问者能通过同样的方法访问新的Pod;
    (2)、存储状态 。这就意味着应用绑定了存储数据,不论什么时候,不论什么情况,对应用来说,只要存储里的数据没有变化,读取到的数据应该是同一份;

    所以StatefulSet的核心功能就是以某种方式记录Pod的状态,然后在Pod被重新创建时,通过某种方法恢复其状态。

    二、Headless Service

    在介绍StatefulSet之前先了解一下什么是Headless Service。
    我们知道,在Kubernetes中,Service是为一组Pod提供外部访问的一种方式。通常,我们使用 Service访问Pod有一下两种方式:
    (1)、通过Cluster IP,这个Clustre IP就相当于VIP,我们访问这个IP,就会将请求转发到后端Pod上;
    (2)、通过DNS方式,通过这种方式首先得确保Kubernetes集群中有DNS服务。这个时候我们只要访问"my-service.my-namespace.svc,cluster.local",就可以访问到名为my-service的Service所代理的后端Pod;

    而对于第二种方式,有下面两种处理方法:
    (1)、Normal Service,即解析域名,得到的是Cluster IP,然后再按照方式一访问;
    (2)、Headless Service,即解析域名,得到的是后端某个Pod的IP地址,这样就可以直接访问;

    对于第二种处理方法我们可以看到这里并不需要Cluster IP,而是通过域名直接解析后端Pod的IP地址进行访问。
    下面即为一个简单的Headless Service的YAML文件定义:

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

    通过上面的YAML文件可以看出和我们普通的Service定义没有太大的区别,唯一的不同就是clusterIP设置为None,也就是不需要cluster,我们通过kubectl apply -f创建这个Service,然后查看这个Service的Cluster IP为None。
    image.png

    这样创建的Service就是一个Headless Service,它没有VIP,所以会以DNS记录的方式暴露它所代理的Pod。

    三、使用StatefulSet

    在创建StatefulSet之前先创建PV,如下:

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: pv01
      labels:
        release: stable
    spec:
      capacity:
        storage: 1Gi
      accessModes:
      - ReadWriteOnce
      persistentVolumeReclaimPolicy: Recycle
      hostPath:
        path: /tmp/data
    
    ---
    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: pv02
      labels:
        release: stable
    spec:
      capacity:
        storage: 1Gi
      accessModes:
      - ReadWriteOnce
      persistentVolumeReclaimPolicy: Recycle
      hostPath:
        path: /tmp/data
    

    然后执行kubectl apply -f 启动PV,如下:

    [root@master statefulset]# kubectl apply -f pv.yaml 
    persistentvolume/pv01 created
    persistentvolume/pv02 created
    [root@master statefulset]# kubectl get pv
    NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
    pv01   1Gi        RWO            Recycle          Available                                   10s
    pv02   1Gi        RWO            Recycle          Available                                   9s
    
    

    可以看到两个PV的状态都为可用状态,然后编写StatefulSet的YAML文件:

    apiVersion: v1
    kind: Service
    metadata:
      name: nginx
    spec:
      ports:
      - port: 80
        name: web
      clusterIP: None
      selector:
        app: nginx
        role: stateful
    
    ---
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: web
    spec:
      serviceName: "nginx"
      replicas: 2
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
            role: stateful
        spec:
          containers:
          - name: nginx
            image: cnych/nginx-slim:0.8
            ports:
            - containerPort: 80
              name: web
            volumeMounts:
            - name: www
              mountPath: /usr/share/nginx/html
      volumeClaimTemplates:
      - metadata:
          name: www
        spec:
          accessModes: [ "ReadWriteOnce" ]
          resources:
            requests:
              storage: 1Gi
    

    注意上面的 YAML 文件中和volumeMounts进行关联的是一个新的属性:volumeClaimTemplates,该属性会自动声明一个 pvc 对象和 pv 进行管理,而serviceName: "nginx"表示在执行控制循环的时候,用nginx这个Headless Service来保存Pod的可解析身份。

    然后这里我们开启两个终端窗口。在第一个终端中,使用 kubectl get 来查看 StatefulSet 的 Pods 的创建情况。

    $ kubectl get pods -w -l role=stateful
    

    在另一个终端中,使用 kubectl create 来创建定义在 statefulset-demo.yaml 中的 Headless Service 和 StatefulSet。

    $ kubectl create -f statefulset-demo.yaml
    service "nginx" created
    statefulset.apps "web" created
    

    然后我们观察Pod的启动顺序:

    [root@master ~]# kubectl get pods -w -l role=stateful
    web-0   0/1   Pending   0     0s
    web-0   0/1   Pending   0     0s
    web-0   0/1   ContainerCreating   0     0s
    web-0   1/1   Running             0     3m12s
    web-1   0/1   Pending             0     0s
    web-1   0/1   Pending             0     0s
    web-1   0/1   Pending             0     1s
    web-1   0/1   ContainerCreating   0     1s
    web-1   1/1   Running             0     5s
    

    通过 上面的创建过程,我们可以看出,StatefulSet给它所管理的Pod进行了编号,其命名规则为[statefulset-name]-[index],其index从0开始,与StatefulSet的每个Pod实例一一对应,绝不重复。更重要的是其Pod的创建过程是顺序的,如上web-0进入running状态后web-1才进入pending状态。

    我们通过命令来查看其创建结果:

    [root@master statefulset]# kubectl get statefulset web
    NAME   READY   AGE
    web    2/2     17m
    [root@master statefulset]# kubectl get pods -l role=stateful
    NAME    READY   STATUS    RESTARTS   AGE
    web-0   1/1     Running   0          17m
    web-1   1/1     Running   0          14m
    [root@master statefulset]# kubectl get svc nginx
    NAME    TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
    nginx   ClusterIP   None         <none>        80/TCP    17m
    

    当两个Pod都进入running状态后,就可以查看其各自的网络身份了,我们通过kubectl exec来查看,如下:

    [root@master statefulset]# kubectl exec web-0 -- sh -c 'hostname'
    web-0
    [root@master statefulset]# kubectl exec web-1 -- sh -c 'hostname'
    web-1
    

    可以看到这两个pod的hostname和pod的名字是一致的,都被分配为对应的编号,接下来我们用DNS的方式来访问Headless Service。
    我们先用如下命令启动一个Pod:

    kubectl run -i --tty --image busybox dns-test --restart=Never --rm /bin/sh 
    

    然后在这个Pod里用nslookup解析一个Pod对应的Headless Service:

    kubectl run -i --tty --image centos dns-test --restart=Never --rm /bin/sh
    sh-4.2# yum install bind-utils -y
    sh-4.2# nslookup web-0.nginx
    Server:		10.68.0.2
    Address:	10.68.0.2#53
    
    Name:	web-0.nginx.default.svc.cluster.local
    Address: 172.20.2.63
    
    sh-4.2# nslookup web-1.nginx
    Server:		10.68.0.2
    Address:	10.68.0.2#53
    
    Name:	web-1.nginx.default.svc.cluster.local
    Address: 172.20.2.64
    
    

    从nslookup的结果分析,在访问web-0.nginx的时候解析的是web-0这个Pod的IP,另一个亦然。
    这时候如果我们把两个Pod删掉,再观察其重启Pod的过程:

    [root@master statefulset]# kubectl delete pod -l role=stateful
    pod "web-0" deleted
    pod "web-1" deleted
    

    然后查看其重启顺序如下:

    [root@master ~]# kubectl get pods -w -l role=stateful
    NAME    READY   STATUS    RESTARTS   AGE
    web-0   0/1     ContainerCreating   0          0s
    web-0   1/1     Running             0          2s
    web-1   0/1     Pending             0          0s
    web-1   0/1     Pending             0          0s
    web-1   0/1     ContainerCreating   0          0s
    web-1   1/1     Running             0          2s
    
    

    我们可以看到,我们把Pod删除后,其重启顺序还是按原先的编号重启,并且其网络标识和原来依然一样。
    通过这种严格的对应规则,StatefulSet就保证了Pod的网络标识的稳定性,通过这个方法,就可以把Pod的拓扑状态按照Pod的名字+编号的方式固定起来。此外,Kubernetes还为每一个Pod提供了一个固定并且唯一的访问入口,即这个Pod的DNS记录。

    我们还可以查看一下PV和PVC的绑定情况:

    [root@master statefulset]# kubectl get pv
    NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
    pv01   1Gi        RWO            Recycle          Bound    default/www-web-0                           129m
    pv02   1Gi        RWO            Recycle          Bound    default/www-web-1                           129m
    [root@master statefulset]# kubectl get pvc
    NAME        STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    www-web-0   Bound    pv01     1Gi        RWO                           124m
    www-web-1   Bound    pv02     1Gi        RWO                           116m
    
    

    由此,我们对StatefulSet梳理如下:
    (1)、StatefulSet直接管理的是Pod。这是因为StatefulSet里的Pod实例不像ReplicaSet中的Pod实例完全一样,它们是有细微的区别,比如每个Pod的名字、hostname等是不同的,而且StatefulSet区分这些实例的方式就是为Pod加上编号;
    (2)、Kubernetes通过Headless Service为这个编号的Pod在DNS服务器中生成带同样编号的记录。只要StatefulSet能保证这个Pod的编号不变,那么Service中类似于web-0.nginx.default.svc.cluster.local这样的DNS记录就不会变,而这条记录所解析的Pod IP地址会随着Pod的重新创建自动更新;
    (3)、StatefulSet还可以为每个Pod分配并创建一个和Pod同样编号的PVC。这样Kubernetes就可以通过Persitent Volume机制为这个PVC绑定对应的PV,从而保证每一个Pod都拥有独立的Volume。这种情况下即使Pod被删除,它所对应的PVC和PV依然会保留下来,所以当这个Pod被重新创建出来过后,Kubernetes会为它找到同样编号的PVC,挂载这个PVC对应的Volume,从而获取到以前Volume以前的数据;

    四、总结

    StatefulSet这个控制器的主要作用之一,就是使用Pod模板创建Pod的时候,对它们进行编号,并且按照编号顺序完成作业,当StatefulSet的控制循环发现Pod的实际状态和期望状态不一致的时候,也会按着顺序对Pod进行操作。

    当然 StatefulSet 还拥有其他特性,在实际的项目中,我们还是很少回去直接通过 StatefulSet 来部署我们的有状态服务的,除非你自己能够完全能够 hold 住,对于一些特定的服务,我们可能会使用更加高级的 Operator 来部署,比如 etcd-operator、prometheus-operator 等等,这些应用都能够很好的来管理有状态的服务,而不是单纯的使用一个 StatefulSet 来部署一个 Pod就行,因为对于有状态的应用最重要的还是数据恢复、故障转移等等。

  • 相关阅读:
    如何在 Knative 中部署 WebSocket 和 gRPC 服务?
    全球首个开放应用模型 OAM 开源 | 云原生生态周报 Vol. 23
    从零开始入门 K8s | Kubernetes 网络概念及策略控制
    重磅发布 | 全球首个云原生应用标准定义与架构模型 OAM 正式开源
    成都,我们来啦 | Dubbo 社区开发者日
    一文读懂分布式架构知识体系(内含超全核心知识大图)
    阿里巴巴开源 Dragonwell JDK 最新版本 8.1.1-GA 发布
    可能是国内第一篇全面解读 Java 现状及趋势的文章
    从零开始入门 K8s | 可观测性:监控与日志
    阿里巴巴的云原生与开发者
  • 原文地址:https://www.cnblogs.com/coolops/p/13046890.html
Copyright © 2020-2023  润新知