阿里云k8s服务springboot项目应用升级时出现502错误

     2022-04-02     607

关键词:

背景
随着小步快跑、快速迭代的开发模式被越来越多的互联网企业认同和采用,应用的变更、升级频率变得越来越频繁。为了应对不同的升级需求,保证升级过程平稳顺利地进行,诞生了一系列的部署发布模式。

停机发布 - 把老版的应用实例完全停止,再发布新的版本。这种发布模式主要为了解决新老版本互不兼容、无法共存的问题,缺点是一段时间内服务完全不可用。
蓝绿发布 - 在线上同时部署相同数量的新老版本应用实例。待新版本测试通过后,将流量一次性地切到新的服务实例上来。这种发布模式解决了停机发布中存在的服务完全不可用问题,但会造成比较大的资源消耗。
滚动发布 - 分批次逐步替换应用实例。这种发布模式不会中断服务,同时也不会消耗过多额外的资源,但由于新老版本实例同时在线,可能导致来自相同客户端的请求在新老版中切换而产生兼容性问题。
金丝雀发布 - 逐渐将流量从老版本切换到新版本上。如果观察一段时间后没有发现问题,就进一步扩大新版本流量,同时减少老版本上流量。
A/B 测试 - 同时上线两个或多个版本,收集用户对这些版本的反馈,分析评估出最好版本正式采用。
随着越来越多的应用被容器化,如何方便地让容器应用平稳顺利升级受到了广泛关注。本文将介绍 k8s 中不同部署形式下应用的升级方法,并重点介绍如何对 Deployment 中的应用实施滚动发布(本文所作的调研基于k8s 1.13)。

K8s 应用升级
在 k8s 中,pod 是部署和升级的基本单位。一般来说,一个 pod 代表一个应用实例,而 pod 又会以 Deployment、StatefulSet、DaemonSet、Job 等形式部署运行,下面依次介绍在这些部署形式下 pod 的升级方法。

Deployment
Deployment 是 pod 最常见的部署形式,这里将以基于 spring boot 的 java 应用为例进行介绍。该应用是基于真实应用抽象出来的简单版本,非常具有代表性,它有如下特点:

应用启动后,需要花费一定的时间加载配置,在这段时间内,无法对外提供服务。
应用能够启动并不意味着它能够正常提供服务。
应用如果无法提供服务不一定能自动退出。
在升级过程中需要保证即将下线的应用实例不会接收到新的请求且有足够时间处理完当前请求。
参数配置
为了让具有上述特点的应用实现零宕机时间和无生产中断的升级,需要精心地配置 Deployment 中的相关参数。这里和升级有关的配置如下(完整配置参见 spring-boot-probes-v1.yaml)。

kind: Deployment
...
spec:
replicas: 8
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 3
maxUnavailable: 2
minReadySeconds: 120
...
template:
...
spec:
containers:

  • name: spring-boot-probes
    image: registry.cn-hangzhou.aliyuncs.com/log-service/spring-boot-probes:1.0.0
    ports:
    • containerPort: 8080
      terminationGracePeriodSeconds: 60
      readinessProbe:
      httpGet:
      path: /actuator/health
      port: 8080
      initialDelaySeconds: 30
      periodSeconds: 10
      successThreshold: 1
      failureThreshold: 1
      livenessProbe:
      httpGet:
      path: /actuator/health
      port: 8080
      initialDelaySeconds: 40
      periodSeconds: 20
      successThreshold: 1
      failureThreshold: 3
      ...
        

配置 strategy
通过 strategy 可以配置 pod 的替换策略,主要参数如下。

.spec.strategy.type - 用于指定替换 pod 的策略类型。该参数可取值 Recreate 或 RollingUpdate,默认为 RollingUpdate。

