zoukankan      html  css  js  c++  java
  • kubernetes中有状态应用的优雅缩容

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

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

    imgScaling a StatefulSet

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

    imgRedistributing data on scale-down

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

    您可能会想:“既然Kubernetes支持Pod正常关闭的机制,那么Pod是否可以在关闭过程中简单地将其数据重新分配给其他实例呢?”事实上,它不能。为什么不这样做有两个原因:

    • Pod(或更确切地说,其容器)可能会收到除缩容以外的其他原因的终止信号。容器中运行的应用程序不知道为什么终止该程序,因此不知道是否要清空数据。
    • 即使该应用程序可以区分是缩容还是由于其他原因而终止,它也需要保证即使经过数小时或数天也可以完成关闭程序。 Kubernetes不提供该保证。如果应用程序进程在关闭过程中死掉,它将不会重新启动,因此也就没有机会完全分发数据。

    因此,相信在正常关闭期间Pod能够重新分发(或以其他方式处理其所有数据)并不是一个好主意,并且会导致系统非常脆弱。

    使用 tear-down 容器?

    如果您不是Kubernetes的新手,那么你很可能知道什么是初始化容器。它们在容器的主要容器之前运行,并且必须在主要容器启动之前全部完成。

    如果我们有tear-down容器(类似于init容器),但是在Pod的主容器终止后又会运行,该怎么办?他们可以在我们的有状态Pod中执行数据重新分发吗?

    img

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

    但是?

    可悲的是,当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,与使用注释相比,一切都变得更好了。

  • 相关阅读:
    如何在网页上显示html代码?
    3s自动跳转到登陆界面
    数据交互 ajax代码整理
    45种Javascript技巧大全【转藏】
    react环境搭建
    overflow 那些我们忽略的特点
    CSS3 动画效果合集
    2016年上半年前端资源汇总
    math.js 使用...
    php Apache No input file
  • 原文地址:https://www.cnblogs.com/zisefeizhu/p/14218343.html
Copyright © 2011-2022 走看看