有状态集:在Kubernetes中轻松运行和扩展有状态应用程序

编者注:这篇文章是 系列深入文章 Kubernetes 1.5的新功能

在最新版本中, Kubernetes 1.5,我们已将以前称为PetSet的功能移至Beta中, 有状态集。除了社区选择的名称外,API对象没有重大更改,但是我们添加了“每个索引最多一个pod”的语义,用于在集合中部署Pod。除了有序部署,有序终止,唯一的网络名称和持久稳定的存储,我们认为我们有正确的原语来支持许多容器化的有状态工作负载。我们并不声称该功能是100%完整的(毕竟它是软件),但是我们认为该功能以其当前形式很有用,并且随着我们朝着一个新的方向发展,我们可以向后兼容的方式扩展该API。最终GA发布。

什么时候StatefulSet是我的存储应用程序的正确选择?

部署副本集 是在Kubernetes上运行应用程序的无状态副本的一种好方法,但是它们的语义对于部署有状态的应用程序并不正确。 有状态集的目的是为控制器提供用于部署各种有状态工作负载的正确语义。但是,将存储应用程序移至Kubernetes并非总是正确的选择。在继续融合存储层和业务流程框架之前,您应该问自己几个问题。

您的应用程序可以使用远程存储运行还是需要本地存储介质?

当前,我们建议将StatefulSets与远程存储一起使用。因此,您必须准备好容忍网络连接存储对性能的影响。即使使用存储优化实例,您也可能无法实现与本地连接的固态存储介质相同的性能。云上网络附加存储的性能是否使您的存储应用程序能够满足其SLA?如果是这样,从自动化的角度来看,在StatefulSet中运行应用程序将提供引人注目的好处。如果在其上运行存储应用程序的节点发生故障,则可以将包含该应用程序的Pod重新安排到另一个节点上,并且由于它正在使用网络连接的存储介质,因此重新安排之后,其数据仍然可用。

您是否需要扩展存储应用程序?

您希望通过在StatefulSet中运行应用程序而获得的好处是什么?您是否为整个组织拥有一个存储应用程序实例?扩展存储应用程序确实是您遇到的问题吗?如果您有几个存储应用程序实例,并且它们能够成功满足组织的需求,并且这些需求并没有迅速增加,那么您已经处于最佳状态。

但是,如果您拥有微服务生态系统,或者经常删除包括存储应用程序在内的新服务资源,则可能会受益于自动化和整合。如果您已经在使用Kubernetes来管理生态系统的无状态层,则应考虑使用相同的基础架构来管理存储应用程序。

可预测的绩效有多重要?

Kubernetes尚不支持跨容器隔离网络或存储I / O。将存储应用程序与嘈杂的邻居并置在一起可以降低应用程序可以处理的QPS。您可以通过将包含存储应用程序的Pod调度为节点上的唯一租户(从而为其提供专用计算机)或使用Pod反关联性规则来分隔争用网络或磁盘的Pod,来缓解这种情况。必须积极识别和缓解热点。

如果不是要从存储应用程序中挤出绝对最大QPS,而是您愿意并能够减轻热点以确保存储应用程序满足其SLA,以及是否容易获得新的“足迹”(服务),或服务集合),扩展它们以及灵活地重新分配资源是您的首要考虑,Kubernetes和StatefulSet可能是解决此问题的正确解决方案。

您的应用程序是否需要专用的硬件或实例类型?

如果您在高端硬件或超大型实例大小上运行存储应用程序,而其他工作负载在商品硬件或较小,较便宜的映像上运行,则可能不希望部署异构集群。如果您可以针对所有类型的应用程序在单个实例大小上实现标准化,那么您可以从Kubernetes获得的灵活资源重新分配和整合中受益。

一个实际的例子-ZooKeeper

动物园管理员 有状态集是一个有趣的用例,有两个原因。首先,它证明了StatefulSet可用于在Kubernetes上运行分布式,高度一致的存储应用程序。其次,这是运行诸如 阿帕奇Hadoop阿帕奇·卡克法 在Kubernetes上一个 深入的教程 Kubernetes文档中提供了有关在Kubernetes上部署ZooKeeper集成的信息,我们将在下面概述一些关键功能。

创建一个ZooKeeper合奏
创建合奏就像使用一样简单 kubectl创建 生成存储在清单中的对象。

