EKS Kubernetes メモリオーバー時の挙動

前回、Podの割り当てリソース量が小さいほど、急増する負荷や、Nodeのスケールアウト時に弱いのでは疑惑が上昇しました。

その対策として、メモリのLimitを外すことはアリなのかどうか。メモリの NoLimit はそもそもどういう挙動なのか、を知っておくために行った攻めの検証となります。



概要というか検証前の頭ン中

PodのメモリもCPUみたいに NoLimit で動かすなんて、無理っていうか非人道的っていうか、絶対死しか待っていないでしょって思い込むのは良くない!と思い直すことにした。

Pod で動かすWEB・APP系のデーモンの機能次第ではあるんだけど、もしメモリを allocatable いっぱいまで使ったり、落ち着いたら複数のPodでの使用メモリ量が平均化したり、をできるならば、小さいPod君でも一時的にイキれることで弱点を克服できるかもしれない、と思ったのだ。

多分、ていうかほぼ絶対ムリなんだけど、無理なら無理で無理な理由を突き止めておきたい、あわよくば黒魔術・銀の弾丸を見つけ出したい。そんな下心で検証開始☆


検証準備

ホスト情報

ということで、落ちづらくて小さめな c3.large をスポットで起動し、Allocatable 容量の確認から。2vCPU, 3.75GiB でございます。


パッケージ追加

メモリ消費用のスクリプトで使うので、追加。と、備え付けの ps コマンドだと RSS 見れないので procps も。


メモリ消費スクリプト

適当に大容量の変数を作って、RSS値を出力してガッツリ sleep するだけ。

適当に数字変えてみたけど、めんどくなったので、だいたいこれで 500MiB強 の消費。これを Pod で & つけてバックグラウンドで複数常駐させてオラオラしていく。


1Podで Memory Limit 有りでメモリオーバーした場合

こんな制限のPod。1GiB 超えたらどうなるかチェキ!

ちな、Podで直にメモリ量を見たら、vCPU と一緒で、Nodeと同じ容量がみえる。


実際にオーバーさせた時の状況

シェルを2つ出して、それぞれでスクリプトと、ps を実行。

このときに起こったことは、

  • メモリオーバーが発生した時点で、両方のシェルがフリーズした
  • 先に起動したプロセスが kill され、後発のスクリプトは正常に処理が完了した
  • そのあと、シェルはどちらも操作できるようになった

dmesg

ちょっと長いけど、メモなので該当部を記載しておく。
cgroup 先生がしゃしゃり出てきて殺られたのがわかる。


わかること

まずは、Limits memory は正常に機能している、ということを確認できた。一番大事なことだ。


1Pod でメモリ NoLimit でオーバーした場合

allocatable 約3655MiB に対し、約500MiB強 のプロセスを常駐させていくと、7~8個目でどうなるか、という実験。

