cgroup について理解する - いますぐ実践! Linuxシステム管理 / Vol.228

いますぐ実践! Linux システム管理 / Vol.228 / 読者数:2194名

こんばんは、うすだです。

転職して1ヶ月半ですので、当たり前といえば当たり前なのですが、 ここのところ、社外の方々と初めてお会いする、という機会が増えています。

で、そこであらためて、強烈に認識させられたことがあります。
それは… 自分のコミュニケーション能力の低さです。

ひとりで打合せにいくことは今のところないのですが、名刺交換などした後は、 メインのひとたちの会話を横で伺って、 はーはーふんふんと相槌を打ちながらメモすることしかできていません。

その分野に疎いなりに、なにか質問などして、感心を示したり、 存在感をアピールするなど、 社会人として最低限やるべきことは多々あると思うのですが、 頭が回転せずそのまま終わってしまっております。

当メルマガの読者のみなさまは、若い方が多いと思っていますが、 こんな中年にならぬよう、いまのうちから積極的な行動を意識しておくことを、 盛大におすすめいたします。

まずは懇親会に参加せねば…と思いつつ、今回もはりきってまいります。

今回のお題 - cgroup について理解する

それなりの期間、UNIXやUNIXライクなOSと戯れていますと、 大昔に学んだ知識がそのまま通用する部分と、 どんどん進化して通用しなくなっていく部分があることを、日々痛感させられます。

たとえば、UNIX系の基本的なコマンドは、 細かいオプションなどの違いはあるかもしれませんが、 基本的な使い方は太古の昔から変わってません。

かたや、起動スクリプトなどは、SysV系のものが今でも使えたりはしますが、 中核は Upstart や systemd などへ移行しています。
(そのまた大昔は、rc.local に直接書いていたりもしましたね…。)

他にも、仮想化やファイルシステム、ネットワークなどの中には、 新たに生み出された技術というモノが、結構あるように思います。

その中でも、最近まで知らなくて、その存在に衝撃を受けたのが、 cgroup という機能です。

実は、個人的には、まだつまみ食いしている程度の知識しかありません。
ですので、今回、おのれの知識の再確認や、そのまとめという意味合いも込めて、 cgroup を取り上げたいと思います。

cgroup とは?

cgroup とは、複数のグループをまとめて管理するための、 Linuxカーネルの機能です。
2.6.24 に導入されていますので、いまどきのディストリビューションであれば、 大抵は使えると思います。

cgroup を使うと、CPU時間やメモリ、ネットワークなどといったリソースを、 グループ単位で割り当てたり、制限をかけることができます。
また、名前空間というモノを使って、 PIDやネットワークなどをそれぞれ独立して持たせることができます。 LXC(Linux Container)という仮想化の一種(?)では、これを使って実現しています。

…とまあ、説明だけではアレですので、実際に手を動かしながら、 説明をしていきたいと思います。

まずは cgroup が使えるか確認を

まずは、cgroup が使える状態か、確認しましょう。
以下では、主要なディストリビューションでどうか、 確認した結果を淡々と書きました。バージョンが微妙ですので、 最新では違っている可能性があります。もしそうでしたら、 たいへん申し訳ございません。

Ubuntu 12.04 の場合、最初から cgroup-lite というパッケージが入っています。 /sys/fs/cgroup にいろいろ揃っています。

  $ ls /sys/fs/cgroup/
  blkio/      cpuacct/    devices/    memory/     
  cpu/        cpuset/     freezer/    perf_event/ 

ただし、後述のコマンド等を使用するには、Debian と同様、 cgroup-binパッケージをインストールしておく必要があります。

  # apt-get install cgroup-bin

Debian 6.0.2 の場合、cgroup-bin パッケージを入れると、 /mnt/cgroups になにやら見えるようになります。

  # apt-get install cgroup-bin
  # ls /mnt/cgroups/
  cpu  cpuacct  devices

