Deployment 实际上并不足以覆盖所有的应用编排问题。

造成这个问题的根本原因,在于 Deployment 对应用做了一个简单化假设。

它认为,一个应用的所有 Pod,是完全一样的。所以,它们互相之间没有顺序,也无所谓运行在哪 台宿主机上。需要的时候,Deployment 就可以通过 Pod 模板创建新的 Pod;不需要的时候, Deployment 就可以“杀掉”任意一个 Pod。

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

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

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

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

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

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

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

这种情况最典型的例子,就是一个数据库应用的多个存储实例。 所以,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 记录,来作为它的访问入口。 实际上,在部署“有状态应用”的时候,应用的每个实例拥有唯一并且稳定的“网络标识”,是一 个非常重要的假设。

StatefulSet(一):拓扑状态的更多相关文章

  1. Statefulset的拓扑状态

    Statefulset: 实例之间有不对等关系,以及实例对外部数据有依赖关系的应用,就被称为“有状态应用”(Stateful Application). StatefulSet 的设计其实非常容易理解 ...

  2. [Kubernetes]深入理解StatefulSet

    前面我写的一系列博客,如果你能够耐心看到这一篇,那你应该对一个概念就不是太陌生了:Deployment. 为什么提这个概念呢,这就要说到Deployment的一个不足了.Deployment不足以覆盖 ...

  3. Kubernetes之StatefulSet

    什么是StatefulSet StatefulSet 是Kubernetes中的一种控制器,他解决的什么问题呢?我们知道Deployment是对应用做了一个简化设置,Deployment认为一个应用的 ...

  4. 5.深入k8s:StatefulSet控制器

    转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com 在上一篇中,讲解了容器持久化存储,从中我们知道什么是PV和PVC,这一篇我们讲通过Sta ...

  5. k8s负载资源StatefulSet工作解析

    在k8s中工作负载资源StatefulSet用于管理有状态应用. 什么是无状态? 组成一个应用的pod是对等的,它们之前没有关联和依赖关系,不依赖外部存储. 即我们上篇小作文中deployment创建 ...

  6. 技术进阶:Kubernetes高级架构与应用状态部署

    在了解Kubernetes应用状态部署前,我们先看看Kubernetes的高级架构,方便更好的理解Kubernetes的状态. Kubernetes 的高级架构 包括应用程序部署模型,服务发现和负载均 ...

  7. 什么是StatefulSet

    简单说来,StatefulSet其实就是一种升级版的Deployment,大体工作原理如下 1.为每个Pod名字按顺序编号,按顺序启动 # kubectl get po -o wide -l app= ...

  8. kubernetes学习Service之headless和statefulSet结合

    一.首先说headless Service和普通Service的区别 headless不分配clusterIP headless service可以通过解析service的DNS,返回所有Pod的地址 ...

  9. k8s之StatefulSet介绍(六)

    复制有状态的Pod replicaSet通过一个pod模版创建多个pod副本.这些副本除了它们的名字和IP地址不同外,没有别的差异.如果pod模版里描述了一个关联到特定持久卷声明的数据卷,那么Repl ...

随机推荐

  1. asp.net MVC NPOI导出excel通用

    一.创建一个类文件添加 public class ExportToExcelColumn { public ExportToExcelColumn(string _Columnnames, strin ...

  2. 《Web安全深度剖析》

    书名 <Web安全深度剖析> 图片  时间  2018-11月   总结  算是我安全的启蒙书  前五章都是工具  看完差不多算个脚本小子 后面的实战感觉很空洞没什么实战

  3. MySQL高级特性——绑定变量

    从MySQL 4.1 版本开始,就支持服务器端的绑定变量,这大大提高了客户端和服务器端数据传输的效率 介绍 当创建一个绑定变量 SQL 时,客户端会向服务器发送一个SQL语句的原型.服务器端收到这个S ...

  4. JavaScript是如何工作的:深入类和继承内部原理 + Babel和TypeScript 之间转换

    这是专门探索 JavaScript 及其所构建的组件的系列文章的第 15 篇. 如果你错过了前面的章节,可以在这里找到它们: JavaScript 是如何工作的:引擎,运行时和调用堆栈的概述! Jav ...

  5. vue px转换为rem

    前端开发中还原设计图的重要性毋庸置疑,目前来说应用最多的应该也还是使用rem.然而很多人依然还是处于刀耕火种的时代,要么自己去计算rem值,要么依靠编辑器安装插件转换. 而本文的目标就是通过一系列的配 ...

  6. java 线程方法 ---- join()

    class MyThread2 implements Runnable{ @Override public void run() { for (int i = 0; i < 5; i++){ S ...

  7. 关于如何使用xposed来hook某支付软件

    由于近期有业务上的需要,所以特地花时间去研究了一下如何使用hook技术.但是当我把xposed环境和程序编写完成时,突然发现手机上的某个支付软件无法使用了.这个时候我意识到,应该是该软件的安全机制在起 ...

  8. 四、View的工作原理

    1.ViewRoot和DecorView ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完 ...

  9. c/c++ 多线程 绕过mutex的保护

    多线程 绕过mutex的保护 mutex,能够解决线程安全的问题,但它不是万能的.下面的例子虽然使用了mutex,但是恶意注入了一个外部函数,导致把被mutex保护的双向链表,让一个外部的指针指向了, ...

  10. 微信小程序上手项目

    小程序刚发布的时候何其风光,可能大家习惯性的对微信给予了过高的期待,加上一开始小程序的功能确实很孱弱,扫了很多人的兴. 经过最开始的热闹和喧嚣,如今微信小程序热度大减,但随着不断迭代,如今小程序的功能 ...