Recreate - K8s 会先删掉全部原有 pod 再创建新的 pod。该方式适用于新老版本互不兼容、无法共存的场景。但由于该方式会造成一段时间内服务完全不可用,在上述场景之外须慎用。
RollingUpdate - K8s 会将 pod 分批次逐步替换掉,可用来实现服务热升级。
.spec.strategy.rollingUpdate.maxSurge - 指定在滚动更新过程中最多可创建多少个额外的 pod,可以是数字或百分比。该值设置得越大、升级速度越快,但会消耗更多的系统资源。
.spec.strategy.rollingUpdate.maxUnavailable - 指定在滚动更新过程中最多允许多少个 pod 不可用, 可以是数字或百分比。该值设置得越大、升级速度越快,但服务会越不稳定。
通过调节 maxSurge 和 maxUnavailable,可以满足不同场景下的升级需求。

如果您希望在保证系统可用性和稳定性的前提下尽可能快地进行升级,可以将 maxUnavailable 设置为 0,同时为 maxSurge 赋予一个较大值。
如果系统资源比较紧张,pod 负载又比较低,为了加快升级速度,可以将 maxSurge 设置为 0,同时为 maxUnavailable 赋予一个较大值。需要注意的是,如果 maxSurge 为 0,maxUnavailable 为 DESIRED,可能造成整个服务的不可用,此时 RollingUpdate 将退化成停机发布。
样例选择了一个折中方案,将 maxSurge 设置为 3,将 maxUnavailable 设置为 2,平衡了稳定性、资源消耗和升级速度。

配置探针
K8s 提供以下两类探针:

ReadinessProbe - 默认情况下,一旦某个 pod 中的所有容器全部启动,k8s 就会认为该 pod 处于就绪状态,从而将流量发往该 pod。但某些应用启动后,还需要完成数据或配置文件的加载工作才能对外提供服务,因此通过容器是否启动来判断其是否就绪并不严谨。通过为容器配置就绪探针,能让 k8s 更准确地判断容器是否就绪,从而构建出更健壮的应用。K8s 保证只有 pod 中的所有容器全部通过了就绪探测,才允许 service 将流量发往该 pod。一旦就绪探测失败,k8s 会停止将流量发往该 pod。
LivenessProbe - 默认情况下,k8s 会认为处于运行状态下的容器是可用的。但如果应用在出现问题或不健康时无法自动退出(例如发生严重死锁),这种判断就会出现问题。通过为容器配置活性探针,能让 k8s 更准确地判断容器是否正常运行。如果容器没有通过活性探测,kubelet 会将其停止,并根据重启策略决定下一步的动作。
探针的配置非常灵活,用户可以指定探针的探测频率、探测成功阈值、探测失败阈值等。各参数的含义和配置方法可参考文档 Configure Liveness and Readiness Probes。

样例为目标容器配置了就绪探针和活性探针:

就绪探针的 initialDelaySeconds 设置成 30,这是因为应用平均需要 30 秒时间完成初始化工作。
在配置活性探针时,需要保证容器有足够时间到达就绪状态。如果参数 initialDelaySeconds、periodSeconds、failureThreshold 设置得过小,可能造成容器还未就绪就被重启,以至于永远无法达到就绪状态。样例中的配置保证如果容器能在启动后的 80 秒内就绪就不会被重启,相对 30 秒的平均初始化时间有足够的缓冲。
就绪探针的 periodSeconds 设置成 10,failureThreshold 设置成 1。这样当容器异常时,大约 10 秒后就不会有流量发往它。
活性探针的 periodSeconds 设置成 20,failureThreshold 设置成 3。这样当容器异常时,大约 60 秒后就不会被重启。
配置 minReadySeconds
默认情况下,一旦新创建的 pod 变成就绪状态 k8s 就会认为该 pod 是可用的,从而将老的 pod 删除掉。但有时问题可能会在新 pod 真正处理用户请求时才暴露,因此一个更稳健的做法是当某个新 pod 就绪后对其观察一段时间再删掉老的 pod。

参数 minReadySeconds 可以控制 pod 处于就绪状态的观察时间。如果 pod 中的容器在这段时间内都能正常运行,k8s 才会认为新 pod 可用,从而将老的 pod 删除掉。在配置该参数时,需要仔细权衡,如果设置得过小,可能造成观察不充分,如果设置得过大,又会拖慢升级进度。样例将 minReadySeconds 设置成了 120 秒,这样能保证处于就绪状态的 pod 能经历一个完整的活性探测周期。

