kubernetes中有状态应用的优雅缩容

将有状态的应用程序部署到Kubernetes是棘手的。 StatefulSet使它变得容易得多,可是它们仍然不能解决全部问题。最大的挑战之一是如何缩小StatefulSet而不将数据留在断开链接的PersistentVolume成为孤立对象上。在这篇博客中,我将描述该问题和两种可能的解决方案。git

经过StatefulSet建立的每一个Pod都有本身的PersistentVolumeClaim(PVC)和PersistentVolume(PV)。当按一个副本按比例缩小StatefulSet的大小时,其Pod之一将终止,但关联的PersistentVolumeClaim和绑定到其的PersistentVolume保持不变。在随后扩大规模时,它们会从新链接到Pod。github

imgScaling a StatefulSetapi

如今,想象一下使用StatefulSet部署一个有状态的应用程序,其数据在其pod中进行分区。每一个实例仅保存和处理一部分数据。当您缩小有状态应用的规模时,其中一个实例将终止,其数据应从新分配到其他的Pod。若是您不从新分配数据,则在再次进行扩展以前,它仍然不可访问。app

imgRedistributing data on scale-downide

在正常关机期间从新分发数据

您可能会想:“既然Kubernetes支持Pod正常关闭的机制,那么Pod是否能够在关闭过程当中简单地将其数据从新分配给其余实例呢?”事实上,它不能。为何不这样作有两个缘由:post

  • Pod(或更确切地说,其容器)可能会收到除缩容之外的其余缘由的终止信号。容器中运行的应用程序不知道为何终止该程序,所以不知道是否要清空数据。
  • 即便该应用程序能够区分是缩容仍是因为其余缘由而终止,它也须要保证即便通过数小时或数天也能够完成关闭程序。 Kubernetes不提供该保证。若是应用程序进程在关闭过程当中死掉,它将不会从新启动,所以也就没有机会彻底分发数据。

所以,相信在正常关闭期间Pod可以从新分发(或以其余方式处理其全部数据)并非一个好主意,而且会致使系统很是脆弱。测试

使用 tear-down 容器?

若是您不是Kubernetes的新手,那么你极可能知道什么是初始化容器。它们在容器的主要容器以前运行,而且必须在主要容器启动以前所有完成。code

若是咱们有tear-down容器(相似于init容器),可是在Pod的主容器终止后又会运行,该怎么办?他们能够在咱们的有状态Pod中执行数据从新分发吗?对象

img

假设tear-down容器可以肯定Pod是否因为缩容而终止。并假设Kubernetes(更具体地说是Kubelet)将确保tear-down容器成功完成(经过在每次返回非零退出代码时从新启动它)。若是这两个假设都成立,咱们将拥有一种机制,可确保有状态的容器始终可以按比例缩小规模从新分配其数据。blog

可是?

可悲的是,当tear-down容器自己发生瞬态错误,而且一次或屡次从新启动容器最终使它成功完成时,像上述的tear-down容器机制将只处理那些状况。可是,在tear-down过程当中托管Pod的集群节点死掉的那些不幸时刻又如何呢?显然,该过程没法完成,所以没法访问数据。

如今很明显,咱们不该该在Pod关闭时执行数据从新分配。相反,咱们应该建立一个新的Pod(可能安排在一个彻底不一样的集群节点上)以执行从新分发过程。

这为咱们带来了如下解决方案:

缩小StatefulSet时,必须建立一个新的容器并将其绑定到孤立的PersistentVolumeClaim。咱们称其为“drain pod”,由于它的工做是将数据从新分发到其余地方(或以其余方式处理)。Pod必须有权访问孤立的数据,而且可使用它作任何想作的事情。因为每一个应用程序的从新分发程序差别很大,所以新的容器应该是彻底可配置的-用户应该可以在drain Pod内运行他们想要的任何容器。

StatefulSet Drain Controller

因为StatefulSet控制器当前尚不提供此功能,所以咱们能够实现一个额外的控制器,其惟一目的是处理StatefulSet缩容。我最近实现了这种控制器的概念验证。您能够在GitHub上找到源代码:

