K8s+Nacos实现应用的优雅上下线【生产实践】

打印 上一主题 下一主题

主题 826|帖子 826|积分 2478

`

  

前言

我们在利用k8s部署应用的时间,虽然k8s是利用滚动升级的,先启动一个新Pod 等这个新Pod运行乐成后,再干掉旧Pod;在这个过程中Pod会不停吸收请求的,如果在Pod被干掉的那一刻恰好有一部门请求打进来了,那么Pod被杀死了,就不会给这个请求返回结果,就会导致客户端出现请求500错误,这样就做不到平滑升级了,我们要做的就是在Pod升级的时间不能或者只管制止这种环境;
我们公司利用的是java,中间件用的是nacos,应用在启动时会注册到nacos,然后走应用之间的内部调用,服务会不间断的向注册中心nacos发送自己的心跳(3s) 以及在每个 Pod 服务里本地也有一份缓存映射表(也有一个窗口时间更新30s),服务在停止的时间,天然也会在nacos中举行下线 但是如果有请求在应用下线的这个窗口期发起的话,就会出现K8s Pod 服务已下线,但是 Nacos 在窗口期之内注册列表未更新,导致请求达到一个根本不存在的旧服务里导致请求返回404或者旧请求已经打到旧服务里,但是高峰期时,步伐处理较慢,还没来及返回响应体,服务就被关闭了返回500;
我们利用的解决方案是,在应用下线的第一时间(Pod被删除)先举行在nacos的下线利用不让其担当新的请求,然后等候Pod已吸收的请求处理完成后 再举行删除Pod;
这里会利用到的知识以及必要自身考虑的点有:


  • k8s的prestop钩子(容器关闭前实行利用)
  • 必要判断自己应用处理的请求的时间(基本上30s内都能处理完成 如果不放心的话调整成50s 但是这样的话也会相应的增加上线时长,必要留意)
  • 必要在nacos(v2.x)中设置Nacos自动整理逾期服务的逾期时间(删除服务的元数据信息),防止请求过多/代码问题导致Pod的cpu打满 触发Pod的康健查抄后 Pod重启以后依然是下线状态(不可用) 这样就出大问题了;

一、环境描述

名称版本部署方式kubernetesv1.20.11二进制nacosv2.0.3集群模式 二、模拟请求报错

模拟请求报错就是不加任何设置直接利用测试脚本(这里用的是jmeter)不间断的去调用我们的应用,然后发布我们的新应用会有失败的请求
  1. ##现在的deployment文件为如下##
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5.   namespace: data-center
  6.   name: energy-order-api
  7.   labels:
  8.     app: energy-order-api
  9. spec:
  10.   replicas: 3
  11.   selector:
  12.     matchLabels:
  13.       app: energy-order-api
  14.   template:
  15.     metadata:
  16.       labels:
  17.         app: energy-order-api
  18.     spec:
  19.       imagePullSecrets:
  20.         - name: harbor-secret
  21.       containers:
  22.       - name: energy-order-api
  23.         image: registry.xxxx/hqt-registry-pro/energy-order-api:P-1391-2023xxxx-15.47.45
  24.         imagePullPolicy: IfNotPresent
  25.         command: ["/bin/sh"]
  26.         args: ["-c","java -jar
  27.               -Xmx2688m
  28.               -Xms2688m
  29.               -Xmn961m
  30.               -XX:MaxMetaspaceSize=512m
  31.               -XX:MetaspaceSize=512m
  32.               -Xloggc:/logs/gc-%t.log
  33.               -XX:+HeapDumpOnOutOfMemoryError  
  34.               -XX:HeapDumpPath=/data/logs/heapdump_$MY_POD_NAME.hprof
  35.               -XX:+PrintGCDetails
  36.               -XX:+UnlockExperimentalVMOptions
  37.               -XX:+UseCGroupMemoryLimitForHeap
  38.               -XX:NativeMemoryTracking=detail
  39.               -javaagent:/data/skywalking/skywalking-agent.jar=agent.service_name=energy-order-api,agent.instance_name=$MY_POD_NAME,collector.backend_service=internal-skywalking.xxxx.xxm:11800
  40.               -Dapollo.meta=http://apollo-configservice.infrastructure.svc.cluster.local:8080
  41.               -Denv=pro /data/app.jar;/sbin/tini -s"]
  42.         env:
  43.         #获取pod实例名称,因为一个pod可能会有多个副本,所以需要根据名称来进行区分;
  44.         - name: MY_POD_NAME
  45.           valueFrom:
  46.             fieldRef:
  47.               fieldPath: metadata.name
  48.         resources:
  49.           requests:
  50.             memory: "4Gi"
  51.             cpu: "2000m"
  52.           limits:
  53.             memory: "4Gi"
  54.             cpu: "2000m"
  55.         volumeMounts:
  56.         - name: energy-order-api-logs
  57.           mountPath: /data/logs
  58.           subPathExpr: $(MY_POD_NAME)
  59.         ports:
  60.         - containerPort: 80
  61.         livenessProbe:
  62.           httpGet:
  63.             path: /actuator/info
  64.             port: 80
  65.           initialDelaySeconds: 70 #pod启动多长时间后开始去探测;
  66.           periodSeconds: 5 #每隔多长时间去探测一次;
  67.           failureThreshold: 6
  68.         readinessProbe:
  69.           httpGet:
  70.             path: /actuator/info
  71.             port: 80
  72.           initialDelaySeconds: 70
  73.           periodSeconds: 5
  74.           failureThreshold: 6
  75.       affinity:
  76.         #节点亲和性
  77.         nodeAffinity:
  78.           #硬策略
  79.           requiredDuringSchedulingIgnoredDuringExecution:
  80.             nodeSelectorTerms:
  81.             - matchExpressions:
  82.               #标签键
  83.               - key: ResourcePool
  84.                 operator: In
  85.                 #标签值
  86.                 values:
  87.                 - core
  88.         #pod反亲和性配置
  89.         podAntiAffinity:
  90.           requiredDuringSchedulingIgnoredDuringExecution:
  91.           - labelSelector:
  92.               matchExpressions:
  93.               - key: app
  94.                 operator: In
  95.                 values:
  96.                 - energy-order-api
  97.             topologyKey: kubernetes.io/hostname        
  98.       volumes:
  99.       - name: energy-order-api-logs
  100.         persistentVolumeClaim:
  101.           claimName: energy-order-api-logs
  102. ---
  103. kind: PersistentVolumeClaim
  104. apiVersion: v1
  105. metadata:
  106.   name: energy-order-api-logs
  107.   namespace: data-center
  108. spec:
  109.   accessModes:
  110.   - ReadWriteMany
  111.   storageClassName: alicloud-nas-subpath
  112.   resources:
  113.     requests:
  114.       storage: 1Gi
复制代码
Pod启动乐成如下:


设置30个线程开始去请求我们服务的接口

可以看到现在的请求都是乐成的,此时我们修改镜像apply可以模拟下我们的服务发布,当新Pod启动后 删除旧Pod时留意观察请求是否有报错!


可以发现在删除pod时的这个动作会出现错误请求,随后就会正常

以上就是发版(新Pod更换旧Pod)的过程中会出现的问题;
三、设置优雅上下线

1.修改nacos设置

  1. [root@iZbp1iz5ayf044rk5cqq26Z ~]# vim /hqtbj/hqtwww/nacos_workspace/conf/application.properties
  2. #打开注释并修改
  3. ### The interval to clean expired metadata, unit: milliseconds.
  4. nacos.naming.clean.expired-metadata.interval=5000
  5. ### The expired time to clean metadata, unit: milliseconds.
  6. nacos.naming.clean.expired-metadata.expired-time=5000
复制代码
保存后重启nacos
2.修改depolyment设置

必要添加k8s的prestop钩子,以及设置逼迫关闭pod的时间要比sleep的时间长

terminationGracePeriodSeconds: 40
如上下令的作用:

  • 利用curl将注册到nacos的实例权重设置为0,设置为0后就不会再担当请求了,也可以调用nacos的下线接口,只必要将weight=0改为enabled=false即可;
  • 不担当请求后sleep就寝30秒 用于处理已经发送过来的请求;
  • 然后再kill -SIGTERM举行优雅的关闭服务;
  • 等候Pod中的服务完全停止,如果在 terminationGracePeriodSeconds 40s内 (默认 30s) 还未完全停止,就发送 SIGKILL 信号逼迫杀死进程(kill -9)。
  1. #添加prestop钩子
  2. lifecycle:
  3.           preStop:
  4.             exec:
  5.               command: ["/bin/sh","-c",'curl -X PUT "http://my-nacos.xxx.com/nacos/v1/ns/instance?serviceName=energy-order-api&ip=${POD_IP}&port=80&weight=0" && sleep 30 && PID=`pidof java` && kill -SIGTERM $PID && while ps -p $PID > /dev/null; do sleep 1; done']  
  6. #设置强制杀死Pod(kill -9)的时间,默认为30s   
  7. terminationGracePeriodSeconds: 40
复制代码
完整deployment内容如下
  1. ---
  2. apiVersion: apps/v1
  3. kind: Deployment
  4. metadata:
  5.   namespace: data-center
  6.   name: energy-order-api
  7.   labels:
  8.     app: energy-order-api
  9. spec:
  10.   replicas: 3
  11.   selector:
  12.     matchLabels:
  13.       app: energy-order-api
  14.   template:
  15.     metadata:
  16.       labels:
  17.         app: energy-order-api
  18.     spec:
  19.       imagePullSecrets:
  20.         - name: harbor-secret
  21.       containers:
  22.       - name: energy-order-api
  23.         image: registry.xxxx/hqt-registry-pro/energy-order-api:P-1391-2023xxxx-15.47.45
  24.         imagePullPolicy: IfNotPresent
  25.         command: ["/bin/sh"]
  26.         args: ["-c","java -jar
  27.               -Xmx2688m
  28.               -Xms2688m
  29.               -Xmn961m
  30.               -XX:MaxMetaspaceSize=512m
  31.               -XX:MetaspaceSize=512m
  32.               -Xloggc:/logs/gc-%t.log
  33.               -XX:+HeapDumpOnOutOfMemoryError  
  34.               -XX:HeapDumpPath=/data/logs/heapdump_$MY_POD_NAME.hprof
  35.               -XX:+PrintGCDetails
  36.               -XX:+UnlockExperimentalVMOptions
  37.               -XX:+UseCGroupMemoryLimitForHeap
  38.               -XX:NativeMemoryTracking=detail
  39.               -javaagent:/data/skywalking/skywalking-agent.jar=agent.service_name=energy-order-api,agent.instance_name=$MY_POD_NAME,collector.backend_service=internal-skywalking.xxxx.xxm:11800
  40.               -Dapollo.meta=http://apollo-configservice.infrastructure.svc.cluster.local:8080
  41.               -Denv=pro /data/app.jar;/sbin/tini -s"]
  42.         env:
  43.         #获取pod实例名称,因为一个pod可能会有多个副本,所以需要根据名称来进行区分;
  44.         - name: MY_POD_NAME
  45.           valueFrom:
  46.             fieldRef:
  47.               fieldPath: metadata.name
  48.         - name: POD_IP
  49.               valueFrom:
  50.                 fieldRef:
  51.                   apiVersion: v1
  52.                   fieldPath: status.podIP
  53.         resources:
  54.           requests:
  55.             memory: "4Gi"
  56.             cpu: "2000m"
  57.           limits:
  58.             memory: "4Gi"
  59.             cpu: "2000m"
  60.         volumeMounts:
  61.         - name: energy-order-api-logs
  62.           mountPath: /data/logs
  63.           subPathExpr: $(MY_POD_NAME)
  64.         ports:
  65.         - containerPort: 80
  66.         lifecycle:
  67.           preStop:
  68.             exec:
  69.               command:
  70.                 - /bin/sh
  71.                 - '-c'
  72.                 - >-
  73.                   curl -X PUT
  74.                   "http://nacosfat1.internal.dns:8848/nacos/v1/ns/instance?serviceName=`echo
  75.                   ${MY_POD_NAME} | awk -F'-' '{print
  76.                   $1"-"$2"-"$3}'`&ip=${POD_IP}&port=80&weight=0" && sleep 30
  77.                   && PID=`pidof java` && kill -SIGTERM $PID && while ps -p
  78.                   $PID > /dev/null; do sleep 1; done
  79.         terminationGracePeriodSeconds: 40
  80.         livenessProbe:
  81.           httpGet:
  82.             path: /actuator/info
  83.             port: 80
  84.           initialDelaySeconds: 70 #pod启动多长时间后开始去探测;
  85.           periodSeconds: 5 #每隔多长时间去探测一次;
  86.           failureThreshold: 6
  87.         readinessProbe:
  88.           httpGet:
  89.             path: /actuator/info
  90.             port: 80
  91.           initialDelaySeconds: 70
  92.           periodSeconds: 5
  93.           failureThreshold: 6
  94.       affinity:
  95.         #节点亲和性
  96.         nodeAffinity:
  97.           #硬策略
  98.           requiredDuringSchedulingIgnoredDuringExecution:
  99.             nodeSelectorTerms:
  100.             - matchExpressions:
  101.               #标签键
  102.               - key: ResourcePool
  103.                 operator: In
  104.                 #标签值
  105.                 values:
  106.                 - core
  107.         #pod反亲和性配置
  108.         podAntiAffinity:
  109.           requiredDuringSchedulingIgnoredDuringExecution:
  110.           - labelSelector:
  111.               matchExpressions:
  112.               - key: app
  113.                 operator: In
  114.                 values:
  115.                 - energy-order-api
  116.             topologyKey: kubernetes.io/hostname        
  117.       volumes:
  118.       - name: energy-order-api-logs
  119.         persistentVolumeClaim:
  120.           claimName: energy-order-api-logs
  121. ---
  122. kind: PersistentVolumeClaim
  123. apiVersion: v1
  124. metadata:
  125.   name: energy-order-api-logs
  126.   namespace: data-center
  127. spec:
  128.   accessModes:
  129.   - ReadWriteMany
  130.   storageClassName: alicloud-nas-subpath
  131.   resources:
  132.     requests:
  133.       storage: 1Gi
