發(fā)現(xiàn)k8s某一個(gè)節(jié)點(diǎn)負(fù)載較高,但是查看內(nèi)存、CPU、IO一切都正常

image.png

image.png
最后通過執(zhí)行dmesg查看系統(tǒng)日志,發(fā)現(xiàn)以下幾行
[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
大致可以理解為無法給緩存申請(qǐng)內(nèi)存。這個(gè)SLUB是個(gè)什么東西呢
linux內(nèi)核內(nèi)存模型
然后深入了解了一下內(nèi)核內(nèi)存模型。一共有以下4種
伙伴系統(tǒng)(大對(duì)象)
物理連續(xù)內(nèi)存,指數(shù)增加的連續(xù)頁,優(yōu)點(diǎn)快速合并slab
cpu高速緩存中,slab列表(池)分配小對(duì)象slob
適合嵌入式系統(tǒng)slub(2.6.24以上)
slab升級(jí)版,簡化了數(shù)據(jù)結(jié)構(gòu)。分配優(yōu)先級(jí)1.緩存池 2.cpu未分配的緩存 3.node、也就是伙伴系統(tǒng)
看了一下__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;
}
如果沒有本地活動(dòng) slab,轉(zhuǎn)到 (f) 步驟獲取 slab 。
如果本處理器所在節(jié)點(diǎn)與指定節(jié)點(diǎn)不一致,轉(zhuǎn)到 (e) 步驟。
檢查處理器活動(dòng) slab 沒有空閑對(duì)象,轉(zhuǎn)到 (e) 步驟。
此時(shí)活動(dòng) slab 尚有空閑對(duì)象,將 slab 的空閑對(duì)象隊(duì)列指針復(fù)制到 kmem_cache_cpu 結(jié)構(gòu)的 freelist 字段,把 slab 的空閑對(duì)象隊(duì)列指針設(shè)置為空,從此以后只從 kmem_cache_cpu 結(jié)構(gòu)的 freelist 字段獲得空閑對(duì)象隊(duì)列信息。
取消當(dāng)前活動(dòng) slab,將其加入到所在 NUMA 節(jié)點(diǎn)的 Partial 隊(duì)列中。
優(yōu)先從指定 NUMA 節(jié)點(diǎn)上獲得一個(gè) Partial slab。
加入 gfpflags 標(biāo)志置有 __GFP_WAIT,開啟中斷,故后續(xù)創(chuàng)建 slab 操作可以睡眠。
創(chuàng)建一個(gè) slab,并初始化所有對(duì)象。
如果內(nèi)存不足,無法創(chuàng)建 slab,調(diào)用 kmalloc_large(實(shí)際調(diào)用物理頁框分配器)分配對(duì)象。
根據(jù)源碼和日志,可以看出應(yīng)該是NUMA無法從node0申請(qǐng)內(nèi)存,導(dǎo)致每次都調(diào)用kmalloc_large,對(duì)性能消耗特別大。
很有可能是Cgroup的BUG,由于機(jī)器重啟了,問題暫時(shí)沒有更加深入排查,待下次復(fù)現(xiàn)的時(shí)候繼續(xù)排查
相關(guān)鏈接