docker psコマンドが応答しなくなる問題の解析

/var/log/messagesを確認したところ、以下のエラーメッセージが出力されていました: XFS: possible memory allocation deadlock in kmem_alloc (mode:0x250)

Linuxカーネルのソースコードを調べたところ、該当する実装は以下の通りです:

void *
kmem_alloc(size_t size, xfs_km_flags_t flags)
{
    int     retries = 0;
    gfp_t   lflags = kmem_flags_convert(flags);
    void    *ptr;

    do {
        ptr = kmalloc(size, lflags);
        if (ptr || (flags & (KM_MAYFAIL|KM_NOSLEEP)))
            return ptr;
        if (!(++retries % 100))
            xfs_err(NULL,
                "possible memory allocation deadlock in %s (mode:0x%x)",
                __func__, lflags);
        congestion_wait(BLK_RW_ASYNC, HZ/50);
    } while (1);
}

コード解析:

  • メモリ確保に成功した場合、すぐにポインタを返す。
  • 確保に失敗した場合、HZ/50ジフィー待機後に再試行し、成功するまでループを続ける。
  • 100回失敗するごとに、"possible memory allocation deadlock in %s (mode:0x%x)"を出力する。
  • メモリが不足している場合、永久にこのループ内でブロックされることを示す。

modeパラメータは、要求されるメモリフラグ (GFP_IO|GFP_WAIT|GFP_NOWARN) を示します。

ログを確認したところ、"XFS: possible memory allocation deadlock in kmem_alloc (mode:0x250)"が長時間連続して出力されていました。以前はこのメッセージが稀にしか表示されませんでした。

使用されているファイルシステムはXFSです。調査により以下の情報を得ました:

大きな連続ページがほぼ存在しない状態であることが分かります。XFSのメモリ確保処理では、以下のような大きな連続メモリブロックの要求が存在するためです:

6000:   map = kmem_alloc(subnex * sizeof(*map), KM_MAYFAIL | KM_NOFS);

推測される原因は、少量の空きメモリは存在するものの、それらが非常に断片化されており、比較的大きな連続メモリ要求を満たせないことです。

既にecho 1 > /proc/sys/vm/drop_cachesを実行していました。別のマシンの状態を確認します:

/proc/buddyinfoは、Linuxのbuddyシステムによる物理メモリ管理のデバッグ情報です。Linuxはbuddyアルゴリズムを使用して物理メモリの外部断片化問題を解決し、空きメモリを2のべき乗サイズで11個のブロックリストに分類します(1、2、4、8、16、32、64、128、256、512、1024ページ単位)。NUMAアーキテクチャでは、各ノードにローカルメモリがあり、buddyinfo内のNode0はノードIDを示します。各ノードのメモリは、DMA、Normal、HighMemなどのゾーンに分割されます。

Normalゾーンに注目すると、2列目の値100は、システム内のNormalゾーンで利用可能な連続2ページのメモリが100 * 2 * PAGE_SIZEであることを示します。3列目は52で、連続4ページのメモリが52 * 2^2 * PAGE_SIZEです。

cat /proc/buddyinfo
Node 0, zone      DMA     23     15      4      5      2      3      3      2      3      1      0
Node 0, zone   Normal    149    100     52     33     23      5     32      8     12      2     59
Node 0, zone  HighMem     11     21     23     49     29     15      8     16     12      2    142

5列目以降では、44 * 16 * PAGE_SIZEのページブロックのみが残り、その後は1 * 32 * PAGE_SIZE、1 * 64 * PAGE_SIZE、2 * 128 * PAGE_SIZEとなり、256や512のページブロックは存在しません。

したがって、問題発生時にはメモリの断片化が進行しており、アプリケーション実行時にXFSが大きな連続メモリブロック(例: kmem_alloc(subnex * sizeof(*map), KM_MAYFAIL | KM_NOFS))を要求すると、メモリが割り当てられません。例えば、docker psdocker execコマンドを実行すると、kmem_allocのループ内で永続的にブロックされ、メモリが断片化されたままであるため確保できず、コマンドも応答しなくなります。これにより、lssshなどのコマンド(必要なページサイズが小さい)は正常に動作する理由が説明できます。

echo 1 > /proc/sys/vm/drop_cachesを実行すると、断片化されたページが再編成され、その後大きなページメモリを確保できるようになり、ブロックが解消されます。

しかし、毎回このコマンドを実行するのは現実的ではないため、以下の対策を採用しました:

参考: http://www.cnblogs.com/itfriend/archive/2011/12/14/2287160.html

Linuxはmin_free_kbytesパラメータを提供しており、システムがメモリ回収を開始するしきい値を決定し、空きメモリを制御します。値が高いほど、カーネルは早期にメモリ回収を開始し、空きメモリ量が増加します。

/proc/sys/vm/min_free_kbytesの値を4Gバイトに設定:

echo 4194304 > /proc/sys/vm/min_free_kbytes

現在の値は90Mバイトです。

設定後の効果:

メモリ断片化の状態が大幅に改善されました。

この修正後、docker psコマンドが応答しなくなる現象は再発しませんでした。

タグ: Docker xfs Linuxメモリ管理 kmem_alloc メモリ断片化

6月11日 22:20 投稿