配置 terminationGracePeriodSeconds
当 k8s 准备删除一个 pod 时,会向该 pod 中的容器发送 TERM 信号并同时将 pod 从 service 的 endpoint 列表中移除。如果容器无法在规定时间(默认 30 秒)内终止,k8s 会向容器发送 SIGKILL 信号强制终止进程。Pod 终止的详细流程可参考文档 Termination of Pods。

由于应用处理请求最长耗时 40 秒,为了让其在关闭前能够处理完已到达服务端的请求,样例设置了 60 秒的优雅关闭时间。针对不同的应用,您可以根据实际情况调整 terminationGracePeriodSeconds 的取值。

观察升级行为
上述配置能够保证目标应用的平滑升级。我们可以通过更改 Deployment 中 PodTemplateSpec 的任意字段触发 pod 升级,并通过运行命令kubectl get rs -w观察升级行为。这里观察到的新老版本的 pod 副本数的变化情况如下:

创建 maxSurge 个新 pod。这时 pod 总数达到了允许的上限,即 DESIRED + maxSurge。
不等新 pod 就绪或可用,立刻启动 maxUnavailable 个老 pod 的删除流程。这时可用 pod 数为 DESIRED - maxUnavailable。
某个老 pod 被完全删除,这时会立刻补充一个新 pod。
某个新 pod 通过了就绪探测变成了就绪态,k8s 会将流量发往该 pod。但由于未达到规定的观察时间,该 pod 并不会被视作可用。
某个就绪 pod 在观察期内运行正常被视作可用,这时可以再次启动某个老 pod 的删除流程。
重复步骤 3、4、5 直到所有老 pod 被删除,并且可用的新 pod 达到目标副本数。
失败回滚
应用的升级并不总会一帆风顺,在升级过程中或升级完成后都有可能遇到新版本行为不符合预期需要回滚到稳定版本的情况。K8s 会将 PodTemplateSpec 的每一次变更(如果更新模板标签或容器镜像)都记录下来。这样,如果新版本出现问题,就可以根据版本号方便地回滚到稳定版本。回滚 Deployment 的详细操作步骤可参考文档 Rolling Back a Deployment。

StatefulSet
StatefulSet 是针对有状态 pod 常用的部署形式。针对这类 pod,k8s 同样提供了许多参数用于灵活地控制它们的升级行为。好消息是这些参数大部分都和升级 Deployment 中的 pod 相同。这里重点介绍两者存在差异的地方。

策略类型
在 k8s 1.7 及之后的版本中,StatefulSet 支持 OnDelete 和 RollingUpdate 两种策略类型。

OnDelete - 当更新了 StatefulSet 中的 PodTemplateSpec 后,只有手动删除旧的 pod 后才会创建新版本 pod。这是默认的更新策略,一方面是为了兼容 k8s 1.6 及之前的版本,另一方面也是为了支持升级过程中新老版本 pod 互不兼容、无法共存的场景。
RollingUpdate - K8s 会将 StatefulSet 管理的 pod 分批次逐步替换掉。它与 Deployment 中 RollingUpdate 的区别在于 pod 的替换是有序的。例如一个 StatefulSet 中包含 N 个 pod,在部署的时候这些 pod 被分配了从 0 开始单调递增的序号,而在滚动更新时,它们会按逆序依次被替换。
Partition
可以通过参数.spec.updateStrategy.rollingUpdate.partition实现只升级部分 pod 的目的。在配置了 partition 后,只有序号大于或等于 partition 的 pod 才会进行滚动升级,其余 pod 将保持不变。

Partition 的另一个应用是可以通过不断减少 partition 的取值实现金丝雀升级。具体操作方法可参考文档 Rolling Out a Canary。

DaemonSet
DaemonSet 保证在全部(或者一些)k8s 工作节点上运行一个 pod 的副本,常用来运行监控或日志收集程序。对于 DaemonSet 中的 pod,用于控制它们升级行为的参数与 Deployment 几乎一致,只是在策略类型方面略有差异。DaemonSet 支持 OnDelete 和 RollingUpdate 两种策略类型。

