K8S为何杀死我的应用

首发公众号:二进制社区,转载联系:binary0101@126.com

导读

"K8S为我们提供自动部署调度应用的能力,并通过健康检查接口自动重启失败的应用,确保服务的可用性,但这种自动运维在某些特殊情况下会造成我们的应用陷入持续的调度过程导致业务受损,本文就生产线上一个核心的平台应用被K8S频繁重启调度问题展开剖解,抽丝剥茧一步步从系统到应用的展开分析,最后定位到代码层面解决问题"


现象

在搭建devops基础设施后,业务已经全盘容器化部署,并基于k8s实现自动调度,但个别业务运行一段时间后会被k8s自动重启,且重启的无规律性,有时候发生在下午,有时发生在凌晨,从k8s界面看,有的被重启了上百次:

image


分析

平台侧分析:了解平台的重启策略

k8s是根据pod yaml里定义的重启策略执行重启,这个策略通过: .spec.restartPolicy 进行设置,支持以下三种策略:


    • Always:当容器终止退出后,总是重启容器,默认策略。
    • Onfailure:当容器种植异常退出(退出码非0)时,才重启容器。
    • Never:当容器终止退出时,才不重启容器。


出问题的应用是走CICD自动打包发布,Yaml也是CD环节自动生成,并没有显示指定重启策略,所以默认采用Always策略,那么k8s在哪些情况会触发重启呢,主要有以下场景:


    • 1. POD正常退出
    • 2. POD异常退出
    • 3. POD使用从CPU超过yaml里设置的CPU上限,或者超过容器所在namespace里配置的CPU上限
    • 4. POD使用从内存超过yaml里设置的Memory上限,或者超过容器所在namespace里配置的Memory上限
    • 5. 运行时宿主机的资源无法满足POD的资源(内存 CPU)时会自动调度到其他机器,也会出现重启次数+1
    • 6. 创建POD时指定的image找不到或者没有node节点满足POD的资源(内存 CPU)需求,会不断重启


出问题的应用正常运行一段时间才出现的重启,并且POD本身的Yaml文件以及所在的namespace并没设置CPU上限,那么可以排除:1 3 4 6, 业务是采用Springboot开发的,如果无故退出,JVM本身会产生dump文件,但由重启行为是K8s自己触发的,即使POD里产生里dump文件,因为运行时没有把dump文件目录映射到容器外面,所以没法去查看上次被重启时是否产生里dump文件,所以2 5都有可能导致k8s重启该业务,不过k8s提供命令可以查看POD上一次推出原因,具体命令如下:


NAMESPACE=prod
SERVER=dts
POD_ID=$(kubectl get pods -n ${NAMESPACE} |grep ${SERVER}|awk '{print $1}')
kubectl describe pod $POD_ID -n ${NAMESPACE}


命令运行结果显示POD是因为memory使用超限,被kubelet组件自动kill重启(如果reason为空或者unknown,可能是上述的原因2或者是不限制内存和CPU但是该POD在极端情况下被OS kill,这时可以查看/var/log/message进一步分析原因),CICD在创建业务时默认为每个业务POD设置最大的内存为2G,但在基础镜像的run脚本中,JVM的最大最小都设置为2G:

