InfraAcademy

InfraAcademy Blog

freeの空きが少ないのは、異常ではありません。Linuxのメモリ不足をavailableとOOM Killerの記録から見極める

| #Linux #トラブルシューティング #systemd
Linuxをブラウザで試してみる

Linux・ネットワーク・AWSを、環境構築なしで実践学習できます

こんにちは、インフラエンジニアのryuです。

朝、出社したら監視からアラートが届いていて、動いているはずのプロセスが消えていた。アプリケーションのログには、落ちた形跡が何も残っていない。

こういう経験、ありませんか。

そして誰かに状況を聞かれて、とりあえず free -h を叩く。free の列がほとんど残っていないのを見て、「メモリが足りていませんね」と報告する。

実はこの報告、半分くらいの確率で間違っています。Linuxの空きメモリは、少ないのが普通の状態だからです。

今日は、Linuxでメモリまわりの調査をするときに、どの数字をどの順番で見るかという話をします。コマンドそれぞれの使い方はLinuxのメモリ使用率を確認する方法にまとめてあるので、この記事では読み方と切り分けの順番に絞ります。

freeの「空き」を見ても、足りているかは分からない

まず、free -h の出力を見てみましょう。

$ free -h
               total        used        free      shared  buff/cache   available
Mem:            7.7Gi       2.1Gi       230Mi        12Mi       5.4Gi       5.3Gi
Swap:           2.0Gi          0B       2.0Gi

free が 230MiB しかありません。全体の3%です。

この数字だけを見ると、いまにも倒れそうに見えますよね。けれど右端の available は 5.3GiB あります。

この2つは、まったく違うことを表しています。

buff/cacheは、空けようと思えば空けられるメモリ

Linuxは、使われていないメモリをそのまま遊ばせておきません。ディスクから読んだ内容をページキャッシュとして持っておき、次に同じファイルを読むときに速く返せるようにします。

free の出力で言えば、これが buff/cache の列です。man ページでは buff/cache を buffers と cache の合計と説明しています。

たとえるなら、机の上に出しっぱなしにしてある資料のようなものです。片付いてはいませんが、必要になれば数秒でどければ済みます。

そして実際、アプリケーションがメモリを要求すると、カーネルはこのキャッシュを捨てて渡します。だから free が小さいこと自体は、健康な状態の証拠でもあります。

見るべきなのは free ではなく available

available は、スワップせずに新しいアプリケーションを起動するために使えるメモリの見積もりです。free の man ページには、cache や free の値と違ってページキャッシュを考慮に入れる、と書かれています。

この値は /proc/meminfo の MemAvailable に対応していて、Linux 3.14 から追加されたものです。

MemAvailable: An estimate of how much memory is available for starting new applications, without swapping.

つまり メモリが足りているかどうかを判断する列は available であって、free ではありません。ここを取り違えたまま調査を進めると、原因のない場所を延々と掘ることになります。

各列の意味を整理しておきます。

何を表すか 少ないと困るか
total 搭載メモリの合計
used total から free と buff/cache を引いた値 単体では判断材料にならない
free どこにも使われていないメモリ 少なくても問題ない
buff/cache ページキャッシュとバッファ、slab 多いのは正常。むしろ効率がよい
available すぐに使えるメモリの見積もり ここが小さいと本当に危ない
Swap used スワップに追い出された量 増え続けているなら要注意

目安としては、available が total の1割を切ったあたりから警戒し始めるとよいです。監視のしきい値も、used ではなく available で設定し直すと誤報が大きく減ります。

ちなみにディスクまわりでも同じような読み違いが起きます。そちらはディスクが100%なのに、duで数えると空いているに書きました。数字が示しているものと、自分が知りたいことがずれている、という構図はよく似ています。

プロセスが消えたなら、誰に殺されたのかを先に確かめる

ここからが本題です。available が小さかったとして、次に何を見るか。

アプリケーションが自分で終了したのか、カーネルに殺されたのかで、対処がまったく変わります。ですから犯人探しより先に、死因の確認をします。

カーネルのOOM Killerが動いた記録を探す

メモリが本当に枯渇すると、Linuxカーネルは OOM Killer を動かします。システム全体が停止するのを避けるために、プロセスをひとつ選んで強制終了する仕組みです。

このとき、カーネルは必ずログを残します。探し方はこうです。

# カーネルリングバッファから探す(-T で人が読める時刻表示)
sudo dmesg -T | grep -i -E 'out of memory|oom'

# journald を使っているなら、カーネルログだけに絞って探す
sudo journalctl -k --since "yesterday" | grep -i -E 'out of memory|oom'

ヒットすると、Out of memory: Killed process 1234 (java) のような行が出てきます。