OnDelete - 当更新了 DaemonSet 中的 PodTemplateSpec 后,只有手动删除旧的 pod 后才会创建新版本 pod。这是默认的更新策略,一方面是为了兼容 k8s 1.5 及之前的版本,另一方面也是为了支持升级过程中新老版本 pod 互不兼容、无法共存的场景。
RollingUpdate - 其含义和可配参数与 Deployment 的 RollingUpdate 一致。
滚动更新 DaemonSet 的具体操作步骤可参考文档 Perform a Rolling Update on a DaemonSet。

Job
Deployment、StatefulSet、DaemonSet 一般用于部署运行常驻进程,而 Job 中的 pod 在执行完特定任务后就会退出,因此不存在滚动更新的概念。当您更改了一个 Job 中的 PodTemplateSpec 后,需要手动删掉老的 Job 和 pod,并以新的配置重新运行该 job。

总结
K8s 提供的功能可以让大部分应用实现零宕机时间和无生产中断的升级,但也存在一些没有解决的问题,主要包括以下几点:

目前 k8s 原生仅支持停机发布、滚动发布两类部署升级策略。如果应用有蓝绿发布、金丝雀发布、A/B 测试等需求,需要进行二次开发或使用一些第三方工具。
K8s 虽然提供了回滚功能,但回滚操作必须手动完成,无法根据条件自动回滚。
有些应用在扩容或缩容时同样需要分批逐步执行,k8s 还未提供类似的功能。
livenessProbe:
failureThreshold: 3
httpGet:
path: /user/service/test
port: 8080
scheme: HTTP
initialDelaySeconds: 40
periodSeconds: 20
successThreshold: 1
timeoutSeconds: 1
name: dataline-dev
ports:

  • containerPort: 8080
    protocol: TCP
    readinessProbe:
    failureThreshold: 1
    httpGet:
    path: /user/service/test
    port: 8080
    scheme: HTTP
    initialDelaySeconds: 30
    periodSeconds: 10
    successThreshold: 1
    timeoutSeconds: 1

k8s应用案例介绍——阿里云

原文链家:为什么K8s在阿里能成功?|问底中国IT技术演进 着重描述了阿里巴巴基于K8s的云原生改造实践过程的三大能力升级,在对应能力升级过程中沉淀的技术解决方案,以及通过这些能力升级所取得的业务价值。云原生技... 查看详情

为什么k8s在阿里能成功(转)

 作者:曾凡松阿里云云原生应用平台高级技术专家张振阿里云云原生应用平台高级技术专家导读:本文描述了阿里巴巴在容器管理领域的技术演进历程,解读了为什么K8s最终能够大获成功的原因,以及到今年双11阿里巴巴内... 查看详情

如何将springboot项目部署到阿里云服务器(jar包)

...装了ftp以及运行所需要的环境(怎么安装环境)1.部署方式Springboot和普通web应用程序不一样,其本质上是一个Java应用程序,那么又如何部署呢? 通常来说,Springboot部署会采用两种方式:全部打包成一个jar,... 查看详情

如何将springboot项目部署到阿里云服务器(jar包)

...装了ftp以及运行所需要的环境(怎么安装环境)1.部署方式Springboot和普通web应用程序不一样,其本质上是一个Java应用程序,那么又如何部署呢? 通常来说,Springboot部署会采用两种方式:全部打包成一个jar,... 查看详情

云原生kubernetesk8s集群部署springboot项目(代码片段)

...比较接近实际业务的使用场景,使用k8s集群部署一个springboot的项目,我们的需求是:部署SpringBoot项目到阿里云服务器 ;基于容器打包,推送私有镜像仓库;采用K8S集群部署,对外暴露服务,pod副... 查看详情

阿里云(winserver)部署前后端分离项目(springboot+vue+android)

winserver部署前后端分离项目(springboot+vue+android)记录一下,也给小白一些参考首先得会开发springboot、vue、android项目才需要看本文一、前期准备1.轻量级应用服务器2.域名3.备案二、服务器部署springboot项目0.开发... 查看详情

更新应用时,如何实现k8s零中断滚动更新?(代码片段)

