Allbet Gaming

记一次k8s pod频仍重启的优化之旅

Allbet登录网址 2021年09月26日 科技 31 0

2022卡塔尔世界杯www.22223388.com)实时更新发布最新最快的2022卡塔尔世界杯网址、2022卡塔尔世界杯会员线路、2022卡塔尔世界杯备用登录网址、2022卡塔尔世界杯手机管理端、2022卡塔尔世界杯手机版登录网址、2022卡塔尔世界杯皇冠登录网址。

要害词:k8s、jvm、高可用

 

1.靠山

最近有运维反馈某个微服务频仍重启,客户映像稀奇欠好,需要我们尽快看一下。

 

听他说完我立马到监控平台去看这个服务的运行情形,确实重启了许多次。对于手艺职员来说,这既是压力也是动力,大多数时刻我们都是陶醉在单调的营业开发中,对自我的提升有限,久而久之可能会陷入一种恬静区,遇到这种救火案例一时间会有点无所适从,然则没关系,究竟......

 

“我只是收了火,但没有熄炉”,借用影戏中的一句话表达一下此时的心情。

 

2.初看日志

我立刻就看这个服务的运行日志,内里有大量的oom异常,如下:

org.springframework.web.util.NestedServletException: Handler dispatch failed; 
nested exception is java.lang.OutOfMemoryError: GC overhead limit exceeded

整个服务基本可以说处于不能用状态,任何请求过来险些都市报oom,然则这跟重启有什么关系呢?是谁触发了重启呢?这里我暂时卖个关子,后面举行解答。 

 

3.k8s康健检查先容

我们的服务部署在k8s中,k8s可以对容器执行定期的诊断,要执行诊断,kubelet 挪用由容器实现的 Handler (处置程序)。有三种类型的处置程序:

  • ExecAction:在容器内执行指定数令。若是下令退出时返回码为 0 则以为诊断乐成。

  • TCPSocketAction:对容器的 IP 地址上的指定端口执行 TCP 检查。若是端口打开,则诊断被以为是乐成的。

  • HTTPGetAction:对容器的 IP 地址上指定端口和路径执行 HTTP Get 请求。若是响应的状态码大于即是 200 且小于 400,则诊断被以为是乐成的。

每次探测都将获得以下三种效果之一:

  • Success(乐成):容器通过了诊断。

  • Failure(失败):容器未通过诊断。

  • Unknown(未知):诊断失败,因此不会接纳任何行动。

针对运行中的容器,kubelet 可以选择是否执行以下三种探针,以及若何针对探测效果作出反映:

  • livenessProbe:指示容器是否正在运行。若是存活态探测失败,则 kubelet 会杀死容器, 而且容器将凭证其重启计谋决议未来。若是容器不提供存活探针, 则默认状态为 Success。

  • readinessProbe:指示容器是否准备好为请求提供服务。若是停当态探测失败, 端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址。初始延迟之前的停当态的状态值默以为 Failure。若是容器不提供停当态探针,则默认状态为 Success。

  • startupProbe: 指示容器中的应用是否已经启动。若是提供了启动探针,则所有其他探针都市被 禁用,直到此探针乐成为止。若是启动探测失败,kubelet 将杀死容器,而容器依其 重启计谋举行重启。若是容器没有提供启动探测,则默认状态为 Success。

以上探针先容内容泉源于https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-lifecycle/#container-probes

 

看完探针的相关先容,可以基本回覆上面的疑点“oom和重启有什么关系?”,是livenessProbe的锅,简朴形貌一下为什么:

  1. 服务启动;

  2. k8s通过livenessProbe中设置的康健检查Handler做定期诊断(我们设置的是HttpGetAction);

  3. 由于oom以是HttpGetAction返回的http status code是500,被k8s认定为Failure(失败)-容器未通过诊断;

  4. k8s以为pod不康健,决议“杀死”它然后重新启动。

 

这是服务的Deployment.yml中关于livenessProbe和restartPolicy的设置

livenessProbe:
  failureThreshold: 5
  httpGet:
    path: /health
    port: 8080
    scheme: HTTP
  initialDelaySeconds: 180
  periodSeconds: 20
  successThreshold: 1
  timeoutSeconds: 10

restartPolicy: Always

  

4.第一次优化

