因为简单的Deployment不能覆盖所有的应用编排的能力,那么我们需要再次基础上进行相关的扩展工作

尤其是分布式应用,多个实例之间,往往有依赖关系,比如主从,主备关系

还有就是数据存储类的应用,多个实例之间,都是在本地磁盘上保存一份数据,而这些实例一旦被杀掉,实例可能也会无法保存数据存储位置的对应关系

这种有不对等关系,实例对外部数据由依赖关系的应用,称为有状态应用

容器技术诞生后,一开始都是封装无状态应用的,但是一旦是运行有状态应用的时候,其困难程度就会直线上升,导致很长一段时间,有技术应用称为容器技术的难题

Kuberenetes提出的对有状态应用的初步支持,就是利用了StatefulSet

其将有状态的应用分为了两种情况来查看

1.拓补状态,这种情况意味着,应用的多个实例之间不是完全对等的关系

这些应用实例,必须按照某些顺序启动,比如应用的主节点A要先于节点B启动,如果将A和B两个Pod删掉,那么被创建出来也必须要按照这两个顺序,并且网络标识需要一样

2,存储状态,这种情况表示,应用的多个实例分别绑定了不同的存储数据,PodA读取到的数据和隔了十分钟后读取的数据是同一份才对,那么A已经被重新创建了,就好比一个数据库应用的多个存储实例

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

然后是Kuberentes中一个实用的概念,Headless Service

Service是Kubernetes项目中用来将一组Pod暴露给外界的一种机制,比如一个Deployment有3个Pod,只需要定义一个Service,就能够根据这个Service,访问到具体的Pod

如何设置Service的固定访问呢?

第一种方式,是以Service的VIP去访问,访问10.0.23.1这个Service的IP地址的时候,10.0.23.1就是一个虚拟VIP,将请求转发给这个Service所代理的某一个Pod上

第二种方式,是以Service的DNS的方式,定义一个固定的DNS,然后就能够根据DNS来访问到对应的Pod

关于DNS的使用方式,往往处理起来可以直接是导向VIP或者直接导向一个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

在上面的clusterIP字段中,设置的值是None,这就说明呢,我们没有使用一个VIP作为一个头,而是直接将对应的Pod IP的进行暴露

所代理的Pod,就是Label Selector机制选择出来,代理了所有携带app=nginx标签的Pod,都被Service代理起来了

那么创建了这个Headless Service之后,所代理的所有Pod的IP地址,都会被绑定一个这样格式的DNS记录

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

这个DNS记录,正是Kubernetes为Pod分配的唯一可解析身份

有了这个可解析的身份,就可以直接通过这个DNS记录访问到Pod的IP地址

那么,StatefulSet是如何利用这个DNS记录来维持Pod的拓补状态的呢?

我们先来编写一个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

这个StatefulSet和一般的deployment的唯一区别,就是多了一个serviceName=nginx的字段

这个字段的作用,就是告诉StatefulSet控制器,执行控制循环时候,使用nginx这个Headless Service来保证Pod的可解析

然后进行了创建

$ 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

在这个statefulSet创建的过程中,我们可以通过Events字段看到这些信息

在创建StatefulSet的过程中,StatefulSet给管理的所有Pod都加上了编号

这些编号都是从0开始累加,与每一个StatefulSet的每一个Pod实例都一一对应,不会重复

而且Pod的创建,也是按照编号顺序的,比如web-0在进入Ready之前,web-1会一直处于Pending的状态

当这两个Pod都进入Running状态后,就可以看各自的网络身份了

kubectl exec命令进入容器中查看其hostname

kubectl exec web-0 — sh -c ‘hostname’

kubectl exec web-1 — sh -c ‘hostname’

接下来,我们尝试使用DNS的方式来访问对应的Headless Service

首先是启动一个一次性的Pod 在后面使用了-rm,意味着Pod退出了就会被删除掉,在这个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.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

从nslookup命令的输出结果中,可以看到不同的ip,但是利用的是一个DNS服务器

如果将这两个有状态的POD删除了

kubectl delete pod -l app=nginx

但是在获取对应的pod的时候,可以看到这两个pod又一次的被创建了

而且Kuberenetes会按照原来编号的顺序,创建出了两个新的Pod,并且Kubernetes依次为其分配了原来的网络身份

web-0.nginx和web-1.nginx

通过这种严格的规则,statefulSet就保证了Pod的网络标识的稳定性

如果web-0是一个需要先启动的主节点,web-1是一个后启动的从节点,那么只要这个对应的StatefulSet不会被删除,那么这两个访问地址也不会发生任何的改变(只要你访问的是利用的DNS)

通过这个StatefulSet,两个Pod的网络标识web-01.nginx,再次解析到了正确的IP地址

通过这种方式,Kubernetes就成功将Pod的拓补状态,固定了下来,而且以Pod的 名字+编号的方式进行了固定,除此外,Kubernetes还为每一个Pod提供了一个对应的DNS记录

这些,StatefulSet的整个生命周期中,这些状态是不会改变的,不会因为单个Pod的删除或重新创建而失效

而且,我们需要保证,对于有状态应用的访问,必须使用DNS记录或者hostname的方式,而不是直接访问Pod的IP地址

在本章中,我们分享了StatefulSet的基本概念,应用的状态

然后是StatefulSet如何去保证应用实例之间的拓补状态的稳定性,

其利用了Pod模板去创建Pod的时候,会对其进行编号,并且按照编号顺序进行完成创建,当按照编号的顺序去逐一的创建完成创建时候,就会开始维护这些Pod的编号顺序

所以StatefulSet可以认为是对Deployment的改良

运营过什么有拓补状态的应用呢?

这些应用之间的拓补关系,可能借助这些为Pod实例编号的方式表达出来呢?

发表评论

邮箱地址不会被公开。 必填项已用*标注