转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.com

在上一篇中,讲解了容器持久化存储,从中我们知道什么是PV和PVC,这一篇我们讲通过StatefulSet来使用它们。

我们在第三篇讲的Deployment控制器是应用于无状态的应用的,所有的Pod启动之间没有顺序,Deployment可以任意的kill一个Pod不会影响到业务数据,但是这到了有状态的应用中就不管用了。

而StatefulSet就是用来对有状态应用提供支持的控制器。

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

  1. 拓扑状态。应用的多个实例之间不是完全对等的关系。这些应用实例,必须按照某些顺序启动,比如应用的主节点 A 要先于从节点 B 启动。并且,新创建出来的 Pod,必须和原来 Pod 的网络标识一样。
  2. 存储状态。应用的多个实例分别绑定了不同的存储数据,对于这些应用实例来说,Pod A 第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间 Pod A 被重新创建过。

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

拓扑状态

在k8s中,Service是用来将一组 Pod 暴露给外界访问的一种机制。Service可以通过DNS的方式,代理到某一个Pod,然后通过DNS记录的方式解析出被代理 Pod 的 IP 地址。

如下:


  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: nginx
  5. labels:
  6. app: nginx
  7. spec:
  8. ports:
  9. - port: 80
  10. name: web
  11. clusterIP: None
  12. selector:
  13. app: nginx

这个Service会通过Label Selector选择所有携带了 app=nginx 标签的 Pod,都会被这个 Service 代理起来。

它所代理的所有 Pod 的 IP 地址,都会被绑定一个这样格式的 DNS 记录,如下所示:

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

所以通过这个DNS记录,StatefulSet就可以使用到DNS 记录来维持 Pod 的拓扑状态。

如下:

  1. apiVersion: apps/v1
  2. kind: StatefulSet
  3. metadata:
  4. name: web
  5. spec:
  6. serviceName: "nginx"
  7. replicas: 2 # by default is 1
  8. selector:
  9. matchLabels:
  10. app: nginx # has to match .spec.template.metadata.labels
  11. template:
  12. metadata:
  13. labels:
  14. app: nginx # has to match .spec.selector.matchLabels
  15. spec:
  16. containers:
  17. - name: nginx
  18. image: nginx:1.9.1
  19. ports:
  20. - containerPort: 80
  21. name: web

这里使用了serviceName=nginx,表明StatefulSet 控制器会使用nginx 这个Service来进行网络代理。

我们可以如下创建:

  1. $ kubectl create -f svc.yaml
  2. $ kubectl get service nginx
  3. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  4. nginx ClusterIP None <none> 80/TCP 10s
  5. $ kubectl create -f statefulset.yaml
  6. $ kubectl get statefulset web
  7. NAME DESIRED CURRENT AGE
  8. web 2 1 19s

然后我们可以观察pod的创建情况:

  1. $ kubectl get pods -w -l app=nginx
  2. NAME READY STATUS RESTARTS AGE
  3. web-0 1/1 Running 0 76m
  4. web-1 1/1 Running 0 76m

我们通过-w命令可以看到pod创建情况,StatefulSet所创建的pod编号都是从0开始累加,在 web-0 进入到 Running 状态、并且细分状态(Conditions)成为 Ready 之前,web-1 会一直处于 Pending 状态。

然后我们使用exec查看pod的hostname:

  1. $ kubectl exec web-0 -- sh -c 'hostname'
  2. web-0
  3. $ kubectl exec web-1 -- sh -c 'hostname'
  4. web-1

然后我们可以启动一个一次性的pod用 nslookup 命令,解析一下 Pod 对应的 Headless Service:

  1. $ kubectl run -i --tty --image busybox:1.28.4 dns-test --restart=Never --rm /bin/sh
  2. $ nslookup web-0.nginx
  3. Server: 10.68.0.2
  4. Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local
  5. Name: web-0.nginx
  6. Address 1: 172.20.0.56 web-0.nginx.default.svc.cluster.local
  7. $ nslookup web-1.nginx
  8. Server: 10.68.0.2
  9. Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local
  10. Name: web-1.nginx
  11. Address 1: 172.20.0.57 web-1.nginx.default.svc.cluster.local

如果我们删除了这两个pod,然后观察pod情况:

  1. $ kubectl delete pod -l app=nginx
  2. $ kubectl get pod -w -l app=nginx
  3. web-0 1/1 Terminating 0 83m
  4. web-1 1/1 Terminating 0 83m
  5. web-0 0/1 Pending 0 0s
  6. web-1 0/1 Terminating 0 83m
  7. web-0 0/1 ContainerCreating 0 0s
  8. web-0 1/1 Running 0 1s
  9. web-1 0/1 Pending 0 0s
  10. web-1 0/1 ContainerCreating 0 0s
  11. web-1 1/1 Running 0 1s