Fedora や CentOS の場合は、libcgroup というパッケージを入れると、 前者は /sys/fs/cgroup、後者は /cgroup にもろもろが出現します。
下記は、CentOS 6.3 上でインストールを行った場合の例です。

  # yum install libcgroup
  # chkconfig cgconfig on
  # service cgconfig start
  # ls /cgroup/
  blkio  cpu  cpuacct  cpuset  devices  freezer  memory  net_cls

Fedora 16 ですと、libcgroup は最初から入っていますが、 コマンド等は libcgroup-tools パッケージに入っています。
コマンドなどを使いたいなら、インストールしておきましょう。

  # yum install libcgroup-tools

リソース毎に設定します

前述の ls の結果からわかるように、リソース毎に、 サブシステムというモノに分かれています。 主なサブシステムの概要をざっくり示します。

名 前概 要
blkioブロックデバイスの入出力
cpuCPUリソースの制限
cpuacctCPUの走行時間などの情報収集
cpusetCPUの割り付けに関する制限
devicesデバイスへのアクセス許可・拒否
freezerグループに属するプロセスの一時停止・再開
memoryメモリリソースの制限

今、どのサブシステムが使用できるか確認するには、 lssubsys コマンドを実行します。オプションをつけずに実行してください。

  $ lssubsys
  cpu
  cpuacct
  devices
  memory
  freezer

上記の場合(Ubuntu 12.04 です)、5つのサブシステムが使える状態です。 -a オプションをつけて実行すると、設定すれば使用できるサブシステムも含めて、 全部列挙します。

  $ lssubsys -a
  perf_event
  cpuset
  cpu
  ...後略...

とあるサブシステムを使えるようにしたい場合、 /etc/cgconfig.conf の mount セクションに追加します。
たとえば、Debian の場合、デフォルトでは以下のようになっています。

  mount {
      cpu = /mnt/cgroups/cpu;
      cpuacct = /mnt/cgroups/cpuacct;
      devices = /mnt/cgroups/devices;
  }

cpuset や freezer も使いたい場合は、以下のように追加します。
(/mnt/cgroups の部分は、ご使用の環境に合わせて変更してください。)

  mount {
      cpu = /mnt/cgroups/cpu;
      cpuacct = /mnt/cgroups/cpuacct;
      devices = /mnt/cgroups/devices;
      cpuset = /mnt/cgroups/cpuset;
      freezer = /mnt/cgroups/freezer;
  }

そして、cgconfig サービスを再起動します。

  # service cgconfig restart   (RedHat系やDebianの場合)
  # restart cgconfig           (Ubuntuの場合)

問題なければ、どーんと増えているはずです。

  $ lssubsys
  cpu
  cpuacct
  cpuset
  devices
  freezer

とりあえずグループを作ってみましょう

最初は、みんな同じグループに属していて、 親分は init というプロセスになっています。 (最初にカーネルから起動されるプロセスですね。)

サブシステムの下には task というファイルがあります。taskを参照すると、 そのグループに属しているプロセスがわかります。
たとえば、freezerサブシステムの直下にあるtaskを参照すると、 今動作しているプロセスが全部、どどーんと列挙されます。
(くどいようですが、/cgroup の部分は、ご使用の環境に合わせて、 適時変更してくださいませ。)

  $ cat /cgroup/freezer/tasks
  1
  2
  3
  ...後略...

ここで、試しに別のグループを作ってみましょう。
まず、サブシステムの直下にディレクトリを作成します。
以下では、test というディレクトリを作成しています。

  # ls /cgroup/freezer/
  cgroup.event_control  notify_on_release     tasks
  cgroup.procs          release_agent
  # mkdir /cgroup/freezer/test

すると、ディレクトリを作成しただけなのに、 いろんなファイルが自動的に作られています。

  # ls /cgroup/freezer/test/
  cgroup.event_control  freezer.state         tasks
  cgroup.procs          notify_on_release     

ここで、なにかプロセス(以下ではxterm)を実行してみましょう。

  $ xterm &
  [1] 4618

プロセスIDが4618で起動されました。
このプロセスは、当然、freezerサブシステム直下のtaskに含まれます。
(Debian や Ubuntu の場合 /cgroup/freezer/sysdefault/tasks です。)

  # grep 4618 /cgroup/freezer/tasks 
  4618

