微服务部署的六种策略

现在你已经了解了如何将服务部署到生产环境,以及如何验证这些服务是否工作正常(如果不正常,则重新启动它们),现在我们需要来决定如何删除旧版本的服务,并使用新版本来替换它们。

这个问题在持续交付之前不存在。在手动部署服务时,企业会选择活动用户最少的时间,例如通常是周末或者午夜,并且告诉大家需要一个足够的时间窗口来维护系统。在这个窗口期间,运维团队会停止旧的版本,部署新的版本,并检查一切是否恢复正常。

但是现在,由于我们不断地将新版本部署到生产环境中,不能简单地认为部署就意味着停机,因为这样系统会一直有某个部分是处于停机状态的。你需要考虑新的部署策略,包括在部署期间可以容忍对系统有多大的影响,以及你愿意投入多少资源来控制这种影响。

本节介绍了实现该目标的6种策略。每个策略背后的理念都如其名所示:单目标部署、一次性全部部署、最小服务部署、滚动部署、蓝/绿部署和金丝雀部署。为了更好地介绍和对比它们,我们先介绍一组共用的术语。

期望的实例数量

这是当服务功能完全正常时,预计将运行的服务副本数量。如果将该数字设为n,那么表示在任何一次部署中,你需要将n个旧版本的服务实例,变为n个新版本的服务实例。我们将它简称为desired。

最小的健康实例数量

当删除旧的服务实例并且启动新服务实例的时候,你可能希望至少有一些实例是处于健康状态的,无论它们是新服务实例还是旧服务实例。这样做可以保证最低限度地提供服务。我们将其简称为minimum,根据平台不同,通常会用总数的百分比或者绝对数来表示。

最大的实例数量

有时你可能希望在删除旧服务实例之前,先启动一些新的服务实例,以便控制服务部署过程中的停机时间。这意味着需要更多的资源。通过限制最大的实例数量,你同时也限制了部署期间使用的最大资源。我们将其简称为maximum。同样,根据平台不同,它也可以用一个百分比(表示允许使用多少额外的实例。例如,如果最大值设置为100%,则意味着允许部署期间的实例数量加倍),或者绝对数量(表示允许平台创建多少个额外的实例)来表示。

图表展示

对于每个策略,我们通过一个图表来表示部署过程中的事件连续性。浅色方块表示旧版本的服务实例,而黑色方块表示新版本的实例,条纹方块表示正在启动、尚不可用的新版本实例。每一行都表示某个时间点上的快照,其中较旧的快照位于顶部,较新的快照位于底部。

大多数编排平台都提供了设置这些值的方法。对于Kubernetes来说,你可以在服务定义中添加一个额外的策略项,其中replicas表示的是desired的值,maxUnavailable表示与minimum相反的值(minimum=100%- maxUnavailable), maxSurge表示maximum的值:

    spec:

      replicas: 5

      type: RollingUpdate

        rollingUpdate:

          maxUnavailable: 25%

          maxSurge: 25%

      [...]

在Amazon ECS中,你可以在创建或者更新一个服务的时候,通过任务定义指定这些参数:

    # Creation

    aws ecs create-service \

        --desired-count 5 \

        --deployment-configuration \

            'maximumPercent=25, minimumHealthyPercent=25' \

        # other parameters

    # Update

    aws ecs update-service \

        --desired-count 5 \

        --deployment-configuration \

            'maximumPercent=25, minimumHealthyPercent=25' \

        # other parameters

无论使用什么平台,一旦知道了如何设置这些部署参数,你就可以根据需求来选择合适的策略,以下让我们来了解它们。

一、单目标部署

这是最简单的策略,也是需要资源最少的策略。在这种策略下,你可以假设服务只有一个正在运行的实例,无论何时都必须先停止它,然后再部署新的实例。这意味着服务会存在中断,但是不需要额外的资源。因此,定义该策略的值如下所示。

· desired: 1

· minimum: 0%

· maximum: 0%

图10-9展示了该策略的实施步骤。

· 开始:存在一个旧版本的实例。

· 步骤1:该实例已经被另一个新版本的实例替换,在新实例启动前,服务实际上不可用。

· 结束:新实例已经启动并且正在运行,可以开始接收外部请求。

图10-9:单目标部署:停止旧实例,替换为新的实例

二、一次性全部部署

该策略类似于单目标部署策略,唯一的区别是,你可以拥有任意固定数量的实例,而不是只有一个实例。部署时当前所有实例都会被关闭,等它们停止运行后,启动所有新的实例。与之前的情况一样,这种策略升级期间不需要额外的资源,但是也存在服务中断。该策略的配置参数如下所示。

· desired: n

· minimum: 0%

· maximum: 0%

图10-10展示了该策略的实施步骤。

· 开始:存在5个旧版本的实例。

· 步骤1:同时停止所有5个实例,替换为5个新版本的实例,在新实例启动之前,该服务实际上不可用。

· 结束:5个新实例现在已经启动并且正在运行。

