Kubernetes:StatefulSet剖析

一. 简介

实例之间有不对等关系,以及实例对外部数据有依赖关系的应用,就被称为“有状态应用”(Stateful Application)

Kubernetes的StatefulSet组件就是为了解决如下两种情况:

  • 拓扑状态
    应用的多个实例之间存在不是完全对等的关系。这些应用实例,必须按照某些顺序启动,比如应用的主节点 A 要先于从节点 B 启动。而如果把 A 和 B 两个 Pod 删除掉,它们再次被创建出来时也必须严格按照这个顺序才行。并且,新创建出来的 Pod,必须和原来 Pod 的网络标识一样,这样原先的访问者才能使用同样的方法,访问到这个新 Pod。
  • 存储状态
    应用的多个实例分别绑定了不同的存储数据。对于这些应用实例来说,Pod A 第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间 Pod A 被重新创建过。

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

关于本文的项目的代码,都放于链接:GitHub资源

二. 拓扑状态

通过 Headless Service 的方式,StatefulSet 为每个 Pod 创建了一个固定并且稳定的 DNS 记录,来作为它的访问入口。

2.1 Service

Service 类型一般有如下的三种:

  • VIP(Virtual IP,即:虚拟 IP)
    Service提供一个 VIP,我们访问这个IP地址时,它会把请求转发到该 Service 所代理的某一个 Pod 上。
  • DNS (Normal Service)
    这种情况下,访问“my-svc.my-namespace.svc.cluster.local”解析到的,正是 my-svc 这个 Service 的 VIP,后面的流程就跟 VIP 方式一致。
  • DNS (Headless Service)
    这种情况下,访问“my-svc.my-namespace.svc.cluster.local”解析到的,直接就是 my-svc 代理的某一个 Pod 的 IP 地址。所以,Headless Service 不需要分配一个 VIP,而是可以直接以 DNS 记录的方式解析出被代理 Pod 的 IP 地址。

2.2 Headless Service

2.2.1 案例

参考如下的demo-service.yaml案例:

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

创建成功后可以查看到如下的内容:


demo-service

2.2.2 clusterIP

关于于上面的这行配置clusterIP: None
clusterIP 是服务的IP地址,通常由主服务器随机分配还具有如下特点:

  • 如果地址是手动指定的,并且未被其他人使用,则该地址将分配给服务;否则,服务创建将失败
  • 无法通过更新更改此字段。
  • 有效值为“ None”,空字符串(“”)或有效IP地址。
  • 当不需要代理时,可以为无头服务指定“无”。
  • 仅适用于ClusterIP,NodePort和LoadBalancer类型。
  • 如果type为ExternalName,则忽略。

Headless Service 是一个标准 Service 的 YAML 文件。只不过,它的 clusterIP 字段的值是:None。这个 Service 被创建后并不会被分配一个 VIP,而是会以 DNS 记录的方式暴露出它所代理的 Pod。

2.2.3 DNS 记录

当按照这样的方式创建了一个 Headless Service 之后,它所代理的所有 Pod 的 IP 地址,都会被绑定一个这样格式的 DNS 记录,如下所示:

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

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

2.3 案例

2.3.1 创建Statefulset

执行创建,demo-statefulset.yaml内容如下

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: demo-statefulset
spec:
  serviceName: "demo-service"
  replicas: 3
  selector:
    matchLabels:
      app: demo-nginx
  template:
    metadata:
      labels:
        app: demo-nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: nginx

这个 YAML 文件,和我们在前面文章中用到的 nginx-deployment 的唯一区别,就是多了一个 serviceName=nginx 字段。这个字段的作用,就是告诉 StatefulSet 控制器,在执行控制循环(Control Loop)的时候,请使用 nginx 这个 Headless Service 来保证 Pod 的“可解析身份”。

创建成功后,可以看到如下的状态:


statefulsets

2.3.2 检查状态

执行如下的指令,检查Pods相关状态

kubectl get pods -w -l app=demo-nginx
# result
NAME                 READY   STATUS    RESTARTS   AGE
demo-statefulset-0   1/1     Running   0          5m17s
demo-statefulset-1   1/1     Running   0          5m16s
demo-statefulset-2   1/1     Running   0          5m14s

StatefulSet 给它所管理的所有 Pod 的名字,进行了编号。
编号规则是:statefulset.name +-+ index。这些 Pod 的创建,也是严格按照编号顺序进行的。而且这些编号都是从 0 开始累加,与 StatefulSet 的每个 Pod 实例一一对应,绝不重复。

2.3.3 检查hostname