ここで、先ほど作成した test に登録してみましょう。
task にプロセスIDを書き込むと、そのグループに所属が変わります。

  # echo 4618 > /cgroup/freezer/test/tasks 
  # cat /cgroup/freezer/test/tasks 
  4618
  # grep 4618 /cgroup/freezer/tasks 
  #

freezer直下にはいなくなったことがわかります。
そして、freezer.state ファイルを参照すると、 現在の状態がわかるようになっています。
(thaw は 解けるとか戻るという意味なので、 一時停止状態だったものが再開されている、と考えればよいと思います。)

  # cat /cgroup/freezer/test/freezer.state
  THAWED

ここで、一時停止してみましょう。上記ファイルに FROZEN と書き込むと停止します。

  # echo FROZEN > /cgroup/freezer/test/freezer.state
  # cat /cgroup/freezer/test/freezer.state
  FROZEN

xterm に何か文字を入力しても、無反応になります。
そして、THAWED と書き込むと、実行を再開します。

  # echo THAWED > /cgroup/freezer/test/freezer.state
  # cat /cgroup/freezer/test/freezer.state
  THAWED

先ほど打ち込んだ文字が、xterm上に表示されました。

…って、全然グループじゃないので、例としては非常に微妙ですが…。
(いっぱい登録して同じことをすれば、みんな一時停止したり再開したりします。)

グループを削除します

xterm を終了するか、もとのグループに書き戻すと、 先ほど作った test に属するプロセスがいなくなります。

  # echo 4618 > /cgroup/freezer/tasks 
  # grep 4618 /cgroup/freezer/tasks 
  4618
  # cat /cgroup/freezer/test/tasks 
  #

tasks が空になれば、test ディレクトリを削除することができます。

  # rmdir /cgroup/freezer/test

おわりに

以上、cgroup について、大枠だけを超簡単にご説明しました。

…大枠過ぎましたので、次回は、 もうちょっと面白いと思える例を考えてご紹介したいと思います。

上記のように、ちょろっと試してささっと戻しておけば、 既存のシステムに何か悪い影響を及ぼしたり、といったことはありません。
…ですが、できれば、どうなってもいい仮想環境等でお試しいただけますと幸いです。

宿題の答え

前回の宿題は、

  スクリプトで自身をインタープリタに指定するとどうなるでしょうか?

でした。

たとえば、こんなスクリプトを2つ、用意します。
1つは、たとえば /home/usu/loop1.sh というファイル名で、 もう一方のスクリプトをインタープリタに指定します。

#!/home/usu/loop2.sh
echo "1: $0"

もう1つは、たとえば /home/usu/loop2.sh というファイル名で、 前述のスクリプトをインタープリタに指定します。
(各所の /home/usu は、適時都合のよいパスに書き換えてください。)

#!/home/usu/loop1.sh
echo "2: $0"

これを実行すると、loop1.sh は loop2.sh をインタープリタに指定しているため、 loop2.shを実行しようとして、 でも loop2.sh は loop1.sh をインタープリタに指定しているため、 loop1.shを実行しようとして… という負のスパイラルを、 地球が滅びるまで続けそうに思われます。

ですが、さすがにそうはならず、 カーネルがどこかでストップをかけるのではないかと推測しました。
で、実際、以下のように実行権をつけて実行してみました。
すると…

  $ chmod +x loop1.sh loop2.sh
  $ ./loop1.sh
  1: ./loop1.sh
  $ ./loop2.sh
  1: ./loop2.sh

…フツーに、シェルスクリプトとして実行されてしまいました。

ですが、シェルから実行するのではなく、下記のようなCのプログラムを利用して、 システムコールから実行しようとすると、様子が違いました。

/* man execve に記載されている例から、毛を数本抜いたものです */
#include 
#include 

int main(void)
{
	char *eargv[] = { "/home/usu/loop1.sh", NULL };
	char *eenv[] = { NULL };
	execve(eargv[0], eargv, eenv);
	perror("execve");
	return 1;
}

