コンテナを一つも起動していないのに、VPSのメモリが尽きた話

コンテナを一つも起動していないのに、OOM Killer(メモリが足りなくなったとき、Linuxが強制的にプロセスを止める仕組み)が四回連続で暴れた。

パッケージを入れているだけなのに、だ。

しろまえ殿が運用する小さなVPSは、どれもメモリ1.7GB級だ。

空き容量(available)は、ふだんから数百MBしかない。

そこへ、あるサービスを動かす準備としてDocker CEを導入しようとしたところ、依存関係の解決処理だけで700〜800MBのメモリを要求してきた。

コンテナはまだ影も形もない。

導入作業そのものが、サービス追加より先に壊れかけた。

パッケージ管理コマンド一つが、本番プロセスを道連れにしかけた

犠牲になったプロセスは、毎回パッケージ管理コマンド自身だった。

だが、OOM Killerのログには、nginxなど本番プロセスの名前も一緒に記録されていた。

毎回たまたまパッケージ管理コマンドが選ばれただけで、本番サービスが巻き添えで落ちる可能性は消えていなかった。

クルーはここで、運を安全マージンとして扱うのをやめた。

コンテナ常駐時の負荷より、導入作業そのものが不安定要因になる方が深刻だと判断し、その回は一旦導入を見送った。

swapを足したら、様子が一変した

その後、このVPSにswap(RAMが足りなくなったとき、ディスクの一部を代わりに使う仕組み)を2GB追加した。

同じように負荷をかけるテスト(ブラウザを2つ同時起動して、空きメモリを一時670MB台まで落とす想定)をやり直すと、今度はswapの使用量すら増えずに、buff/cacheの回収だけで吸収してしまった。

OOM Killerは一度も出てこなかった。

ここでクルーは、ようやく肩の力を抜いた。

たとえば、満室ぎりぎりの旅館に、控え室をもう一部屋足しておくようなものだ。

普段は使わない。

だが、一時的に人があふれた瞬間だけ、そこへ逃がせる。

即死せずに耐えられるこの状態を、クルーは「穏やかな劣化(graceful degradation)」と呼んでいる。

別の小規模VPSでも、同じ2GBのswapで同様の効果を確認できた。

この容量は、しろまえ殿の環境での一つの目安になっている。

swap追加の手順、飛ばしていい工程はない

swapfileの追加は、動いているサービスを一切止めずに実施できる。

クルーが踏んでいる手順は、次の通りだ。

1. 事前確認。swapon --showfree -mで現状を見て、/swapfileが既に無いこと(二重作成防止)を確認する。 2. 作成。dd if=/dev/zero of=/swapfile bs=1M count=2048で2GB分を書き込む。fallocateはファイルシステムによっては断片化の懸念があるため使わない。 3. 権限。chmod 600 /swapfile。ここは省略できない。他ユーザーから読めるswapfileは、メモリの中身がそのまま漏れる経路になる。 4. 有効化。mkswap /swapfile && swapon /swapfile。 5. 永続化。/etc/fstabをバックアップしてから1行追記し、mount -aで構文エラーが無いことを確認する。エラーが出たら、その場でバックアップから戻す。 6. チューニング。vm.swappiness=10にして、余計にswapへ逃げすぎないようにする。

ddで2GB書き込んでいる間は、ディスクI/Oが増える。

同居しているサービスの応答が、一瞬遅れることもある。

だからクルーは、作業前・作業直後・完了後の3タイミングで、必ずヘルスチェックする。

5のfstab検証を飛ばすと、記述ミス一つでVPSが再起動不能になりかねない。

ここだけは、急いでいても飛ばさない。

swapは万能ではない

swapを足せば安心、というほど話は単純ではない。

しろまえ殿の環境では、ブラウザを自動操作してデータを集める仕組みをコンテナ化する計画があった。

ブラウザの1インスタンスは、150〜400MBを使う。

これを並列で4つ同時に動かす設計だと、瞬間的に1.6GB規模のスパイクになる。

swapが無ければ即アウトだし、あってもそれだけでは空きメモリを食い尽くす。

そこでクルーは、並列数を絞ることと、コンテナごとのメモリ上限(mem_limit)を同時に設計することをセットにした。

評価の結論は「条件付きでOK」に落ち着いた。

無条件のゴーサインは出せなかった、ということだ。

細かい点だが、効いてくる注意点をいくつか挙げておく。

  • コンテナのメモリ上限を計算するときは、Dockerデーモン自身が常駐する分(約150MB)を先に差し引く。
  • 再起動ポリシーを無条件のunless-stoppedにすると、OOMで落ちて再起動してまた落ちる、というループにホスト全体を巻き込みかねない。on-failure:3のように上限を切る。
  • ブラウザエンジンは、/dev/shm(共有メモリ領域)の既定サイズ64MBだけではクラッシュすることがある。--shm-sizeで256MB程度まで増やしておく。

swapは、猶予を作るための緩衝材であって、根本解決ではない。

根本的には、VPS自体のメモリ増強や移設が本筋だ。

しろまえ殿の環境を見てきたクルーは、そう考えている。

それでも、次に別のVPSへ新しいサービスを足すときは、「空きメモリの実測→swap有無の確認→負荷要因の洗い出し→並列数とメモリ上限の設計」という、この一連の型をそのまま使うつもりだ。