手把手教你解决 EKS 环境下的应用升级 50x 问题

75aa3338504a67a75c54a8cb9fec0b15.jpeg

背景介绍

Amazon EKS 是一项全托管的 Kubernetes 服务,可帮助您轻松地在 Amazon 上运行 Kubernetes 应用程序。在 EKS 中,Amazon Load Balancer Controller 是一种 Kubernetes 控制器,监视 Services 或 Ingress 资源,用于创建和管理 Network Load Balancer (NLB) 和 Application Load Balancer (ALB)。

通常在 Kubernetes 场景下,为实现应用新版本的零停机升级,会采用多副本部署,并在升级时使用 Rolling Update 策略,保证在旧的容器实例被终止前,有新的实例被创建来为后续的新请求服务。同时使用 Readiness 探针机制来确认只有 Pod 中的应用已经就绪时才加入到前端 Service 的 Endpoints 列表中,从而正常接受请求输入。

在使用 Amazon 托管的 EKS 和 Amazon Load Balancer Controller 时,除将 Pod 加入到Services 的 Endpoints 列表中,还需通过 Instance 或者 IP 模式将 NodePort 或者 Pod 注册成为负载均衡器的目标。使用 Instance 模式时请求流量进入负载均衡器后先转发至节点 NodePort 然后抵达 Pod,而 IP 模式时请求流量进入负载均衡器后即转发至 Pod 中,IP 模式减少了一层流量转发因此更多的被采用。

19aee704fa545d9f0c77319b12c10769.png

除了 Instance 模式和 IP 模式下的流量路径不同,负载均衡器的目标类型不同也会给应用升级带来不同的影响。

当采用 Instance 模式注册目标,如果不涉及到新旧节点的更换,NLB / ALB 目标组中的目标不会发生变化,NodePort 服务类型下请求会通过 Iptables / IPVS 的方式转发到相应的 Pod,此时实现 Deployment 级别的无停机更新即可。

当采用 IP 模式注册目标,更新时 NLB / ALB 目标组需删除原有 Pod 并加入新的 Pod 目标,此时 EKS 的应用更新会涉及到 ELB Target Group 和 K8s API 的调用且这两个调用是异步的,ELB 目标和 Pod 处在的不同状态及其时间长短会对流量的处理带来不同的结果,因此极有可能会引入中断。整体的处理流程可参考下图:

9fdeca0db67e471f97f573ff04c98a09.png

上图可见应用升级的过程中,Pod 和负载均衡器目标的会分别经历不同的状态,其中 Pod 的启停时间由于工作负载内容各不相同也有所差异。为实现应用的零停机升级,需要充分测试并了解处在不同阶段的 Pod 和负载均衡器目标所带来的影响。

本文目的

本文将以 ALB + IP 模式实现服务暴露为例,逐步测试和分析 EKS 应用升级中遇到的 50x 问题,并采取相应的方案以最终实现应用零停机升级。

环境准备

为方便复现测试场景,本文采用官方发布的 EKS workshop 环境和 game 2048(请根据 API 版本调整相应的 YAML 文件字段,文中实验为方便观察将默认 replicas 数目由默认的 5 减少至 2 )应用作为示例,此外根据文档开启 ALB 日志。

首先将官方  game 2048 Yaml 文件中的 Deployment 添加 date 注解并保存为 2048_full_template.yaml 文件:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment-2048
  namespace: game-2048
spec:
  selector:
    matchLabels:
      app: app-2048
  replicas: 2
  template:
    metadata:
      labels:
        app: app-2048
      annotations:
        date: "<DATE>"

使用如下命令实现通过改变 date 的简单更新部署并显示当前时间以做观察:

cp 2048_full_template.yaml 2048_full.yaml && \
sed -i "s@<DATE>@`date +%s`@g" 2048_full.yaml && \
kubectl apply -f 2048_full.yaml && \
 date

左滑查看更多

初次部署 game 2048 应用后,如下图所示在 EC2 控制台中找到相应的目标组获得其 ARN:

9a27a0909c076170a1db7dc1d32e165e.png

并通过以下命令获取 ALB URL:

kubectl get ingress/ingress-2048 -n game-2048
NAME           CLASS    HOSTS   ADDRESS                                                                   PORTS   AGE
ingress-2048   <none>   *       k8s-game2048-ingress2-xxxxxxxxxx-yyyyyyyyyy.region-code.elb.amazonaws.com   80      2m32s

左滑查看更多

根据以上信息编写测试脚本 aws_alb_test_2048.sh ,此测试脚本将持续获得应用 Pod 和 ALB 目标组状态,并返回对 game 2048 的 curl 请求结果。

#!/bin/bash
for i in {1..999};
do
    echo "$i starts kube pod" $(date "+[%Y-%m-%d %H:%M:%S.%6N]") \;
    kubectl get pods --selector=app.kubernetes.io/name=app-2048 -o wide -n game-2048;
    aws elbv2 describe-target-health \
    --target-group-arn arn:aws:elasticloadbalancing:region-code:xxxxxxx:targetgroup/k8s-game2048-service2-xxxxxxx/xxxxxxx;
    echo "$i starts curl " $(date "+[%Y-%m-%d %H:%M:%S.%6N]") \;
    curl -s -o /dev/null -w "http_code=%{http_code}"  https://k8s-game2048-ingress2-xxxxxxxxxx-yyyyyyyyyy.region-code.elb.amazonaws.com/;
    echo \;
    echo "$i ends curl" `date "+[%Y-%m-%d %H:%M:%S.%6N]"`\;
done

左滑查看更多

应用更新的同时在新的终端窗口运行上述脚本监控应用状态:

sh aws_alb_test_2048.sh

测试一:

使用多副本、滚动升级 和  Readiness 探针

根据前文所提到的通常在 Kubernetes 场景下的零停机更新方法,将 2048_full_template.yaml 文件中 deployment 配置为 2 replicas ,采用默认的 Rolling Update 更新策略,并添加 Readiness 探针。

spec:
      containers:
      - image: public.ecr.aws/l6m2t8p7/docker-2048:latest
        imagePullPolicy: Always
        name: app-2048
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 10

左滑查看更多

更新 game-2048 部署并执行 aws_alb_test_2048.sh 监测应用状态,观察到更新过程中至少有一个 Pod 处于 running 状态:

0dfb87c543ee6366f1429b523b804b07.png

而在执行 aws_alb_test_2048.sh 脚本的终端窗口中除正常返回 200 的 http_code,如下图所示存在某时刻两个新 Pod 处于 running 状态,而目标组中四个 Target 皆处于 registration 和 deregistration 的过程中,game-2048 服务返回 http_code=504。

16b89f82c5dd7d27849ff383f1fb7f5d.png

此时如在控制台观察 ALB 目标组,会发现目标组中并无任何 healthy 状态的目标。

0df710f05094a5aaa336bce391b0150c.png

如上所述,即使采用了滚动升级和 Readiness 探针,ALB 目标组的健康检查状态并没有和 Pod 的生命周期联动,旧的 Pod 已经 terminated,处于 running 状态的新 Pod 在 ALB 中的目标状态不是 healthy 无法接受请求,导致响应状态码为 504。

106342f0909d665ccee3c16d694a4f05.png

测试二:

使用多副本、滚动升级和 Amazon Load Balancer Controller Pod Readiness Gate

Amazon Load Balancer Controller Pod Readiness Gate 可通过 EKS 持续监控 ELB 目标状态,直到其状态为 healthy 才认为 Pod 已经就绪,将 READNIESS GATES 设为 1 。

启用 Pod Readiness Gate 并更新应用后,可查看 Pod 的 STATUS 和 READNIESS GATES 的两个状态。

88b40ffc2071c5ea139ee33835654d6b.png

8506d07e17876d87966f4296dbb616e0.png

此时再次更新 game-2048 部署并执行 aws_alb_test_2048.sh 监测应用状态,控制台中 ALB 目标组未出现所有目标不健康的状况,可见 pod_readiness_gate 保证了目标组在有新的目标注册且 healthy 后才会取消注册旧目标。

e0828f0388b4fa70df345e80eb228bb5.png