無事、7プロセス目で死亡を確認。
そして予想外の dmesg である。長過ぎるけど、大事なのでファイルで添付しておく。

  • 1pod-NoLimit-dmesg

  • cgroup の記述はどこにもなく、Nodeで素の OOM_KILLER 先生が走ったようなログが観測できた。kill られたプロセスだけ抜粋すると、なんと aws-k8s-agent までぶった切られているのがわかる。


    わかったこと

    ここでわかったフリしても何もないので、次へ行く。


    落ち着くためメモリ変動の確認

    予想外のログをみたことで動揺したので、メモリ容量は実体と監視であっているのか、の確認をしておいた。


    気を取り直して、次へ行く。


    複数Pod でメモリ NoLimit でオーバーした場合

    1Pod目が、あと500MiB 追加で死ぬって状況で、2Pod目を起動してみる。そもそも起動するのか。

    した。Requests memory 的には、実体と関係ないただの要求容量の合計値なので、これは想定通り。そして、やはりメモリ状況をみると、Node のそれが見えるので共通した内容になる。

    次に、Pod 1 がパンパンの状態で、Pod 2 で同様のプロセスを1つ起動してみる。

    1Pod での検証と同じような結果になった。オーバー時にフリーズするが、kill られたあとは処理が正常に完了し、シェルも操作できるようになる。

    dmesg

    どちらの Pod にも、時間を含めた完全に同じ内容が記録されていた。


    NoLimit において Node上ではどのようなことが起きているか

    まず、メモリオーバーが起きる前の、Node上でのプロセスの下の方を記録。


    まさに死んだ瞬間を記録。ただし、Pod同様、一時的にフリーズするので、フリーズ解除直後の記録。
    めちゃ systemd が復活の儀をしているのがわかる。


    さらに数秒後、何事もなかったようにしれっと色んなプロセスが復活した。


    この時の dmesg は、Node も各Pod も、完全に同じ内容である。


    Memory NoLimit は採用する価値があるかどうか

    検証結果

    大雑把だけどこんな感じ。

  • Limits Memory をつけると、オーバーした場合は cgroup の挙動によって OOM_KILLER が発動するため、Pod で動作が完結する(※Requests = Limits の場合。Limits 合計が Allocatable を超える場合は知らん)

  • Limits Memory をつけない場合、Node での OOM_KILLER が起こるため、Node / Pod 全てが殺られる対象となる。また、一時的な全体フリーズとなり、その後は systemd 管理下のプロセスは復活する

  • 急増する負荷に対して

    例えば、新Nodeで起動した1Pod が、想定とする Requests 以上のリソースを必要とした時、NoLimit によって次のPodが起動するまでに余っているリソースを扱えることは、一見強度が上がるように見える。

    トラフィックの上がり方が中規模ならば、その Pod は Node の 1/2 程度のリソースを食う程度で収まるかもしれないが、7/8 くらい食った場合はどうなるだろうか。次に起動してくる Pod によって、おそらく Node での OOM_KILLER が起こることになる。

    どの程度 Turbo Boost するかによって、無事で済むか、Node破壊が起こるか、のような不安定な運用は採用できるはずがない。

    アプリケーションのメモリ変動

    そもそも、WEBサーバーやアプリケーション・サーバーといったデーモンが、どのようにメモリを扱うのか、が先にくる話だったりする。

    常駐プロセス(やスレッド)数が一定なのか、動的なのか。動的ならば最大値はいくらなのか、不要になった分はどれくらいの期間で削減されていくのか。

    仮に、動的 かつ NoLimit で運用したとして、ブースト耐久できるし、複数Podのメモリ量合計値が Node 容量を上回らないように増減する・・・そんなものは存在しないし、工夫で確実になんとかなるものでもないだろう。

    ただでさえ、アプリケーション・プロセスのメモリ容量は、想定以上に膨れることが多いのに、さらに不安定要素を組み込んでいくのは、あまりに現実的ではない。

    Node の死だけは避けたい

    Node の死 = Podの死 に等しいとすると、
    Pod, Node とスケールアウトした時、Node が死にかけるとそこのリソースが一時的にいなくなり、スケールアウトとして機能しないことになる。

    しかし、Node さえ生きていれば、仮に Pod がいっぱいいっぱい起動してリソース的にも限界だとしても、スケールアウトしてリソースが増加しているという事実は変わらない。

    Node の生き死にが不安定ということは、スケールアウトの機能も不安定になるということなので、たとえ全体のキャパシティオーバーが地獄だとしても、少しずつスケールアウトして解決に向かっていくという安定さだけは確保したいところだ。



    結論。

    Limits Memory はつける。

    Pod 内での最大使用メモリ容量の試算を頑張る。できれば指定リソースとアプリケーション・サーバー設定による、1Pod あたりの性能── 例えば同時接続数や、処理可能な平均 req/s などの試算精度を向上していく。

    Autoscaling のリスクについては、また別途、試算と調整をする。



    風邪気味だったので文章が適当だけど、穴埋めが1つ進んだのでよしとする。
    にんげんだもの。