2015年7月6日月曜日

cgroupsを利用したリソースコントロール入門

こんにちは、北野です。今回は、cgroups(Control Groups)について調べてみました。

ちなみに、ネット上にはcgroups、cgroupという2つの表記が混在しているようなのですが、カーネル付属のドキュメントでは「cgroups」となっていましたので、ここではその表記に準拠させていただきました。また、カーネル機能について述べているのか、設定したグループについて述べているのかも紛らわしいように思いましたので、後者は「コントロールグループ」と表記してあります。

cgroupsとは

cgroupsは、「プロセスをグループ化して、リソースの利用をコントロール」するカーネル機能で、Linux 2.6.24からマージされています。具体的には、グループ単位でリソースの割り当て、優先度、管理、モニタリングなどが設定できます。 cgroupsそのものはプロセスを「コントロールグループ」と呼ばれる単位にまとめるだけで、リソースコントロールを行うにはコントロールグループに「サブシステム」と呼ばれる抽象化されたリソース群をつなげる必要があります。

主なサブシステム(リソースコントローラとも呼ばれています)には、次のようなものがあります。

サブシステム 機能
cpu CPUへのアクセス
cpuacct CPUについての自動レポートを生成
cpuset マルチコアCPUのコア単位およびメモリノードを割り当て
memory メモリに対する制限設定とメモリリソースについての自動レポートの生成
blkio ブロックデバイスの入出力アクセス
devices デバイスへのアクセス
net_cls ネットワークパケットへのタグ付け
net_prio ネットワークトラフィックの優先度を動的に設定
freezer タスクを一時停止または再開

たとえば、「cpu_mem」というコントロールグループを作成し、cpuとmemoryの両サブシステムを接続してパラメータを変更すれば、そのグループに属するタスク(プロセス)のCPUやメモリの利用をコントロールできます。コントロールグループはシステム上に複数作成できるうえ、階層構造にできるので、きめ細かいコントロールが可能です。

cgroupsの始め方

今回検証で利用したCentOS 7では、デフォルトのままではcgroupsを操作するためのライブラリやツールがインストールされていませんでしたので、下記の2つのパッケージをインストールします(ただし、RHEL 6/CentOS 6の場合は、libcgroupパッケージにツールも含まれているため、libcgroupだけで十分です)。

・RHEL 7/CentOS 7

$ sudo yum install libcgroup libcgroup-tools

・RHEL 6/CentOS 6

$ sudo yum install libcgroup

デフォルト状態のCentOS 7では、関連サービスが起動しておらず、自動起動も設定されていませんでしたので、それらの対応も行っておきます。cgroupsの管理を行うcgconfigサービスと設定ファイル(/etc/cgrules.conf)にしたがってタスクをcgroupsに移動するcgredサービスを起動し、自動起動も有効にしておきます。

・RHEL 7/CentOS 7

$ sudo /usr/bin/systemctl start cgconfig
$ sudo /usr/bin/systemctl enable cgconfig
$ sudo /usr/bin/systemctl start cgred
$ sudo /usr/bin/systemctl enable cgred

・RHEL 6/CentOS 6

$ sudo service cgconfig start
$ sudo chkconfig cgconfig on
$ sudo service cgred start
$ sudo chkconfig cgred on

これで準備完了です。

cgroupsの設定方法

コントロールグループの設定には、大まかに3つの方法があります。いずれの方法もユーザー/グループやプログラムやプロセスに関連づけることで制限が有効になります。

1.設定ファイルの編集

1つ目は、cgconfigサービスが参照している/etc/cgconfig.confファイルを変更する方法です。 たとえば、「cpu-limit-50というコントロールグループを作成して、cpuの利用率を50%までで制限するよう設定する」なら、/etc/cgconfig.confに次のように書きます。

/etc/cgconfig.conf

group cpu-limit-50  {
    cpu {
        cpu.cfs_quota_us = 50000;
    }
}

これは、コントロールグループからCPUリソースへのアクセス再割当てをする一定間隔を示すcpu.cfs_period_usがデフォルトの100000μsで、このコントロールグループに割り当てる最大時間としてcpu.cfs_quota_usに50000μsを設定しているので、50%まで利用を許可するという意味になります。このあと、cgconfigサービスを再起動すれば、この設定が有効になります。

