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

`

文章目录

  • 前言
  • 一、环境描述
  • 二、模拟请求报错
  • 三、配置优雅上下线
    • 1.修改nacos配置
    • 2.修改depolyment配置
    • 3.重新apply deployment后测试
    • 4.整体(下单)测试流程验证是否生效
  • 四、期间遇到的问题


前言

我们在使用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)不间断的去调用我们的应用,然后发布我们的新应用会有失败的请求

##现在的deployment文件为如下##
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: data-center
  name: energy-order-api
  labels:
    app: energy-order-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: energy-order-api
  template:
    metadata:
      labels:
        app: energy-order-api
    spec:
      imagePullSecrets:
        - name: harbor-secret
      containers:
      - name: energy-order-api
        image: registry.xxxx/hqt-registry-pro/energy-order-api:P-1391-2023xxxx-15.47.45
        imagePullPolicy: IfNotPresent
        command: ["/bin/sh"]
        args: ["-c","java -jar 
              -Xmx2688m 
              -Xms2688m 
              -Xmn961m 
              -XX:MaxMetaspaceSize=512m 
              -XX:MetaspaceSize=512m 
              -Xloggc:/logs/gc-%t.log 
              -XX:+HeapDumpOnOutOfMemoryError  
              -XX:HeapDumpPath=/data/logs/heapdump_$MY_POD_NAME.hprof
              -XX:+PrintGCDetails 
              -XX:+UnlockExperimentalVMOptions 
              -XX:+UseCGroupMemoryLimitForHeap 
              -XX:NativeMemoryTracking=detail 
              -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 
              -Dapollo.meta=http://apollo-configservice.infrastructure.svc.cluster.local:8080 
              -Denv=pro /data/app.jar;/sbin/tini -s"]
        env:
        #获取pod实例名称,因为一个pod可能会有多个副本,所以需要根据名称来进行区分;
        - name: MY_POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        resources:
          requests:
            memory: "4Gi"
            cpu: "2000m"
          limits:
            memory: "4Gi"
            cpu: "2000m"
        volumeMounts:
        - name: energy-order-api-logs
          mountPath: /data/logs
          subPathExpr: $(MY_POD_NAME)
        ports:
        - containerPort: 80
        livenessProbe:
          httpGet:
            path: /actuator/info
            port: 80
          initialDelaySeconds: 70 #pod启动多长时间后开始去探测;
          periodSeconds: 5 #每隔多长时间去探测一次;
          failureThreshold: 6
        readinessProbe:
          httpGet:
            path: /actuator/info
            port: 80
          initialDelaySeconds: 70
          periodSeconds: 5
          failureThreshold: 6
      affinity:
        #节点亲和性
        nodeAffinity:
          #硬策略
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              #标签键
              - key: ResourcePool
                operator: In
                #标签值
                values:
                - core
        #pod反亲和性配置
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - energy-order-api
            topologyKey: kubernetes.io/hostname        
 
      volumes:
      - name: energy-order-api-logs
        persistentVolumeClaim:
          claimName: energy-order-api-logs

---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: energy-order-api-logs
  namespace: data-center
spec:
  accessModes:
  - ReadWriteMany
  storageClassName: alicloud-nas-subpath
  resources:
    requests:
      storage: 1Gi

Pod启动成功如下:
在这里插入图片描述

在这里插入图片描述
设置30个线程开始去请求我们服务的接口
可以看到目前的请求都是成功的,此时我们修改镜像apply可以模拟下我们的服务发布,当新Pod启动后 删除旧Pod时注意观察请求是否有报错!

在这里插入图片描述

在这里插入图片描述
可以发现在删除pod时的这个动作会出现错误请求,随后就会正常
在这里插入图片描述
以上就是发版(新Pod替换旧Pod)的过程中会出现的问题;

三、配置优雅上下线

1.修改nacos配置

[root@iZbp1iz5ayf044rk5cqq26Z ~]# vim /hqtbj/hqtwww/nacos_workspace/conf/application.properties
#打开注释并修改
### The interval to clean expired metadata, unit: milliseconds.
nacos.naming.clean.expired-metadata.interval=5000
### The expired time to clean metadata, unit: milliseconds.
nacos.naming.clean.expired-metadata.expired-time=5000

保存后重启nacos

2.修改depolyment配置

需要添加k8s的prestop钩子,以及设置强制关闭pod的时间要比sleep的时间长
'curl -X PUT "http://my-nacos.xxx.com/nacos/v1/ns/instance?serviceName=服务名称&ip=服务IP&port=服务端口&weight=0" && sleep 30 && PID= && kill -SIGTERM $PID && while ps -p $PID > /dev/null; do sleep 1; done'
terminationGracePeriodSeconds: 40
如上命令的作用:

  1. 使用curl将注册到nacos的实例权重设置为0,设置为0后就不会再接受请求了,也可以调用nacos的下线接口,只需要将weight=0改为enabled=false即可;
  2. 不接受请求后sleep睡眠30秒 用于处理已经发送过来的请求;
  3. 然后再kill -SIGTERM进行优雅的关闭服务;
  4. 等待Pod中的服务完全停止,如果在 terminationGracePeriodSeconds 40s内 (默认 30s) 还未完全停止,就发送 SIGKILL 信号强制杀死进程(kill -9)。
#添加prestop钩子
lifecycle:
          preStop:
            exec:
              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']  
#设置强制杀死Pod(kill -9)的时间,默认为30s   
terminationGracePeriodSeconds: 40

完整deployment内容如下

---
apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: data-center
  name: energy-order-api
  labels:
    app: energy-order-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: energy-order-api
  template:
    metadata:
      labels:
        app: energy-order-api
    spec:
      imagePullSecrets:
        - name: harbor-secret
      containers:
      - name: energy-order-api
        image: registry.xxxx/hqt-registry-pro/energy-order-api:P-1391-2023xxxx-15.47.45
        imagePullPolicy: IfNotPresent
        command: ["/bin/sh"]
        args: ["-c","java -jar 
              -Xmx2688m 
              -Xms2688m 
              -Xmn961m 
              -XX:MaxMetaspaceSize=512m 
              -XX:MetaspaceSize=512m 
              -Xloggc:/logs/gc-%t.log 
              -XX:+HeapDumpOnOutOfMemoryError  
              -XX:HeapDumpPath=/data/logs/heapdump_$MY_POD_NAME.hprof
              -XX:+PrintGCDetails 
              -XX:+UnlockExperimentalVMOptions 
              -XX:+UseCGroupMemoryLimitForHeap 
              -XX:NativeMemoryTracking=detail 
              -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 
              -Dapollo.meta=http://apollo-configservice.infrastructure.svc.cluster.local:8080 
              -Denv=pro /data/app.jar;/sbin/tini -s"]
        env:
        #获取pod实例名称,因为一个pod可能会有多个副本,所以需要根据名称来进行区分;
        - name: MY_POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        resources:
          requests:
            memory: "4Gi"
            cpu: "2000m"
          limits:
            memory: "4Gi"
            cpu: "2000m"
        volumeMounts:
        - name: energy-order-api-logs
          mountPath: /data/logs
          subPathExpr: $(MY_POD_NAME)
        ports:
        - containerPort: 80
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh","-c",'curl -X PUT "http://nacos.wonxxxnk.cc/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']
        terminationGracePeriodSeconds: 40
        livenessProbe:
          httpGet:
            path: /actuator/info
            port: 80
          initialDelaySeconds: 70 #pod启动多长时间后开始去探测;
          periodSeconds: 5 #每隔多长时间去探测一次;
          failureThreshold: 6
        readinessProbe:
          httpGet:
            path: /actuator/info
            port: 80
          initialDelaySeconds: 70
          periodSeconds: 5
          failureThreshold: 6
      affinity:
        #节点亲和性
        nodeAffinity:
          #硬策略
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              #标签键
              - key: ResourcePool
                operator: In
                #标签值
                values:
                - core
        #pod反亲和性配置
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - energy-order-api
            topologyKey: kubernetes.io/hostname        
 
      volumes:
      - name: energy-order-api-logs
        persistentVolumeClaim:
          claimName: energy-order-api-logs

---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: energy-order-api-logs
  namespace: data-center
spec:
  accessModes:
  - ReadWriteMany
  storageClassName: alicloud-nas-subpath
  resources:
    requests:
      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,并没进行接收请求
在这里插入图片描述
请求依旧报错
在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/480785.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

处理登录失效后提示多个错误

问题: 我的场景是后端规定,即使登录失效返回的code仍是200,然后data的code是999什么的; 原本代码: 修改版代码: 通过节 const NotLoginEvent () > {router.replace("/login");localStorage.clear();M…

队列——数据结构——day5

队列 队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。 队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头。假设队列是q(a1,a2,……,an),那么a1就是队头元…

银行数字人民币系统应用架构设计

2019年10月,01区块链联合数字资产研究院发布了《人民币3.0:中国央行数字货币运行框架与技术解析》,从数字货币界定和人民币发展历程出发,区分了央行数字货币与比特币、移动支付等的区别,全面介绍了央行数字货币的发展历…

C#,图论与图算法,有向图(Direct Graph)广度优先遍历(BFS,Breadth First Search)算法与源程序

1 图的广度优先遍历 图的广度优先遍历(或搜索)类似于树的广度优先遍历(参见本文的方法2)。这里唯一需要注意的是,与树不同,图可能包含循环,因此我们可能再次来到同一个节点。为了避免多次处理节点,我们使用布尔访问数组。为简单起见,假设所有顶点都可以从起始顶点到达…

ChatGPT智能聊天系统源码v2.7.6全开源Vue前后端+后端PHP

测试环境:Linux系统CentOS7.6、宝塔、PHP7.4、MySQL5.6,根目录public,伪静态thinkPHP,开启ssl证书 具有文章改写、广告营销文案、编程助手、办公达人、知心好友、家庭助手、出行助手、社交平台内容、视频脚本创作、AI绘画、思维导图等功能 ai通道:文心一言、MiniMax、智…

GraalVM详细安装及打包springboot、java、javafx使用教程(打包springboot2篇)

前言 在当前多元化开发环境下,Java作为一种广泛应用的编程语言,其应用部署效率与灵活性的重要性日益凸显。Spring Boot框架以其简洁的配置和强大的功能深受开发者喜爱,而JavaFX则为开发者提供了构建丰富桌面客户端应用的能力。然而&#xff…

Linux设备驱动开发 - 三色LED呼吸灯分析

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 展锐UIS7885呼吸灯介绍呼吸灯调试方法亮蓝灯亮红灯亮绿灯展锐UIS7885呼吸灯DTS配置ump9620 PMIC驱动ump9620中的LED呼吸灯驱动LED的tr…

python--容器、列表

1.python官方内置的容器 list: set: tuple: dict: 弱数据类语言通通没有数组,因为数组指的是 类型固定、大小固定、连续的内存空间。 2.链表: 非连续内存空间 python用的是双向链表 单向链表:优点:不浪费内存&#xf…

代码随想录day28(1)二叉树:二叉搜索树中的插入操作(leetcode701)

题目要求:给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据保证,新值和原始二叉搜索树中的任意节点值都不同。 思路:对于二叉搜索树来说&…

如何使用ospf (enps) 简单实践ospf协议

1. OSPF的基本概念 OSPF(Open Shortest Path First,开放式最短路径优先)是一种广泛应用于TCP/IP网络中的内部网关协议(Interior Gateway Protocol, IGP),主要用于在同一自治系统(Autonomous Sys…

SpringBoot项目集成XXL-job

文章目录 首先引入依赖配置信息配置类定义定时任务执行方法配置任务执行器配置任务执行计划 在集成 XXL-job 前,首先确保部署了 XXL-job 的 admin 服务, 如果还没有部署的话请参照 Docker安装部署XXL-Job 将 XXL-job 部署起来. 此时, XXL-job 已经部署好了, 下来一步一步的来集…

【Python 滑块不同的操作】对滑块进行处理,列如切割、还原、去除、无脑识别距离等等

文章日期:2024.03.23 使用工具:Python 类型:图片滑块验证的处理(不限于识别距离) 使用场景:? 文章全程已做去敏处理!!! 【需要做的可联系我】 AES解密处理&a…

BGP4+简介

定义 BGP是一种用于自治系统AS(Autonomous System)之间的动态路由协议,常用版本是BGP-4,BGP-4只能传递IPv4路由。针对IPv6的BGP4扩展,通常称为BGP4。 目的 BGP4用于在AS之间传递路由信息,并不是所有情况…

Python爬取歌曲宝音乐:轻松下载Jay的歌

歌曲宝是一个不用付费就能听jay的歌曲,但是每次都只能播放一首不方便,于是今天想把它下载下来,本地循环播放,它所用到的接口是某我的还不错哈 获取搜索接口 分析html请求接口,获取到的数据是直接渲染好的HTML内容&…

lvs+keepalived+nginx实现高可用

主机:192.168.199.132 备机:192.168.199.133 真实服务器1:192.168.199.134 真实服务器2:192.168.199.135 问题: 防火墙没关 132配置ipvsadm进行dr模式 132配置keepalived.conf 133配置ipvsadm进行dr模式 133配置ke…

【测试开发学习历程】计算机编程语言

前言: 学习完数据库,我们便要进入到编程语言的内容当中了。 这里先对编程语言写出大致的分类, 在这之后,我们会以Python为重点, 开始测试开发为重点的编程语言学习。 目录 1 计算机编程语言的发展 2 语言的分类…

高蛋白的猫粮品牌有哪些?

亲爱的猫友们,你们是否也在寻找一款高蛋白的猫粮,希望给自家的小猫咪提供最好的营养呢?🐱 在市面上,高蛋白的猫粮品牌确实不少,但要说到真正让人信赖的,福派斯鲜肉无谷猫粮无疑是一个不错的选择…

如何自己构建 Ollama 模型

如何自己构建 Ollama 模型 0. 引言1. 下载原始模型2. 创建 Modelfile 文件3. 构建 Ollama 模型4. 运行自构建的 Ollama 模型 0. 引言 针对模型新出的大模型,可能 Ollama Models Library 不提供,或者会在今后的某个时点提供。还有可能 Ollama Models Lib…

计算机视觉之三维重建(3)---单视几何

文章目录 一、问题提出二、无穷远点、无穷远线、无穷远平面2.1 2D空间2.2 3D空间 三、影消点和影消线3.1 2D平面上的无穷远点,无穷远线变换3.2 影消点3.3 影消线 四、单视重构 一、问题提出 1. 当摄像机标定后,内部参数 K K K 已知,外部参数…

R语言逻辑回归与lasso模型

一、数据描述 数据集heart_learning.csv与heart_test.csv是关于心脏病的数据集,heart_learning.csv是训练数据集,heart_test.csv是测试数据集。 变量名称变量说明age年龄sex性别,取值1代表男性,0代表女性pain 胸痛的类型&#x…