LoginSignup
19
18

Docker で CPU 使用率 100% のコンテナを軽減・回避する

Last updated at Posted at 2020-08-08

foreach ループを使い Server-Sent Events を監視している Docker コンテナがあります。コンテナを起動して、しばらくするとマシンが「フォーーっ」と唸り始めたので、 docker stats コマンドで確認すると CPU 使用率が 100% を超えていました。
どうしよう

Qiita 記事に絞って「Docker CPU 使用率 100% コンテナ 軽減」でググってもヒットしなかったので、自分のググラビリティとして。

TL; DR (今北産業)

  1. 条件により空ループ(何も処理をしないでループする)箇所が発生していないか確認する。
    for/foreach/while ループなど)
  2. 該当ループ処理の最後に支障をきたさない範囲で sleep を入れる
    (1 〜 0.5 秒入れるだけでも劇的に変わる)
  3. 上記を確認した上で Docker もしくは docker-compose の設定で最大使用率に制限をかけるのがベター

TS; DR (kwsk)

強制的に制限する(docker 編)

もともと Docker にはコンテナごとに CPU の最大使用率を制限する --cpus オプションがあります。つまり、コンテナに対して使用する CPU の個数を指定してリソースを分配することができるため、対処治療(対症療法)に使うことができます。

例えば 4 コア CPU の場合、CPU を 2 つ割り当てると使用率を最大 50% に制限できます。

2つぶんのCPUを割り当てる(2/4=0.5=50%)
docker run -it --cpus="2" ubuntu /bin/bash

同じく 4 コアで最大 80% に制限したい場合は以下。

3.2個ぶんのCPUを割り当てる(3.2/4=0.8=80%)
docker run -it --cpus="3.2" ubuntu /bin/bash

強制的に制限する(docker-compose 編)

docker-compose で制限したい場合は、deploy ディレクティブで cpus を指定します。

docker-compose.yml(4コアのCPUを80%に制限する例)
version: '3.8'

services:
  my-service:
    container_name: my-cont1
    build: .
    deploy:
      resources:
        limits:
          cpus: "3.2"

注意点として、docker-compose up 時に --compatibility を付る必要があります。

これは docker-compose.yaml における deploy の項目は、基本的に Docker Swarm 用の設定だからです。つまり、docker-compose コマンドではデフォルトで無視されてしまいます。docker-compose でも使えるようにするには --compatibility フラグ・オプションを付けて互換モードで動作させる必要があります。

compatibilityオプションを付けないとdeployの内容は有効にならない
- docker-compose up myservice
+ docker-compose --compatibility up myservice

この設定は、コンテナが暴走した場合に備えて被害の範囲をあらかじめ抑えておくパターンです。つまり、これらは制限をかけているだけということです。

逆に言えば正常稼働していても、それ以上の CPU リソースを使うことができません。

そもそも、抜本的に CPU の使用率を下げない限り、リソースは制限値まで無駄に消費されることになります。おそらく大抵の場合は Docker の問題ではなく、プログラムの組み方に問題があるのは容易に想像できます。

プログラムを見直す

今回のプログラム自体は、SSL 接続のソケットからデータを読み込み Server-Sent Events のメッセージを受信して文字列を置き換える処理を行うだけのシンプルなものです。ファイルのアクセスすらありません。

そうなると、メッセージ読み取りと文字列処理のループ箇所が臭います。

そこで、VS Code のデバッグ機能を使いステップインで1行ずつ実行を確認してみます。

ポチ、ポチ、、、ポチ、、、あ...

どうやら、データが流れてこない間は処理を行わない「空ループ」が発生している箇所がありました。つまり、何も処理しない最速のループが実行されているわけで、「そりゃ CPU 使用率 100% になるわ」と納得しました。

$read = false;
while ($read === false) {
    $read = readLine($socket); // データが届いてない間は false で高速ループ
}

そこで、PHP の「ループ処理を行った際の CPU の負荷」や .NET の「CPU使用率100%を回避する方法」を参考に、試しに1秒間の sleep をループの最後に入れてみました。

$read = false;
while ($read === false) {
    $read = readLine($socket);
    sleep(1);
}

これが、効果てきめん! 100〜110% であった使用率が 0.00〜0.01% をウロチョロする程度にまで押さえ込めました。sleep が 0.5 秒でも 0.01〜0.03% 程度の負荷でした。

以上から、PHP に限らず、まずはステップ実行などでループ箇所を見直し、ループの支障をきたさない範囲で sleep を入れるのは効果的だと思いました。その上で、保険として dockerdocker-composecpus で最大値を制限するのがベターだと思われます。

参考文献

19
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
18