Kubernetes Pod OOM 排查日记
<h2 id="一发现问题">一、发现问题</h2><p>在一次系统上线后,我们发现某几个节点在长时间运行后会出现内存持续飙升的问题,导致的结果就是Kubernetes集群的这个节点会把所在的Pod进行驱逐OOM;如果调度到同样问题的节点上,也会出现Pod一直起不来的问题。我们尝试了杀死Pod后手动调度的办法(label),当然也可以排除调度节点。但是在一段时间后还会复现,我们通过监控系统也排查了这段时间的流量情况,但应该和内存持续占用没有关联,这时我们意识到这可能是程序的问题。</p>
<h2 id="二现象-内存居高不下">二、现象-内存居高不下</h2>
<p>发现个别业务服务内存占用触发告警,通过 Grafana 查看在没有什么流量的情况下,内存占用量依然拉平,没有打算下降的样子:</p>
<p><img src="https://mnur-prod-public.oss-cn-beijing.aliyuncs.com/0/tech/20200807105651.png" alt="" loading="lazy"></p>
<p>并且观测的这些服务,早年还只是 100MB。现在随着业务迭代和上升,目前已经稳步 4GB,容器限额 Limits 纷纷给它开道,但我想总不能是无休止的增加资源吧,这是一个很大的问题。</p>
<h3 id="三pod频繁重启">三、Pod频繁重启</h3>
<p>有的业务服务,业务量小,自然也就没有调整容器限额,因此得不到内存资源,又超过额度,就会进入疯狂的重启怪圈:</p>
<p><img src="https://mnur-prod-public.oss-cn-beijing.aliyuncs.com/0/tech/20200807105732.png" alt="" loading="lazy"></p>
<p>重启将近 200 次,告警通知已经爆炸!</p>
<h2 id="四排查">四、排查</h2>
<h3 id="猜想一频繁申请重复对象">猜想一:频繁申请重复对象</h3>
<p>出现问题服务的业务特点,那就是基本为图片处理类的功能,例如:图片解压缩、批量生成二维码、PDF 生成等,因此就怀疑是否在量大时频繁申请重复对象,而程序本身又没有及时释放内存,因此导致持续占用。</p>
<h4 id="内存池">内存池</h4>
<p>想解决频繁申请重复对象,可以用最常见的 sync.Pool</p>
<blockquote>
<p>当多个 goroutine 都需要创建同⼀个对象的时候,如果 goroutine 数过多,导致对象的创建数⽬剧增,进⽽导致 GC 压⼒增大。形成 “并发⼤-占⽤内存⼤-GC 缓慢-处理并发能⼒降低-并发更⼤”这样的恶性循环。</p>
</blockquote>
<h4 id="场景验证">场景验证</h4>
<p>在描述中关注到几个关键字,分别是并发大,Goroutine 数过多,GC 压力增大,GC 缓慢。也就是需要满足上述几个硬性条件,才可以认为是符合猜想的。</p>
<p>通过拉取 PProf goroutine,可得知 Goroutine 数并不高:</p>
<p><img src="https://mnur-prod-public.oss-cn-beijing.aliyuncs.com/0/tech/20200807110224.png" alt="" loading="lazy"></p>
<p>没有什么流量的情况下,也不符合并发大,Goroutine 数过多的情况,若要更进一步确认,可通过 Grafana 落实其量的高低。</p>
<p>从结论上来讲,我认为与其没有特别直接的关系,但猜想其所对应的业务功能到导致的间接关系应当存在。</p>
<h3 id="猜想二未知的内存泄露">猜想二:未知的内存泄露</h3>
<p>内存居高不下,其中一个反应就是猜测是否存在泄露,而我们的容器中目前只跑着一个进程:</p>
<p><img src="https://mnur-prod-public.oss-cn-beijing.aliyuncs.com/0/tech/20200807110411.png" alt="" loading="lazy"></p>
<p>显然其提示的内存使用不高,也不像进程内存泄露的问题,因此也将其排除。</p>
<h3 id="猜想三容器环境的机制">猜想三:容器环境的机制</h3>
<p>既然不是业务代码影响,也不是GC影响,那是否与环境本身有关呢,我们可以得知容器 OOM 的判别标准是 container_memory_working_set_bytes(当前工作集)。</p>
<p>而 container_memory_working_set_bytes是由 cadvisor 提供的,对应下述指标:</p>
<p><img src="https://mnur-prod-public.oss-cn-beijing.aliyuncs.com/0/tech/20200807111044.png" alt="" loading="lazy"></p>
<p>从结论上来讲,Memory 换算过来是 4GB+,石锤。接下来的问题就是 Memory 是怎么计算出来的呢,显然和 RSS 不对标。</p>
<h2 id="原因">原因</h2>
<p>从 cadvisor/issues/638 可得知 container_memory_working_set_bytes 指标的组成实际上是 RSS + Cache。而 Cache 高的情况,常见于进程有大量文件 IO,占用 Cache 可能就会比较高,猜测也与 Go 版本、Linux 内核版本的 Cache 释放、回收方式有较大关系。</p>
<p><img src="https://mnur-prod-public.oss-cn-beijing.aliyuncs.com/0/tech/20200807111120.png" alt="" loading="lazy"></p>
<p>出问题的常见功能,如:</p>
<ul>
<li>批量图片解压缩。</li>
<li>批量二维码生成。</li>
<li>批量上传渲染后图片。</li>
</ul>
<h2 id="解决方案">解决方案</h2>
<p>在本场景中 cadvisor 所提供的判别标准 container_memory_working_set_bytes 是不可变更的,也就是无法把判别标准改为 RSS,因此我们只能考虑掌握主动权。</p>
<h3 id="开发角度">开发角度</h3>
<p>使用类 sync.Pool 做多级内存池管理,防止申请到 “不合适”的内存空间,常见的例子: ioutil.ReadAll:</p>
<pre><code class="language-golang">func (b *Buffer) ReadFrom(r io.Reader) (n int64, err error) {
…
for {
if free := cap(b.buf) - len(b.buf); free < MinRead {
newBuf := b.buf
if b.off+free < MinRead {
newBuf = makeSlice(2*cap(b.buf) + MinRead)// 扩充双倍空间
copy(newBuf, b.buf)
}
}
}
}
</code></pre>
<p>核心是做好做多级内存池管理,因为使用多级内存池,就会预先定义多个 Pool,比如大小 100,200,300的 Pool 池,当你要 150 的时候,分配200,就可以避免部分的内存碎片和内存碎块。</p>
<p>但从另外一个角度来看这存在着一定的难度,因为你怎么知道什么时候在哪个集群上会突然出现这类型的服务,何况开发人员的预期情况参差不齐,写多级内存池写出 BUG 也是有可能的。</p>
<p>让业务服务无限重启,也是不现实的,<strong>被动重启,没有控制,且告警,存在风险</strong>。</p>
<h3 id="运维角度">运维角度</h3>
<p>可以使用定期重启的常用套路。可以在部署环境可以配合脚本做 HPA,当容器内存指标超过约定限制后,起一个新的容器替换,再将原先的容器给释放掉,就可以在预期内替换且业务稳定了。</p>
<p><img src="https://mnur-prod-public.oss-cn-beijing.aliyuncs.com/0/tech/20200807115519.png" alt="" loading="lazy"></p>
<h2 id="总结">总结</h2>
<p>根据上述排查和分析结果,原因如下:</p>
<ul>
<li>应用程序行为:文件处理型服务,导致 Cache 占用高。</li>
<li>Linux 内核版本:版本比较低(BUG?),不同 Cache 回收机制。</li>
<li>内存分配机制:在达到 cgroup limits 前会尝试释放,但可能内存碎片化,也可能是一次性索要太多,无法分配到足够的连续内存,最终导致 cgroup oom。</li>
</ul>
<p>从根本上来讲,应用程序需要去优化其内存使用和分配策略,又或是将其抽离为独立的特殊服务去处理。并不能以目前这样简单未经多级内存池控制的方式去使用,否则会导致内存使用量越来越大。</p>
<p>而从服务提供的角度来讲,我们并不知道这类服务会在什么地方出现又何时会成长起来,因此我们需要主动去控制容器的 OOM,让其实现优雅退出,保证业务稳定和可控。</p>
<h2 id="最后">最后</h2>
<p>最近在写基于Golang的工具和框架,还请多多Star.<br>
YoyoGo 是一个用 Go 编写的简单,轻便,快速的 微服务框架,目前已实现了Web框架的能力,但是底层设计已支持多种服务架构。</p>
<h2 id="github">Github</h2>
<p>https://github.com/yoyofx/yoyogo<br>
https://github.com/yoyofxteam</p>
</div>
<div id="MySignature" role="contentinfo">
<div class="esa-post-signature">
<p>作者:
YOYOFx
</p>
<p>出处:https://www.cnblogs.com/maxzhang1985/p/12673160.html</p>
<p>版权:本文采用「署名-非商业性使用-相同方式共享 4.0 国际」知识共享许可协议进行许可。</p>
<p></p>
</div>
<b>欢迎大家关注微信号。扫下面的二维码或者收藏下面的二维码关注吧(长按下面的二维码图片、并选择识别图中的二维码)</b>
<img src="https://images.cnblogs.com/cnblogs_com/maxzhang1985/366082/o_200511090003qrcode_for_gh_58872286e96b_860.jpg" alt="微信公众平台" style="max-width:300px;max-height:300px"><br><br>
来源:https://www.cnblogs.com/maxzhang1985/p/13451936.html
頁:
[1]