复制代码
3.重新apply deployment后测试



如上,现在Pod已经启动乐成并且在nacos中也是可用状态
开始测试

如上当开始停止旧pod时, 会先调用我们设置的prestop钩子 如下 先把nacos中旧pod的权重改为0 不让其担当请求,然后再处理已担当的请求最后彻底关闭pod

可以看到整个流程下来是没有发现有请求失败的!

这是单对这一个order服务举行的测试,接下来必要走一遍整体下单的流程,调用多个服务举行测试的同时重新发布这个order服务看是否有失败的请求
4.整体(下单)测试流程验证是否见效

已启动的pod以及nacos状态如下:


开始测试


如上可以发现在停止Pod时,跟上面的结果是一样的,都是先把nacos中旧pod的权重改为0,然后等候处理请求再彻底关闭pod;
如下测试,整体一个下单流程再发布期间也是不回受到影响的,无报错

四、期间碰到的问题

如果不设置nacos整理元数据信息的话,会导致当cpu/内存利用凌驾限制而导致康健查抄重启时(Pod实例自身重启而不是会起一个新pod),会出现即使pod重启完nacos里注册的服务权重是0/下线,导致服务直接不可用,只能手动再去启用!!所以下面在nacos的设置肯定要举行利用!

如下:
服务因康健查抄失败开始重启



此时的请求开始报错

服务的权重变为0,不吸收请求

pod重启完成

发现nacos里注册的服务权重依然为0,并没举行吸收请求

请求仍然报错



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

tsx81429

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表