因为简单的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实例编号的方式表达出来呢?