大事なのは、その直前の行も一緒に読むことです。OOM Killer は判断の材料として、そのときのプロセス一覧を pid・rss・oom_score_adj などの列で丸ごとダンプしています。

つまりここには、倒れた瞬間に誰がどれだけメモリを持っていたかの記録が残っているわけです。事後の ps では絶対に手に入らない情報なので、まずこのダンプを保存してください。

なお、選ばれる基準は「いちばんメモリを食っているプロセス」とは限りません。カーネルは各プロセスに oom_score という点数を付け、その点数がいちばん高いものを選びます。

点数は /proc/<pid>/oom_score_adj で補正でき、-1000 から +1000 の範囲を取ります。man ページによれば、最小値の -1000 はそのプロセスの OOM Killer による終了を事実上無効にします。

systemd経由で止まった場合は、記録の場所が違う

もうひとつ、見落としやすい経路があります。ユニット単位のメモリ上限に当たったケースです。

サービスが systemd の管理下にあるとき、systemctl status にはこんな行が出ます。

$ systemctl status myapp
   Active: failed (Result: oom-kill) since ...
  Process: 1234 ExecStart=/usr/bin/myapp (code=killed, status=9/KILL)
   Memory: 512.0M (max: 512.0M)

Memory: の行で現在値と max が同じ値になっていたら、システム全体のメモリではなく、このユニットに設定された上限に当たっています。

サーバー全体には空きがあるのに、特定のサービスだけが繰り返し落ちる。そういうときは、まずここを疑ってください。

こうした上限は、誰かが意図して書いたとは限りません。パッケージが標準で置いていく設定ファイルや、コンテナの実行時オプションで入っていることもあります。

自分で設定した覚えがないからといって、上限がないとは限らないわけです。まずは値を読んで確かめる、という順番にしておくと空振りが減ります。

設定値は次のように確認できます。

# ユニットに設定されたメモリ関連の値を見る
systemctl show myapp | grep -i memory

# cgroup 側の現在値と上限を直接読む
cat /sys/fs/cgroup/system.slice/myapp.service/memory.current
cat /sys/fs/cgroup/system.slice/myapp.service/memory.max

systemd ユニットの MemoryMax= は、cgroup v2 の memory.max 属性を設定します。ドキュメントにも、上限に収まらない場合はユニットの内側で OOM Killer が動く、と明記されています。

Ubuntu や Fedora などでは、さらに systemd-oomd というユーザー空間の仕組みが動いていることもあります。これは cgroup v2 と PSI を使って、カーネル空間で OOM が起きる前に手を打つサービスです。

そのため「カーネルログに OOM の記録がない」としても、まだ安心はできません。systemd 側のログも確認してください。ユニットのログの追い方は設定を直して再起動したのに、何も変わらないでも扱っています。

犯人を絞り込むときに見る順番

死因が分かったら、次は原因のプロセスです。ここでも順番があります。

実際にメモリを持っているプロセスを並べる

まずは単純に、常駐サイズの大きい順に並べます。

# RSS の大きい順に上位10件
ps aux --sort=-rss | head -11

# 合計値も確認する(単位は KB)
ps -eo rss= | awk '{s+=$1} END {printf "%.1f GiB\n", s/1024/1024}'

ps の詳しい使い方はpsコマンドの使い方にまとめてあります。

ここで気をつけたいのは、RSS を全プロセスぶん足しても total にならないことです。共有ライブラリのように複数のプロセスで共有されている領域は、それぞれに二重に計上されているためです。

「足し算が合わない」と悩む必要はありません。RSS はあくまで順位を見るための数字だと思ってください。

増え方を見る。一瞬の値では判断できない

もうひとつ重要なのが、時間の軸です。

1回の ps で分かるのは、その瞬間の姿だけです。メモリ不足の調査で本当に知りたいのは、それが増え続けているのかどうかですよね。

# 1分おきに上位プロセスを記録しておく(調査中の簡易ロギング)
while true; do
  date '+%F %T'
  ps -eo pid,rss,comm --sort=-rss | head -6
  sleep 60
done >> /var/tmp/mem-watch.log

数時間ぶんのログを見て、右肩上がりに増えているプロセスがあれば、リークを疑う材料になります。逆に一定量で落ち着いているなら、単純に搭載メモリが足りていないという話です。

この違いは、対処がまったく変わるので大事です。リークならアプリケーション側の修正か定期再起動が要りますが、単に足りないだけならメモリを増やせば終わります。

ここを確かめずに増設すると、リークだった場合は寿命が数日延びるだけで、また同じ時間に落ちます。「増やしたのに直らない」の多くは、この確認を飛ばしたときに起きます。