但在应用状态监测的终端中仍然出现了 http_code=504,如下图所示,新的 Pod 处于 running 状态且目标状态为 healthy,旧的 Pod 已删除而其目标仍处在取消注册进度中。

50507c576b2252b711861d69089f296f.png

查看 ALB 日志可发现此时的请求转发给了正在 draining 的目标:

6f7d15414225c847de09160d749eed93.png

点击查看大图

由此可见,在新的目标处于 healthy 状态时,旧的处于 draining 状态的目标仍有可能接受请求,根据 Deregistration delay 描述 Pod 终止过快时客户端会收到 50x 响应,正如此次测试的结果。

c337062f2932253fb139c54fa7114e17.png

博客提及的测试场景中 Pod Readiness Gate 解决了服务中断问题,而在上述测试中 Pod Readiness Gate 虽然保证了 ALB 中至少有一个 healthy 的目标,但并未能完全解决服务升级中断问题,由此可推测其效果也取决于应用服务本身如 Pod 终止速度及其他因素。

测试三:

使用多副本、滚动升级和 Amazon Load Balancer Controller Pod Readiness Gate、Prestop hook

为解决测试二中的问题引入 pre-stop hook 延长 pod terminating 的时间,确保在目标 draining 结束前旧 Pod 不被彻底移除以处理完遗留请求。

在 2048_full_template.yaml 文件中添加 prestop hook,根据上述测试结果请求返回504的整个时间段不长,设置 sleep 20s 即可保证目标 draining 完成前旧 Pod 可继续处理请求。

spec:
      containers:
      - image: public.ecr.aws/l6m2t8p7/docker-2048:latest
        imagePullPolicy: Always
        name: app-2048
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 20"]

左滑查看更多

更新 game-2048 部署并执行 aws_alb_test_2048.sh 监测应用状态,可发现此次测试中并未收到 http_code=200 以外的响应。

28f384276733b20734f2b105c72f59f8.png

此时请求如果传给新的 healthy 的目标可正确处理,传给旧的目标时 Pod 尚未终止也可正确处理流量。

18d2d4940b246b7766f37086d3168d1b.png

此外,笔者在相同环境下做了以下测试:

ALB 结合 Ingress 对外暴露应用时,以 Instance 模式注册 NodePort 为 ALB 目标,仅配置滚动升级,应用更新过程中持续 curl 请求的响应状态码皆为 200;

NLB 结合 Services 对外暴露应用时,以 IP 模式注册 Pod 为 NLB 目标,测试结果与 ALB 类似,区别在于 curl 请求暂停响应而不是返回 504 响应状态码;

NLB 结合 Services 对外暴露应用时,以 Instance 模式注册 NodePort 为 NLB 目标,仅配置滚动升级,应用更新过程中持续 curl 请求的响应状态码也皆为 200。

测试过程此文省略。

结论

基于以上测试结果,正如开篇针对 NLB / ALB 的目标生命周期管理的分析,采用 IP 模式注册 Pod 目标时应用更新会产生 50x 问题,此时建议使用 Pod Readiness Gate 关联 ELB 目标状态和 Pod 生命周期以减少服务中断的发生,如若仍不能解决 50x,可根据应用处理请求的时长和 Pod 终止的速度选择设置 pre-stop hook。

最后,请关注 terminationGracePeriodSeconds,Deregistration delay,Connection Idle Timeout 等参数数值大小关系以实现 EKS 环境下的应用零停机升级。

本篇作者

c23c0eeedd05e41770ed56643a3a8363.jpeg

杜晨曦

亚马逊云科技解决方案架构师,负责基于亚马逊云计算方案架构的咨询和设计,在国内推广亚马逊云平台技术和各种解决方案。

2023亚马逊云科技中国峰会即将开启!

👇👇👇点击下方图片即刻注册👇👇👇

5b46fa58dee03c143ee859febf321b6a.jpeg

e7055e4fd76d8b379f6b70af4b14caf8.gif

2bd278a1b059eac8d206141c9d36e42f.gif

听说,点完下面4个按钮

就不会碰到bug了!

a45ac7e3ede89287d6caade394cc192c.gif

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值