一次k8s node节点负载高的问题

发现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种

  1. 伙伴系统(大对象)
    物理连续内存,指数增加的连续页,优点快速合并

  2. slab
    cpu高速缓存中,slab列表(池)分配小对象

  3. slob
    适合嵌入式系统

  4. 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,由于机器重启了,问题暂时没有更加深入排查,待下次复现的时候继续排查

相关链接

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容