在最新发布的 Kubernetes 1.5 咱们将过去的 PetSet 功能升级到了 Beta 版本,并从新命名为StatefulSet。除了依照社区民意改了名字以外,这一 API 对象并无太大变化,不过咱们在向集合里部署 Pod 的过程当中加入了“每索引最多一个”的语义。有了顺序部署、顺序终结、惟一网络名称以及持久稳定的存储,咱们认为,对于大量的有状态容器化负载,咱们已经具有了必定的支持能力。咱们并非宣称这一功能已经彻底完成,可是咱们相信他已经处于一个可用状态,而且咱们会在推进其正式发布的过程当中保持其兼容性。html
在 Kubernetes 中,Deployment 和 ReplicaSets 都是运行无状态应用的有效手段。但这两种方式对于有状态应用来讲就不太合适了。StatefulSet 的目的就是给为数众多的有状态负载提供正确的控制器支持。然而须要注意的是,不必定全部的有存储应用都是适合移植到 Kubernetes 上的,在移植存储层和编排框架以前,须要回答如下几个问题。git
目前,咱们推荐用远程存储来使用 StatefulSets,就要对由于网络形成的存储性能损失有一个准备:即便是专门优化的实例,也没法同本地加载的 SSD 相提并论。你的云中的网络存储,可以知足 SLA 要求么?若是答案是确定的,那么利用 StatefulSet 运行这些应用,就可以得到自动化的优点。若是应用所在的 Node 发生故障,包含应用的 Pod 会调度到其余 Node 上,在这以后会从新加载他的网络存储以及其中的数据。github
用 StatefulSet 运行应用会带来什么好处呢?你的整个组织是否只须要一个应用实例?对该应用的伸缩是否会引发问题?若是你只须要较少的应用实例数量,这些实例可以知足组织现有的须要,并且能够预见的是,应用的负载不会很快增加,那么你的本地应用可能无需移植。服务器
然而,若是你的系统是微服务所构成的生态系统,就会比较频繁的交付新服务,若是更近一步,服务是有状态的,那么 Kubernetes 的自动化和健壮性特性会对你的系统有很大帮助。若是你已经在使用 Kubernetes 来管理你的无状态服务,你可能会想要在同一个体系中管理你的有状态应用。网络
Kubernetes 还不支持网络或存储在 Pod 之间的隔离。若是你的应用不巧和嘈杂的邻居共享同一个节点,会致使你的 QPS 降低。解决方式是把 Pod 调度为该 Node 的惟一租户(独占服务器),或者使用互斥规则来隔离会争用网络和磁盘的 Pod,可是这就意味着用户必须鉴别和处置(竞争)热点。app
若是榨干有状态应用的最大 QPS 不是你的首要目标,并且你愿意也有能力处理竞争问题,似的有状态应用可以达到 SLA 须要,又若是对服务的移植、伸缩和从新调度是你的主要需求,Kubernetes 和 StatefulSet 可能就是解决问题的好方案了。框架
若是你的有状态应用在高端硬件或高规格实例上运行,而其余应用在通用硬件或者低规格实例上运行,你可能不想部署一个异构的集群。若是能够把全部应用都部署到统一实例规格的实例上,那么你就可以从 Kubernetes 得到动态资源调度和健壮性的好处。less
有两个缘由让 [ZooKeeper] 成为 StatefulSet 的好例子。首先,StatefulSet 在其中演示了运行分布式、强一致性存储的应用的能力;其次,ZooKeeper 也是 Apache Hadoop 和 Apache Kafka 在 Kubernetes 上运行的前置条件。在 Kubernetes 文档中有一个 深度教程 说明了在 Kubernetes 集群上部署 ZooKeeper Ensemble 的过程,这里会简要描述一下其中的关键特性。分布式
建立 Ensemble 很容易,只要用 kubectl create 来根据定义来建立对象就能够了。微服务
$ kubectl create -f https://raw.githubusercontent.com/kubernetes/kubernetes.github.io/master/docs/tutorials/stateful-application/zookeeper.yaml service "zk-headless" created configmap "zk-config" created poddisruptionbudget "zk-budget" created statefulset "zk" created
接下来 StatefulSet 控制器开始顺序建立各个 Pod,在建立后续 Pod 以前,首先要等前面的 Pod 运行成功并进入到就绪状态。
$ kubectl get -w -l app=zk NAME READY STATUS RESTARTS AGE zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 0s zk-0 0/1 Pending 0 7s zk-0 0/1 ContainerCreating 0 7s zk-0 0/1 Running 0 38s zk-0 1/1 Running 0 58s zk-1 0/1 Pending 0 1s zk-1 0/1 Pending 0 1s zk-1 0/1 ContainerCreating 0 1s zk-1 0/1 Running 0 33s zk-1 1/1 Running 0 51s zk-2 0/1 Pending 0 0s zk-2 0/1 Pending 0 0s zk-2 0/1 ContainerCreating 0 0s zk-2 0/1 Running 0 25s zk-2 1/1 Running 0 40s
检查一下 StatefulSet 中每一个 Pod 的主机名称,你会发现 Pod 的主机名也包含了 Pod 的顺序:
$ for i in 0 1 2; do kubectl exec zk-$i -- hostname; done zk-0 zk-1 zk-2
ZooKeeper 在一个名为 “myid” 的文件中保存了每一个服务器的惟一标识符。这个标识符只是天然数。在 Ensemble 的服务器中,”myid” 文件中保存的数字就是 Pod 主机名中的顺序号加一。
$ for i in 0 1 2; do echo "myid zk-$i";kubectl exec zk-$i -- cat /var/lib/zookeeper/data/myid; done myid zk-0 1 myid zk-1 2 myid zk-2 3
基于主机名,每一个 Pod 都有独立的网络地址,这个网域由 zk-headless 这一 Headless 服务所控制。
$ for i in 0 1 2; do kubectl exec zk-$i -- hostname -f; done zk-0.zk-headless.default.svc.cluster.local zk-1.zk-headless.default.svc.cluster.local zk-2.zk-headless.default.svc.cluster.local
Pod 具备了惟一的序号和网络地址,就能够用来在 ZooKeeper 的配置文件中设置 Ensemble 成员了。
kubectl exec zk-0 -- cat /opt/zookeeper/conf/zoo.cfg clientPort=2181 dataDir=/var/lib/zookeeper/data dataLogDir=/var/lib/zookeeper/log tickTime=2000 initLimit=10 syncLimit=2000 maxClientCnxns=60 minSessionTimeout= 4000 maxSessionTimeout= 40000 autopurge.snapRetainCount=3 autopurge.purgeInteval=1 server.1=zk-0.zk-headless.default.svc.cluster.local:2888:3888 server.2=zk-1.zk-headless.default.svc.cluster.local:2888:3888 server.3=zk-2.zk-headless.default.svc.cluster.local:2888:3888
StatefulSet 让用户能够用稳定、可重复的方式来部署 ZooKeeper。不会建立具备重复 ID 的服务器,服务器之间能够经过稳定的网络地址互相通讯,由于 Ensemble 具备稳定的成员关系,所以 Leader 选拔和写入复制能力也获得了保障。
检查 Ensemble 工做情况的最简单方式就是向一台服务器写入一个值,而后从另外一台服务器中读取。能够利用 ZooKeeper 自带的 “zkCli.sh” 脚原本建立包含数据的 ZNode。
$ kubectl exec zk-0 zkCli.sh create /hello world ... WATCHER:: WatchedEvent state:SyncConnected type:None path:null Created /hello
使用同一脚本,能够从 Ensemble 另一台服务器中读取数据。
$ kubectl exec zk-1 zkCli.sh get /hello ... WATCHER:: WatchedEvent state:SyncConnected type:None path:null world ...
能够用删除 zk StatefulSet 的方式停掉 Ensemble。
$ kubectl delete statefulset zk statefulset "zk" deleted
级联删除会销毁 StatefulSet 中的每一个 Pod,而且按照建立顺序的反序来执行,只有在成功终结后面一个以后,才会继续下一个删除操做。
$ kubectl get pods -w -l app=zk NAME READY STATUS RESTARTS AGE zk-0 1/1 Running 0 14m zk-1 1/1 Running 0 13m zk-2 1/1 Running 0 12m NAME READY STATUS RESTARTS AGE zk-2 1/1 Terminating 0 12m zk-1 1/1 Terminating 0 13m zk-0 1/1 Terminating 0 14m zk-2 0/1 Terminating 0 13m zk-2 0/1 Terminating 0 13m zk-2 0/1 Terminating 0 13m zk-1 0/1 Terminating 0 14m zk-1 0/1 Terminating 0 14m zk-1 0/1 Terminating 0 14m zk-0 0/1 Terminating 0 15m zk-0 0/1 Terminating 0 15m zk-0 0/1 Terminating 0 15m
可使用 kubectl apply 命令来重建 zk StatefulSet,并从新部署 Ensemble。
$ kubectl apply -f http://k8s.io/docs/tutorials/stateful-application/zookeeper.yaml service "zk-headless" configured configmap "zk-config" configured statefulset "zk" created
若是使用 “zkCli.sh” 脚原本尝试获取删除 StatefulSet 以前写入的数据,会发现数据依然存在。
$ kubectl exec zk-2 zkCli.sh get /hello ... WATCHER:: WatchedEvent state:SyncConnected type:None path:null world ...
及时全部的 Pod 都被销毁,他们一旦被从新调度,StatefulSet 也能保证 Ensemble 可以选拔新的 Leader 并继续提供服务。
ZooKeeper 会在 Ensmble 的服务器中复制他的状态机,用于应对 Node 故障。缺省状况下 Kubernetes 调度器能够在同一个 Node 上部署属于 zk StatefulSet 的多个 Pod,假设 zk-0 和 zk-1 两个 Pod 被部署在同一个 Node 上,若是这一 Node 出现故障,ZooKeepers Ensemble 会由于数量不足形成没法提交写入,ZooKeeper 会出现服务中断,直到 Pod 被从新调度。
在集群中,建议为关键进程预留更多资源,这样就能保证故障状况发生的时候可以迅速从新调度 Pod,缩短故障时间。
若是这样没法 SLA 规定的停机时间,那么就应该使用 PodAntiAffinity( Pod 互斥性)注解。用来建立 Ensemble 的定义文件中就包含了这样的注解,他会要求 Kubernetes 调度器不要把 zk StatefulSet 中的多个 Pod 部署在同一 Node 上。
用于建立 ZooKeeper Ensemble 的描述文件还建立了一个 PodDistruptionBudget( Pod 中断预算 )对象:zk-budget。zk-budget 用于指示 Kubernetes, 这一服务可以容忍的中断 Pod (不健康 Pod)的上限。
{ "podAntiAffinity": { "requiredDuringSchedulingRequiredDuringExecution": [ { "labelSelector": { "matchExpressions": [ { "key": "app", "operator": "In", "values": [ "zk-headless" ] } ] }, "topologyKey": "kubernetes.io/hostname" } ] } }
$ kubectl get poddisruptionbudget zk-budget NAME MIN-AVAILABLE ALLOWED-DISRUPTIONS AGE zk-budget 2 1 2h
zk-budget 定义,至少要有两个处于可用状态的成员才能保障 Ensemble 的健康。若是在离线以前对 Node 进行 Drain 操做,若是这一操做过程当中终止的 Pod 会违反预算,Drain 操做就会失败。若是使用 kubectl drain,来对 Node 进行 cordon 操做并驱逐全部其中运行的 Node,PodDistruption 让你能够确认这一操做不会中断有状态应用的服务。
由于 Kubernetes 的开发工做目标是可用,咱们但愿得到更多来自用户的设想。若是你想要帮咱们处理问题,能够看看 GitHub 上关于 statful 的 Issues。然而为了 API 的易于理解,咱们并不许备实现全部的功能请求。咱们会优先实现一些可以对全部有状态应用产生改善的功能,例如滚动更新支持、Node 升级的集成、使用高速的本地存储等。StatefulSet 的目的在于支持尽量多而不是所有的有状态应用。基于这些考虑,咱们会避免依赖隐藏特性或者技术的方式来充实 StatefulSet。每一个人均可以开发一个想 StatefulSets 的控制器。咱们称之为 “making it forkable”。明年,咱们但愿更多的流行的有状态应用可以有本身的社区支持的独立控制器或 “操做器”。咱们已经据说了 etcd、Redis 和 ZooKeeper 的自定义控制器的开发工做。咱们期待更多相似案例不断涌现。
ETCD 和 Prometheus 的 Operator 来自 CoreOS,演示了一种超出 StatefulSet 能力的自动化和集成能力。另外一方面,使用 Deployment 和 StatefulSet 这样的通用控制器可以用同一种对象管理大量有状态应用。Kubernetes 用户有了运行有状态应用的能力,并且能够自行在两种方式之中进行选择。
转自https://www.kubernetes.org.cn/1130.html