K8s 服务优雅下线调试记录

K8s 服务优雅下线的背景

做每一件事情都是有前因后果的,我为什么要去调研优雅下线的解决方案,是因为一次线上发布版本的事故导致了订单数据没有存储。

在停服切换时,当前还有正在处理的订单已经支付的订单,此时已经调用三方扣款但是在生成订单的时候duang停服了。就出现了那么一个订单。因为发生的概率也挺小的,但是我们的支付平台24小时都有支付过来,没有发布版本的窗口期,只能热更新。

K8s 服务优雅下线的环境

环境: k8s 集群 包含 一个master、三个节点

调试工具: jemeter

测试目标:支付服务,目前支付服务有两个实例如下图所示

image
上图为使用kubesphere搭建的k8s集群的pay服务

k8s的服务暴露端口为31553

image

优雅下线k8s配置

以下优雅下线的配置为Deployment yaml 仅供参考,由于环境我使用的外部挂载文件方式给容器提供的jar。

整个核心就在于prestop调用的服务内部的接口通知注册中心服务下线。

kind: Deployment
apiVersion: apps/v1
metadata:
name: pay
labels:
app: pay
annotations:
deployment.kubernetes.io/revision: '45'
kubesphere.io/creator: admin
kubesphere.io/description: 支付服务
spec:
replicas: 2
selector:
matchLabels:
app: pay
template:
metadata:
creationTimestamp: null
labels:
app: pay
annotations:
kubesphere.io/containerSecrets: ''
kubesphere.io/restartedAt: '2020-09-11T06:25:18.431Z'
logging.kubesphere.io/logsidecar-config: '{}'
spec:
volumes:
  - name: host-time
hostPath:
path: /etc/localtime
type: ''
  - name: app
hostPath:
path: /app/backend/pay/app.jar
type: ''
containers:
  - name: container-09awts
image: 'pay:v2.1-beta.76'
ports:
  - name: http-8011
containerPort: 8011
protocol: TCP
env:
  - name: TZ
value: Asia/Shanghai
         - name: LC_ALL
value: zh_CN.utf8
         - name: LANG
value: zh_CN.utf8
         - name: LANGUAGE
value: zh_CN.utf8
         - name: JVM_XMS
value: 768m
         - name: JVM_XMX
value: 768m
         - name: JVM_XMN
value: 384m
         - name: APP_ENV
value: dev
         - name: REGISTER_HOST
value: nacos
         - name: REGISTER_NAMESPACE
value: 442b0427-eb60-1234-ac69-8bc598294c4d
         - name: RABBIT_MQ_PASSWORD
valueFrom:
secretKeyRef:
name: rabbitmq
key: rabbitmq-password
       - name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: reids
key: redis-password
       - name: DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: rollbank-db
key: db-password
       - name: REDIS_HOST
value: redis
         - name: REDIS_PORT
value: '6379'
  - name: RABBIT_MQ_HOST
value: rabbitmq
         - name: DATASOURCE_HOST
valueFrom:
secretKeyRef:
name: rollbank-db
key: db-host
resources: {}
volumeMounts:
  - name: host-time
readOnly: true
mountPath: /etc/localtime
             - name: app
mountPath: /app/app.jar
readinessProbe:
httpGet:
path: /actuator/health
port: 8011
scheme: HTTP
initialDelaySeconds: 30
timeoutSeconds: 10
periodSeconds: 10
successThreshold: 1
failureThreshold: 3
lifecycle:
preStop:
exec:
command:
  - /bin/sh
  - '-c'
  - >-
  wget -qdO- --post-data ""
  http://127.0.0.1:8011/actuator/service-registry?status=DOWN
  --header
  "Content-Type:application/vnd.spring-boot.actuator.v2+json;charset=UTF-8";sleep
  120
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
restartPolicy: Always
terminationGracePeriodSeconds: 120
dnsPolicy: ClusterFirst
serviceAccountName: default
serviceAccount: default
securityContext: {}
affinity: {}
schedulerName: default-scheduler
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
revisionHistoryLimit: 10
progressDeadlineSeconds: 600

jemter测试

Jemter的请求配置信息,并且采集了每一条请求数据和聚合统计数据。

image

  • 开始测试,当前跑了pay服务的两个实例

image

  • 重新发版本测试,新启动了一个实例,由于启动的就绪检查还没有通过,实例的状态为没有就绪。原理是通过健康检查接口来判断服务是否可用。

image

  • k8s准备删除老版本实例,当新的实例已经就绪的情况下,会自动删除老版本的一个实例,并且再次启动一个新版本的实例。删除实例不是立马删除,而是通过调用下线接口不再接受新的请求,并且等待一段时间让已经请求的数据处理完毕后停服。

image

  • 和上面一样的状态,主要对比下面jemeter的请求一直没有停止,并且没有产生异常的。

image

  • 老版本已经停止,并且在等待另一个新版本启动就绪。

image

  • 第二个新版本启动完成,删除旧版的。

image

  • 整个灰度(滚动)发版完成

image

最终在这场灰度发布过程出现了4条失败的请求.通过调取所有的请求记录,并没有发现200以外的状态码.但是发现jemeter在存储请求数据时,出现了线程并发问题.

1599805677891,15,HTTP请求,200,OK,线程组 1-7,text,true,,498,185,10,10,http://192.168.1.27:31553/api/pay/open/merchant/info?deviceCode=HY203047281956&skip=true,15,0,0
1599805677902,4,HTTP请求,20HTTP请求,200,O0,OK,线程组 1-1,text,true,,498,185,10,10,http://192.168.1.27:31553/api/pay/open/merchant/info?deviceCode=HY203047281956&skip=true,4,0,0
1599805677902,5,HTTP请求,200,OK,线程组 1-10,text,true,,498,185,10,10,http://192.168.1.27:31553/api/pay/open/merchant/info?deviceCode=HY203047281956&skip=true,5,0,0
1599805677892,15,HTTP请求,200,OK,线程组 1-6,text,true,,498,185,10,10,http://192.168.1.27:31553/api/pay/open/merchant/info?deviceCode=HY203047281956&skip=true,15,0,0
1599805677892,15,HTTP请求,200,OK,线程组 1-5,text,true,,498,185,10,10,http://192.168.1.27:31553/api/pay/open/merchant/info?deviceCode=HY203047281956&skip=true,14,0,0

进过搜索200 和50x状态码并没有匹配到数据,可能是jemete自己出了点问题导致了.整个更新过程完全没有停止服务.

image

总结

只要细粒度的去处理问题,所有问题都不是问题.

来源: 雨林博客(www.yl-blog.com)