当我们把这两个 Pod 删除之后,Kubernetes 会按照原先编号的顺序,创建出了两个新的 Pod。并且,Kubernetes 依然为它们分配了与原来相同的“网络身份”:web-0.nginx 和 web-1.nginx。

但是网络结构虽然没变,但是pod对应的ip是改变了的,我们再进入到pod进行DNS解析:

  1. $ nslookup web-0.nginx
  2. Server: 10.68.0.2
  3. Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local
  4. Name: web-0.nginx
  5. Address 1: 172.20.0.59 web-0.nginx.default.svc.cluster.local
  6. $ nslookup web-1.nginx
  7. Server: 10.68.0.2
  8. Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local
  9. Name: web-1.nginx
  10. Address 1: 172.20.0.60 web-1.nginx.default.svc.cluster.local

存储状态

在讲存储状态的时候,需要大家掌握上一节有关pv和pvc的知识才好往下继续,建议大家看完再来看本节。

在上一节中,我们了解到Kubernetes 中 PVC 和 PV 的设计,实际上类似于“接口”和“实现”的思想。而 PVC、PV 的设计,也使得 StatefulSet 对存储状态的管理成为了可能。

比如我们声明一个如下的StatefulSet:

  1. apiVersion: apps/v1
  2. kind: StatefulSet
  3. metadata:
  4. name: web
  5. spec:
  6. serviceName: "nginx"
  7. replicas: 1
  8. selector:
  9. matchLabels:
  10. app: nginx
  11. template:
  12. metadata:
  13. labels:
  14. app: nginx
  15. spec:
  16. containers:
  17. - name: nginx
  18. image: nginx:1.9.1
  19. ports:
  20. - containerPort: 80
  21. name: web
  22. volumeMounts:
  23. - name: local-volume-a
  24. mountPath: /usr/share/nginx/html
  25. volumeClaimTemplates:
  26. - metadata:
  27. name: local-volume-a
  28. spec:
  29. accessModes:
  30. - ReadWriteMany
  31. storageClassName: "local-volume"
  32. resources:
  33. requests:
  34. storage: 512Mi
  35. selector:
  36. matchLabels:
  37. key: local-volume-a-0

在这个StatefulSet中添加了volumeClaimTemplates字段,用来声明对应的PVC的定义;也就是说这个PVC中使用的storageClass必须是local-volume,需要的存储空间是512Mi,并且这个pvc对应的pv的标签必须是key: local-volume-a-0。

然后我们准备一个PV:

  1. apiVersion: v1
  2. kind: PersistentVolume
  3. metadata:
  4. name: local-volume-pv-0
  5. labels:
  6. key: local-volume-a-0
  7. spec:
  8. capacity:
  9. storage: 0.5Gi
  10. volumeMode: Filesystem
  11. accessModes:
  12. - ReadWriteMany
  13. persistentVolumeReclaimPolicy: Retain
  14. storageClassName: local-volume
  15. local:
  16. path: /mnt/disks/vol1
  17. nodeAffinity:
  18. required:
  19. nodeSelectorTerms:
  20. - matchExpressions:
  21. - key: kubernetes.io/hostname
  22. operator: In
  23. values:
  24. - node1

我把这个PV创建在node1节点上,并且将本地磁盘挂载声明为PV。

然后我们创建这个PV:

  1. $ kubectl apply -f local-pv-web-0.yaml
  2. $ kubectl get pv
  3. NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM
  4. STORAGECLASS REASON AGE
  5. local-volume-pv-0 512Mi RWX Retain Available default/local-vo

然后我们在创建这个StatefulSet的时候,会自动创建PVC:

  1. $ kubectl apply -f statefulset2.yaml
  2. $ kubectl get pvc
  3. NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
  4. local-volume-a-web-0 Bound local-volume-pv-0 512Mi RWX local-volume 15m

创建的PVC名字都是由:<PVC 名字 >-<StatefulSet 名字 >-< 编号 >构成,编号从0开始,并且我们可以看到上面的PV已经处于Bound状态

这个时候我们进入到Pod中,写入一个文件:

  1. $ kubectl exec -it web-0 -- /bin/bash
  2. $ echo helloword >/usr/share/nginx/html/index.html

这样就会在Pod 的 Volume 目录里写入一个文件,如果我们把这个Pod删除,那么在被删除之后这个Pod还是会被创建出来,并且还会再和原来的PV:local-volume-pv-0绑定起来。