2.専用コマンドの実行

2つ目は、cgroups操作のための専用コマンドを利用する方法です。専用のコマンドには下記のものがあります。

コマンド 機能
cgcreate コントロールグループの作成
cgdelete コントロールグループの削除
cgset サブシステムのパラメーター設定
cgget コントロールグループのパラメーター表示
cgclassify コントロールグループのタスク移動
cgexec コントロールグループ内のプロセス開始
cgsnapshot 既存サブシステムから設定ファイルを生成
cgconfigparser 設定ファイルを解析して階層をマウント
cgclear コントロールグループのアンロード
lscgroup コントロールグループの確認
lssubsys サブシステムのマウントポイント表示

先ほどと同じ条件の設定(cpu-limit-50というコントロールグループを作成して、cpuの利用率を50%までで制限する)を専用コマンドで設定するなら、次のコマンドを実行します。

$ sudo cgcreate -g cpu:/cpu-limit-50
$ sudo cgset -r cpu.cfs_quota_us=50000 cpu-limit-50

3.仮想ファイルシステムの操作

3つ目は、仮想ファイルシステム上を変更する方法です。コントロールグループの階層構造は仮想ファイルシステム上にマッピングされているので(RHEL 7/CentOS 7では、/sys/fs/cgroup以下、RHEL 6/CentOS 6では、/cgroup以下にマッピング)、mkdirmountといった通常のファイル操作コマンドで変更することができます。また、パラメータも、仮想ファイル上の特殊ファイルに書かれているので、catechoコマンドなどで参照や変更が可能です。この辺りの変更方法については、/procと同様ですね。

こちらも、同じ条件の設定(cpu-limit-50というコントロールグループを作成して、cpuの利用率を50%までで制限する)をファイル操作系コマンドで設定するなら、次のコマンドを実行します。RHEL 7系とRHEL 6系ではパスが違うので注意してください。

・RHEL 7/CentOS 7

$ sudo mkdir /sys/fs/cgroup/cpu/cpu-limit-50
$ sudo sh -c "echo 50000 > /sys/fs/cgroup/cpu/cpu-limit-50/cpu.cfs_quota_us"

・RHEL 6/CentOS 6

$ sudo mkdir /cgroup/cpu/cpu-limit-50
$ sudo sh -c "echo 50000 > /cgroup/cpu/cpu-limit-50/cpu.cfs_quota_us"

ただし、2つ目と3つ目の方法はシステムを再起動すると設定が消えてしまいますので、永続化させたい場合はcgsnapshotコマンドでファイルに書き出すのを忘れないようにしてください。

特定ユーザーのCPU利用を制限する

では、CentOS 7上で、先ほどの設定を利用して特定ユーザーのCPU利用を制限してみましょう。まず、制限前の状態を確認するため、kitanoユーザーで下記のコマンドを実行します。

[kitano@centos7 ~]$ cat /dev/zero > /dev/null &

topコマンドでCPU利用率を確認すると、ほぼ100%近くまで上昇していることがわかります。確認したら、このプログラムはkillコマンドなどで停止しておきます。

PID  USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
2936 kitano    20   0  107912    620    508 R 99.8  0.1   0:25.77 cat

では、先ほど紹介したものと同じ、CPU利用率を50%程度に制限する設定を/etc/cgconfig.confに書き込みます。

・/etc/cgconfig.conf

group cpu-limit-50  {
  cpu {
      cpu.cfs_quota_us = 50000;
  }
}

さらに、cgredサービスが参照している/etc/cgrules.confに下記の設定を行い、cgconfig.confで設定したコントロールグループをkitanoユーザーに関連づけます。

・/etc/cgrules.conf

kitano cpu cpu-limit-50

設定はこれで完了しましたので、関連する2つのサービスを再起動させます。

$ sudo systemctl restart cgconfig
$ sudo systemctl restart cgred

再度、先ほどのコマンドライン(cat /dev/zero > /dev/null &)をkitanoユーザーで実行します。

PID  USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
2339 kitano    20   0  107912    624    508 R 50.2  0.1   0:56.79 cat

%CPUの値を見ると、CPU利用率が50%前後に制限されているのがわかるかと思います。