これを loopexec.c というファイル名で保存し、コンパイルして実行すると、 以下のように、実行されずにエラーとなりました。

  $ gcc -o loopexec loopexec.c
  $ ./loopexec
  execve: Exec format error

実は、カーネルでは、一定回数を越えて(具体的には 4回)インタープリタを呼び出すと、 エラー(ENOEXEC)になるよう仕組まれています。
(詳しくは、fs/binfmt_script.c の load_script() や、 呼び出し元となる fs/exec.c の search_binary_handler() をご覧ください。)

ですので、ここからは推測ですが、実行に失敗したら、 シェルスクリプトだろうとシェルが判断して実行してしまうのではないかと思われます。

たとえば、loop1.sh を以下のように書き換えますと、

#!
echo "1: $0"

システムコールから直接実行した場合、やはりエラーになります。

  $ ./loopexec
  execve: Exec format error

でも、シェルから実行した場合、シェルスクリプトとして実行されます。

  $ ./loop1.sh
  1: ./loop1.sh

あまり遭遇することはないかもしれませんが、 シェルから実行する場合とそうでない場合とで、様子が異なることがある、 ということを、心の片隅に置いておくと、何かのトラブルの際に役立つかもしれません。

今回の宿題

今回の宿題は、

  freezerサブシステムに新たなグループを作り、なにか効果的な使い方
  をしてみましょう。

です。

…そいつぁ宿題じゃねえ! という気がプンプンしておりますが…。OTZ

あとがき

IT業界(?)の実作業員としては、 おじいさんにカテゴライズされる年齢になってしまったように思っております。
そのため、興味も、自然と技術一辺倒から、 徐々に管理とかの方へ移行しつつあるような気がしております。
(まだまだ技術的に未熟であるにもかかわらず…ですが…。)

で、ここのところ気になったページがありましたので、ご紹介します。

もうすぐ家が建たなくなる - レジデント初期研修用資料
http://medt00lz.s59.xrea.com/wp/archives/176

…なんですが、以下からたどりました。

これってIT業界も全く同じじゃねえ? あるいは何故デカイ店のコックは育たないか:プロジェクトマジック:ITmediaオルタナティブブログ
http://blogs.itmedia.co.jp/magic/2012/09/it-9d7a.html

ざっくり要約しますと、部分だけ特化してできるようになるだけではダメで、 小さくても全体を通して作り上げるという経験が必要なのでは、 的なご指摘…だとわたしは思いました。

わたしが20代のころは、もうほんとに技術至上主義的で、 とにかくできるようにならねばという感じで生きていました。
でも、習得した技術を使って、お客さんが望むモノを提供できないと意味がない、 というところまでは考えられず、技術を知った時点で、 満足して(思考が)止まってしまっていました。

今の社長は、 ドライバ書いたりとかどこかの国で安くできることではもう食いっぱぐれる、 日本の(中小企業の)技術屋は上流を抑えないとダメだ、と常々言われております。

それを聞いてなるほどと思いつつ、具体的な行動にはまだ移せていないのですが、 上記を読んで、ますます危機感をつのらせつつも、 方向はあっているのだという気持ちは強まりました。

問題は、まずどうしていけばいいのか、そしてどうなりたいのか、 というところが、未だにはっきりしてない(考えられてない)ことでしょうか…。

というわけで、迷える42歳は、依然として迷走しております…。

今回も、ここまで読んでいただき、たいへんありがとうございました。
次回は、10月7日(日) の未明にお会いしましょう!

「いますぐ実践! Linux システム管理」の解除は、以下からできます。
http://www.usupi.org/sysad/ (まぐまぐ ID:149633)

バックナンバーは、こちらにほぼ全部そろっています。
http://www.usupi.org/sysad/backno.html

「栗日記」- 栗の季節到来!! さあがんばりますよー。
http://www.usupi.org/kuri/ (まぐまぐ ID:126454)
http://usupi.seesaa.net/ (栗日記ブログ)
http://usupi.org/k/ (モバイル栗日記)
http://twitter.com/kuriking/ (栗つぶやき)
http://facebook.com/kuriking3 (栗顔本)


[バックナンバーのトップへ] [Linux システム管理のトップへ]