可以看到,这两个 Pod 的 hostname 与 Pod 名字是一致的,都被分配了对应的编号。
使用 kubectl exec 命令进入到容器中查看它们的 hostname:

kubectl exec demo-statefulset-0 -- sh -c 'hostname'
kubectl exec demo-statefulset-1 -- sh -c 'hostname'
kubectl exec demo-statefulset-2 -- sh -c 'hostname'

我们将看到如下结果:


Host name

2.3.4 检查DNS

我们启动了一个一次性的 Pod,因为–rm 意味着 Pod 退出后就会被删除掉,创建并进入Pod:

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

进入这个 Pod 的容器里面,尝试用 nslookup 命令,解析一下 Pod 对应的 Headless Service

nslookup demo-service
# result
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      demo-service
Address 1: 10.1.3.77 demo-statefulset-1.demo-service.default.svc.cluster.local
Address 2: 10.1.3.76 demo-statefulset-0.demo-service.default.svc.cluster.local
Address 3: 10.1.3.78 demo-statefulset-2.demo-service.default.svc.cluster.local

实际执行结果可以看到如下内容:


DNS

2.4 滚动更新

滚动更新时,把这几个 Pod 删除之后,Kubernetes 会按照原先编号的顺序,创建出了两个新的 Pod。并且,Kubernetes 依然为它们分配了与原来相同的“网络身份”:demo-statefulset-0 ,demo-statefulset-1 和demo-statefulset-2。通过这种严格的对应规则,StatefulSet 就保证了 Pod 网络标识的稳定性。
可以理解为:Podname就是唯一主键。

2.5 小结

Kubernetes 成功地将 Pod 的拓扑状态,按照 Pod 的“名字 + 编号”的方式固定了下来。此外,Kubernetes 还为每一个 Pod 提供了一个固定并且唯一的访问入口,即:这个 Pod 对应的 DNS 记录。这些状态,在 StatefulSet 的整个生命周期里都会保持不变,绝不会因为对应 Pod 的删除或者重新创建而失效。
当 StatefulSet 的“控制循环”发现 Pod 的“实际状态”与“期望状态”不一致,需要新建或者删除 Pod 进行“调谐”的时候,它会严格按照这些 Pod 编号的顺序,逐一完成这些操作。

三. 存储状态

Kubernetes 项目引入了一组叫作 Persistent Volume Claim(PVC)Persistent Volume(PV)的 API 对象,大大降低了用户声明和使用持久化 Volume 的门槛。

3.1 PVC

3.1.1 定义

PVC 其实就是一种特殊的 Volume。只不过一个 PVC 具体是什么类型的 Volume,要在跟某个 PV 绑定之后才知道。这些 PVC,都以“--< 编号 >”的方式命名,并且处于 Bound 状态。这个 StatefulSet 创建出来的所有 Pod,都会声明使用编号的 PVC。

3.1.2 案例

‘demo-pv-pod.yam’l案例的如下:

apiVersion: v1
kind: Pod
metadata:
  name: demo-pv-pod
spec:
  containers:
    - name: demo-nginx
      image: nginx
      ports:
        - containerPort: 80
          name: "http-server"
      volumeMounts:
        - mountPath: "/usr/share/nginx/html"
          name: pv-storage
  volumes:
    - name: pv-storage
      persistentVolumeClaim:
        claimName: pv-claim
  • persistentVolumeClaim
    在这个 Pod 的 Volumes 定义中,只需要指定 PVC 的名字,而完全不必关心 Volume 本身的定义。

3.2 PV

3.2.1 定义

PV来自于由运维人员维护的 PV(Persistent Volume)对象。PVC 和 PV 的设计,实际上类似于“接口”和“实现”的思想。

  • 开发者只要知道并会使用“接口”,即:PVC。
  • 而运维人员则负责给“接口”绑定具体的实现,即:PV。

3.2.2 案例

参考‘demo-pv.yaml’相关代码:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: demo-pv
  labels:
    type: local
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  rbd:
    monitors:
    - 'IPs'
    pool: kube
    image: foo
    fsType: ext4
    readOnly: true
    user: admin
    keyring: /etc/ceph/keyring

PV 对象的 spec.rbd 字段,是 Ceph RBD Volume 的详细定义。而且,它还声明了这个 PV 的容量是 5 GiB。这样,Kubernetes 就会为我们刚刚创建的 PVC 对象绑定这个 PV。

3.2.3 PV状态

PersistentVolume(PV)的状态:

  • Available(可用): 块空闲资源还没有被任何声明绑定
  • Bound(已绑定): 卷已经被声明绑定, 注意:但是不一定不能继续被绑定,看accessModes而定
  • Released(已释放): 声明被删除,但是资源还未被集群重新声明
  • Failed(失败): 该卷的自动回收失败

