CPU は遅くし、メモリは殺す
一言でいうと
CPUの上限とメモリの上限は、名前が似ているだけで、まったく違うかみ方をします。1つは遅くし、1つは殺します。
なぜ必要なのか
limitsを書くことは、たいてい習慣です。前の人が書いた値をコピーするか、審査で「上限を指定してください」と言われて、適当な数字を入れます。そうすると、2種類の事件が、数か月後にやって来ます。1つは「何も変えていないのに、急に遅くなった」で、もう1つは「再起動が繰り返されるのに、ログには何の手がかりもない」です。2つの事件は原因が違うのに、どちらもlimitsの1行から出てきました。
どう動くのか
Podとコンテナのリソース管理のドキュメントは、2つの上限の強制方式が互いに違うと、明確に区別しています。
cpuの上限は、スロットリングで強制されます。カーネルが決められた分だけCPUを使わせ、その分を使い切ると、次の周期が来るまで、そのコンテナを止めておきます。カーネルが強制する固い上限なので、コンテナは上限より多くは使えません。その代わり、死にはしません。ドキュメントが「ランタイムは、CPUを多く使ったからといって、Podやコンテナを終了しない」と、わざわざ別に書くほど、よく誤解される部分です。症状は、遅くなることだけで、プロセスもプローブもすべて生きているので、ログには何も残りません。
memoryの上限は、OOM killで強制されます。コンテナが上限を超えると、カーネルが終了させることがあります。ただし、ドキュメントは、これがリアクティブだと書いています。カーネルがメモリ圧迫を検知したときに起きるので、上限を超えたコンテナが、すぐには死なないこともあります。死ぬときは、終了コード137とOOMKilledの理由が残ります。
집행 방식 증상 남는 흔적
cpu 스로틀링 지연만 늘어난다 없음(로그가 조용하다)
memory OOM kill 재시작·CrashLoop exit 137 · OOMKilled
ここに、あまり知られていない落とし穴がもう1つあります。メモリを媒体として使うemptyDirボリュームは、コンテナのメモリ使用量として計算されます。同じドキュメントが、「kubeletは、tmpfsのemptyDirボリュームを、ローカルの一時ストレージではなく、コンテナのメモリ使用として追跡する」と書いています。一時ファイルを速く書くためにメモリボリュームを付けておくと、ディスクにファイルを書いていると思っていたコードが、メモリの上限を押し上げて、コンテナを殺します。そして、症状は「ファイルを書いている最中に死んだ」ではなく、ただのOOMKilledです。
requestsとlimitsの役割も、分けて覚える必要があります。requestsは、スケジューラーがどこに置くかを決めるときに使う値で、limitsは、kubeletとカーネルがどれだけ使わせるかを決めるときに使う値です。そのため、requestsだけを書くと、ノードが空いているときは、それより多く使ってもよく、limitsだけを書くと、Kubernetesが同じ値をrequestsにコピーして入れます。
もう1つ。上限を下げるときは、リクエスト量との関係も一緒に見る必要があります。上限はリクエスト量より小さくできないので、メモリの上限だけを40Miに下げようとしても、リクエスト量が64Miと書かれていると、APIサーバーがその変更をそもそも拒否します。実験を準備していると、「故障が起きるはずだったのに、コマンドが拒否される」場合によく出会いますが、そのときはたいてい、このような整合性のルールに引っかかっています。このラボのアプリがリクエスト量を十分低く設定しているのも、実験中にこの壁にぶつからないようにするためです。
現場での姿
最もよくある事故は、「安全のために」cpuの上限を100mに下げておいた配置です。普段は何も起きず、トラフィックが少し上がった瞬間に、応答時間が数倍に跳ね上がります。成功率はそのままなのでアラートが鳴らず、CPU使用率のグラフは、上限にぴったりくっついて平らになっているので、かえって「余裕があるように見えます」。このような事件は、スロットリングを直接測ってみるまで、原因を見つけられません。
逆方向の事故もあります。メモリの上限を十分に与えればOOMはなくなりますが、リクエスト量を一緒に上げておかないと、ノードが逼迫したときに、そのPodが先に追い出されます。ドキュメントは、リクエスト量を超えて使っているコンテナがあるPodは、ノードのメモリが足りないときに、エビクションされる可能性が高いと書いています。つまり、上限は個々のコンテナの上限を決め、リクエスト量は、リソースが足りないときの順序を決めます。実験で確認するときも、この2つを別々に触って初めて、何が原因だったかを言えます。
そのため、リソースに関する事件を扱うときは、症状でまず分岐します。ログが静かな遅延ならcpu側を、再起動と終了コード137ならmemory側を、先に見ます。この分岐をきちんとするだけで、調査時間が大きく減ります。逆に、「遅くなったからメモリを増やしてみよう」のような対応は、何も変えられないまま、コストだけを上げます。上限を触る変更は、ロールアウトを起こしてPodを作り直すので、変更した直後は、一時的に良くなったように見える錯覚まで生まれます。その錯覚にだまされないためには、変更の前と後を同じ方法で測った数字が必要です。
次のクイズで確認すること
cpuの上限とmemoryの上限の強制方式の違い、OOMの強制がリアクティブだという意味、メモリ媒体のemptyDirが何として計算されるのかを確認します。