内存溢出无外乎内存不够用了,而这种不够用又大略分两种情形:

  1. 存在内存泄露情形,原本应该整理的工具然则并没有被整理,好比HashMap以自界说工具作为Key时对hashCode和equals方式处置不那时可能会发生;

  2. 内存确实不够用了,好比接见量突然上来了;

由于我们这个是一个老服务,而且在多个客户私有化环境都部署过,都没出过这个问题,以是我直接清扫了内存泄露的情形,那就将眼光投向第二种“内存确实不够用”,通过对比接见日志和询问营业职员后得知最近客户在鼎力推广系统,以是接见量确实是上来了。

 

“不要一最先就陷入手艺职员的固化头脑,以为是程序存在问题”

 

知道了缘故原由那解决手段也就很粗暴了,加内存呗,分分钟改完重新公布。

 

 

终于公布完成,我打开监控平台查看服务的运行情形,这越日志里确实没有oom的字样,本次优化以迅雷不及掩耳之势这么快就完了?果真是我想多了,一阵事后我眼睁睁看着pod再次重启,但诡异的是程序日志里没有oom,这一次是什么造成了它重启呢?

 

我使用kubectl describe pod下令查看一下pod的详细信息,重点关注Last State,内里包罗上一次的退出缘故原由和退回code。

 

可以看到上一次退出是由于OOMKilled,字面意思就是pod由于内存溢出被kill,这里的OOMKilled和之条件到的程序日志中输出的oom异常可万万不要混为一谈,若是pod 中的limit 资源设置较小,会运行内存不足导致 OOMKilled,这个是k8s层面的oom,这里借助官网的文档顺便先容一下pod和容器中的内存限制。

 

以下pod内存限制内容泉源于https://kubernetes.io/zh/docs/tasks/configure-pod-container/assign-memory-resource/ 

要为容器指定内存请求,请在容器资源清单中包罗 resources:requests 字段。同理,要指定内存限制,请包罗 resources:limits

以下deployment.yml将确立一个拥有一个容器的 Pod。容器将会请求 100 MiB 内存,而且内存会被限制在 200 MiB 以内:

apiVersion: v1
kind: Pod
metadata:
  name: memory-demo
  namespace: mem-example
spec:
  containers:
  - name: memory-demo-ctr
    image: polinux/stress
    resources:
      limits:
        memory: "200Mi"
      requests:
        memory: "100Mi"
    command: ["stress"]
    args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]

当节点拥有足够的可用内存时,容器可以使用其请求的内存。然则,容器不允许使用跨越其限制的内存。若是容器分配的内存跨越其限制,该容器会成为被终止的候选容器。若是容器继续消耗超出其限制的内存,则终止容器。若是终止的容器可以被重启,则 kubelet 会重新启动它,就像其他任何类型的运行时失败一样。

回归到我们的场景中来讲,虽然把jvm内存提高了,然则其运行环境(pod、容器)的内存限制并没有提高,以是自然是达不到预期状态的,解决方式也是很简朴了,提高deployment.yml中memory的限制,好比jvm中-Xmx为1G,那memory的limits至少应该大于1G。

 

至此,第一次优化算是真正告一段落。

 

5.第二次优化

第一次优化只给我们带来了短暂的镇静,重启次数确实有所下降,然则离我们追求的目的照样相差甚远,以是亟需来一次更彻底的优化,来捍卫手艺职员的尊严,究竟我们都是头顶别墅的人。

 头顶撑不住的时刻,吃点好的补补

  

上一次频仍重启是由于内存不足导致大量的oom异常,最终k8s康健检查机制以为pod不康健触发了重启,优化手段就是加大jvm和pod的内存,这一次的重启是由于什么呢?

 

前面说过k8s对http形式的康健检查地址做探测时,若是响应的状态码大于即是 200 且小于 400,则诊断被以为是乐成的,否则就以为失败,这里着实忽略了一种极其普遍的情形“超时”,若是超时了也一并会归为失败。

欧博会员开户www.aLLbetgame.us)是欧博集团的官方网站。欧博官网开放Allbet注册、Allbe代理、Allbet电脑客户端、Allbet手机版下载等业务。

 

为什么这里才引出超时呢,由于之前日志中有大量的报错,很直观的可以遐想到康健检查一定失败,反观这次并没有直接证据,强制着施展想象力(着实厥后知道通过kubectl describe pod是可以直接考察到超时这种情形的)。

现在我们就去反推有哪些情形会造成超时:

1.cpu 100%,这个之前确实遇到过一次,是由于宿主机cpu 100%,造成大量pod住手响应,最终大面积重启;

2.网络层面出了问题,好比tcp行列被打满,导致请求得不四处置。感兴趣的可以参考我之前的文章CLOSE_WAIT导致服务假死;

3.web容器好比tomcat、jetty的线程池饱和了,这时厥后的义务会聚积在线程池的行列中;

4.jvm卡顿了,好比闪开发心惊胆战的fullgc+stw;

 

以上四种只是通过我的认知枚举的,水平有限,更多情形迎接人人弥补。

现在我们逐一清扫,揪出元凶

 

1.看了监控宿主机负载正常,cpu正常,以是清扫宿主机的问题;

2.ss -lnt查看tcp行列情形,并没有聚积、溢出情形,清扫网络层面问题;

3.jstack查看线程情形,worker线程稍多但没有到最大值,清扫线程池满的情形;

4.jstat gcutil查看gc情形,gc对照严重,暮年月gc执行一次平均耗时1秒左右,延续时间50s到60s左右嫌疑异常大。

 

通过上面的清扫法暂定是gc带来的stw导致jvm卡顿,最终导致康健检查超时,顺着这个思绪我们先优化一把看看效果。

 

最先之前先补一张gc耗时的截图,为了查看的直观性,此图由arthas的dashboard发生。

 

说真话我对gc是没有什么调优履历的,虽然看过对照多的文章,然则连纸上谈兵都达不到,这次也是硬着头皮举行一次“调参”,调优这件事真是不敢当。

 

详细怎么调参呢,通过上面gc耗时的漫衍,很直观的拿到一个讯息,暮年月的gc耗时有点长,而且次数对照频仍,虽然图里只有40次,然则相对于这个服务的启动时间来讲已经算频仍了,以是目的就是降低暮年月gc频率。

从我领会的gc知识来看,暮年月gc频仍是由于工具过早提升导致,原本应该等到age到达提升阈值才提升到暮年月的,然则由于年轻代内存不足,以是提条件升到了暮年月,提升率过高是导致暮年月gc频仍的主要缘故原由,以是最终转化为若何降低提升率,有两种设施:

1.增大年轻代巨细,工具在年轻代获得接纳,只有生命周期长的工具才进入暮年月,这样暮年月增速变慢,gc频率也就降下来了;

2.优化程序,降低工具的生计时间,只管在young gc阶段接纳工具。

 

由于我对这个服务并不是很熟悉,以是很自然的倾向于方式1“调整内存”,详细要怎么调整呢,这里借用一下美团手艺博客中提到的一个公式来抛砖一下:

图片内容泉源于https://tech.meituan.com/2017/12/29/jvm-optimize.html

 

连系之前的那张gc耗时图可以知道这个服务活跃数据巨细为750m,进而得出jvm内存各区域的配好比下:

年轻代:750m*1.5 = 1125m

暮年月:750m*2.5 = 1875m

 

接下来通过调整过的jvm内存配比重新公布验证效果,通过一段时间的考察,暮年月gc频率很显著降下来了,大部门工具在新生代被接纳,整体stw时间削减,运行一个多月再没有遇到自动重启的情形,由此也验证了我之前的展望“由于延续的gc导致康健检查超时,进而触发重启”。

 

至此,第二次优化告一段落。

 6.第三次优化

第二次优化确实给我们带来了一段时间的安宁,后续的一个多月宕机率的统计不至于啪啪打架构部的脸。

 

 刚安生几天,这不又来活了

 

有运维反馈某服务在北京客户的私有化部署环境有重启征象,频率基本上在2天一次,吸收到这个讯息以后我们立马重视起来,先确定两个事:

1.个例照样普遍征象-个例,只在这个客户环境泛起

2.会不会和前两次优化的问题一样,都是内存设置不合适导致的-不是,新服务,内存占用不高,gc也还好

 

连系前面的两个推论“个例”+“新服务,各项指标暂时正常“,我嫌疑会不会是给这个客户新做的某个功效存在bug,由于现在使用频率不高,以是积攒一段时间才把服务拖垮。带着这个疑惑我接纳了守株待兔的方式,shell写一个准时义务,每5s输出一下要害指标数据,准时义务如下:

#!/bin/bash

while true ; do
/bin/sleep 5

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'  >> netstat.log
ss -lnt  >> ss.log
jstack 1 >> jstack.log
done