作者|子白(阿里云开发工程师)、溪恒(阿里云技术专家)<关注阿里巴巴云原生公众号,回复?排查?即可下载电子书>《深入浅出Kubernetes》一书共汇集12篇技术文章,帮助你一次搞懂6个核心原理,吃透基础理论,一次学会6... 查看详情

阿里云搭建docker私有镜像仓库与springboot项目推送远程镜像仓库

...技术的学习。首先初学Docker,我的想法很简单。创建一个SpringBoot项目,如何将SpringBoot项目打包成容器镜像,然后推送至远程的Docker服务上部署。带着这个目的查阅了一些资料后,整体的实现思路如下:环境描述:准备一台阿里... 查看详情

intellijidea部署springboot/springcloud应用到阿里云

SpringCloud和SpringBoot可以说是当前最流行的微服务开发框架了,在本文中,将向读者介绍如何在在IntellijIDEA中部署SpringBoot/SpringCloud应用到阿里云。(Eclipse平台请移步《在Eclipse中部署SpringBoot/SpringCloud应用到阿里云》)本地开发无... 查看详情

springcloud微服务升级总结

参考技术ASpringBoot框架是由Pivotal团队提供的全新框架,其设计目的是用来简化基于Spring应用的初始搭建以及开发过程。SpringBoot框架使用了特定的方式来进行应用系统的配置,从而使开发人员不再需要耗费大量精力去定义模板化... 查看详情

k8s部署springboot项目(一篇够用)(代码片段)

...天这篇文章主要介绍如何从0开始搭建一套基于K8s部署的SpringBoot案例教程。基础环境准备 查看详情

springboot在k8s下实现优雅停机

...,否则滚动升级时,还是会影响到业务。所以,我们希望SpringBoot应用实现优雅停机。此次教程基于SpringBoot2.5.0。默认情况下,SpringBoot是直接关机的,所以,需要将优雅停机配置打开。在applicatoin.yaml中配置:这时我们只需要在发... 查看详情

小程序后端项目springboot框架部署到阿里云服务器支持https访问

前言:我的后端项目是Java写的,用的Springboot框架。在部署服务器并配置https访问过程中,因为做了一些令人窒息的操作(事后发现),所以老是不能成功。不成功具体点说就是:域名地址可以正常访问(http/https均可),而部署... 查看详情

springboot整合阿里云oss(代码片段)

文章目录SpringBoot整合阿里云OSS1.准备工作1.1开通“对象存储OSS”服务1.2创建Bucket1.3创建RAM子用户2.SpringBoot整合阿里云OSS2.1创建SpringBoot项目2.2配置application.properties2.3创建常量读取工具类2.4上传图片至阿里云2.5使用swagger测试SpringBoo... 查看详情

springboot项目本地可以发送邮件,部署到阿里云服务器发送邮件失败的解决方法

 在application.yml文件中配置#邮件设置spring:mail:host:smtp.exmail.qq.comport:465username:xxx@qq.compassword:xxxdefault-encoding:UTF-8#配置阿里云服务器发送邮件properties:mail:smtp:ssl:trust:smtp.exmail.qq.comsocketFact 查看详情

如何将springboot项目部署到阿里云服务器(jar包)

...装了ftp以及运行所需要的环境(怎么安装环境)1.部署方式Springboot和普通web应用程序不一样,其本质上是一个Java应用程序,那么又如何部署呢? 通常来说,Springboot部署会采用两种方式:全部打包成一个jar,... 查看详情

小程序后端项目springboot框架部署到阿里云服务器支持https访问(代码片段)

前言:  我的后端项目是Java写的,用的Springboot框架。在部署服务器并配置https访问过程中,因为做了一些令人窒息的操作(事后发现),所以老是不能成功。  不成功具体点说就是:域名地址可以正常访问(http/https均可)... 查看详情

超详细springboot+vue项目部署到阿里云服务器上(代码片段)

目录1.springBoot部署到阿里云1.1导出sprigBoot项目1.2将jar文件上传至服务器1.3部署后端文件2.Vue项目打包到服务器上2.1导出vue项目2.2将vue项目上传至服务器3.部署前端文件1.springBoot部署到阿里云1.1导出sprigBoot项目打开idel在控制台输入&... 查看详情