StatefulSet(一):拓扑状态

Deployment 实际上并不足以覆盖全部的应用编排问题。nginx

形成这个问题的根本缘由,在于 Deployment 对应用作了一个简单化假设。web

它认为,一个应用的全部 Pod,是彻底同样的。因此,它们互相之间没有顺序,也无所谓运行在哪 台宿主机上。须要的时候,Deployment 就能够经过 Pod 模板建立新的 Pod;不须要的时候, Deployment 就能够“杀掉”任意一个 Pod。数据库

可是,在实际的场景中,并非全部的应用均可以知足这样的要求。 尤为是分布式应用,它的多个实例之间,每每有依赖关系,好比:主从关系、主备关系。 还有就是数据存储类应用,它的多个实例,每每都会在本地磁盘上保存一份数据。而这些实例一旦 被杀掉,即使重建出来,实例与数据之间的对应关系也已经丢失,从而致使应用失败。 因此,这种实例之间有不对等关系,以及实例对外部数据有依赖关系的应用,就被称为“有状态应 用”(Stateful Application)。api

容器技术诞生后,你们很快发现,它用来封装“无状态应用”(Stateless Application),尤为是 Web 服务,很是好用。可是,一旦你想要用容器运行“有状态应用”,其困难程度就会直线上升。 并且,这个问题解决起来,单纯依靠容器技术自己已经无能为力,这也就致使了很长一段时间 内,“有状态应用”几乎成了容器技术圈子的“忌讳”,你们一听到这个词,就纷纷摇头。 不过,Kubernetes 项目仍是成为了“第一个吃螃蟹的人”。网络

得益于“控制器模式”的设计思想,Kubernetes 项目很早就在 Deployment 的基础上,扩展出了 对“有状态应用”的初步支持。这个编排功能,就是:StatefulSet。 StatefulSet 的设计其实很是容易理解。app

 

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

1. 拓扑状态。这种状况意味着,应用的多个实例之间不是彻底对等的关系。这些应用实例,必须按 照某些顺序启动,好比应用的主节点 A 要先于从节点 B 启动。分布式

而若是你把 A 和 B 两个 Pod 删除 掉,它们再次被建立出来时也必须严格按照这个顺序才行。而且,新建立出来的 Pod,必须和原 来 Pod 的网络标识同样,这样原先的访问者才能使用一样的方法,访问到这个新 Pod。oop

2. 存储状态。这种状况意味着,应用的多个实例分别绑定了不一样的存储数据。对于这些应用实例来 说,Pod A 第一次读取到的数据,和隔了十分钟以后再次读取到的数据,应该是同一份,哪怕在 此期间 Pod A 被从新建立过。spa

 

这种状况最典型的例子,就是一个数据库应用的多个存储实例。 因此,StatefulSet 的核心功能,就是经过某种方式记录这些状态,而后在 Pod 被从新建立时,能 够为新 Pod 恢复这些状态。 在开始讲述 StatefulSet 的工做原理以前,我就必须先为你讲解一个 Kubernetes 项目中很是实用的 概念:Headless Service。

svc.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。

而它所代理的 Pod,依然是采用我在前面第 12 篇文章《牛刀小试:个人第一个容器化应用》中提 到的 Label Selector 机制选择出来的,即:全部携带了 app=nginx 标签的 Pod,都会被这个 Service 代理起来。

而后关键来了。 当你按照这样的方式建立了一个 Headless Service 以后,它所代理的全部 Pod 的 IP 地址,都会被 绑定一个这样格式的 DNS 记录,以下所示:

 

<pod-name>.<svc-name>.<namespace>.svc.cluster.local
# 如
web-1.nginx.default.svc.cluster.local

 

这个 DNS 记录,正是 Kubernetes 项目为 Pod 分配的惟一的“可解析身份”(Resolvable Identity)。

有了这个“可解析身份”,只要你知道了一个 Pod 的名字,以及它对应的 Service 的名字,你就可 以很是肯定地经过这条 DNS 记录访问到 Pod 的 IP 地址。

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

为了回答这个问题,如今咱们就来编写一个 StatefulSet 的 YAML 文件,以下所示:  

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 文件,和咱们在前面文章中用到的 nginx-deployment 的惟一区别,就是多了一个 serviceName=nginx 字段。

这个字段的做用,就是告诉 StatefulSet 控制器,在执行控制循环(Control Loop)的时候,请使 用 nginx 这个 Headless Service 来保证 Pod 的“可解析身份”。

因此,当你经过 kubectl create 建立了上面这个 Service 和 StatefulSet 以后,就会看到以下两个 对象:

kubectl create -f svc.yaml

kubectl get service nginx

  

 

