发现k8s某一个节点负载较高,但是查看内存、CPU、IO一切都正常
image.png
image.png
最后通过执行dmesg查看系统日志,发现以下几行
[716542.548123] SLUB: Unable to allocate memory on node -1 (gfp=0x8050)
[716542.548127] cache: buffer_head(3379:6ce139bebcf4c4f6181a32767d2c5414c0769f9da75a5888f1b643a567350c71), object size: 104, buffer size: 104, default order: 0, min order: 0
[716542.548130] node 0: slabs: 0, objs: 0, free: 0
[716547.247467] ___slab_alloc: 107 callbacks suppressed
大致可以理解为无法给缓存申请内存。这个SLUB是个什么东西呢
linux内核内存模型
然后深入了解了一下内核内存模型。一共有以下4种
伙伴系统(大对象)
物理连续内存,指数增加的连续页,优点快速合并slab
cpu高速缓存中,slab列表(池)分配小对象slob
适合嵌入式系统slub(2.6.24以上)
slab升级版,简化了数据结构。分配优先级1.缓存池 2.cpu未分配的缓存 3.node、也就是伙伴系统
看了一下__slab_alloc.c 的源码
static void *__slab_alloc(struct kmem_cache *s,
gfp_t gfpflags, int node, void *addr, struct kmem_cache_cpu *c)
{
void **object;
struct page *new;
gfpflags &= ~__GFP_ZERO;
if (!c->page) (a)
goto new_slab;
slab_lock(c->page);
if (unlikely(!node_match(c, node))) (b)
goto another_slab;
stat(c, ALLOC_REFILL);
load_freelist:
object = c->page->freelist;
if (unlikely(!object)) (c)
goto another_slab;
if (unlikely(SlabDebug(c->page)))
goto debug;
c->freelist = object[c->offset]; (d)
c->page->inuse = s->objects;
c->page->freelist = NULL;
c->node = page_to_nid(c->page);
unlock_out:
slab_unlock(c->page);
stat(c, ALLOC_SLOWPATH);
return object;
another_slab:
deactivate_slab(s, c); (e)
new_slab:
new = get_partial(s, gfpflags, node); (f)
if (new) {
c->page = new;
stat(c, ALLOC_FROM_PARTIAL);
goto load_freelist;
}
if (gfpflags & __GFP_WAIT) (g)
local_irq_enable();
new = new_slab(s, gfpflags, node); (h)
if (gfpflags & __GFP_WAIT)
local_irq_disable();
if (new) {
c = get_cpu_slab(s, smp_processor_id());
stat(c, ALLOC_SLAB);
if (c->page)
flush_slab(s, c);
slab_lock(new);
SetSlabFrozen(new);
c->page = new;
goto load_freelist;
}
if (!(gfpflags & __GFP_NORETRY) &&
(s->flags & __PAGE_ALLOC_FALLBACK)) {
if (gfpflags & __GFP_WAIT)
local_irq_enable();
object = kmalloc_large(s->objsize, gfpflags); (i)
if (gfpflags & __GFP_WAIT)
local_irq_disable();
return object;
}
return NULL;
debug:
if (!alloc_debug_processing(s, c->page, object, addr))
goto another_slab;
c->page->inuse++;
c->page->freelist = object[c->offset];
c->node = -1;
goto unlock_out;
}
如果没有本地活动 slab,转到 (f) 步骤获取 slab 。
如果本处理器所在节点与指定节点不一致,转到 (e) 步骤。
检查处理器活动 slab 没有空闲对象,转到 (e) 步骤。
此时活动 slab 尚有空闲对象,将 slab 的空闲对象队列指针复制到 kmem_cache_cpu 结构的 freelist 字段,把 slab 的空闲对象队列指针设置为空,从此以后只从 kmem_cache_cpu 结构的 freelist 字段获得空闲对象队列信息。
取消当前活动 slab,将其加入到所在 NUMA 节点的 Partial 队列中。
优先从指定 NUMA 节点上获得一个 Partial slab。
加入 gfpflags 标志置有 __GFP_WAIT,开启中断,故后续创建 slab 操作可以睡眠。
创建一个 slab,并初始化所有对象。
如果内存不足,无法创建 slab,调用 kmalloc_large(实际调用物理页框分配器)分配对象。
根据源码和日志,可以看出应该是NUMA无法从node0申请内存,导致每次都调用kmalloc_large,对性能消耗特别大。
很有可能是Cgroup的BUG,由于机器重启了,问题暂时没有更加深入排查,待下次复现的时候继续排查
相关链接