3.3 volumeClaimTemplates

3.3.1 定义

凡是被这个 StatefulSet 管理的 Pod,都会声明一个对应的 PVC;而这个 PVC 的定义,就来自于 volumeClaimTemplates 这个模板字段。

3.3.2 案例

参考‘demo-statefulset-v2.yaml’相关代码:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: demo-statefulset
spec:
  serviceName: "demo-service"
  replicas: 3
  selector:
    matchLabels:
      app: demo-nginx
  template:
    metadata:
      labels:
        app: demo-nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: tmp
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: tmp
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi

这个自动创建的 PVC,与 PV 绑定成功后,就会进入 Bound 状态,这就意味着这个 Pod 可以挂载并使用这个 PV 了。

执行如下查询指令:

kubectl get pvc -l app=demo-nginx

成功后的情况如下:


PVC bouding

这个 PVC 的名字,会被分配一个与这个 Pod 完全一致的编号。

3.3.3 滚动更新

当进行滚动更新或者手动删除一个Pod时,PVC 维持有状态的经历如下的流程:

  • 删除一个Pod
    这个 Pod 对应的 PVC 和 PV,并不会被删除,而这个 Volume 里已经写入的数据,也依然会保存在远程存储服务里。
  • 创建一个‘新的且同名’的Pod
    StatefulSet 控制器就会重新创建一个‘新的且同名’的Pod,“纠正”这个不一致的情况。
  • 基于 volumeClaimTemplates 模版创建
    在这个新的 Pod 对象的定义里,它声明使用的 PVC 的名字与旧 PVC名称一致。这个 PVC 的定义,还是来自于 PVC 模板(volumeClaimTemplates),这是 StatefulSet 创建 Pod 的标准流程。
  • PVC与PV绑定
    在这个新的 Pod 被创建出来之后,Kubernetes 为它查找对应 PVC 时,就会直接找到旧 Pod 遗留下来的同名的 PVC,进而找到跟这个 PVC 绑定在一起的 PV。

基于上面的流程,新的 Pod 就可以挂载到旧 Pod 对应的那个 Volume,并且获取到保存在 Volume 里的数据。
通过这种方式,Kubernetes 的 StatefulSet 就实现了对应用存储状态的管理。

3.3.4 命名规则

根据 volumeClaimTemplates ,为每个Pod 创建一个pvo,pvc的命名规则匹配模式:

(volumeClaimTemplates.name)-(pod_name)

比如上面的 volumeMounts.name=tmp, Podname=demo-statefulset-[0-2],因此创建出来的PVC是 tmp-demo-statefulset-0、tmp-demo-statefulset-1、 tmp-demo-statefulset-2。

3.4 小结

PVC 和 PV 的设计,就是“接口”和“实现”的思想,‘volumeClaimTemplates’进行了再一层的封装抽象,提供了更加模版的开发方式,降低开发与运维之间的耦合性。

四. 总结

4.1 启停顺序

  • 有序部署
    部署Statefulset时,如果有多个Pod副本,它们会被顺序地创建(从0到N-1)并且,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态。
  • 有序删除
    当Pod被删除时,它们被终止的顺序是从N-1到0。
  • 有序扩展
    当对Pod执行扩展操作时,与部署一样,它前面的Pod必须都处于Running和Ready状态。

4.2 使用场景

  • 稳定的持久化存储
    即Pod重新调度后还是能访问到相同的持久化数据,基于PVC 来实现。
  • 稳定的网络标识符
    即Pod 重新调度后其iPodName 和 HostName不变。
  • 有序部署,有序扩展
    基于init containers 来实现。
  • 有序收缩

最后,StatefulSet 其实是一种特殊的 Deployment,只不过这个“Deployment”的每个 Pod 实例的名字里,都携带了一个唯一并且固定的编号。这个编号的顺序,固定了 Pod 的拓扑关系;这个编号对应的 DNS 记录,固定了 Pod 的访问方式;这个编号对应的 PV,绑定了 Pod 与持久化存储的关系。所以,当 Pod 被删除重建时,这些“状态”都会保持不变。

欢迎收藏个人博客: Wyatt's Blog ,非常感谢~

Reference

https://www.cnblogs.com/baoshu/p/13281876.html
https://time.geekbang.org/column/article/41154?utm_campaign=guanwang&utm_source=baidu-ad&utm_medium=ppzq-pc&utm_content=title&utm_term=baidu-ad-ppzq-title
https://kubernetes.io/zh/docs/tutorials/stateful-application/basic-stateful-set/
https://blog.csdn.net/renfeigui0/article/details/101214865

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容