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

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

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

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

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

应用启动后,须要花费必定的时间加载配置,在这段时间内,没法对外提供服务。
应用可以启动并不意味着它可以正常提供服务。
应用若是没法提供服务不必定能自动退出。
在升级过程当中须要保证即将下线的应用实例不会接收到新的请求且有足够时间处理完当前请求。
参数配置
为了让具备上述特色的应用实现零宕机时间和无生产中断的升级,须要精心地配置 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。orm

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 的删除流程。
重复步骤 三、四、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: 8080protocol: TCPreadinessProbe:failureThreshold: 1httpGet:path: /user/service/testport: 8080scheme: HTTPinitialDelaySeconds: 30periodSeconds: 10successThreshold: 1timeoutSeconds: 1
相关文章
相关标签/搜索