K8s 服务优雅下线调试记录
K8s 服务优雅下线的背景
做每一件事情都是有前因后果的,我为什么要去调研优雅下线的解决方案,是因为一次线上发布版本的事故导致了订单数据没有存储。
在停服切换时,当前还有正在处理的订单已经支付的订单,此时已经调用三方扣款但是在生成订单的时候duang停服了。就出现了那么一个订单。因为发生的概率也挺小的,但是我们的支付平台24小时都有支付过来,没有发布版本的窗口期,只能热更新。
K8s 服务优雅下线的环境
环境: k8s 集群 包含 一个master、三个节点
调试工具: jemeter
测试目标:支付服务,目前支付服务有两个实例如下图所示
上图为使用kubesphere搭建的k8s集群的pay服务
k8s的服务暴露端口为31553
优雅下线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的请求配置信息,并且采集了每一条请求数据和聚合统计数据。
- 开始测试,当前跑了pay服务的两个实例
- 重新发版本测试,新启动了一个实例,由于启动的就绪检查还没有通过,实例的状态为没有就绪。原理是通过健康检查接口来判断服务是否可用。
- k8s准备删除老版本实例,当新的实例已经就绪的情况下,会自动删除老版本的一个实例,并且再次启动一个新版本的实例。删除实例不是立马删除,而是通过调用下线接口不再接受新的请求,并且等待一段时间让已经请求的数据处理完毕后停服。
- 和上面一样的状态,主要对比下面jemeter的请求一直没有停止,并且没有产生异常的。
- 老版本已经停止,并且在等待另一个新版本启动就绪。
- 第二个新版本启动完成,删除旧版的。
- 整个灰度(滚动)发版完成
最终在这场灰度发布过程出现了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自己出了点问题导致了.整个更新过程完全没有停止服务.
总结
只要细粒度的去处理问题,所有问题都不是问题.