luksa/statefulset-scaledown-controllergithub.com图标

下面咱们解释一下它是如何工做的。

在将控制器部署到Kubernetes集群后,您只需在StatefulSet清单中添加注释,便可将drain容器模板添加到任何StatefulSet中。这是一个例子:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: datastore
  annotations:
    statefulsets.kubernetes.io/drainer-pod-template: |
      {
        "metadata": {
          "labels": {
            "app": "datastore-drainer"
          }
        },
        "spec": {
          "containers": [
            {
              "name": "drainer",
              "image": "my-drain-container",
              "volumeMounts": [
                {
                  "name": "data",
                  "mountPath": "/var/data"
                }
              ]
            }
          ]
        }
      }
spec:
  ...

该模板与StatefulSet中的主要Pod模板没有太大区别,只不过它是经过注释定义的。您能够像日常同样部署和扩展StatefulSet。

当控制器检测到按比例缩小了StatefulSet时,它将根据指定的模板建立新的drain容器,并确保将其绑定到PersistentVolumeClaim,该PersistentVolumeClaim先前已绑定至因按比例缩小而删除的有状态容器。

Drain容器得到与已删除的有状态容器相同的身份(即名称和主机名)。这样作有两个缘由:

  • 一些有状态的应用程序须要稳定的身份-这也可能在数据从新分发过程当中适用。
  • 若是在执行drain过程时再次扩容StatefulSet,则这将阻止StatefulSet控制器建立重复的容器并将其附加到同一PVC。

若是drain pod或其主机节点崩溃,则drain pod将从新安排到另外一个节点上,在该节点上能够重试/恢复其操做。Drain pod完成后, Pod和PVC将被删除。备份StatefulSet时,将建立一个新的PVC。

示例

首先部署drain控制器:

$ kubectl apply -f https://raw.githubusercontent.com/luksa/statefulset-drain-controller/master/artifacts/cluster-scoped.yaml

接着部署示例StatefulSet:

$ kubectl apply -f https://raw.githubusercontent.com/luksa/statefulset-drain-controller/master/example/statefulset.yaml

这将运行三个有状态的Pod。将StatefulSet缩小为两个时,您会看到其中一个Pod开始终止。而后,删除Pod后,drain控制器将当即建立一个具备相同名称的新drain Pod:

$ kubectl scale statefulset datastore --replicas 2
statefulset.apps/datastore scaled
$ kubectl get po
NAME          READY     STATUS        RESTARTS   AGE
datastore-0   1/1       Running       0          3m
datastore-1   1/1       Running       0          2m
datastore-2   1/1       Terminating   0          49s
$ kubectl get po
NAME          READY     STATUS    RESTARTS   AGE
datastore-0   1/1       Running   0          3m
datastore-1   1/1       Running   0          3m
datastore-2   1/1       Running   0          5s    <-- the drain pod

当drain pod 完成其工做时,控制器将其删除并删除PVC:

$ kubectl get po
NAME          READY     STATUS    RESTARTS   AGE
datastore-0   1/1       Running   0          3m
datastore-1   1/1       Running   0          3m
$ kubectl get pvc
NAME               STATUS    VOLUME             CAPACITY   ...
data-datastore-0   Bound     pvc-57224b8f-...   1Mi        ...
data-datastore-1   Bound     pvc-5acaf078-...   1Mi        ...

控制器的另外一个好处是它能够释放PersistentVolume,由于它再也不受PersistentVolumeClaim约束。若是您的集群在云环境中运行,则能够下降存储成本。

总结

请记住,这仅是概念验证。要成为StatefulSet缩容问题的正确解决方案,须要进行大量工做和测试。理想状况下,Kubernetes StatefulSet控制器自己将支持这样的运行drain容器,而不是须要一个与原始控制器竞争的附加控制器(当您缩容并当即再次扩容时)。

经过将此功能直接集成到Kubernetes中,能够在StatefulSet规范中用常规字段替换注释,所以它将具备模板,volumeClaimTemplatesrainePodTemplate,与使用注释相比,一切都变得更好了。

相关文章
相关标签/搜索