一 有状态和无状态理解
之前的博客我们学习了 'Deployment 和 ReplicaSet' 两种'资源对象'的使用
在实际使用的过程中,'Deployment 并不能编排'所有类型的应用,对'无状态服务编排'是非常容易的,但是对于'有状态服务'就无能为力了
那么问题来了:什么是'有状态'服务?什么是'无状态'服务
有状态案例讲解
我们常见的 'WEB 应用',是通过 'Session' 来保持用户的'登录状态'的
1)如果我们将 Session '持久化到节点上',那么该应用就是一个'有状态的服务了'
2)因为我现在登录进来你把我的 'Session 持久化到节点 A 上了',下次我'登录的时候'可能会将'请求路由到节点 B 上去了',但是'节点 B' 上根本就'没有我当前的 Session 数据',就会被认为是'未登录状态了'
3)这样就导致我'前后两次请求得到的结果不一致了'
4)所以一般为了'横向扩展',我们都会把这类 WEB 应用'改成无状态的服务',怎么改?
5)将 Session 数据'拆分'出来,存入一个'公共的地方',比如 Redis 里面;对于一些'客户端请求 API' 的情况,我们就'不使用 Session' 来保持用户状态,改成用 'JWT Token-->目前比较流行的' 也是可以的
备注:session和cookier理解,以及业界session共享方案
'容器化应用程序'的难点: 设计'有状态分布式组件'的部署'体系'结构
注意: 有状态服务和无状态服务'完全切分-->或者叫做解耦',不要'混合'使用
核心点: '稳定-->stable'、'有序-->sort'
1)稳定的、'唯一的'网络标识符 --> '这个是重点'
2)稳定的、'持久化'的存储
3)有序的、优雅的'部署和缩放' -->创建顺序列[1,n]
4)有序的、优雅的'删除和终止' -->删除'逆序'
5)有序的、自动'滚动更新'
二 StatefulSet
(1)Headless Service
因为StatefulSet是依赖于'Headless Service',所以先讲解'Headless Service'的特点,'不打算深入讲解',该篇服务于'StatefulSet'的,后续会'细讲'
(2)案例
说明: Headless Service本身也是'Service'的一种,'不指定'ClusterIP参数会'自动分配一个IP',所以Headless类型的Service'必须指定该参数是None'
备注: None和""的效果是一样的
注意:每次测试一定要保证'实验环境'的干净 -->字段不理解的话'--help'或者'explain'看帮助文档
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: default
labels:
app: nginx
spec:
ports:
- name: http
port: 80
clusterIP: None
selector:
app: nginx
1)headless service会为'关联的Pod分配一个域'
<service name>.$<namespace name>.svc.cluster.local
2)StatefulSet会为'关联的Pod保持一个不变的Pod Name'
statefulset中'Pod的hostname'格式为$(StatefulSet name)-$('pod序号')
3)StatefulSet会为'关联的Pod分配一个dnsName'
'<Pod Name>'.$<service name>.$<namespace name>.svc.cluster.local
(3)应用场景
三 StatefulSet
Pod名字称为'网络标识'-->'hostname'
'FQDN': $(podname).(headless service name).namespace.svc.cluster.local
'手动创建的PVC名称'必须符合之后创建的StatefulSet命名规则:'volumeClaimTemplates.name-pod_name'
(1)实验前准备
在开始之前,我们'先准备两个' 1G 的存储卷(PV),后面会详细'讲解 PV 和 PVC' 的使用方法的,这里我们'先不深究'
实现:'模拟nginx'作为'有状态'应用
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /tmp/pv001
--- '创建两个pv' --> 'hostPath方式提供本地存储'
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv002
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /tmp/pv002
备注: 这种'提供存储'方式是'宿主机挂载方式',对比理解docker 宿主机挂载的特点-->'覆盖'
注意: 此时node1和node2对应'路径并没有创建','之后'使用的时候'才会创建' --> 两个节点都'没有对应的路径'
(2)StatefulSet讲解
容器通过'resources'来申请'计算资源'-->'cpu、memory',通过' volumeClaimTemplates'来申请'存储资源'
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
namespace: default
spec:
serviceName: "nginx" --> 'Headless Service的名称'
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- name: web
containerPort: 80
volumeMounts:
- name: www --> '一个pod肯嗯有多个pv,具体容器使用哪个pv'
mountPath: /usr/share/nginx/html --> '容器中的挂载点'
volumeClaimTemplates: --> 'pod申请使用的存储的申请' --> '针对该StatefulSet下所有的pod'
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
注意: 如果系统没有复合要求的PV,pod会一直处于Pending,是因为没有可用的pv,查看对应的pvc的状态'也是pending'
一旦'pv被使用',hostPath这种方式,才会在node创建对应的'存储路径'
查看'pod所在的节点'hostpath形式,访问节点403原因 -->是'mount'的形式,所以会'覆盖'容器目录数据(index.html)
继续实验:访问另外一个pod(在另外一个pod上),发现访问'也是403' -->二者的'数据来源'不一样 --> '有状态'
测试
我们'仔细观察整个过程'出现了两个 Pod:web-0 和 web-1,而且这两个 Pod 是'按照顺序进行创建的',web-0 '启动起来后' web-1 '才开始'创建
如同上面 StatefulSet 概念中所提到的,StatefulSet 中的 '每个Pod' 拥有一个具有'稳定的、独一无二'的身份标志。这个标志基于 StatefulSet 控制器分配给每个 Pod 的唯一顺序索引。
Pod 的名称的形式为'<statefulset name>-<ordinal index>',我们这里的对象拥有两个副本,所以它创建了两个 'Pod 名称'分别为:'web-0 和 web-1'
继续探究: 我们可以使用 kubectl exec 命令'进入到容器中'查看它们的 'hostname'
现象: StatefulSet '对应的这两个 Pod 的' hostname 与 Pod '名字是一致的',都被'分配了'对应的编号
继续探究
'pv'和对应的'pod'进行绑定 --> '一旦绑定'-->pvc名称 = 'pod中声明的名称 + pod name'
我们'可以看到'Controlled By: StatefulSet/web,证明我们的 Pod 是'直接受到 StatefulSet 控制器管理的',没有'中间商赚差价'
现在我们创建一个 busybox'该镜像中有一系列的工具'的容器,在容器中用 DNS 的方式来访问一下这个 Headless Service
这里写成yaml文件的形式,如果只是'单纯的为了测试',所以'没必要写资源清单文'件来声明,用kubectl run命令'启动一个测试的容器'即可
kubectl run -it --image busybox:1.28.3 test --restart=Never --rm /bin/sh
备注: busybox '最新版本的镜像有 BUG',会出现nslookup'提示无法解析'的问题,我们这里使用'老一点的镜像版本'1.28.3
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: busybox
name: busybox-deployment
namespace: default
spec:
# dp的副本数
replicas: 1
# 工作负载选择那些标签的pod,组成组
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
containers:
# PID为1的常驻进程
- command:
- sleep
- "36000"
image: busybox:1.28.3
# 思考:镜像的标签为none出现的原因
imagePullPolicy: IfNotPresent
name: busybox
dnsPolicy: ClusterFirst
# dnsPolicy: Default
# 特点:只继承,不使用内部的dns
restartPolicy: Always
1)我们直接解析 'Headless Service' 的名称,可以看到得到的是'两个 Pod 的解析记录' --> 而不是服务的ip,'本身也就没有ip'
2)但实际上如果我们通过'nginx这个 DNS' 去访问我们的服务的话,并'不会随机或者轮询'背后的两个 Pod,而是访问到'一个固定的 Pod',所以'不能代替'普通的 Service
问题: 如果分别解析对应的 Pod 呢?
可以看到'解析 web-0.nginx' 的时候解析到了 'web-0 这个 Pod 的 IP'web-1.nginx 解析到了 web-1 这个 Pod 的 IP,而且这个 'DNS 地址还是稳定的',因为 'Pod 名称就是固定的'
备注: 其它Pod就可以通过'pod的域名'来访问'StatefulSet创建'的pod
继续
可以看到 StatefulSet 控制器仍然会'按照顺序'创建出两个 Pod 副本出来,而且 Pod 的'唯一标识'依然没变,所以这两个 Pod 的'网络标识还是固定的',我们依然可以通过web-0.nginx去访问到web-0这个 Pod
备注: 虽然 'Pod 已经重建了',对应 'Pod IP 已经变化了',但是'访问这个 Pod 的地址-->域名'依然没变,并且他们'依然还是关联的之前的 PVC',数据并不会丢失
强调: 我们关系的是'数据是否持久话',而'不是pod变化与否',所以'pod重建导致ip变化不影响'
(3)通过控制器删除pod
'逆序'删除,删除后'数据也没有丢失'
(4)结构图
(5)自己的一些理解
(1)提供稳定的'网络标识'
StatefulSet 创建的pod的名称,按照'从零开始'的顺序索引,这个会体现在'pod的名称和主机名称上',同样还会体现在'pod对应的固定存储上'
1)'StatefulSet名称': web
2)'Headless Service'名称: nginx
3)'pod名称': web-0 和 web-1 -->格式: 'web-${index}'
4)'volumeClaimTemplates': 'www'
5)创建出来的'pvc名称':www-web-0 www-web-0 --> 格式: 'sts卷申请模板名称-${pod_name}'
6)pod的在coredns中'fqdn名称': web-0.nginx.default.svc.cluster.local -->格式: '${pod_name}'.'${Headless Service name}'.'${namespace}'.svc.vluster.local
+++++++++++++++ '分割线'
服务暴露的方式: 直接指定某个'具体的pod'
一个StatefulSet要求你'创建一个用来记录'每个pod'网络标记'的headless Service
通过这个Service,每个pod将拥有'独立的DNS记录',这样'集群里它的伙伴'或者客户端就可以通过'主机名'找到它。
+++++++++++++++ '分割线'
(2)StatefulSet的保障机制
一个有状态的pod总会被一个'完全一致的'pod-->'替换两者相同的名称,主机名和存储等',这个替换发生在kubernetes发现'旧pod不存在时'-->例如'手动删除'这个pod
那么当Kubernetes'不能确定'一个pod的状态呢?如果它创建一个完全一致的pod,那系统中就会有两个完全一致的pod在同时运行,这两个pod会'绑定到相同的存储',所以这两个相同标记的进程会'同时写相同'的文件。
为了保证两个拥有'相同标记和绑定相同'持久卷声明的有状态的pod实例'不会同时运行',statefulset遵循at-most-one语义,也就是说一个StatefulSet必须在'准确确认一个pod不再运行后',才会去创建它的'替换pod'
+++++++++++++++ '分割线'
(3)pod重启后是'如何找到'对应存储的
首先重启之后-->'重点:sts配置文件没有变化',网络标识别'hostname=pod_name'不变,'Headless Service'不变,该pod申请的'pvc名称不变-->能够找到对应的存储',同时该pod在dns中的'fqdn-->注意fqdn的组成'没有发生变化
可以这样理解: pod的'fqdn'和申请的'pvc'具有'强一致性的关联'
client --> 通过该pod的'fqdn'找到'对应的存储'
(5)相关参数解读
① 管理策略
② 更新策略
达到的效果: '灰度发布'
(6)有状态应用的高级用法