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

这篇具有很好参考价值的文章主要介绍了K8s+Nacos实现应用的优雅上下线【生产实践】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

`


前言

我们在使用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重启以后依然是下线状态(不可用) 这样就出大问题了;
    developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生

一、环境描述

名称 版本 部署方式
kubernetes v1.20.11 二进制
nacos v2.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启动成功如下:
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生

developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
设置30个线程开始去请求我们服务的接口developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
可以看到目前的请求都是成功的,此时我们修改镜像apply可以模拟下我们的服务发布,当新Pod启动后 删除旧Pod时注意观察请求是否有报错!

developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生

developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
可以发现在删除pod时的这个动作会出现错误请求,随后就会正常
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
以上就是发版(新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的时间长
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
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后测试

developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
如上,现在Pod已经启动成功并且在nacos中也是可用状态

开始测试
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
如上当开始停止旧pod时, 会先调用我们配置的prestop钩子 如下 先把nacos中旧pod的权重改为0 不让其接受请求,然后再处理已接受的请求最后彻底关闭pod

developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
可以看到整个流程下来是没有发现有请求失败的!
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生

这是单对这一个order服务进行的测试,接下来需要走一遍整体下单的流程,调用多个服务进行测试的同时重新发布这个order服务看是否有失败的请求

4.整体(下单)测试流程验证是否生效

已启动的pod以及nacos状态如下:
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
开始测试
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
如上可以发现在停止Pod时,跟上面的结果是一样的,都是先把nacos中旧pod的权重改为0,然后等待处理请求再彻底关闭pod;

如下测试,整体一个下单流程再发布期间也是不回受到影响的,无报错
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生

四、期间遇到的问题

如果不配置nacos清理元数据信息的话,会导致当cpu/内存使用超过限制而导致健康检查重启时(Pod实例自身重启而不是会起一个新pod),会出现即使pod重启完nacos里注册的服务权重是0/下线,导致服务直接不可用,只能手动再去启用!!所以下面在nacos的配置一定要进行使用!
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生

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

developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
此时的请求开始报错
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
服务的权重变为0,不接收请求
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
pod重启完成
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
发现nacos里注册的服务权重依然为0,并没进行接收请求
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生
请求依旧报错
developement 优雅下线 nacos,Kubernetes学习分享;,kubernetes,容器,云原生文章来源地址https://www.toymoban.com/news/detail-856889.html


到了这里,关于K8s+Nacos实现应用的优雅上下线【生产实践】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • React 前端应用中快速实践 OpenTelemetry 云原生可观测性(SigNoz/K8S)

    OpenTelemetry 可用于跟踪 React 应用程序的性能问题和错误。您可以跟踪从前端 web 应用程序到下游服务的用户请求。 OpenTelemetry 是云原生计算基金会(CNCF)下的一个开源项目,旨在标准化遥测数据的生成和收集。已成为下一代可观测平台的事实标准。 React (也称为 React.js 或 ReactJ

    2024年02月14日
    浏览(37)
  • 微服务优雅上下线的实践方法

    本文介绍了微服务优雅上下线的实践方法及原理,包括适用于 Spring 应用的优雅上下线逻辑和服务预热,以及使用 Docker 实现无损下线的 Demo。同时,本文还总结了优雅上下线的价值和挑战。 作者简介 颜松柏 腾讯云微服务架构师 拥有超过10年的 IT 从业经验,精通软件架构设计

    2024年02月16日
    浏览(18)
  • 构建 dotnet&vue 应用镜像->推送到 Nexus 仓库->部署为 k8s 服务实践

    前面分享了 k8s 的部署安装,本篇来点实操,将会把一个 .net core + vue 的项目(zhontai),打包构建成 docker 镜像,推送到 nexus 镜像仓库,并部署到 k8s 中 要实现项目的部署,除了准备要部署的环境(k8s),还需要准备项目所用到的各中间件,本文旨在分享部署的一个整体流程

    2024年02月03日
    浏览(45)
  • spring cloud gateway k8s优雅启停

    通过配置readiness探针和preStop hook,实现优雅启动和停止(滚动部署) 1. k8s工作负载配置 2. 网关改造 经过测试发现,可以实现请求0失败

    2024年03月22日
    浏览(50)
  • K8S--部署Nacos

    原文网址:K8S--部署Nacos-CSDN博客 本文介绍K8S部署Nacos的方法。Nacos版本是:2.2.3。 部署方案 本文为了简单,使用此部署方式:使用本地pv+configmap,以embedded模式部署单机nacos。以nodePort方式暴露端口。 正式环境可以这样部署:使用nfs,以mysql方式部署集群nacos,以ingress方式暴露

    2024年01月20日
    浏览(33)
  • k8s集群生产环境的问题处理

    2 k8s上的服务均无法访问 执行命令 kubectl get pods -ALL ,k8s集群中的服务均是running状态 1 kuboard 网页无法访问 kuboard无法通过浏览器访问,但是查看端口是被占用的

    2024年02月12日
    浏览(38)
  • 【Nacos】基于k8s容器化部署Nacos集群

    近期,在机器上部署了三个节点的nacos集群服务用于几个小型微服务的注册配置中心,并使用了Nginx简单代理了一下,随即简单研究了下集群部署分布式部署稍微提高可用性。部署完后能够正常使用,但是发现一个问题,刷新Nacos集群节点列表,总会有一个或者两个节点时不时

    2024年02月11日
    浏览(60)
  • k8s部署单节点nacos

    单节点         由于nacos server需要mysql数据库,我们可以先把官方提供的mysql 脚本准备好,如果直接使用官方提供的镜像nacos/nacos-server:latest, 那么在启动时会自动帮你在mysql数据库里初始化好nacos需要的数据库,不需要再执行如下SQL脚本。         注:以下SQL为nacos官方提供运

    2024年02月05日
    浏览(54)
  • k8s部署nacos集群模式

    主要是在k8s集群部署nacos集群(3节点),数据库使用外置的 mysql ,由于有现成的阿里云RDS,就直接使用了。相比官方的在k8s内创建数据库的方案更方便。所有nacos配置 全部保存在数据库中,不用担心重启掉线等异常导致配置文件丢失。 Nacos及所有相关服务都部署在default 命名空间

    2024年02月16日
    浏览(41)
  • 最后的组合:K8s 1.24 基于 Hekiti 实现 GlusterFS 动态存储管理实践

    知识点 定级: 入门级 GlusterFS 和 Heketi 简介 GlusterFS 安装部署 Heketi 安装部署 Kubernetes 命令行对接 GlusterFS 实战服务器配置(架构 1:1 复刻小规模生产环境,配置略有不同) 主机名 IP CPU 内存 系统盘 数据盘 用途 ks-master-0 192.168.9.91 2 4 50 100 KubeSphere/k8s-master ks-master-1 192.168.9.92 2 4 5

    2024年02月09日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包