图10-10:一次性全部部署。同时停止所有旧的实例,统一替换为新的实例

三、最小服务部署

前面这两个策略的主要问题在于,它们都会中断服务。你可以调整策略来改善这一点,比如确保始终存在着一部分健康的服务实例。这样就不需要关闭所有的旧实例,而只需关闭其中的一部分,等它们完全停止后再创建新的实例。一旦新的实例启动并运行,就可以关闭另一部分旧实例,再替换为新实例。可以不断重复这个过程,直到所有的旧实例都被新的实例所替换。

这种方式可以在不需要额外资源的情况下避免服务中断,但是这些存活的实例必须能够承接住额外的流量,因为毕竟实例的数量变少了。不能将这个阈值设置得太低,否则剩余的实例可能无法承担过多的流量。

对于图10-11所示的情况,配置参数如下所示。

· desired: 5

· minimum: 40%(或2,如果用绝对数字表示的话)

· maximum: 0%

整个过程如下所示。

· 开始:存在5个旧版本的实例。

· 步骤1:由于至少需要保留2个实例可以一直提供服务,所以只能停止其他3个实例并将它们替换成新版本。在新的实例启动时,部署仍在进行中。

· 步骤2:新实例运行后,可以停止之前的2个旧实例,并将它们替换为新的版本。

· 结束:所有的新实例现在都处于运行中。

图10-11:最小服务部署。在任意时刻,不管是新版本还是旧版本,至少有2个实例可以提供服务

四、滚动部署

你可以将滚动部署看作最小服务部署的另一种形式,不过它的重点不在于健康实例的最小数量,而在于停止实例的最大数量。滚动部署最典型的情况是将该最大值设置为1,意味着在任意时间只有一个实例处于更新过程中。即停止一个旧的实例,然后更新并启动一个新的实例,只有当新的实例正常运行后,才能继续停止下一个实例。在某些情况下,滚动部署可以提升最大值,同时允许两个、三个或者更多的实例进行更新。

作为最小服务部署的另一种形式,滚动部署具有与其几乎相同的特点,它可以在不需要额外资源的情况下避免服务中断。与最小服务部署相比,它的主要优点在于,通过限制同时停止实例的数量,你可以控制需要保留多少实例来承接额外的负载,它的缺点是部署需要更长的时间,并且根据实例的数量、启动时间和重新部署的速度,可能需要分多次操作来部署实例。

对于滚动部署的策略,定义参数如下所示。

· desired: 5

· minimum: 80%(或者4,如果用绝对数字表示的话)

· maximum: 0%

注意,因为滚动部署相当于最小服务部署的另一种形式,所以minimum = desired -1。滚动部署的步骤如图10-12所示。

· 开始:存在5个旧版本的实例。

· 步骤1:停止其中一个实例并将它替换为新的实例,在新实例启动时,部署仍处于进行中。

· 步骤2:当步骤1中创建的新实例运行后,停止另一个旧实例,将其也替换为新的实例,在新的实例启动时,部署仍处于进行中。

· 步骤3、4和5:对其余旧实例重复相同的过程。

· 结束:所有的新实例现在都可正常运行。

图10-12:滚动部署。其中同时停止实例的最大数量为1

五、蓝/绿部署

蓝/绿部署是微服务领域中最受欢迎的一种部署策略,也许出于这个原因,它的含义并不明确。一般来说,它指的是两个同名但略有不同的策略,其中每个策略都用来解决一个问题。

蓝/绿部署出现的目的,是为了解决最小服务和滚动部署的一个缺点,即在升级期间,承担总负载的健康实例数量会减少,可能会带来额外的压力。为了解决这个问题,蓝/绿部署首先创建一些新的实例,只有当启动这些实例后才开始停止旧的实例。健康实例的数量永远不会低于需要的实例总数,但是这意味着在升级期间需要额外的资源。考虑到标准化参数,蓝/绿部署的配置如下所示。

· desired: n

· minimum: 100%

· maximum: m% (0 < m ≤ 100)

将m设置得更高会加剧资源的使用峰值,但也会缩短部署的时间。

不过,蓝/绿部署的第二个版本超出了这个范围,不能简单地通过组合desired、minimum、maximum这几个参数来完成。之前的策略中还有另外一个缺点,即在部署期间,生产环境中会混合新旧两个版本的应用程序。在大多数情况下,如果我们小心地发布功能(请参阅本章后面的“发布功能”一节),这可能没什么问题。因此,为了避免这种版本混合的情况,蓝/绿部署的第二个版本增加了一个条件,即只有当所有的新实例都准备好的时候,用户才能够访问新版本的服务,同时所有的旧实例立即变为不可用。

为了实现这一目标,你需要控制请求路由和服务编排,图10-13展示了其中的各个步骤。

· 开始:存在许多旧版本的实例(在本示例中为两个)。传入的请求通过负载均衡器/路由器(在图中用圆筒表示)被发送到旧版本的服务。

