• k8s-statefulset介绍


    概述

    该篇介绍 statefulset 的特点,我们通过实践部分验证 statefulset 的特点 .

    有状态应用和无状态应用

    实例之间有不对等关系,以及实例对外部数据有依赖关系的应用,就被称为“有状态应用”(Stateful Application),例如 : web 应用和数据库,比如我们可以用;相反,没有与外界有关系的应用,例如一个计算模块(输入一个值经过计算返回一个结果的模块应用),比如一个推送模块 , StatefulSet 和 Deployment 可以说对应着有状态应用和无状态应用 ,也可以说 StatefulSet 是一种特殊的 Deployment。

    StatefulSet

    k8s 这个工具是能够支持有状态应用的部署的, 得益于“控制器模式”的设计思想,Kubernetes 项目很早就在 Deployment 的基础上,扩展出了对“有状态应用”的初步支持。这个编排功能,就是:StatefulSet。 StatefulSet 的设计其实非常容易理解。它把真实世界里的应用状态,抽象为了两种情况:

    1. 拓扑状态。这种情况意味着,应用的多个实例之间不是完全对等的关系。这些应用实例,必须按照某些顺序启动,比如应用的主节点 A 要先于从节点 B 启动。而如果你把 A 和 B 两个 Pod 删除掉,它们再次被创建出来时也必须严格按照这个顺序才行。并且,新创建出来的 Pod,必须和原来 Pod 的网络标识一样,这样原先的访问者才能使用同样的方法,访问到这个新 Pod。(操作的时候有顺序)

    2. 存储状态。这种情况意味着,应用的多个实例分别绑定了不同的存储数据。对于这些应用实例来说,Pod A 第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间 Pod A 被重新创建过。这种情况最典型的例子,就是一个数据库应用的多个存储实例。(依赖于某个特定的外部条件)

    我们细读上面这两个特点,可不可以用两种场景来表述呢?

    • 拓扑关系,我必须保证 A 优先于 B 启动,当删除的时候,B 必须优先于 A 先删除
    • 存储关系,比如 A 的 DNS 是 example.a.com , B的是 example.b.com , 当某些情况下,A 和 B 都异常退出后 ,k8s 会重新为 A B分配 node ,再次启动 pod , 那么此时我访问 example.a.com ,必须访问的还是 A , 访问 example.b.com 还是访问的 。

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

    Headless Service

        以下描述来自课程,非原创 
    

    我在和你一起讨论 Kubernetes 架构的时候就曾介绍过,Service 是 Kubernetes 项目中用来将一组 Pod 暴露给外界访问的一种机制。比如,一个 Deployment 有 3 个 Pod,那么我就可以定义一个 Service。然后,用户只要能访问到这个 Service,它就能访问到某个具体的 Pod。

    那么,这个 Service 又是如何被访问的呢?

    • 第一种方式,是以 Service 的 VIP(Virtual IP,即:虚拟 IP)方式。比如:当我访问 10.0.23.1 这个 Service 的 IP 地址时,10.0.23.1 其实就是一个 VIP,它会把请求转发到该 Service 所代理的某一个 Pod 上。这里的具体原理,我会在后续的 Service 章节中进行详细介绍。

    • 第二种方式,就是以 Service 的 DNS 方式。比如:这时候,只要我访问“my-svc.my-namespace.svc.cluster.local”这条 DNS 记录,就可以访问到名叫 my-svc 的 Service 所代理的某一个 Pod。

    而在第二种 Service DNS 的方式下,具体还可以分为两种处理方法:

    • 第一种处理方法,是 Normal Service。这种情况下,你访问“my-svc.my-namespace.svc.cluster.local”解析到的,正是 my-svc 这个 Service 的 VIP,后面的流程就跟 VIP 方式一致了。

    • 第二种处理方法,正是 Headless Service。这种情况下,你访问“my-svc.my-namespace.svc.cluster.local”解析到的,直接就是 my-svc 代理的某一个 Pod 的 IP 地址。可以看到,这里的区别在于,Headless Service 不需要分配一个 VIP,而是可以直接以 DNS 记录的方式解析出被代理 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
    

    可以看到,所谓的 Headless Service,其实仍是一个标准 Service 的 YAML 文件。只不过,它的 clusterIP 字段的值是:None,即:这个 Service,没有一个 VIP 作为“头”。这也就是 Headless 的含义。所以,这个 Service 被创建后并不会被分配一个 VIP,而是会以 DNS 记录的方式暴露出它所代理的 Pod。 当你按照这样的方式创建了一个 Headless Service 之后,它所代理的所有 Pod 的 IP 地址,都会被绑定一个这样格式的 DNS 记录,如下所示:

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

    这个 DNS 记录,正是 Kubernetes 项目为 Pod 分配的唯一的“可解析身份”(Resolvable Identity)。有了这个“可解析身份”,只要你知道了一个 Pod 的名字,以及它对应的 Service 的名字,你就可以非常确定地通过这条 DNS 记录访问到 Pod 的 IP 地址。

    实践

    创建 Headless Service 和 StatefulSet

    Headless Service 的 yaml 文件

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

    StatefulSet 的 yaml 文件

    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: web
    spec:
      serviceName: "nginx"
      replicas: 2
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: nginx
            image: nginx:1.9.1
            ports:
            - containerPort: 80
              name: web
    

    然后我们通过命令运行这两个 yaml

    $ 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
    [root@k8s-master ~]# kubectl get pod 
    NAME                                READY   STATUS    RESTARTS   AGE
    lifecycle-demo                      1/1     Running   4          18d
    nginx-deployment-5d59d67564-bdn4d   1/1     Running   0          3h54m
    nginx-deployment-5d59d67564-cj9qh   1/1     Running   0          3h54m
    test-projected-volume               1/1     Running   3          17d
    web-0                               1/1     Running   0          3h54m
    web-1                               1/1     Running   0          3h54m
    
    

    我们可以看到 web-0web-1是 statefulset 生成的,可以看到默认生成的pod 命令上有编号,该编号就是启动顺序相关的。

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

    上面的命令的可以查询到这两个pod的主机名。下面我们需要验证是否可以通过 DNS 找到对应的 pod .

    通过 DNS 查询到对应 pod 的 ip

    我们通过创建一个新的 pod ,然后在 pod 里查询 web 的信息。

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

    通过这条命令,我们启动了一个一次性的 Pod ,对应的镜像是 busybox:1.28.4,因为–rm 意味着 Pod 退出后就会被删除掉。然后,在这个 Pod 的容器里面,我们尝试用 nslookup 命令,解析一下 Pod 对应的 Headless Service:

    $ nslookup web-0.nginx
    Server:    10.0.0.10
    Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
     
    Name:      web-0.nginx
    Address 1: 10.244.1.7
     
    $ nslookup web-1.nginx
    Server:    10.0.0.10
    Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
     
    Name:      web-1.nginx
    Address 1: 10.244.2.7
    

    然后我们在另外的会话中,我们先删除我们刚创建的pod,不出意外的话,新的 pod 将会创建 ,那么我们重新执行上面的步骤,在新的pod里查看 DNS 信息,假如依旧是保持原有的 DNS 信息,那么就成功验证了 statefulset 第二条的 "存储状态" 的特点.

    $ kubectl delete pod -l app=nginx
    pod "web-0" deleted
    pod "web-1" deleted
    
    

    如果手速够快,我们还可以看到 pod 的创建过程, web-0 总是优先于 web-1 ,删除的话则是相反的顺序 .

    $ kubectl get pod -w -l app=nginx
    NAME      READY     STATUS              RESTARTS   AGE
    web-0     0/1       ContainerCreating   0          0s
    NAME      READY     STATUS    RESTARTS   AGE
    web-0     1/1       Running   0          2s
    web-1     0/1       Pending   0         0s
    web-1     0/1       ContainerCreating   0         0s
    web-1     1/1       Running   0         32s
    
    

    总结

    StatefulSet 这个控制器的主要作用之一,就是使用 Pod 模板创建 Pod 的时候,对它们进行编号,并且按照编号顺序逐一完成创建工作。而当 StatefulSet 的“控制循环”发现 Pod 的“实际状态”与“期望状态”不一致,需要新建或者删除 Pod 进行“调谐”的时候,它会严格按照这些 Pod 编号的顺序,逐一完成这些操作。
    

    参考资料

    • 深入剖析Kubernetes课程
    • 重要-动手实践 https://kubernetes.io/zh/docs/tasks/run-application/run-replicated-stateful-application/
    • https://blog.csdn.net/weixin_43936969/article/details/106289127
    • https://draveness.me/kubernetes-statefulset/
  • 相关阅读:
    17. 偏函数
    16. 装饰器
    vim详解
    linux用户管理sudo 磁盘分区
    linux用户管理
    linux文件与目录(四)
    linux特殊权限
    linux文件和目录(二)
    linux文件和目录
    配置网络
  • 原文地址:https://www.cnblogs.com/Benjious/p/14852772.html
Copyright © 2020-2023  润新知