exec java -Xmx2g -Xms2g -jar  ${WORK_DIR}/*.jar


应用侧分析:剖析解JVM的运行状况


在分析应用运行的环境和,我们进一步分析应用使用的JVM本身的状态,首先看下JVM内存使用情况

命令: jmap -heap {PID}

image


JVM申请的内存: (eden)675.5+(from)3.5+(to)3.5+(OldGeneration)1365.5=2048mb

理论上JVM一启动就会OOMKill,但事实是业务运行一段时间后才被kill,虽然JVM声明需要2G内存,但是没有立即消耗2G内存,通过top命令查看:

image

PS: top和free命令在docker里看到的内存都是宿主机的,要看容器内部的内存大小和使用,可以使用下列命令:

cat /sys/fs/cgroup/memory/memory.limit_in_bytes

当配-Xmx2g -Xms2g时,虚拟机会申请2G内存,但提交的页面在首次访问之前不会消耗任何物理存储,该业务进程当时实际使用的内存为1.1g,随着业务运行,到一定时间后JVM的使用内存会逐步增加,直到达到2G被kill。

内存管理相关文章推荐:

Reserving and Committing Memory

JvmMemoryUsage


代码级分析: 剖解问题的根源

执行命令:

jmap -dump:format=b,file=./dump.hprof [pid]

导入JvisualVM分析,发现里面有大量的Span对象未被回收,未被回收的原因是被队列里item对象引用:

image

隔断时间执行:

jmap -histo pid |grep Span

发现span对象个数一直在增加,span属于业务工程依赖的分布式调用链追踪系统DTS里的对象,DTS是一个透明化无侵入的基础系统,而该业务也没有显示持有Span的引用,在DTS的设计里,Span是在业务线程产生,然后放入阻塞队列,等待序列化线程异步消费,生产和消费代码如下:

image

从以上代码看,Span在持续增加,应该就是消费者线程本身的消费速度小于了生产者的速度,消费线程执行的消费逻辑是顺序IO写盘,按照ECS普通盘30-40m的IOPS算,每个Span通过dump看到,平均大小在150byte,理论上每秒可以写:30*1024*1024/150=209715,所以不应该是消费逻辑导致消费率降缓,再看代码里有个sleep(50)也就是每秒最多可以写20个Span,该业务有个定时任务在运行,每次会产生较多的Span对象,且如果此时有其他业务代码在运行,也会产生大量的Span,远大于消费速度,所以出现了对象的积压,随着时间推移,内存消耗逐步增大,导致OOMKill。dump该业务的线程栈:

jstack  pid >stack.txt

却发现有两个写线程,一个状态始终是waiting on condition,另一个dump多次为sleep:

image

但是代码里是通过Executors.newSingleThreadExecutor(thf);起的单线程池,怎么会出现两个消费者呢? 进一步查看代码记录,原来始终11月份一次修改时把发送后端的逻辑集成到核心代码里,该功能在之前的版本里采用外部jar依赖注入的方式自动装配的,这样在现在的版本中会出现两个Sender对象,其中自动创建的Sender对象没有被DTS系统引用,他里面的队列始终未empty,导致旗下的消费者线程始终阻塞,而内置的Sender对象因为Sleep(50)导致消费速度下降从而出现堆积,Dump时是无法明确捕获到他的running状态,看上去一直在sleep,通过观察消费线程系列化写入的文件,发现数据一直在写入,说明消费线程确实是在运行的.

image


通过代码提交记录了解到,上上个版本业务在某些情况会产生大量的Span,Span的消费速度非常快,会导致该线程CPU飙升的比较厉害,为了缓解这种情况,所以加了sleep,实际上发现问题后业务代码已经进行优化,DTS系统是不需要修改的,DTS应是发现问题,推动业务修复和优化,基础系统的修改应该非常慎重,因为影响面非常广。

针对POD的最大内存等于虚拟机最大内存的问题,通过修改CD代码,默认会在业务配置的内存大小里加200M,为什么是200M不是更多呢?因为k8s会计算当前运行的POD的最大内存来评估当前节点可以容量多少个POD,如果配置为+500m或者更多,会导致K8S认为该节点资源不足导致浪费,但也不能过少过少,因为应用除了本身的代码外,还会依赖部分第三方共享库等,也可能导致Pod频繁重启.


总结

上述问题的根因是人为降低了异步线程的消费速度,导致消息积压引起内存消耗持续增长导致OOM,但笔者更想强调的是,当我们把应用部署到K8S或者Docker时,POD和Docker分配的内存需要比应用使用的最大内存适当大一些,否则就会出现能正常启动运行,但跑着跑着就频繁重启的场景,如问题中的场景,POD指定里最大内存2G,理论上JVM启动如果立即使用里2G肯定立即OOM,开发或者运维能立即分析原因,代价会小很多,但是因为现代操作系统内存管理都是VMM(虚拟内存管理)机制,当JVM参数配置为: -Xmx2g -Xms2g时,虚拟机会申请2G内存,但提交的页面在首次访问之前不会消耗任何物理存储,所以就出现理论上启动就该OOM的问题延迟到应用慢慢运行直到内存达到2G时被kill,导致定位分析成本非常高。另外,对于JVM dump这种对问题分析非常重要的日志,一定要映射存储到主机目录且保证不被覆盖,不然容器销毁时很难去找到这种日志。


更多深度文章,关注:二进制社区
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 非常感谢您的问题。Kubernetes是一种可扩展的开源容器编排和管理平台,它可以帮助我们轻松地管理和部署应用程序。Spring Boot是一种轻量级的Java框架,可以帮助我们快速构建可靠的应用程序。在Kubernetes上部署Spring Boot应用程序需要执行以下步骤: 1. 创建一个Docker镜像,并将Spring Boot应用程序打包到Docker镜像中。 2. 创建一个Kubernetes Deployment对象,定义应用程序的容器镜像和其他配置。 3. 创建一个Kubernetes Service对象,以公开应用程序并在群集中暴露其端点。 4. 如果需要,可以创建其他Kubernetes对象,例如ConfigMaps和Secrets,以管理应用程序的配置和敏感信息。 在完成这些步骤后,您的Spring Boot应用程序将在Kubernetes集群中成功部署和运行。希望这个回答能够帮助到您! ### 回答2: Kubernetes(简称为K8s)是一个用于容器编排和管理的开源平台,它可以帮助我们轻松地部署和扩展应用程序。Spring Boot是一个用于构建独立的、生产级的Java应用程序的框架。在K8s中部署Spring Boot应用程序需要以下几个步骤: 1. 编写Dockerfile:首先,我们需要在项目根目录下创建一个Dockerfile文件。在Dockerfile中,我们可以指定基础镜像、添加应用程序文件和依赖项,并设置应用程序的启动命令。 2. 构建Docker镜像:使用Dockerfile,我们可以构建一个镜像,该镜像包含了我们的Spring Boot应用程序和所有的依赖项。我们可以使用Docker命令行工具或Docker客户端来构建镜像。 3. 将镜像推送到镜像仓库:构建完成后,可以将镜像推送到镜像仓库,以便在K8s集群中进行访问和部署。常见的镜像仓库包括Docker Hub、私有镜像仓库等。 4. 创建K8s部署文件:接下来,我们需要创建K8s的部署文件,该文件定义了应用程序的部署配置,例如容器数量、资源限制等。部署文件可以使用YAML或JSON格式编写。 5. 部署应用程序:使用kubectl命令行工具或K8s API,我们可以将部署文件应用K8s集群中,从而部署我们的Spring Boot应用程序。K8s将自动为我们创建和管理应用程序的Pods、Services和其他相关资源。 6. 监控和扩展:一旦应用程序成功部署,我们可以使用K8s提供的监控和扩展功能来监视和调整应用程序的运行。K8s提供了各种工具和指标,可以帮助我们实现应用程序的自动扩展和高可用。 通过以上步骤,我们可以在K8s集群中轻松部署和管理Spring Boot应用程序。K8s提供了强大的容器编排和管理功能,可以帮助我们构建可靠、高可用的应用程序环境。 ### 回答3: K8s(Kubernetes)是一种开源的容器编排平台,它可以帮助我们更方便地部署、管理和扩展容器化应用。Spring Boot是一种快速开发、便捷部署的Java框架,它提供了一种简化Spring应用开发的方式。 在将Spring Boot应用部署到K8s上时,我们可以按照以下步骤进行操作: 1. 创建Docker镜像:首先,我们需要将Spring Boot应用打包成一个Docker镜像。我们可以使用Maven或Gradle构建工具来执行此操作。在创建镜像时,我们需要指定容器运行所需的基础镜像、将Spring Boot应用的JAR文件添加到镜像中,并配置容器的启动命令。 2. 配置K8s资源:接下来,我们需要定义一组K8s资源,用于描述如何部署和运行Spring Boot应用。这些资源包括Deployment、Service和Ingress。Deployment用于定义应用的部署规则,Service用于暴露应用的网络访问入口,Ingress则用于将外部请求路由到应用的Service。 3. 应用部署:在配置完成后,我们可以使用Kubectl命令将应用部署到K8s集群中。Kubectl是Kubernetes的命令行工具,它可以与K8s API进行交互,管理集群中的资源。通过执行`kubectl create`或`kubectl apply`命令,可以创建或更新我们定义的K8s资源。 4. 监控和扩展:一旦应用部署成功,我们可以使用K8s提供的监控工具来监控应用的性能和运行状态。K8s还提供了自动伸缩的功能,可以根据应用的负载情况自动调整副本数量,以提供更好的性能和可用性。 总结来说,将Spring Boot应用部署到K8s上可以帮助我们更好地管理和扩展应用。通过将应用打包成Docker镜像,并使用K8s的资源配置和管理工具,我们可以轻松地将应用部署到K8s集群中,并享受K8s提供的自动扩展和监控功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值