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


Posted in Servers onApril 09, 2022

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

  • 停机发布 - 把老版的应用实例完全停止,再发布新的版本。这种发布模式主要为了解决新老版本互不兼容、无法共存的问题,缺点是一段时间内服务完全不可用。
  • 蓝绿发布 - 在线上同时部署相同数量的新老版本应用实例。待新版本测试通过后,将流量一次性地切到新的服务实例上来。这种发布模式解决了停机发布中存在的服务完全不可用问题,但会造成比较大的资源消耗。
  • 滚动发布 - 分批次逐步替换应用实例。这种发布模式不会中断服务,同时也不会消耗过多额外的资源,但由于新老版本实例同时在线,可能导致来自相同客户端的请求在新老版中切换而产生兼容性问题。
  • 金丝雀发布 - 逐渐将流量从老版本切换到新版本上。如果观察一段时间后没有发现问题,就进一步扩大新版本流量,同时减少老版本上流量。
  • A/B 测试 - 同时上线两个或多个版本,收集用户对这些版本的反馈,分析评估出最好版本正式采用。

随着越来越多的应用被容器化,如何方便地让容器应用平稳顺利升级受到了广泛关注。本文将介绍 k8s 中不同部署形式下应用的升级方法,并重点介绍如何对 Deployment 中的应用实施滚动发布(本文所作的调研基于k8s 1.13)。

K8s 应用升级

在 k8s 中,pod 是部署和升级的基本单位。一般来说,一个 pod 代表一个应用实例,而 pod 又会以 DeploymentStatefulSetDaemonSetJob 等形式部署运行,下面依次介绍在这些部署形式下 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 还未提供类似的功能。

实例配置:

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

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

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

经测试 , 再对sprintboot 应用进行更新时, 访问不再出现502的报错。

Servers 相关文章推荐
Filebeat 采集 Nginx 日志的方法
Mar 31 Servers
nginx 防盗链防爬虫配置详解
Mar 31 Servers
nginx的zabbix 5.0安装部署的方法步骤
Jul 16 Servers
Nginx的基本概念和原理
Mar 21 Servers
Nginx设置HTTPS的方法步骤 443证书配置方法
Mar 21 Servers
Tomcat用户管理的优化配置详解
Mar 31 Servers
Tomcat项目启动失败的原因和解决办法
Apr 20 Servers
阿里云 Windows server 2019 配置FTP
Apr 28 Servers
详解ZABBIX监控ESXI主机的问题
Jun 21 Servers
超越Nginx的Web服务器caddy优雅用法
Jun 21 Servers
windows server 2016 域环境搭建的方法步骤(图文)
Jun 25 Servers
Nginx跨域问题解析与解决
Aug 05 Servers
Consul在linux环境的集群部署
nginx.conf配置文件结构小结
docker-compose部署Yapi的方法
Apr 08 #Servers
Nginx隐藏式跳转(浏览器URL跳转后保持不变)
Apr 07 #Servers
Nginx动静分离配置实现与说明
Nginx禁止ip访问或非法域名访问
Apr 07 #Servers
Nginx流量拷贝ngx_http_mirror_module模块使用方法详解
Apr 07 #Servers
You might like
从C/C++迁移到PHP——判断字符类型的函数
2006/10/09 PHP
PHP学习 运算符与运算符优先级
2008/06/15 PHP
php获取本周开始日期和结束日期的方法
2015/03/09 PHP
Mac环境下php操作mysql数据库的方法分享
2015/05/11 PHP
php进行ip地址掩码运算处理的方法
2016/07/11 PHP
[原创]php正则删除img标签的方法示例
2017/05/27 PHP
javascript void(0)的妙用
2009/10/21 Javascript
Javascript Function对象扩展之延时执行函数
2010/07/06 Javascript
兼容IE和Firefox火狐的上下、左右循环无间断滚动JS代码
2013/04/19 Javascript
JavaScript极简入门教程(二):对象和函数
2014/10/25 Javascript
Jquery Ajax xmlhttp请求成功问题
2015/02/04 Javascript
javascript设计模式Constructor(构造器)模式
2016/08/19 Javascript
移动端js触摸事件详解
2016/09/18 Javascript
常用的js方法合集
2017/03/10 Javascript
vue 将页面公用的头部组件化的方法
2017/12/18 Javascript
解决JQuery的ajax函数执行失败alert函数弹框一闪而过问题
2019/04/10 jQuery
jquery实现动态创建form并提交的方法示例
2019/05/27 jQuery
原生js实现随机点名功能
2019/11/05 Javascript
linux服务器快速卸载安装node环境(简单上手)
2021/02/22 Javascript
Python删除指定目录下过期文件的2个脚本分享
2014/04/10 Python
python 容器总结整理
2017/04/04 Python
Python编程生成随机用户名及密码的方法示例
2017/05/05 Python
python 自定义装饰器实例详解
2019/07/20 Python
wxpython实现按钮切换界面的方法
2019/11/19 Python
浅谈SciPy中的optimize.minimize实现受限优化问题
2020/02/29 Python
美国在线眼镜商城:Eyeglasses.com
2017/06/26 全球购物
日本订房网站,预订日本星级酒店/温泉旅馆:Relux(支持中文)
2020/01/03 全球购物
若通过ObjectOutputStream向一个文件中多次以追加方式写入object,为什么用ObjectInputStream读取这些object时会产生StreamCorruptedException?
2016/10/17 面试题
语文教育专业推荐信范文
2013/11/25 职场文书
2014年幼儿园植树节活动方案
2014/03/02 职场文书
喷漆工的岗位职责
2014/03/17 职场文书
四风自我剖析材料思想汇报
2014/10/01 职场文书
2014年前台接待工作总结
2014/12/05 职场文书
2015年采购工作总结
2015/04/10 职场文书
2016中考冲刺决心书
2015/09/22 职场文书
六年级作文之自救
2019/12/19 职场文书