ちなみに、同じプログラムを2つ起動すると、それぞれのプロセスの割り当てが25%ずつとなり、トータルで50%を超えないようにコントロールしてくれます。

 PID USER     PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
2339 kitano   20   0  107912    624    508 R 25.5  0.1   0:01.26 cat
2333 kitano   20   0  107912    624    508 R 25.1  0.1   4:29.89 cat

プログラム単位でCPU利用を制限する

次に、プログラム単位でCPU利用に制限をかけてみましょう。まず、先ほどのコマンドラインを書いただけのスクリプトをcpu_limit_test.shとして保存します。

・cpu_limit_test.sh

#! /bin/bash

cat /dev/zero > /dev/null &

続いて、スクリプト実行とそれ以外でCPU利用率の違いがわかるよう、もう1つのコントロールグループ「cpu-limit-20」の設定を追記します。

・/etc/cgconfig.conf

group cpu-limit-50  {
  cpu {
      cpu.cfs_quota_us = 50000;
  }
}

group cpu-limit-20  {
  cpu {
      cpu.cfs_quota_us = 20000;
  }
}

ユーザーへの関連づけは、次のように設定します。プログラムの登録そのものは、ユーザー名の後ろに「:」(コロン)とプログラム名を追加するだけです。加えて、関連づける コントロールグループを先ほど設定したcpu-limit-20に変更します。

・/etc/cgrules.conf

kitano:cpu_limit_test.sh cpu cpu-limit-20

続いて、cgconfig、cgredの両サービスを再起動したあと、スクリプト(cpu_limit_test.sh)を実行します。

・cpu_limit_test.sh

PID  USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
2801 kitano    20   0  107912    624    508 R 20.0  0.1   0:01.74 cat

期待どおり、CPU利用率が20%に制限されています。

コントロールグループのタスク移動

なお、ユーザー単位(kitano)で制限したときのタスク(プロセス)は、CPU利用率が50%になっていることからもわかるとおり、実行時点でcpu-limit-50コントロールグループの管理下となっています。そのため、管理下にあるタスク(プロセス)のPIDが羅列されている/sys/fs/cgroup/cpu/cpu-limit-50/tasksに、このプロセスのPIDがあります。

[kitano@centos7 ~]$ grep 2339 /sys/fs/cgroup/cpu/cpu-limit-50/tasks
2339

このPIDをもう1つのコントロールグループであるcpu-limit-20のtasks/sys/fs/cgroup/cpu/cpu-limit-20/tasks)にechoすると、/sys/fs/cgroup/cpu/cpu-limit-20/tasksにPIDが書き込まれのはもちろんのこと、/sys/fs/cgroup/cpu/cpu-limit-50/tasksからもPIDが自動的に消えるため、タスクがコントロールグループを移動したのと同じことになります。

[kitano@centos7 ~]$ sudo sh -c "echo 2339 >> /sys/fs/cgroup/cpu/cpu-limit-20/tasks"
[kitano@centos7 ~]$ grep 2339 /sys/fs/cgroup/cpu/cpu-limit-50/tasks
[kitano@centos7 ~]$ grep 2339 /sys/fs/cgroup/cpu/cpu-limit-20/tasks
2339

そうすると、下記のとおり、今度はcpu-limit-20コントロールグループの制限値が適用され、CPU利用率が20%近くまで下がります(もちろん、逆も可能です)。また、専用コマンドのcgclassifyでも同様のタスク移動が可能です。

PID  USER    PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
2339 kitano  20   0  107912    624    508 R 19.6  0.1   1:49.59 cat

このはてしなく遠い……

今回はcgroupsにを利用したリソースコントロールの仕組みについて調べてみました。ほんの入口程度なので、ここから実運用で使えるレベルになるには、まだまだ「男坂」という感じです。

今回紹介した機能だけでは導入メリットがわかりづらいかと思いますが、systemdが導入されたRHEL 7では、すべてのプロセスがcgroupを利用して管理されていたり(systemd-cglsコマンドやsystemd-cgtopコマンドを実行するとsystemdが管理しているcgroupsの情報を確認できます)、コンテナ技術のリソース管理にも利用されていたりなど、今後のサーバー運用には必須の知識になるように思います。普段から使用して少しずつ慣れていきたいと思います。

0 件のコメント:

コメントを投稿