· 步骤1:创建多个新实例,这些实例不可访问,负载均衡器/路由器仍将所有传入的请求发送到旧的实例。在新实例启动时,部署仍在进行中。

· 步骤2:新实例启动完毕并且正常运行,但是尚未向它们发送任何请求。

· 步骤3:重新配置负载均衡器/路由器,将所有接收的请求转发到新版本的服务。这个开关几乎是瞬间完成的。此时,除已有请求外,没有新请求再被发送到旧版本的服务。

· 结束:旧实例不再有用后,将它们停止。

由此我们可以知道,蓝/绿部署的第二版提供了最佳的用户体验,但是代价是增加了复杂性,以及占用了更多资源。

图10-13:蓝/绿部署。在所有新实例准备好之前,用户无法访问新实例。在新实例准备好之后,路

由会改为指向新实例,使得用户无法再访问旧实例

六、金丝雀部署

金丝雀部署是另一种无法通过组合desired/minimum/maximum参数实现的策略。金丝雀部署允许尝试新版本的服务,但不完全承诺切换到新版本。这样,你只需在原来旧版本的实例中添加一个新版本的实例,而不必用新的版本来替换旧的版本。部署在实例前端的负载均衡器会将一些请求转发到这个金丝雀实例上,你可以通过检查日志、指标来了解新实例的运行情况。如果新实例的运行结果令人满意,那么你可以开始部署所有的新版本。金丝雀部署分为两个步骤执行,如图10-14所示。

· 开始:存在多个旧版本的实例(在本示例中为4个)。

· 步骤1:创建一个新版本的实例,不删除任何旧版本的实例。

· 结束:新的实例启动完成并正常运行,可以与旧实例一起提供服务。

图10-14:金丝雀部署。只需要在当前实例中添加一个新的实例,而不用替换任何当前实例

在编排服务时,金丝雀部署会面临许多挑战。例如,当平台在检查不同实例的健康状态时,需要对金丝雀实例进行特殊处理,如果金丝雀实例不健康,需要将其替换为另一个金丝雀实例,但是如果其他金丝雀实例也不健康,那么需要替换为非金丝雀版本的实例。此外,金丝雀实例有时需要运行较长的时间,才能充分观察到它在新环境下的运行情况,在这个过程中,你可能会部署其他的新版本,这时需要确保只重新部署金丝雀以外的实例。

幸运的是,真正用到金丝雀部署的实际情况非常少。如果你只想向某一部分用户开放新功能,可以使用功能开关来实现(请参阅本章后面“功能开关”一节)。金丝雀部署的另一个好处是可以测试一些底层的配置变化,例如日志或者指标框架的改变、垃圾收集参数的变化,或者试用新的JVM。

金丝雀测试的未来

目前,大多数金丝雀测试部署都是通过部署平台来进行的。但是我们逐渐看到,像Ambassador(目前,大多数金丝雀测试部署都是通过部署平台来进行的。但是我们逐渐看到,像Ambassador(Ambassador | Ship software faster on Kubernetes)等开源API网关也开始支持到多个后端服务的流量动态路由,以及Istio(Istio)等服务网格框架开始提供服务间流量的金丝雀测试能力。Netflix团队还讨论过如何使用Spinnaker和Kayenta(http://bit.ly/2Q5UV7I)进行自动化的金丝雀分析。我们建议你持续了解这些发展的最新动态。

我应该选择哪种部署策略

如我们之前所述,不同的部署策略会以额外的资源和/或复杂性作为代价,解决不同场景下的问题。你的团队必须分析需求以及愿意接受的代价。表10-1整理了不同策略在各个方面的优缺点,以便帮助你进行决策。

表10-1:不同策略的特点及代价总结

注意较长的预热时间

Java技术在大型单体应用程序方面已经发展了多年,并且JVM的性能已经能够适应如下情况:JIT(just-in-time)编译器会检测到经常运行的代码段,将它们从字节码编译为机器码,同时能够识别对象的创建和销毁模式,选择合适的垃圾收集器等。但是,这只有在应用程序已经运行一段时间,并且JVM有机会收集到统计数据,进行相应计算之后才能实现。

在采用持续交付之后,你会更加频繁地部署应用程序,因此JVM在之前执行期间收集的统计信息可能都会丢失。每次新的部署后,JVM都必须从头开始收集,这可能会影响应用程序的整体性能。

另外,如果你有大量同时运行的实例,它们中的每一个都只会接收到一小部分流量,这意味着JVM需要更长的时间、运行多个周期,才能检测出对象的创建和销毁模式。如果再加上频繁的重新部署,那么你的应用程序可能永远无法达到最佳的性能。如果预热时间是一个问题,那么请考虑使用一些高级功能,如CDS(类数据共享)或者AOT(提前)编译,关于这方面的知识请参考Matthew Gilliard写的一篇精彩文章(Gilliard写的一篇精彩文章(Matthew Gilliard's blog || Fast JVM startup with JDK 9)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值