$ kubectl创建 -f [http://k8s.io/docs/tutorials/stateful-application/zookeeper.yaml](//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处于运行就绪状态。

$ 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的主机名,您可以看到Pods的主机名也包含Pods的序号。

$ for i in 0 1 2; do kubectl exec zk-$i -- hostname; done

zk-0

zk-1

zk-2

动物园管理员将每个服务器的唯一标识符存储在一个名为“ myid”的文件中。 动物园管理员服务器使用的标识符只是自然数。对于合奏中的服务器,将“ 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 Service控制的网络域,具有唯一的网络地址。

$  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服务器的配置文件。

$  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的服务器,这些服务器可以通过一个稳定的网络地址找到彼此,并且由于集合具有一致的成员身份,因此它们可以执行领导者选举和复制写入。

验证集成是否正常工作的最简单方法是将值写入一台服务器,然后从另一台服务器读取它。您可以使用ZooKeeper发行版随附的“ zkCli.sh”脚本来创建包含某些数据的ZNode。

$  kubectl exec zk-0 zkCli.sh create /hello world

...


WATCHER::


WatchedEvent state:SyncConnected type:None path:null

Created /hello

您可以使用同一脚本从集合中的另一台服务器读取数据。

$  kubectl exec zk-1 zkCli.sh get /hello

...


WATCHER::


WatchedEvent state:SyncConnected type:None path:null

world

...

您可以通过删除zk 有状态集来降低整体效果。

$  kubectl delete statefulset zk

statefulset "zk" deleted

相对于Pod顺序的相反顺序,级联删除操作会破坏StatefulSet中的每个Pod,并且它会等待每个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适用 重新创建zk 有状态集并重新部署集合。

$  kubectl适用 -f [http://k8s.io/docs/tutorials/stateful-application/zookeeper.yaml](//raw.githubusercontent.com/kubernetes/kubernetes.github.io/master/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

...

有状态集确保即使将StatefulSet中的所有Pod销毁后,也可以对其进行重新安排时,ZooKeeper合奏可以选举新的领导者并继续为请求服务。

容忍节点故障

动物园管理员将其状态机复制到集合中的其他服务器,其明确目的是容忍节点故障。默认情况下,Kubernetes Scheduler可以将zk 有状态集中的多个Pod部署到同一节点。如果将zk-0和zk-1 Pod部署在同一节点上,并且该节点发生故障,则ZooKeeper集成无法形成仲裁来提交写入,并且ZooKeeper服务将遇到中断,直到其中一个Pod成为可能。重新安排。

您应该始终为集群中的关键进程提供净空容量,在这种情况下,如果这样做,Kubernetes Scheduler将在另一个节点上重新调度Pod,并且中断将很短暂。

如果您的服务的SLA甚至避免了由于单个节点故障而造成的短暂中断,则应使用 PodAntiAffinity 注解。用于创建集成的清单包含这样的注释,它告诉Kubernetes Scheduler在同一节点上放置zk 有状态集中的一个Pod不得超过一个。

容许计划维护

用于创建ZooKeeper合奏的清单也创建了一个 PodDistruption预算,zk预算。 zk预算会通知Kubernetes服务可以承受的中断上限(不健康的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表示该合奏必须始终有至少两个成员可用,以使该合奏保持健康。如果尝试在使节点脱机之前耗尽节点,并且耗尽节点将终止违反预算的Pod,则耗尽操作将失败。如果您使用 kubectl排水与PodDisruptionBudgets结合使用,以在维护或退役之前束缚节点并驱逐所有Pod,可以确保该过程不会破坏您的有状态应用程序。

期待

随着Kubernetes开发面向通用航空,我们正在寻找用户的一长串建议。如果您想了解我们的积压订单,请查看 的GitHub问题与状态标签。但是,由于很难理解生成的API,因此我们并不希望实现所有这些功能请求。一些功能请求(例如支持滚动更新,与节点升级更好的集成以及使用快速本地存储)将使大多数类型的有状态应用程序受益,我们希望优先考虑这些功能。 有状态集的目的是能够很好地运行大量应用程序,而不能完美地运行所有应用程序。考虑到这一点,我们避免以依赖隐藏机制或不可访问功能的方式实现StatefulSets。任何人都可以编写类似于StatefulSets的控制器。我们称其为“使其可插拔”。

明年,我们预计许多流行的存储应用程序都将拥有自己的社区支持的专用控制器或“经营者“。我们已经听说过有关etcd,Redis和ZooKeeper的自定义控制器的工作。我们希望自己编写更多内容,并支持社区开发其他内容。

的运营商 普罗米修斯 来自CoreOS的Java演示了一种在Kubernetes上运行有状态应用程序的方法,该方法提供了超出StatefulSet单独级别的自动化和集成级别。另一方面,使用诸如StatefulSet或Deployment这样的通用控制器意味着可以通过了解单个配置对象来管理各种应用程序。我们认为Kubernetes用户会喜欢这两种方法的选择。

-肯尼思·欧文斯(Kenneth Owens)和埃里克·图恩(Eric Tune),Google软件工程师