症状ごとの見どころを、表にしておきます。

症状 まず見るところ よくある原因
特定のサービスだけ繰り返し落ちる systemctl status の Memory 行 ユニットの MemoryMax= に到達
深夜に決まって落ちる cron やバックアップのジョブ 一時的に大量のメモリを使う処理
全体が重く、ディスクが動き続ける vmstat 1 の si / so スワップの出入りが発生している
available が徐々に減り続ける 常駐プロセスのRSSの推移 メモリリーク
空きは多いのにOOMが出る dmesg のOOMダンプの内容 cgroupの上限、NUMAの偏り

スワップが使われていること自体は、異常ではない

Swap used がゼロでないのを見て慌てる方もいますが、これも free と同じで、値そのものより動きを見ます。

長く使われていないメモリがスワップに退避されるのは、むしろ効率のよい状態です。問題なのは、出し入れが頻繁に起きていることのほうです。

その判断には vmstat 1 を使います。si(swap in)と so(swap out)が継続して0より大きいなら、実メモリが足りていない状態が続いています。

なお、スワップの使われ方は vm.swappiness で調整できます。ただし値を0にすると、匿名メモリの退避を避けようとするぶん、負荷の高い状況で OOM Killer に殺されるリスクが上がる点はRed Hatのドキュメントでも注意されています。極端な値にするのは避けたほうが無難です。

再発させないために、どこに線を引くか

原因が分かったら、対処です。増設する前に、できることがいくつかあります。

いちばん効くのは、倒れてほしくないものを守るのではなく、倒れてよいものに先に上限をかけるという発想です。

たとえばバッチ処理やログ収集のように、止まっても業務が即座に困らないユニットには上限を置きます。

# /etc/systemd/system/batch.service.d/limit.conf
[Service]
MemoryHigh=1G
MemoryMax=1500M
OOMPolicy=stop

MemoryHigh= を主な制御に使い、MemoryMax= は最後の防衛線として使うのが推奨されている、とドキュメントには書かれています。上限に近づいた時点で速度を落として回収してくれるので、いきなり殺されるより行儀がよいわけです。

OOMPolicy= は、ユニット内のプロセスがOOMで殺されたときの振る舞いを決めます。continue はログだけ残して動き続け、stop はユニットを正常に停止し、kill は残りのプロセスもまとめて終了させます。

逆に、絶対に落ちてほしくないサービスには OOMScoreAdjust= でマイナス寄りの補正を入れます。ただしこれは順番を後ろにするだけで、無敵になるわけではありません。

最後に、監視のしきい値の話です。used ベースのアラートは誤報だらけになるので、available と swap の出入りを見るように変えてください。この2つだけで、メモリ起因の障害はかなり早い段階で気づけるようになります。

InfraAcademyでは、こうしたメモリやプロセスの見方を含めて、実際に手を動かしながら学べる教材をLinuxのロードマップとして用意しています。現場で断片的に覚えてきた知識を、一度つなげ直したい方にも向いています。

まとめ

今日の内容を振り返ります。

free の free が小さいことは、メモリ不足の根拠になりません。buff/cache はいつでも手放せるメモリなので、見るべきなのは available のほうです。

プロセスが消えたときは、原因探しより先に死因を確認します。dmesg -Tjournalctl -k で OOM Killer の記録を探し、そこに残っているプロセスのダンプを保存しておくこと。これが後の調査を大きく楽にします。

サーバー全体に空きがあるのに特定のサービスだけ落ちるなら、systemd のユニットに設定された上限を疑います。systemctl status の Memory 行と、cgroup の memory.max が手がかりです。

そして原因プロセスは、一瞬の値ではなく増え方で判断します。

メモリの調査は、数字が多くて身構えてしまいがちですよね。でも見る順番さえ決まっていれば、実際に触る数字は今日紹介した5つか6つくらいです。

次にアラートが鳴ったとき、free の free ではなく available から見てみてください。それだけで、たどり着く場所がだいぶ変わるはずです。

参考記事

Next Action

記事で読んだ内容を、講座で実装してみましょう

InfraAcademyでは、ブラウザ上でLinuxやネットワークの実践環境を使いながら学習できます。無料で始められる講座から、学習の流れを試せます。

この記事を書いた人

ryu

InfraAcademy運営 / エンジニア

エンジニア歴10年。Linux、ネットワーク、クラウドを中心に、実務で役立つインフラ技術を初心者にもわかりやすく解説しています。

X: @ryu63614894

Related

関連記事

ブログ一覧へ

Roadmap

まずはこの4講座から

ログインすれば無料で始められる講座です。気になったテーマから手を動かして学べます。

講座一覧を見る