主要关注的指标有网络情形、tcp行列情形、线程栈情形。

 

就这样,一天以后这个服务终于重启了,我逐一检查netstat.log,ss.log,jstack.log这几个文件,在jstack.log中问题缘故原由剥茧抽丝般展现出来,贴一段stack信息让人人一睹为快:

"qtp1819038759-2508" #2508 prio=5 os_prio=0 tid=0x00005613a850c800 nid=0x4a39 waiting on condition [0x00007fe09ff25000]
   java.lang.Thread.State: WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000007221fc9e8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2044)
        at org.apache.http.pool.AbstractConnPool.getPoolEntryBlocking(AbstractConnPool.java:393)
        at org.apache.http.pool.AbstractConnPool.access$300(AbstractConnPool.java:70)
        at org.apache.http.pool.AbstractConnPool$2.get(AbstractConnPool.java:253)
        - locked <0x00000007199cc158> (a org.apache.http.pool.AbstractConnPool$2)
        at org.apache.http.pool.AbstractConnPool$2.get(AbstractConnPool.java:198)
        at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.leaseConnection(PoolingHttpClientConnectionManager.java:306)
        at org.apache.http.impl.conn.PoolingHttpClientConnectionManager$1.get(PoolingHttpClientConnectionManager.java:282)
        at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:190)
        at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
        at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
        at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
        at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
        at com.aliyun.oss.common.comm.DefaultServiceClient.sendRequestCore(DefaultServiceClient.java:125)
        at com.aliyun.oss.common.comm.ServiceClient.sendRequestImpl(ServiceClient.java:123)
        at com.aliyun.oss.common.comm.ServiceClient.sendRequest(ServiceClient.java:68)
        at com.aliyun.oss.internal.OSSOperation.send(OSSOperation.java:94)
        at com.aliyun.oss.internal.OSSOperation.doOperation(OSSOperation.java:149)
        at com.aliyun.oss.internal.OSSOperation.doOperation(OSSOperation.java:113)
        at com.aliyun.oss.internal.OSSObjectOperation.getObject(OSSObjectOperation.java:273)
        at com.aliyun.oss.OSSClient.getObject(OSSClient.java:551)
        at com.aliyun.oss.OSSClient.getObject(OSSClient.java:539)
        at xxx.OssFileUtil.downFile(OssFileUtil.java:212)

大量的线程hang在了        org.apache.http.impl.conn.PoolingHttpClientConnectionManager$1.get(PoolingHttpClientConnectionManager.java:282

这个是做什么的呢?这个正是HttpClient中的毗邻池满了的迹象,线程在守候可用毗邻,最终导致jetty的线程被打满,造成服务假死,自然是不能实时响应康健检查,最终触发k8s的重启计谋。

 

最终通过排查代码发现是由于使用阿里云oss sdk不规范导致毗邻没有准时送还,久而久之就造成了毗邻池、线程池被占满的情形,至于为什么只有北京客户有这个问题是由于只有这一家设置了oss存储,而且这个属于新支持的功效,现在尚处于试点阶段,以是短时间量不大,1到2天才泛起一次重启事故。

 

解决设施很简朴,就是实时关闭ossObject,防止毗邻泄露。

7.总结

通过前前后后一个多月的延续优化,服务的可用性又提高了一个台阶,于我而言收获颇丰,对于jvm知识又回首了一遍,也能连系以往知识举行简朴的调参,对于k8s这一黑盒,也不再那么生疏,学习了基本的观点和一些简朴的运维指令,最后照样要说一句“工程师对于自己写的每一行代码都要心生敬畏,否则可能就会给公司和客户带来资损”。

 

8.推荐阅读


从现实案例聊聊Java应用的GC优化-美团手艺团队

分享一次排查CLOSE_WAIT过多的履历

 

  

  

ug环球开户www.ugbet.us)开放环球UG代理登录网址、会员登录网址、环球UG会员注册、环球UG代理开户申请、环球UG电脑客户端、环球UG手机版下载等业务。

Allbet Gaming声明:该文看法仅代表作者自己,与www.allbetgame.us无关。转载请注明:记一次k8s pod频仍重启的优化之旅
发布评论

分享到:

usdt自动充提教程(www.caibao.it):新闻称高通将推出Android版Switch
你是第一个吃螃蟹的人
发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。