zoukankan      html  css  js  c++  java
  • 5.深入k8s:StatefulSet控制器

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com

    image-20200807220814361

    在上一篇中,讲解了容器持久化存储,从中我们知道什么是PV和PVC,这一篇我们讲通过StatefulSet来使用它们。如果觉得我讲的不错的,可以发个邮件鼓励一下我噢~

    我们在第三篇讲的Deployment控制器是应用于无状态的应用的,所有的Pod启动之间没有顺序,Deployment可以任意的kill一个Pod不会影响到业务数据,但是这到了有状态的应用中就不管用了。

    而StatefulSet就是用来对有状态应用提供支持的控制器。

    StatefulSet把真实世界里的应用状态,抽象为了两种情况:

    1. 拓扑状态。应用的多个实例之间不是完全对等的关系。这些应用实例,必须按照某些顺序启动,比如应用的主节点 A 要先于从节点 B 启动。并且,新创建出来的 Pod,必须和原来 Pod 的网络标识一样。
    2. 存储状态。应用的多个实例分别绑定了不同的存储数据,对于这些应用实例来说,Pod A 第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间 Pod A 被重新创建过。

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

    拓扑状态

    在k8s中,Service是用来将一组 Pod 暴露给外界访问的一种机制。Service可以通过DNS的方式,代理到某一个Pod,然后通过DNS记录的方式解析出被代理 Pod 的 IP 地址。

    如下:

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

    这个Service会通过Label Selector选择所有携带了 app=nginx 标签的 Pod,都会被这个 Service 代理起来。

    它所代理的所有 Pod 的 IP 地址,都会被绑定一个这样格式的 DNS 记录,如下所示:

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

    所以通过这个DNS记录,StatefulSet就可以使用到DNS 记录来维持 Pod 的拓扑状态。

    如下:

    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: web
    spec:
      serviceName: "nginx"
      replicas: 2  # by default is 1
      selector:
        matchLabels:
          app: nginx  # has to match .spec.template.metadata.labels
      template:
        metadata:
          labels:
            app: nginx # has to match .spec.selector.matchLabels
        spec:
          containers:
          - name: nginx
            image: nginx:1.9.1
            ports:
            - containerPort: 80
              name: web
    

    这里使用了serviceName=nginx,表明StatefulSet 控制器会使用nginx 这个Service来进行网络代理。

    我们可以如下创建:

    $ kubectl create -f svc.yaml
    $ kubectl get service nginx
    NAME      TYPE         CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
    nginx     ClusterIP    None         <none>        80/TCP    10s
    
    $ kubectl create -f statefulset.yaml
    $ kubectl get statefulset web
    NAME      DESIRED   CURRENT   AGE
    web       2         1         19s
    

    然后我们可以观察pod的创建情况:

    $ kubectl get pods -w -l app=nginx
    
    NAME    READY   STATUS    RESTARTS   AGE
    web-0   1/1     Running   0          76m
    web-1   1/1     Running   0          76m
    

    我们通过-w命令可以看到pod创建情况,StatefulSet所创建的pod编号都是从0开始累加,在 web-0 进入到 Running 状态、并且细分状态(Conditions)成为 Ready 之前,web-1 会一直处于 Pending 状态。

    然后我们使用exec查看pod的hostname:

    $ kubectl exec web-0 -- sh -c 'hostname'
    web-0
    $ kubectl exec web-1 -- sh -c 'hostname'
    web-1
    

    然后我们可以启动一个一次性的pod用 nslookup 命令,解析一下 Pod 对应的 Headless Service:

    $ kubectl run -i --tty --image busybox:1.28.4 dns-test --restart=Never --rm /bin/sh
    $ nslookup web-0.nginx
    Server:    10.68.0.2
    Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local
    
    Name:      web-0.nginx
    Address 1: 172.20.0.56 web-0.nginx.default.svc.cluster.local
    
    $ nslookup web-1.nginx
    Server:    10.68.0.2
    Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local
    
    Name:      web-1.nginx
    Address 1: 172.20.0.57 web-1.nginx.default.svc.cluster.local
    

    如果我们删除了这两个pod,然后观察pod情况:

    $ kubectl delete pod -l app=nginx
    
    $ kubectl get pod -w -l app=nginx
    web-0   1/1     Terminating   0          83m
    web-1   1/1     Terminating   0          83m
    web-0   0/1     Pending       0          0s
    web-1   0/1     Terminating   0          83m
    web-0   0/1     ContainerCreating   0          0s
    web-0   1/1     Running             0          1s
    web-1   0/1     Pending             0          0s
    web-1   0/1     ContainerCreating   0          0s
    web-1   1/1     Running             0          1s
    

    当我们把这两个 Pod 删除之后,Kubernetes 会按照原先编号的顺序,创建出了两个新的 Pod。并且,Kubernetes 依然为它们分配了与原来相同的“网络身份”:web-0.nginx 和 web-1.nginx。

    但是网络结构虽然没变,但是pod对应的ip是改变了的,我们再进入到pod进行DNS解析:

    $ nslookup web-0.nginx
    Server:    10.68.0.2
    Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local
    
    Name:      web-0.nginx
    Address 1: 172.20.0.59 web-0.nginx.default.svc.cluster.local
    
    $ nslookup web-1.nginx
    Server:    10.68.0.2
    Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local
    
    Name:      web-1.nginx
    Address 1: 172.20.0.60 web-1.nginx.default.svc.cluster.local
    

    存储状态

    在讲存储状态的时候,需要大家掌握上一节有关pv和pvc的知识才好往下继续,建议大家看完再来看本节。

    在上一节中,我们了解到Kubernetes 中 PVC 和 PV 的设计,实际上类似于“接口”和“实现”的思想。而 PVC、PV 的设计,也使得 StatefulSet 对存储状态的管理成为了可能。

    比如我们声明一个如下的StatefulSet:

    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: web
    spec:
      serviceName: "nginx"
      replicas: 1
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: nginx
            image: nginx:1.9.1
            ports:
            - containerPort: 80
              name: web
            volumeMounts:
            - name: local-volume-a
              mountPath: /usr/share/nginx/html
      volumeClaimTemplates:
      - metadata:
          name: local-volume-a
        spec:
          accessModes:
          - ReadWriteMany
          storageClassName: "local-volume"
          resources:
            requests:
              storage: 512Mi
          selector:
            matchLabels:
              key: local-volume-a-0
    

    在这个StatefulSet中添加了volumeClaimTemplates字段,用来声明对应的PVC的定义;也就是说这个PVC中使用的storageClass必须是local-volume,需要的存储空间是512Mi,并且这个pvc对应的pv的标签必须是key: local-volume-a-0。

    然后我们准备一个PV:

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: local-volume-pv-0
      labels:
        key: local-volume-a-0
    spec:
      capacity:
        storage: 0.5Gi
      volumeMode: Filesystem
      accessModes:
      - ReadWriteMany
      persistentVolumeReclaimPolicy: Retain
      storageClassName: local-volume
      local:
        path: /mnt/disks/vol1
      nodeAffinity:
        required:
          nodeSelectorTerms:
          - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
              - node1
    

    我把这个PV创建在node1节点上,并且将本地磁盘挂载声明为PV。

    然后我们创建这个PV:

    $ kubectl apply -f local-pv-web-0.yaml
    
    $ kubectl get pv
    NAME                CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM
                   STORAGECLASS   REASON   AGE
    local-volume-pv-0   512Mi      RWX            Retain           Available       default/local-vo
    

    然后我们在创建这个StatefulSet的时候,会自动创建PVC:

    $ kubectl apply -f statefulset2.yaml
    
    $ kubectl get pvc
    NAME                   STATUS   VOLUME              CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    local-volume-a-web-0   Bound    local-volume-pv-0   512Mi      RWX            local-volume   15m
    

    创建的PVC名字都是由:<PVC 名字 >-<StatefulSet 名字 >-< 编号 >构成,编号从0开始,并且我们可以看到上面的PV已经处于Bound状态

    这个时候我们进入到Pod中,写入一个文件:

    $ kubectl exec -it web-0  -- /bin/bash
    
    $ echo helloword >/usr/share/nginx/html/index.html
    

    这样就会在Pod 的 Volume 目录里写入一个文件,如果我们把这个Pod删除,那么在被删除之后这个Pod还是会被创建出来,并且还会再和原来的PV:local-volume-pv-0绑定起来。

    也就是说当StatefulSet 控制器发现一个名叫 web-0 的 Pod 消失了的时候,控制器就会重新创建一个新的、名字还是叫作 web-0 的 Pod 来,“纠正”这个不一致的情况。并且删除Pod时并不会删除这个 Pod 对应的 PVC 和 PV。需要注意的是,在这个新的 Pod 对象的定义里,它声明使用的 PVC 的名字,还是叫作local-volume-a-web-0。

    通过这种方式,Kubernetes 的 StatefulSet 就实现了对应用存储状态的管理。

    更新策略

    在 Kubernetes 1.7 及之后的版本中,可以为 StatefulSet 设定 .spec.updateStrategy 字段。

    OnDelete

    如果 StatefulSet 的 .spec.updateStrategy.type 字段被设置为 OnDelete,当您修改 .spec.template 的内容时,StatefulSet Controller 将不会自动更新其 Pod。您必须手工删除 Pod,此时 StatefulSet Controller 在重新创建 Pod 时,使用修改过的 .spec.template 的内容创建新 Pod。

    例如我们执行下面的语句更新上面例子中创建的web:

    $ kubectl set image statefulset web nginx=nginx:1.18.0
    
    $ kubectl describe pod web-0
    ....
    Containers:
      nginx:
        Container ID:   docker://7e45cd509db74a96b4f6ca4d9f7424b3c4794f56e28bfc3fbf615525cd2ecadb
        Image:          nginx:1.9.1
    ....
    

    然后我们发现pod的nginx版本并没有发生改变,需要我们手动删除pod之后才能生效。

    $ kubectl delete pod web-0
    pod "web-0" deleted
    
    $ kubectl describe pod web-0
    ...
    Containers:
      nginx:
        Container ID:   docker://0f58b112601a39f3186480aa97e72767b05fdfa6f9ca02182d3fb3b75c159ec0
        Image:          nginx:1.18.0
    ...
    

    Rolling Updates

    .spec.updateStrategy.type 字段的默认值是 RollingUpdate,该策略为 StatefulSet 实现了 Pod 的自动滚动更新。在更新完.spec.tempalte 字段后StatefulSet Controller 将自动地删除并重建 StatefulSet 中的每一个 Pod。

    删除和重建的顺序也是有讲究的:

    • 删除的时候从序号最大的开始删,每删除一个会更新一个。
    • 只有更新完的pod已经是ready状态了才往下继续更新。

    为 RollingUpdate 进行分区

    当为StatefulSet 的 RollingUpdate 字段的指定 partition 字段的时候,则所有序号大于或等于 partition 值的 Pod 都会更新。序号小于 partition 值的所有 Pod 都不会更新,即使它们被删除,在重新创建时也会使用以前的版本。

    如果 partition 值大于其 replicas 数,则更新不会传播到其 Pod。这样可以实现金丝雀发布Canary Deploy或者灰度发布。

    如下,因为我们的web是2个pod组成,所以可以将partition设置为1:

    $ kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":1}}}}'
    

    在这里,我使用了 kubectl patch 命令。它的意思是,以“补丁”的方式(JSON 格式的)修改一个 API 对象的指定字段。

    下面我们执行更新:

    $ kubectl set image statefulset  web nginx=nginx:1.19.1
    statefulset.apps/web image updated
    

    并在另一个终端中watch pod的变化:

    $ kubectl get pods -l app=nginx -w
    NAME    READY   STATUS    RESTARTS   AGE
    web-0   1/1     Running   0          13m
    web-1   1/1     Running   0          93s
    web-1   0/1     Terminating   0          2m16s
    web-1   0/1     Pending       0          0s
    web-1   0/1     ContainerCreating   0          0s
    web-1   1/1     Running             0          16s
    

    可见上面只有一个web-1进行了版本的发布。

    总结

    StatefulSet把有状态的应用抽象为两种情况:拓扑状态和存储状态。

    拓扑状态指的是应用的多个实例之间不是完全对等的关系,包含启动的顺序、创建之后的网络标识等必须保证。

    存储状态指的是不同的实例绑定了不同的存储,如Pod A在它的生命周期中读取的数据必须是一致的,哪怕是重启之后还是需要读取到同一个存储。

    然后讲解了一下StatefulSet发布更新该如何做,updateStrategy策略以及通过partition如果实现金丝雀发布等。

  • 相关阅读:
    linux删除/var/log/下面所有日志 如何重新记录日志
    DIV里的内容自动换行
    it冲突:commit your changes or stash them before you can merge. 解决办法
    git切换到远程分支
    【异常】warning: refname 'feature1.3.0' is ambiguous.导致git merge失败
    在此篇文章中,我们将用 15 分钟对 PHP v7.x 版本更改进行简要回顾
    input元素所有type类型及相关作用
    微信自动关闭内置浏览器页面,返回公众号窗口 WeixinJSBridge.call('closeWindow')
    css背景渐变色
    数组的forEach和map和for方法的区别
  • 原文地址:https://www.cnblogs.com/luozhiyun/p/13455528.html
Copyright © 2011-2022 走看看