万字长文 | 在 Kubernetes 中部署高可用应用程序的最佳实践!

ccbec00d83cc52af1fc7d973386e37a0.gif

新钛云服已为您服务1303

769f90d81d0736c3ef1997a144250553.gif

真正的生产型应用会涉及多个容器。这些容器必须跨多个服务器主机进行部署。容器安全性(https://www.redhat.com/zh/topics/security/container-security)需要多层部署,因此可能会比较复杂。但 Kubernetes 有助于解决这一问题。

Kubernetes 可以提供所需的编排和管理功能,以便您针对这些工作负载大规模部署容器。借助 Kubernetes 编排功能,您可以构建跨多个容器的应用服务、跨集群调度、扩展这些容器,并长期持续管理这些容器的健康状况。有了 Kubernetes,您便可切实采取一些措施来提高 IT 安全性。

高可用性(High Availability,HA)是指应用系统无中断运行的能力,通常可通过提高该系统的容错能力来实现。一般情况下,通过设置 replicas 给应用创建多个副本,可以适当提高应用容错能力,但这并不意味着应用就此实现高可用性。

7c5f6ad2760758ec65abf33ed83cb623.png

众所周知,在Kubernetes环境中部署一个可用的应用程序是一件轻而易举的事。但另外一方面,如果要部署一个可容错,高可靠且易用的应用程序则不可避免地会遇到很多问题。在本文中,我们将讨论在Kubernetes中部署高可用应用程序并以简洁的方式给大家展示,本文也会重点介绍在Kubernetes部署高可用应用所需要注意的故则以及相应的方案。

请注意,我们不会使用任何现有的部署方案。我们也不会固定特定的CD解决方案,我们将省略模板生成 Kubernetes 清单的问题。在本文中,我们只讨论部署到集群时 Kubernetes 清单的最终内容,其它的部分本文不作过多的讨论。


一、副本数量

通常情况下,至少需要两个副本才能保证应用程序的最低高可用。但是,您可能会问,为什么单个副本无法做到高可用?问题在于 Kubernetes 中的许多实体(Node、Pod、ReplicaSet ,Deployment等)都是非持久化的,即在某些条件下,它们可能会被自动删除或者重新被创建。因此,Kubernetes 集群本身以及在其中运行的应用服务都必须要考虑到这一点。

例如,当使用autoscaler服务缩减您的节点数量时,其中一些节点将会被删除,包括在该节点上运行的 Pod。如果您的应用只有一个实例且在运行删除的节点上,此时,您可能会发现您的应用会变的不可用,尽管这时间通常是比较短的,因为对应的Pod会在新的节点上重新被创建。

一般来说,如果你只有一个应用程序副本,它的任何异常停服都会导致停机。换句话说,您必须为正在运行的应用程序保持至少两个副本,从而防止单点故障。

副本越多,在某些副本发生故障的情况下,您的应用程序的计算能力下降的幅度也就越小。例如,假设您有两个副本,其中一个由于节点上的网络问题而失败。应用程序可以处理的负载将减半(只有两个副本中的一个可用)。当然,新的副本会被调度到新的节点上,应用的负载能力会完全恢复。但在那之前,增加负载可能会导致服务中断,这就是为什么您必须保留一些副本。

上述建议与未使用 HorizontalPodAutoscaler 的情况相关。对于有多个副本的应用程序,最好的替代方法是配置 HorizontalPodAutoscaler 并让它管理副本的数量。本文的最后会详细描述HorizontalPodAutoscaler。


二、更新策略

Deployment 的默认更新策略需要减少旧+新的 ReplicaSet Pod 的数量,其Ready状态为更新前数量的 75%。因此,在更新过程中,应用程序的计算能力可能会下降到正常水平的 75%,这可能会导致部分故障(应用程序性能下降)。

strategy.RollingUpdate.maxUnavailable参数允许您配置更新期间可能变得不可用的Pod的最大百分比。因此,要么确保您的应用程序在 25% 的Pod不可用的情况下也能顺利运行,要么降低该maxUnavailable参数。请注意,该maxUnavailable参数已四舍五入。

默认更新策略 ( RollingUpdate)有一个小技巧:应用程序在滚动更新过程中,多副本策略依然有效,新旧版本会同时存在,一直到所有的应用都更新完毕。但是,如果由于某种原因无法并行运行不同的副本和不同版本的应用程序,那么您可以使用strategy.type: Recreate参数. 在Recreate策略下,所有现有Pod在创建新Pod之前都会被杀死。这会导致短暂的停机时间。

其他部署策略(蓝绿、金丝雀等)通常可以为 RollingUpdate 策略提供更好的替代方案。但是,我们没有在本文中考虑它们,因为它们的实现取决于用于部署应用程序的软件。


三、跨节点的统一副本分布

Kubernetes 的设计理念为假设节点不可靠,节点越多,发生软硬件故障导致节点不可用的几率就越高。所以我们通常需要给应用部署多个副本,并根据实际情况调整 replicas 的值。该值如果为1 ,就必然存在单点故障。该值如果大于1但所有副本都调度到同一个节点,仍将无法避免单点故障。

为了避免单点故障,我们需要有合理的副本数量,还需要让不同副本调度到不同的节点。为此,您可以指示调度程序避免在同一节点上启动同一 Deployment 的多个 Pod:

affinity:
        PodAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - PodAffinityTerm:
              labelSelector:
                matchLabels:
                  app: testapp
              topologyKey: kubernetes.io/hostname

最好使用preferredDuringSchedulingaffinity而不是requiredDuringScheduling. 如果新Pod所需的节点数大于可用节点数,后者可能会导致无法启动新 Pod。尽管如此,requiredDuringScheduling当提前知道节点和应用程序副本的数量并且您需要确保两个Pod不会在同一个节点上结束时,亲和性也就会派上用场。


四、优先级

priorityClassName代表您的Pod优先级。调度器使用它来决定首先调度哪些 Pod,如果节点上没有剩余Pod空间,应该首先驱逐哪些 Pod。

您将需要添加多个PriorityClass(https://kubernetes.io/docs/concepts/configuration/Pod-priority-preemption/#priorityclass)类型资源并使用priorityClassName. 以下是如何PriorityClasses变化的示例:

  • Cluster. Priority > 10000:集群关键组件,例如 kube-apiserver。

  • Daemonsets. Priority: 10000:通常不建议将 DaemonSet Pods 从集群节点中驱逐,并替换为普通应用程序。

  • Production-high. Priority: 9000:有状态的应用程序。

  • Production-medium. Priority: 8000:无状态应用程序。

  • Production-low. Priority: 7000:不太重要的应用程序。

  • Default. Priority: 0:非生产应用程序。

设置优先级将帮助您避免关键组件的突然被驱逐。此外,如果缺乏节点资源,关键应用程序将驱逐不太重要的应用程序。



五、停止容器中的进程

STOPSIGNAL中指定的信号(通常为TERM信号)被发送到进程以停止它。但是,某些应用程序无法正确处理它并且无法正常停服。对于在 Kubernetes 中运行的应用程序也是如此。

例如下面描述,为了正确关闭 nginx,你需要一个preStop这样的钩子:

lifecycle:
  preStop:
    exec:
      command:
      - /bin/sh
      - -ec
      - |
        sleep 3
        nginx -s quit

上述的简要说明:

  1. sleep 3 可防止因删除端点而导致的竞争状况。

  2. nginx -s quit正确关闭nginx。镜像中不需要配置此行,因为 STOPSIGNAL: SIGQUIT参数默认设置在镜像中。

STOPSIGNAL的处理方式依赖于应用程序本身。实际上,对于大多数应用程序,您必须通过谷歌搜索或者其它途径来获取处理STOPSIGNAL的方式。如果信号处理不当,preStop钩子可以帮助您解决问题。另一种选择是用应用程序能够正确处理的信号(并允许它正常关闭)从而来替换STOPSIGNAL

terminationGracePeriodSeconds是关闭应用程序的另一个重要参数。它指定应用程序正常关闭的时间段。如果应用程序未在此时间范围内终止(默认为 30 秒),它将收到一个KILL信号。因此,如果您认为运行preStop钩子和/或关闭应用程序STOPSIGNAL可能需要超过 30 秒,您将需要增加 terminateGracePeriodSeconds 参数。例如,如果来自 Web 服务客户端的某些请求需要很长时间才能完成(比如涉及下载大文件的请求),您可能需要增加它。

值得注意的是,preStop hook 有一个锁定机制,即只有在preStop hook 运行完毕后才能发送STOPSIGNAL信号。同时,在preStop钩子执行期间,terminationGracePeriodSeconds倒计时继续进行。所有由钩子引起的进程以及容器中运行的进程都将在TerminationSeconds结束后被终止。

此外,某些应用程序具有特定设置,用于设置应用程序必须终止的截止日期(例如,在Sidekiq 中的--timeout 选项)。因此,您必须确保如果应用程序有此配置,则它的值略应该低于terminationGracePeriodSeconds



六、预留资源

调度器使用 Pod的resources.requests来决定将Pod调度在哪个节点上。例如,无法在没有足够空闲(即非请求)资源来满足Pod资源请求的节点上调度Pod。另一方面,resources.limits允许您限制严重超过其各自请求的Pod的资源消耗。

一个很好的方式是设置limits等于 requests。将limits设置为远高于requests可能会导致某些节点的Pod无法获取请求的资源的情况。这可能会导致节点(甚至节点本身)上的其他应用程序出现故障。。Kubernetes 根据其资源方案为每个Pod分配一个 QoS 类。然后,K8s 使用 QoS 类来决定应该从节点中驱逐哪些 Pod

因此,您必须同时为 CPU 和内存设置requests 和 limits。如果Linux 内核版本早于 5.4(https://engineering.indeedblog.com/blog/2019/12/cpu-throttling-regression-fix/)。在某些情况下,您唯一可以省略的是 CPU 限制(在 EL7/CentOS7 的情况下࿰

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值