kubectl create -f statefulset.yaml

kubectl get statefulset web

  

 

这时候,若是你手比较快的话,还能够经过 kubectl 的 -w 参数,即:Watch 功能,实时查看 StatefulSet 建立两个有状态实例的过程:不过,你依然能够经过这个 StatefulSet 的 Events 看到这些信息。

 

kubectl get pods -w -l app=nginx

 

 经过上面这个 Pod 的建立过程,咱们不难看到,StatefulSet 给它所管理的全部 Pod 的名字,进行 了编号,编号规则是:-。

并且这些编号都是从 0 开始累加,与 StatefulSet 的每一个 Pod 实例一一对应,毫不重复。 更重要的是,这些 Pod 的建立,也是严格按照编号顺序进行的。

好比,在 web-0 进入到 Running 状态、而且细分状态(Conditions)成为 Ready 以前,web-1 会一直处于 Pending 状态。

当这两个 Pod 都进入了 Running 状态以后,你就能够查看到它们各自惟一的“网络身份”了。 咱们使用 kubectl exec 命令进入到容器中查看它们的 hostname:

 

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

  

 能够看到,这两个 Pod 的 hostname 与 Pod 名字是一致的,都被分配了对应的编号。

接下来,我 们再试着以 DNS 的方式,访问一下这个 Headless Service:

注意:busybox 不要使用最新的版本,在这里我使用的是1.28.4的版本

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

  

 经过这条命令,咱们启动了一个一次性的 Pod,由于–rm 意味着 Pod 退出后就会被删除掉。而后, 在这个 Pod 的容器里面,咱们尝试用 nslookup 命令,解析一下 Pod 对应的 Headless Service:

 

nslookup web-0.nginx

nslookup web-1.nginx

  

 

从 nslookup 命令的输出结果中,咱们能够看到,在访问 web-0.nginx 的时候,最后解析到的,正 是 web-0 这个 Pod 的 IP 地址;

而当访问 web-1.nginx 的时候,解析到的则是 web-1 的 IP 地 址。 这时候,若是你在另一个 Terminal 里把这两个“有状态应用”的 Pod 删掉:

  

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

  

 而后,再在当前 Terminal 里 Watch 一下这两个 Pod 的状态变化,就会发现一个有趣的现象:

 

$ 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

  

 能够看到,当咱们把这两个 Pod 删除以后,Kubernetes 会按照原先编号的顺序,建立出了两个新 的 Pod。而且,Kubernetes 依然为它们分配了与原来相同的“网络身份”:web-0.nginx 和 web1.nginx。 经过这种严格的对应规则,StatefulSet 就保证了 Pod 网络标识的稳定性。 好比,若是 web-0 是一个须要先启动的主节点,web-1 是一个后启动的从节点,那么只要这个 StatefulSet 不被删除,你访问 web-0.nginx 时始终都会落在主节点上,访问 web-1.nginx 时,则 始终都会落在从节点上,这个关系绝对不会发生任何变化。 因此,若是咱们再用 nslookup 命令,查看一下这个新 Pod 对应的 Headless Service 的话:

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

nslookup web-0.nginx

nslookup web-1.nginx

  

 

 

 咱们能够看到,在这个 StatefulSet 中,这两个新 Pod 的“网络标识”(好比:web-0.nginx 和 web-1.nginx),再次解析到了正确的 IP 地址(好比:web-0 Pod 的 IP 地址 10.96.0.10)。 经过这种方法,Kubernetes 就成功地将 Pod 的拓扑状态(好比:哪一个节点先启动,哪一个节点后启 动),按照 Pod 的“名字 + 编号”的方式固定了下来。

此外,Kubernetes 还为每个 Pod 提供 了一个固定而且惟一的访问入口,即:这个 Pod 对应的 DNS 记录。 这些状态,在 StatefulSet 的整个生命周期里都会保持不变,毫不会由于对应 Pod 的删除或者从新 建立而失效。 不过,相信你也已经注意到了,尽管 web-0.nginx 这条记录自己不会变,但它解析到的 Pod 的 IP 地址,并非固定的。

这就意味着,对于“有状态应用”实例的访问,你必须使用 DNS 记录或者 hostname 的方式,而毫不应该直接访问这些 Pod 的 IP 地址。

 因此,StatefulSet 其实能够认为是对 Deployment 的改良。 与此同时,经过 Headless Service 的方式,StatefulSet 为每一个 Pod 建立了一个固定而且稳定的 DNS 记录,来做为它的访问入口。 实际上,在部署“有状态应用”的时候,应用的每一个实例拥有惟一而且稳定的“网络标识”,是一 个很是重要的假设。

相关文章
相关标签/搜索