• k8s之StatefulSet介绍(六)


    复制有状态的Pod
    replicaSet通过一个pod模版创建多个pod副本。这些副本除了它们的名字和IP地址不同外,没有别的差异。如果pod模版里描述了一个关联到特定持久卷声明的数据卷,那么ReplicaSet的所有副本都将共享这个持久卷声明,也就是绑定到同一个持久卷声明。
    因为是在pod模版里关联持久卷声明的,又会依据pod模版创建多个副本,则不能对每个副本都指定独立的持久卷声明。所以也不能通过一个ReplicaSet来运行一个每个实例都需要独立存储的分布式数据存储服务,至少通过单个ReplicaSet是做不到的。老实说,之前你学习到的所有API对象都不能提供这样的数据存储服务,还需要一个其他的对象--StatefulSet
     
    我们先看不使用StatefulSet的情况下有没有方法实现多个副本有自己的持久卷声明。
    三种取巧的方法。
    第一种方法,不使用ReplicaSet,使用Pod创建多个pod,每个pod都有独立的持久卷声明。需要手动创建它们,当有的pod消失后(节点故障),需要手动创建它们。因此不是一个好方法。
    第二种方法,多个replicaSet ,每个rs只有一个pod副本。但这看起来很笨重,而且没办法扩缩容。
    第三种方法,使用同一个ReplicaSet,大家也都挂载同一个持久卷声明,应用内部做好互斥,创建多个data数据目录,每一个pod用一个标记为在用,后面应用不能选被标记为在用的目录。这样做很难保证协调的一点没问题,同时大家用同一个持久卷,读写io将成为整个应用的瓶颈。
     
    除了上面的存储需求,集群应用也会要求每一个实例拥有生命周期内唯一标识。pod可以随时被删掉,然后被新的pod替代。当一个ReplicaSet中的pod被替换时,尽管新的pod也可能使用被删除pod数据卷中的数据,但它却是拥有全新主机名和IP的崭新pod.在一些应用中,当启动的实例拥有完全新的网络标识,但还使用旧实例的数据时,很可能引起问题,比如etcd存储服务。
    当然也可以创建多个service ,每一个replicaset对应一个service,那么一样很笨重,且显得很低级。辛运的是,Kubernetes为我们提供了这类需求的完美解决方案--StatefulSet.
     
    了解StatefulSet
    可以创建一个StatefulSet资源代替ReplicaSet来运行这类pod.它们是专门定制的一类应用,这类应用中每一个实例都是不可替代的个体,都拥有稳定的名字和状态。
    对比StatefulSet 与 ReplicaSet 或 ReplicationController
    RS或RC管理的pod副本比较像牛,它们都是无状态的,任何时候它们都可以被一个全新的pod替换。然后有状态的pod需要不同的方法,当一个有状态的pod挂掉后,这个pod实例需要在别的节点上重建,但是新的实例必须与被替换的实例拥有相同的名称、网络标识和状态。这就是StatefulSet如何管理pod的。
    StatefulSet 保证了pod在重新调度后保留它们的标识和状态。它让你方便地扩容、缩容。与RS类似,StatefulSet也会指定期望的副本数,它决定了在同一时间内运行的宠物数。也是依据pod模版创建的,与RS不同的是,StatefulSet 创建的pod副本并不是完全一样的。每个pod都可以拥有一组独立的数据卷(持久化状态)。另外pod的名字都是规律的(固定的),而不是每个新pod都随机获取一个名字。
     
    提供稳定的网络标识
    StatefulSet 创建的pod的名称,按照从零开始的顺序索引,这个会体现在pod的名称和主机名称上,同样还会体现在pod对应的固定存储上。
    有状态的pod与普通的pod不一样的是,有状态的pod有时候需要通过其主机名来定位,而无状态的不需要,因为无状态的都一样,随机选一个就行,但对于有状态的来说,每一个pod都不一样,通常希望操作的是特定的一个。基于这个原因,一个StatefulSet要求你创建一个用来记录每个pod网络标记的headless Service。通过这个Service,每个pod将拥有独立的DNS记录,这样集群里它的伙伴或者客户端就可以通过主机名找到它。比如说一个属于default命名空间,名为foo的控制服务,它的一个pod名称为A-0,那么完整域名为:a-0.foo.default.svc.cluster.local。而在ReplicaSet是行不通的。
    此外我们可以在容器中通过dig foo.default.svc.cluster.local对应的SRV记录,获取一个StatefulSet中所有pod的名称.
     
    StatefulSet扩缩容的特点
    扩容,会按照索引进行
    缩容,也会按照索引,删除索引值最大的 pod
    缩容StatefulSet任何时候只会操作一个pod实例,所以会很慢,不是因为索引要顺序进行,而是为了避免数据丢失。举例来说,一个分布式存储应用副本数为2,如果同时下线两个,一份数据记录就会丢失。
    基于以上原因,StatefulSet在有实例不健康的情况下是不允许进行缩容操作的。一个不健康,你又缩容一个这样相当于两个同时下线。
    持久卷的创建和删除
    扩容Statefulset增加一个副本,会创建两个或更多的API对象(一个pod和一个与之关联的持久卷声明)。但对于缩容来将,只会删除一个pod,而遗留下之前创建的声明。因为当一个声明被删除后,与之绑定的持久卷就会被回收或删除,其上面的数据就会丢失。基于这个原因,你需要释放特定的持久卷时,需要手动删除对应的持久卷声明。
     
    StatefulSet的保障机制。
    一个有状态的pod总会被一个完全一致的pod替换(两者相同的名称,主机名和存储等)。这个替换发生在kubernetes发现旧pod不存在时(例如手动删除这个pod).
    那么当Kubernetes不能确定一个pod的状态呢?如果它创建一个完全一致的pod,那系统中就会有两个完全一致的pod在同时运行。这两个pod会绑定到相同的存储,所以这两个相同标记的进程会同时写相同的文件。
    为了保证两个拥有相同标记和绑定相同持久卷声明的有状态的pod实例不会同时运行,statefulset遵循at-most-one语义。也就是说一个StatefulSet必须在准确确认一个pod不再运行后,才会去创建它的替换pod。这对如何处理节点故障有很大帮助。具体实现,内部的,暂不深入。
    讲了那么多StatefulSet实现有状态pod的好处,下面看看如何创建。
     
    我们假设使用gec创建三个pv

    kind: list
    apiVersion: v1
    item:
    -   apiVersion: v1
      kind: PersistenVolume
      metadata:
        name: pv-a
      spec:
        capacity:
          storage: 1Mi
        accessModes:
        -    ReadWriteOnce
        persistenVolumeReclaimPolicy: Recycle  卷被声明释放后,空间会被回收再利用
        gcePersistentDisk:
          poName: pv-a
          fsType: nfs4
    -   apiVersion: v1
      kind: PersistenVolume
      metadata:
        name: pv-b
    ...
    准备好pv后,我们接下来创建statefulset
     
    如我们之前将到的,在部署一个StatefulSet之前,需要创建一个用于在有状态的pod之间提供网络标识的headless Service

    apiVersion: v1
    kind: Service
    metadata:
      name: kubia
    spec:
      clusterIP: None        (StatefulSet的控制Service必须时None即headless模式)
      selector:
        app: kubia
      ports:
      -   name: http
        port: 80
    创建StatefulSet详单

    apiVersion: apps/v1beta1
    kind: StatefulSet
    metadata:
      name: kubia
    spec:
      serviceName: kubia
      replicas: 2
      template:
        metadta:
          labels:
            app: kubia
        spec:
          containers:
          -   name: kubia
            image: luksa/kubia-pet
            ports:
            -   name: kubia
              containerPort: 8080

    volumeMounts:

            - name: data
                 mountPath: /var/data
      volumeClaimTemplates:
      -   metadata:
        name: data
        spec:
          resources:
            requests:
              storage: 1Mi
          accessModes:
          -    ReadWriteOnce
    创建:
    kubectl create -f kubia-statefulset.yaml
     
    列出pod:
    kubectl get pod
    Name READY
    kubia-0 0/1 ...
    看到会一个个进行
    kubectl get pod
    Name READY
    kubia-0 1/1 ...
    kubia-1 0/1 ...
     
    查看pvc
    kubectl get pvc
    Name STATUS VOlUME
    data-kubia-0 Bound pv - c ...
    data-kubia-1 Bound pv - a ...
     
    可以看到生成的持久卷声明的名称由 volumeClaimTeplate 字段中定义的名称和每个pod的名称组成。
     
    现在你的数据存储集群节点都已经运行,可以开始使用它们了。因为之前创建的Service处于headless模式,所以不能通过service来访问你的pod。需要直接连接每个单独的pod来访问(或者创建一个普通的Service,但是这样还是不允许你访问指定的pod)
     
    我们来创建一个普通的service如下:

    apiVersion: v1
    kind: service
    metadata:
      name: kubia-public
    spec:
      selector:
        app: kubia
      ports:
      -   port: 80
        targetPort: 8080
    StatefulSet 已经运行起来了,那么我们看下如何更新它的pod模版,让它使用新的镜像。同时你也会修改副本数为3.通常会使用kubectl edit命令来更新StatefulSet
    kubectl edit statefulset kubia
    你会看到新的pod实例会使用新的镜像运行,那已经存在的两个副本呢?通过他们的寿命可以看出它们没有更新。这是符合预期的。因为,首先StatefulSet更像ReplicaSet,而不是Deployment,所以在模版被修改后,它们不会重启更新,需要手动删除这些副本,然后StatefulSet会根据新的模版重新调度启动它们。
    kubectl delete po kubia-0 kubia-1
    注意: 从Kubernetes1.7版本开始,statefulSet支持与Deployment和DaemonSet一样的滚动升级。通过kubectl explain 获取StatefulSet的spec.updateStrategy 相关文档来获取更多信息。
     
    前面我们提到StatefulSet的保障机制,那么当一个节点故障了,会出现什么情况。
    statefulset在明确知道一个pod不再运行之前,它不能或者不应当创建一个替换pod。只有当集群的管理者告诉它这些信息时候,它才能明确知道。为了做到这一点,管理者需要删除这个pod,或者删除整个节点。
    当手动停止一个node的网卡,使用kubectl get node,会显示Status notReady
    因为控制台不会再收到该节点发送的状态更新,该节点上吗的所有pod状态都会变为Unknown。
    当一个pod状态为Unknown时会发生什么
    若该节点过段时间正常连接,并且重新汇报它上面的pod状态,那这个pod就会重新被标记为Runing。但如果这个pod的未知状态持续几分钟后(这个时间是可以配置的),这个pod就会自动从节点上驱逐。这是由主节点(kubernetes的控制组件)处理的。它通过删除pod的资源来把它从节点上驱逐。
    当kubelet发现这个pod标记为删除状态后,它开始终止运行该pod。在上面的示例中,kubelet已不能与主节点通信(因为网卡断了),这意味着这个pod会一直运行着。查看
    kubectl describe po kubia-0
    发现status一直为Terminating,原因是NodeLost,在信息中说明的是节点不回应导致不可达。
    这时候你想要手动删除pod
    kubectl delete po kubia-0
    执行完成后,你的想法是会再次运行一个kubia-0
    但是kubectl get po会发现kubia-0 状态为 Unknown 并且还是之前那个旧pod ,因为启动时长没变。
    为什么会这样?因为在删除pod之前,这个pod已经被标记为删除。这是因为控制组件已经删除了它(把它从节点驱逐)。这时你用kubectl describe po kubia-0 查看状态依然是Terminating。
    这时候只能进行强制删除
    kubectl delete po kubia-0 --force --grace-period 0
    你需要同时使用--force和 --grace-period 0两个选项。然后kubectl 会对你做的事发出
    警告信息。如果你再次列举pod,就可以看到一个新的kubia-0 pod被创建出来。
    警告: 除非你确认节点不再运行或者不会再可以访问(永远不会再可以访问),否则不要强制删除有状态的pod.

  • 相关阅读:
    分布式锁的三种实现方式
    sharding-jdbc
    MySQL Proxy 实现 MySQL 读写分离提高并发负载
    python 使用流式游标 读取mysql怎么不会内存溢出
    数据仓库方案
    MySQL percona-toolkit工具详解
    percona-toolkit 主从工具 master-slave
    MySQL sql join 算发
    MySQL5.7.6 general tablespace
    MySQL Data Directory -- Creating file-per-table tablespaces outside the data directory
  • 原文地址:https://www.cnblogs.com/zhming26/p/11719715.html
Copyright © 2020-2023  润新知