也就是说当StatefulSet 控制器发现一个名叫 web-0 的 Pod 消失了的时候,控制器就会重新创建一个新的、名字还是叫作 web-0 的 Pod 来,“纠正”这个不一致的情况。并且删除Pod时并不会删除这个 Pod 对应的 PVC 和 PV。需要注意的是,在这个新的 Pod 对象的定义里,它声明使用的 PVC 的名字,还是叫作local-volume-a-web-0。

通过这种方式,Kubernetes 的 StatefulSet 就实现了对应用存储状态的管理。

更新策略

在 Kubernetes 1.7 及之后的版本中,可以为 StatefulSet 设定 .spec.updateStrategy 字段。

OnDelete

如果 StatefulSet 的 .spec.updateStrategy.type 字段被设置为 OnDelete,当您修改 .spec.template 的内容时,StatefulSet Controller 将不会自动更新其 Pod。您必须手工删除 Pod,此时 StatefulSet Controller 在重新创建 Pod 时,使用修改过的 .spec.template 的内容创建新 Pod。

例如我们执行下面的语句更新上面例子中创建的web:

  1. $ kubectl set image statefulset web nginx=nginx:1.18.0
  2. $ kubectl describe pod web-0
  3. ....
  4. Containers:
  5. nginx:
  6. Container ID: docker://7e45cd509db74a96b4f6ca4d9f7424b3c4794f56e28bfc3fbf615525cd2ecadb
  7. Image: nginx:1.9.1
  8. ....

然后我们发现pod的nginx版本并没有发生改变,需要我们手动删除pod之后才能生效。

  1. $ kubectl delete pod web-0
  2. pod "web-0" deleted
  3. $ kubectl describe pod web-0
  4. ...
  5. Containers:
  6. nginx:
  7. Container ID: docker://0f58b112601a39f3186480aa97e72767b05fdfa6f9ca02182d3fb3b75c159ec0
  8. Image: nginx:1.18.0
  9. ...

Rolling Updates

.spec.updateStrategy.type 字段的默认值是 RollingUpdate,该策略为 StatefulSet 实现了 Pod 的自动滚动更新。在更新完.spec.tempalte 字段后StatefulSet Controller 将自动地删除并重建 StatefulSet 中的每一个 Pod。

删除和重建的顺序也是有讲究的:

  • 删除的时候从序号最大的开始删,每删除一个会更新一个。
  • 只有更新完的pod已经是ready状态了才往下继续更新。

为 RollingUpdate 进行分区

当为StatefulSet 的 RollingUpdate 字段的指定 partition 字段的时候,则所有序号大于或等于 partition 值的 Pod 都会更新。序号小于 partition 值的所有 Pod 都不会更新,即使它们被删除,在重新创建时也会使用以前的版本。

如果 partition 值大于其 replicas 数,则更新不会传播到其 Pod。这样可以实现金丝雀发布Canary Deploy或者灰度发布。

如下,因为我们的web是2个pod组成,所以可以将partition设置为1:

  1. $ kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":1}}}}'

在这里,我使用了 kubectl patch 命令。它的意思是,以“补丁”的方式(JSON 格式的)修改一个 API 对象的指定字段。

下面我们执行更新:

  1. $ kubectl set image statefulset web nginx=nginx:1.19.1
  2. statefulset.apps/web image updated

并在另一个终端中watch pod的变化:

  1. $ kubectl get pods -l app=nginx -w
  2. NAME READY STATUS RESTARTS AGE
  3. web-0 1/1 Running 0 13m
  4. web-1 1/1 Running 0 93s
  5. web-1 0/1 Terminating 0 2m16s
  6. web-1 0/1 Pending 0 0s
  7. web-1 0/1 ContainerCreating 0 0s
  8. web-1 1/1 Running 0 16s

可见上面只有一个web-1进行了版本的发布。

总结

StatefulSet把有状态的应用抽象为两种情况:拓扑状态和存储状态。

拓扑状态指的是应用的多个实例之间不是完全对等的关系,包含启动的顺序、创建之后的网络标识等必须保证。

存储状态指的是不同的实例绑定了不同的存储,如Pod A在它的生命周期中读取的数据必须是一致的,哪怕是重启之后还是需要读取到同一个存储。

然后讲解了一下StatefulSet发布更新该如何做,updateStrategy策略以及通过partition如果实现金丝雀发布等。

