《重识云原生系列》专题索引:
第四章云网络4.9.1节——网络卸载加速技术综述
第四章云网络4.9.2节——传统网络卸载技术
第四章云网络4.9.3.1节——DPDK技术综述
第四章云网络4.9.3.2节——DPDK原理详解
第四章云网络4.9.4.1节——智能网卡SmartNIC方案综述
第四章云网络4.9.4.2节——智能网卡实现
第六章容器6.1.1节——容器综述
第六章容器6.1.2节——容器安装部署
第六章容器6.1.3节——Docker常用命令
第六章容器6.1.4节——Docker核心技术LXC
第六章容器6.1.5节——Docker核心技术Namespace
第六章容器6.1.6节—— Docker核心技术Chroot
第六章容器6.1.7.1节——Docker核心技术cgroups综述
第六章容器6.1.7.2节——cgroups原理剖析
第六章容器6.1.7.3节——cgroups数据结构剖析
第六章容器6.1.7.4节——cgroups使用
第六章容器6.1.8节——Docker核心技术UnionFS
第六章容器6.1.9节——Docker镜像技术剖析
第六章容器6.1.10节——DockerFile解析
第六章容器6.1.11节——docker-compose容器编排
第六章容器6.1.12节——Docker网络模型设计
第六章容器6.2.1节——Kubernetes概述
第六章容器6.2.2节——K8S架构剖析
第六章容器6.3.1节——K8S核心组件总述
第六章容器6.3.2节——API Server组件
第六章容器6.3.3节——Kube-Scheduler使用篇
第六章容器6.3.4节——etcd组件
第六章容器6.3.5节——Controller Manager概述
第六章容器6.3.6节——kubelet组件
第六章容器6.3.7节——命令行工具kubectl
第六章容器6.3.8节——kube-proxy
第六章容器6.4.1节——K8S资源对象总览
第六章容器6.4.2.1节——pod详解
第六章容器6.4.2.2节——Pod使用(上)
第六章容器6.4.2.3节——Pod使用(下)
第六章容器6.4.3节——ReplicationController
第六章容器6.4.4节——ReplicaSet组件
第六章容器基础6.4.5.1节——Deployment概述
第六章容器基础6.4.5.2节——Deployment配置详细说明
第六章容器基础6.4.5.3节——Deployment实现原理解析
第六章容器基础6.4.6节——Daemonset
第六章容器基础6.4.7节——Job
第六章容器基础6.4.8节——CronJob
首先,StatefulSet 的控制器直接管理的是 Pod。这是因为,StatefulSet 里的不同 Pod 实例,不再像 ReplicaSet 中那样都是完全一样的,而是有了细微区别的。比如,每个 Pod 的 hostname、名字等都是不同的、携带了编号的。而 StatefulSet 区分这些实例的方式,就是通过在 Pod 的名字里加上事先约定好的编号。
其次,Kubernetes 通过 Headless Service,为这些有编号的 Pod,在 DNS 服务器中生成带有同样编号的 DNS 记录。只要 StatefulSet 能够保证这些 Pod 名字里的编号不变,那么 Service 里类似于 web-0.nginx.default.svc.cluster.local 这样的 DNS 记录也就不会变,而这条记录解析出来的 Pod 的 IP 地址,则会随着后端 Pod 的删除和再创建而自动更新。这当然是 Service 机制本身的能力,不需要 StatefulSet 操心。
最后,StatefulSet 还为每一个 Pod 分配并创建一个同样编号的 PVC。这样,Kubernetes 就可以通过 Persistent Volume 机制为这个 PVC 绑定上对应的 PV,从而保证了每一个 Pod 都拥有一个独立的 Volume。在这种情况下,即使 Pod 被删除,它所对应的 PVC 和 PV 依然会保留下来。所以当这个 Pod 被重新创建出来之后,Kubernetes 会为它找到同样编号的 PVC,挂载这个 PVC 对应的 Volume,从而获取到以前保存在 Volume 里的数据。
从上面内容可以看出,管理有状态应用 Pod 的关键是提供稳定不变的 Pod 标识和稳定不变的存储。
StatefulSet 中Pod标识和 Pod是绑定的,不管 Pod 被调度到哪个节点上,每个 Pod 将被分配一个整数序号,从 0 到 N-1,该序号在 StatefulSet 上是唯一的。
每个 Pod 根据 StatefulSet 的名称和 Pod 的序号派生出它的主机名。组合主机名的格式为 (StatefulSet名称)(序号);例如StatefulSet 名称为 web,replicas 为 3,那么将会创建三个名称分别为 web-0、web-1、web-2 的 Pod。一旦每个 Pod 创建成功,就会得到一个匹配的 DNS 名称,格式为:. ..svc.cluster.local,其中 service-name 由 StatefulSet 的 serviceName 域来设定;并且这些 Pod 的创建,是严格按照编号顺序进行的。比如,在 web-0 进入到 Running 状态、并且 Conditions 成为 Ready 之前,web-1 会一直处于 Pending 状态。
当我们把这几个 Pod 删除之后,Kubernetes 会按照原先编号的顺序,创建出新的 Pod。并且,Kubernetes 依然为它们分配了与原来相同的“网络身份”;
通过这种严格的对应规则,StatefulSet 就保证了 Pod 网络标识的稳定性;
通过 Headless Service 的方式,StatefulSet 为每个 Pod 创建了一个固定并且稳定的 DNS 记录,来作为它的访问入口。
Service 类型一般有如下的三种:
Service提供一个 VIP,我们访问这个IP地址时,它会把请求转发到该 Service 所代理的某一个 Pod 上。
这种情况下,访问“my-svc.my-namespace.svc.cluster.local”解析到的,正是 my-svc 这个 Service 的 VIP,后面的流程就跟 VIP 方式一致。
这种情况下,访问“my-svc.my-namespace.svc.cluster.local”解析到的,直接就是 my-svc 代理的某一个 Pod 的 IP 地址。所以,Headless Service 不需要分配一个 VIP,而是可以直接以 DNS 记录的方式解析出被代理 Pod 的 IP 地址。
参考如下的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
关于于上面的这行配置clusterIP: None。
clusterIP 是服务的IP地址,通常由主服务器随机分配还具有如下特点:
Headless Service 是一个标准 Service 的 YAML 文件。只不过,它的 clusterIP 字段的值是:None。这个 Service 被创建后并不会被分配一个 VIP,而是会以 DNS 记录的方式暴露出它所代理的 Pod。
当按照这样的方式创建了一个 Headless Service 之后,它所代理的所有 Pod 的 IP 地址,都会被绑定一个这样格式的 DNS 记录,如下所示:
...svc.cluster.local
这个 DNS 记录,正是 Kubernetes 项目为 Pod 分配的唯一的“可解析身份”(Resolvable Identity)。有了这个“可解析身份”,只需要知道了一个 Pod 的名字,以及它对应的 Service 的名字,就可以非常确定地通过这条 DNS 记录访问到 Pod 的 IP 地址。
Stateful状态集在创建和扩展的时候有特殊的限制,如果一个有状态集期望的Pod数量是N,那么有StatefulSet会从0开始依次创建这些Pod在第一个Pod正常运行之前是不会创建第二个Pod。在删除Pod的时候,则是从第N个Pod开始反向依次删除。
StatefulSet的核心功能就是通过某种方式记录这些状态,然后在Pod被重新创建时能够为新Pod恢复这些状态。
只要知道一个Pod的名字以及它对应的Service的名字,就可以通过这条DNS记录访问到Pod的IP地址(pod的名称.service名称) -> Pod的IP。
定义Stateful的对象中有一个serviceName字段来告诉Stateful控制器使用具体的service来解析它所管理的Pod的IP地址。
Pod的名称由StatfulSet对象的名称+Pod创建时所在的索引组成 StatefulSet使用这个DNS记录解析规则来维持Pod的拓扑状态。
StatefulSet给它所管理的所有Pod的名字进行了编号,编号规则是: - .而且这些编号都是从0开始累加与StatefulSet的每个Pod实例一一对应、绝不重复。
这些Pod的创建也是严格按照编号顺序进行的,比如,在web-0进入到Running 状态、并且细分状态(Conditions)成为Ready之前,web-1会一直处于Pending状态。
把这两个Pod删除之后Kubernetes会按照原先编号的顺序,创建出了两个新的 Pod,并且Kubernetes依然为它们分配了与原来相同的“网络身份”:web-0.nginx和 web-1.nginx。
通过永远不改变Pod名称的方式StatefulSet保证了Pod网络标识的稳定性。不管Pod是被删除重启还是被调度到其他节点上都不会改变Pod的名称。
StatefulSet管理的pod使用的镜像是一样的,但是每个pod的命令和初始化流程可以完全不一样来实现主从Pod的拓扑部署。
尽管web-0.nginx这条DNS记录本身不会变但它解析到的Pod的IP地址并不是固定的。这就意味着,对于“有状态应用”实例的访问必须使用DNS记录或者hostname的方式而绝不应该直接访问这些Pod的IP地址。
通过PVC机制来实现存储状态管理。
在StatefulSet对象中除了定义PodTemplate还会定义一个volumeClaimTemplates凡是被这个StatefulSet管理的Pod都会声明一个对应的PVC,这个PVC的定义就来自于 volumeClaimTemplates这个模板字段。
这个PVC的名字,会被分配一个与这个Pod完全一致的编号。
把一个Pod比如web-0删除之后,这个Pod对应的PVC和PV并不会被删除,而这个Volume 里已经写入的数据,也依然会保存在远程存储服务里。
StatefulSet在重新创建web-0这个pod的时候.它声明使用的PVC的名字还是叫作:www-web-0 这个PVC的定义,还是来自于PVC模板(volumeClaimTemplates)这是StatefulSet创建 Pod的标准流程。
Kubernetes为它查找名叫www-web-0的PVC时,就会直接找到旧Pod遗留下来的同名的 PVC进而找到跟这个PVC绑定在一起的PV.这样新的Pod就可以挂载到旧Pod对应的那个Volume并且获取到保存在Volume里的数据.通过这种方式Kubernetes的StatefulSet就实现了对应用存储状态的管理。
StatefulSet其实就是一种特殊的Deployment,而其独特之处在于,它的每个Pod都被编号了.而且,这个编号会体现在Pod的名字和hostname等标识信息上,这不仅代表了Pod的创建顺序,也是Pod的重要网络标识(即:在整个集群里唯一的、可被的访问身份).有了这个编号后StatefulSet就使用Kubernetes里的两个标准功能:Headless Service 和 PV/PVC,实现了对 Pod 的拓扑状态和存储状态的维护。
StatefulSet的实现机制整体流程相对简明,接下来按照Pod管理、状态计算、状态管理、更新策略这几部分来依次讲解。
statefulSet中的pod的名字都是按照一定规律来进行设置的, 名字本身也有含义, k8s在进行statefulset更新的时候,首先会过滤属于当前statefulset的pod,并做如下操作:
通过该流程可以确保当前statefulset关联的Pod要么与当前的对象关联,要么我就释放你,这样可以维护Pod的一致性,即时有人修改了对应的Pod则也会调整成最终一致性。
在经过第一步的Pod状态的修正之后,statefulset会遍历所有属于自己的Pod,同时将Pod分为两个大类:有效副本和无效副本(condemned),前面提到过Pod的名字也是有序的即有N个副本的Pod则名字依次是{0...N-1}, 这里区分有效和无效也是依据对应的索引顺序,如果超过当前的副本即为无效副本
单调更新主要是指的当对应的Pod管理策略不是并行管理的时候,只要当前Replicas(有效副本)中任一一个Pod发生创建、终止、未就绪的时候,都会等待对应的Pod就绪,即你要想更新一个statefulset的Pod的时候,对应的Pod必须已经RunningAndReady
func allowsBurst(set *apps.StatefulSet) bool {
return set.Spec.PodManagementPolicy == apps.ParallelPodManagement
}
滚动更新的实现相对隐晦一点,其主要是通过控制副本计数来实现,首先倒序检查对应的Pod的版本是否是最新版本,如果发现不是,则直接删除对应的Pod,同时将currentReplica计数减一,这样在检查对应的Pod的时候,就会发现对应的Pod的不存在,就需要为对应的Pod生成新的Pod信息,此时就会使用最新的副本去更新。
func newVersionedStatefulSetPod(currentSet, updateSet *apps.StatefulSet, currentRevision, updateRevision string, ordinal int) *v1.Pod {
// 如果发现当前的Pod的索引小于当的副本计数,则表明当前Pod还没更新到,但实际上可能因为别的原因
// 需要重新生成Pod模板,此时仍然使用旧的副本配置
if currentSet.Spec.UpdateStrategy.Type == apps.RollingUpdateStatefulSetStrategyType &&
(currentSet.Spec.UpdateStrategy.RollingUpdate == nil && ordinal < int(currentSet.Status.CurrentReplicas)) ||
(currentSet.Spec.UpdateStrategy.RollingUpdate != nil && ordinal < int(*currentSet.Spec.UpdateStrategy.RollingUpdate.Partition)) {
pod := newStatefulSetPod(currentSet, ordinal)
setPodRevision(pod, currentRevision)
return pod
}
// 使用新的配置生成新的Pod配置
pod := newStatefulSetPod(updateSet, ordinal)
setPodRevision(pod, updateRevision)
return pod
}
无效副本的清理应该主要是发生在对应的statefulset缩容的时候,如果发现对应的副本已经被遗弃,就会直接删除,此处默认也需要遵循单调性原则,即每次都只更新一个副本
if getPodRevision(replicas[target]) != updateRevision.Name && !isTerminating(replicas[target]) {
klog.V(2).Infof("StatefulSet %s/%s terminating Pod %s for update", set.Namespace, set.Name, replicas[target].Name)
err := ssc.podControl.DeleteStatefulPod(set, replicas[target])
status.CurrentReplicas--
return &status, err
}
Pod的版本检测位于对应一致性同步的最后,当代码走到当前位置,则证明当前的statefulSet在满足单调性的情况下,有效副本里面的所有Pod都是RunningAndReady状态了,此时就开始倒序进行版本检查,如果发现版本不一致,就根据当前的partition的数量来决定允许并行更新的数量,在这里删除后,就会触发对应的事件,从而触发下一个调度事件,触发下一轮一致性检查。
if set.Spec.UpdateStrategy.Type == apps.OnDeleteStatefulSetStrategyType {
return &status, nil
}
StatefulSet的更新策略除了RollingUpdate还有一种即OnDelete即必须人工删除对应的 Pod来触发一致性检查,所以针对那些如果想只更新指定索引的statefulset可以尝试该策略,每次只删除对应的索引,这样只有指定的索引会更新为最新的版本。
状态存储其实就是我们常说的PVC,在Pod创建和更新的时候,如果发现对应的PVC的不存在则就会根据statefulset里面的配置创建对应的PVC,并更新对应Pod的配置。
从核心实现分析中可以看出来,有状态应用的实现,实际上核心是基于一致性状态、单调更新、持久化存储的组合,通过一致性状态、单调性更新,保证期望副本的数量的Pod处于RunningAndReady的状态并且保证有序性,同时通过持久化存储来进行数据的保存。
有序的重要性,在分布式系统中比较常见的两个设计就是分区和副本,其中副本主要是为了保证可用性,而分区主要是进行数据的平均分布,二者通常都是根据当前集群中的节点来进行分配的,如果我们节点短暂的离线升级,数据保存在对应的PVC中,在恢复后可以很快的进行节点的信息的恢复并重新加入集群,所以后面如果开发这种类似的分布式应用的时候,可以将底层的恢复和管理交给k8s,数据保存在PVC中,则应用更多的只需要关注系统的集群管理和数据分布问题即,这也是云原生带来的改变。
Statefulset详细解析 - 不懂123 - 博客园
k8s中statefulset资源类型的深入理解
十,StatefulSet简介及简单使用 - 戴红领巾的少年 - 博客园
k8s之StatefulSet详解_最美dee时光的博客-CSDN博客_statefulset
Kubernetes学习笔记 —— 7. StatefulSet - 知乎
容器化部署实战(五)|控制器 StatefulSet 的原理
K8s StatefulSet
kubernetes——StatefulSet详解
Kubernetes 深入理解StatefulSet(一):拓扑状态_富士康质检员张全蛋的博客-CSDN博客
图解kubernetes控制器StatefulSet核心实现原理_8小时的博客-CSDN博客
详解 Kubernetes StatefulSet 实现原理_cbmljs的博客-CSDN博客_kubectl statefulset
Kubernetes:StatefulSet剖析
Kubernetes Deployments vs StatefulSets - Stack Overflow
StatefulSet: Run and Scale Stateful Applications Easily in Kubernetes | Kubernetes
Running ZooKeeper, A Distributed System Coordinator | Kubernetes
StatefulSets | Kubernetes
StatefulSet · Kubernetes Engine