5.深入k8s:StatefulSet控制器的更多相关文章

  1. k8s StatefulSet控制器-独立存储

    k8s-StatefulSet控制器-独立存储 1. StatefulSet控制器-独立存储 独享存储:StatefulSet的存储卷使用VolumeClaimTemplate创建,称为卷申请模板,当 ...

  2. 容器编排系统K8s之StatefulSet控制器

    前文我们聊到了k8s的configmap和secret资源的说明和相关使用示例,回顾请参考:https://www.cnblogs.com/qiuhom-1874/p/14194944.html:今天 ...

  3. 3.k8s资源控制器rs Deployment Job

    k8s资源控制器 #控制器类型 ReplicaSet #rs,确保pod副本数,rs已替代rc Deployment #管理rs,升级.回滚.扩容pod DaemonSet #在每个节点运行一个Pod ...

  4. Kubernetes学习之路(十七)之statefulset控制器

    目录 一.statefulset简介 二.为什么要有headless?? 三.为什么要 有volumeClainTemplate?? 四.statefulSet使用演示 (1)查看statefulse ...

  5. kubernetes学习控制器之StatefulSet控制器

    StatefulSet介绍 一.StatefulSet概述 StatefulSet是用来管理stateful(有状态)应用的StatefulSet管理Pod时,确保Pod有一个按序增长的ID与Depl ...

  6. K8S ingress控制器

    文章转载自: K8S ingress控制器 (一)https://blog.51cto.com/u_13760351/2728917 K8S ingress控制器 (二)https://blog.51 ...

  7. (十一)Kubernetes StatefulSet控制器

    StatefulSet介绍 前面使用Deployment创建的Pod是无状态的,当挂载了volume之后,如果该Pod挂了,Replication Controller会再启动一个Pod来保证可用性, ...

  8. k8s statefulset controller源码分析

    statefulset controller分析 statefulset简介 statefulset是Kubernetes提供的管理有状态应用的对象,而deployment用于管理无状态应用. 有状态 ...

  9. 9、kubernetes之statefulset控制器

    一.StatefulSet 有状态副本集 必要的三个组件:headless service.StatefulSet.volumeClaimTemplate 准备pv apiVersion: v1 ki ...

随机推荐

  1. python之爬虫(十) Selenium库的使用

    一.什么是Selenium selenium 是一套完整的web应用程序测试系统,包含了测试的录制(selenium IDE),编写及运行(Selenium Remote Control)和测试的并行 ...

  2. 重装win7时遇到点小问题

         最近装系统的时候有个头疼的事,事情的起因是这样的,我在工作的时候用的win7,破解的时候各种工具都破解不了,说是有未分配的盘符.并且,当时装的是没更新的win7,工作上要用到ie11只能在w ...

  3. centos7安装配置jdk1.8

    第一步:下载JDK  链接:https://pan.baidu.com/s/1sXWzvL9Tv7HIDxDPIw70SQ    提取码:vpbi 第二步:通过远程连接工具将下载好的JDK8上传到li ...

  4. 集训作业 洛谷P1135 奇怪的电梯

    这个题我见过!!! 我之前在石油大学的网站上做练习赛,提高了很多,这个题是我第一次在比赛里见到深搜. 当时蒙蔽的一批,现在发现好简单…… 这个题和普通的深搜没什么区别,甚至可以说简单了,因为这个是1维 ...

  5. 【Python学习笔记三】一个简单的python爬虫

    这里写爬虫用的requests插件 1.一般那3.x版本的python安装后都带有相应的安装文件,目录在python安装目录的Scripts中,如下:   2.将scripts的目录配置到环境变量pa ...

  6. vue :没有全局变量的计数器

    created: created () { let num = null this.mFun(num) }, methods: methods:{ mFun(m){ if (m === null) { ...

  7. MySQL的权限赋予

    MySQL 赋予用户权限命令的简单格式可概括为:grant 权限 on 数据库对象 to 用户 一.grant 普通数据用户,查询.插入.更新.删除 数据库中所有表数据的权利. grant selec ...

  8. Oracle常见错误以及解决方法

    前言: 本博客为博主在开发中遇到的问题,为大家提供解决方法,如需转载,请注明来源,谢谢! 问题一: 第一次用PLSQL Developer连接数据库,若用sys用户登录并操作则正常,若用普通用户比如x ...

  9. xshell如果通过跳板机登录其他机器

    首先,跳板机设置隧道 目标机器,选择刚才的隧道作为代理

  10. Mysql的复制原理以及流程

    MySQL复制概述 简单来说就是保证主服务器(Master)和从服务器(Slave)的数据是一致性的,向Master插入数据后,Slave会自动从Master把修改的数据同步过来(有一定的延迟),通过 ...