CentOS 7の仮想化、Docker、リソース管理(後編)
cgroupによるハードウェア資源管理
一つのホスト上で複数の隔離空間として稼働するDockerコンテナやKVMの仮想マシンが稼働する環境において、限られたハードウェア資源の利用制限は非常に重要です。特定のユーザーが使用するコンテナがホストマシンのハードウェア資源を食いつぶすようなことがあると、他のユーザーの利用に支障をきたします。そこで、CentOS 7では、cgroupとよばれる資源管理の仕組みが備わっています。以下では、cgroupを使ったネットワーク帯域制御とディスクI/O制御の手法を紹介します。
cgroupとは
「cgroupは、Linuxのカーネルに実装されているリソース制御の仕組みです。CPU、メモリ、ネットワーク通信の帯域幅などのコンピュータ資源を組み合わせ、ユーザーが定義したタスクのグループに割り当て、このグループに対してリソースの利用の制限や開放を設定することが可能となります。この設定は、システムが稼働中に行うことができ、OSの再起動を行うことなく資源の割り当てを動的に行う事が可能となります。特に、マルチコアのシステムにおいて、CPUを効率よく利用するために商用UNIXでも利用されている技術です。マルチコアシステムで稼働させるマルチスレッドのアプリケーションの性能劣化をできるだけ発生させないようにするために、cgroupによってコンピューター資源を割り当てます。
cgroupの初期設定
CentOS 7におけるcgroupを利用するには、cgconfigサービスを起動します。
# systemctl enable cgconfig ln -s '/usr/lib/systemd/system/cgconfig.service' '/etc/systemd/system/sysinit.target.wants/cgconfig.service' # systemctl start cgconfig
cgroupによる資源管理は、/sys/fs/cgroup配下の各種ディレクトリ配下に、ハードウェア資源に対応したサブシステムが存在します。
# lssubsys -am cpuset /sys/fs/cgroup/cpuset ←CPUリソースに関する各種パラメータを格納しているディレクトリ cpu,cpuacct /sys/fs/cgroup/cpu,cpuacct ←CPUリソースに関する自動レポートを生成 memory /sys/fs/cgroup/memory ←メモリーリソースの自動レポートを生成、タスクが使用するメモリの上限の設定 devices /sys/fs/cgroup/devices ←ブロックデバイスや文字デバイスへのアクセス可否を設定 freezer /sys/fs/cgroup/freezer ←タスクの一時停止や再開 net_cls /sys/fs/cgroup/net_cls ←ネットワークパケットのタグ付け blkio /sys/fs/cgroup/blkio ←ブロックデバイスへのI/Oを制御、監視 perf_event /sys/fs/cgroup/perf_event ←プロセスやスレッドをperfツールで監視可能にする hugetlb /sys/fs/cgroup/hugetlb ←大きいサイズの仮想メモリページを利用可能にする
今回は、ネットワーク通信の帯域制御を行うため、net_clsサブシステムを使って、test01という名前のcgroupを作成します。
# cgcreate -t koga:koga -g net_cls:/test01 # ls -lF /sys/fs/cgroup/net_cls/test01/ total 0 -rw-rw-r--. 1 root root 0 Sep 9 02:06 cgroup.clone_children --w--w----. 1 root root 0 Sep 9 02:06 cgroup.event_control -rw-rw-r--. 1 root root 0 Sep 9 02:06 cgroup.procs -rw-rw-r--. 1 root root 0 Sep 9 02:06 net_cls.classid -rw-rw-r--. 1 root root 0 Sep 9 02:06 notify_on_release -rw-rw-r--. 1 koga koga 0 Sep 9 02:06 tasks
作成したtest01というcgroupに対してパラメータを設定します。
# cgset -r net_cls.classid=0x00010002 /test01 # cat /sys/fs/cgroup/net_cls/test01/net_cls.classid 65538
通信の帯域制御を行うためのネットワークインタフェースを確認します。
# ip a ... 2: enp0s25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000 link/ether 70:5a:b6:ab:5e:da brd ff:ff:ff:ff:ff:ff inet 172.16.25.30/16 brd 172.16.255.255 scope global enp0s25 valid_lft forever preferred_lft forever ...
tcコマンドにより、トラフィック量を調整します。
# tc qdisc add dev enp0s25 root handle 1: htb
tcコマンドはトラフィックを制御するコマンドです。qdisc(queueing discipline)は、キューイングに関する規則を表します。OSがデータをどのように送信するかは、どのようなキューイングを使うかに依存します。仮に、Linuxマシンが1GbEのネットワークカードを持っているにも関わらず、接続先のスイッチが100Mb/sにしか対応していない場合、Linuxマシン側で、送信するデータの量を調整する必要があります。この調整を行うためには、データの送信の仕方を工夫する必要があり、その送信の仕方には、キューイングなどが使われています。カーネルは、パケットをインタフェースに送信する時は、インタフェースに対して設定したqdiscにキューイングされます。一般的なqdiscとしては、FIFO型のキューイングがあります。「qdisc add」により、qdiscを追加することを意味します。devには、ネットワークインタフェース名を指定します。今回指定しているネットワークインタフェース名は、enp0s25です。インタフェース名はipコマンドで確認できます。さらに、指定したインタフェースに出入り口を作る必要があります。この出入り口は、root qdiscと呼ばれます。qdiscには、ハンドル(handle)を割り当てます。このハンドルを使ってqdiscを参照することができます。ハンドルは、「メジャー番号:マイナー番号」の書式をとります。ただし、ルートqdiscについては、上記のように、マイナー番号を省略し、「1:」と記述するのが一般的です。これは「1:0」と同じ意味になります。
# tc class add dev enp0s25 parent 1: classid 1:2 htb rate 256kbps
先程作成したルートqdiscの「1:」に繋がるクラス「1:2」を作成します。このクラスを流れるパケットの帯域幅をrateで指定します。htb(hierarchical token bucket)は、階層型トークンバケットと呼ばれ、キューイングの規則にとってかわる高速化の一手法です。
# tc filter add dev enp0s25 parent 1: protocol ip prio 10 handle 1: cgroup
上記は、ネットワークインタフェースenp0s25に対して、フィルタを作成します。「protocol ip」でIPプロトコルを指定しています。キューイングにおける複数のクラスに対して、優先度を設定することができます。
ネットワークインタフェースenp0s25に対する通信性能を検証します。今回は、転送速度の性能検証に用いるファイルをddコマンドで用意します。性能検証用のファイル「testfile」のサイズは30MBとしました。
# cd # dd if=/dev/zero of=/root/testfile bs=1024k count=30 # ls -lh testfile -rw-r--r--. 1 root root 30M Sep 9 02:28 testfile
転送前にtestfileのチェックサムを確認しておきます。遠隔にあるサーバーに転送されたファイルとチェックサムが一致しているかを確認するためです。
# md5sum ./testfile 281ed1d5ae50e8419f9b978aab16de83 ./testfile
testfileのファイル転送は、scpを使います。上記testfileをscpでコピーするスクリプトを作成します。転送先のマシンは、遠隔にある別のLinuxサーバーで構いません。
# vi /root/scp.sh scp /root/testfile 172.16.1.5:/root/ # chmod +x /root/scp.sh
帯域を制限できるかどうかをテストします。ネットワークインタフェースenp0s25のパケット送受信の帯域幅を100キロビット/秒に設定します。
# tc class change dev enp0s25 parent 1: classid 1:2 htb rate 100kbps # cgexec --sticky -g net_cls:test01 ./scp.sh root@172.16.1.5's password: testfile 25% 7952KB 99.0KB/s 03:49 ETA
次に、1メガビット/秒に帯域を制限できるかどうかをテストします。
# tc class change dev enp0s25 parent 1: classid 1:2 htb rate 1mbps # time cgexec --sticky -g net_cls:test01 ./scp.sh root@172.16.1.5's password: testfile 97% 29MB 991.0KB/s 00:00 ETA
最後に、10メガビット/秒に帯域を制限できるかどうかをテストします。
# tc class change dev enp0s25 parent 1: classid 1:2 htb rate 10mbps [root@c70n2530 ~]# time cgexec --sticky -g net_cls:test01 ./scp.sh root@172.16.1.5's password: testfile 100% 30MB 10.0MB/s 00:03
各帯域制限の検証において、ファイル転送完了後は、転送先に保管されたtestfileのMD5チェックサムも確認して下さい。今回は、100kb/s、1Mb/s、10Mb/sの3種類の帯域制限で検証を行いましたが、この値以外の転送速度を指定することも可能です。
ディスクI/Oの帯域制御
以下では、cgroupを使ったディスクI/Oの帯域制御の手順を述べます。まず、test01というcgroupを作成します。/sys/fs/cgroup/blkio/test01ディレクトリ配下に、様々なファイルが用意されます。
# cgcreate -t koga:koga -g blkio:/test01 # ls -l /sys/fs/cgroup/blkio/test01/ total 0 -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.io_merged -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.io_merged_recursive -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.io_queued -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.io_queued_recursive -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.io_service_bytes -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.io_service_bytes_recursive -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.io_serviced -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.io_serviced_recursive -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.io_service_time -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.io_service_time_recursive -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.io_wait_time -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.io_wait_time_recursive -rw-rw-r--. 1 root root 0 Sep 9 05:31 blkio.leaf_weight -rw-rw-r--. 1 root root 0 Sep 9 05:31 blkio.leaf_weight_device --w--w----. 1 root root 0 Sep 9 05:31 blkio.reset_stats -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.sectors -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.sectors_recursive -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.throttle.io_service_bytes -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.throttle.io_serviced -rw-rw-r--. 1 root root 0 Sep 9 05:31 blkio.throttle.read_bps_device -rw-rw-r--. 1 root root 0 Sep 9 05:31 blkio.throttle.read_iops_device -rw-rw-r--. 1 root root 0 Sep 9 05:31 blkio.throttle.write_bps_device -rw-rw-r--. 1 root root 0 Sep 9 05:31 blkio.throttle.write_iops_device -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.time -r--r--r--. 1 root root 0 Sep 9 05:31 blkio.time_recursive -rw-rw-r--. 1 root root 0 Sep 9 05:31 blkio.weight -rw-rw-r--. 1 root root 0 Sep 9 05:31 blkio.weight_device -rw-rw-r--. 1 root root 0 Sep 9 05:31 cgroup.clone_children --w--w----. 1 root root 0 Sep 9 05:31 cgroup.event_control -rw-rw-r--. 1 root root 0 Sep 9 05:31 cgroup.procs -rw-rw-r--. 1 root root 0 Sep 9 05:31 notify_on_release -rw-rw-r--. 1 koga koga 0 Sep 9 05:31 tasks
次に、I/O帯域制御を行いたいストレージデバイスのメジャー番号とマイナー番号をlsコマンドで確認します。今回の場合は、メジャー番号が8、マイナー番号が0です。
# ls -l /dev/sda brw-rw----. 1 root disk 8, 0 Sep 7 12:29 /dev/sda
ディスクI/Oを発生させるスクリプトio.shを作成します。io.shスクリプトは、/usr/share/doc以下の全てのファイルを/dev/nullにアーカイブするスクリプトです。さらに、tarコマンドに--totalsオプションを付与することで、書き出したバイト数を出力することができます。
# vi /root/io.sh echo 3 > /proc/sys/vm/drop_caches time tar cvf /dev/null /usr/share/doc --totals # chmod +x /root/io.sh
ディスクI/Oを10IOPS以下に制限できるかを検証します。I/O帯域制御を行うストレージデバイスのメジャー番号とマイナー番号を「blkio.throttle.read_iops_device」に指定します。
# cgset -r blkio.throttle.read_iops_device="8:0 10" /test01 # cgexec --sticky -g blkio:test01 ./io.sh tar: Removing leading `/' from member names Total bytes written: 95610880 (92MiB, 971KiB/s) real 1m38.296s user 0m0.029s sys 0m0.112s
同様に、ディスクI/Oを50IOPS以下に制限できるかを検証します。
# cgset -r blkio.throttle.read_iops_device="8:0 50" /test01 # cgexec --sticky -g blkio:test01 ./io.sh tar: Removing leading `/' from member names Total bytes written: 95610880 (92MiB, 4.5MiB/s) real 0m21.327s user 0m0.043s sys 0m0.099s
ディスクI/Oを500IOPS以下に制限できるかも検証します。
# cgset -r blkio.throttle.read_iops_device="8:0 500" /test01 # cgexec --sticky -g blkio:test01 ./io.sh tar: Removing leading `/' from member names Total bytes written: 95610880 (92MiB, 8.3MiB/s) real 0m11.549s user 0m0.034s sys 0m0.108s
最後に、cgroupsを適用しない場合のディスクI/O性能を計測しておきます。
# ./io.sh tar: Removing leading `/' from member names Total bytes written: 95610880 (92MiB, 8.4MiB/s) real 0m11.504s user 0m0.029s sys 0m0.113s
ディスクI/Oを10IOPSに設定した場合、io.shスクリプトの実行に、約1分38秒掛っているのに対し、ディスクI/Oを50IOPSに設定した場合は、約21秒に短縮されていることがわかります。このことから、cgroupにより、限られたディスクの性能をユーザーのアプリケーション毎に制限することで、コンピューターシステム全体をより多くのユーザーやアプリケーションで効率的に利用できることがわかります。
systemdを使ったリソース制限
CentOS 7では、systemctlコマンドを使って、httpdサービスのメモリ制限値を設定することができます。以下は、httpdサービスのメモリ使用量の制限値を1GBにする例です。
# systemctl set-property httpd.service MemoryLimit=1G # systemctl daemon-reload ; systemctl restart httpd.service # cat /etc/systemd/system/httpd.service.d/90-MemoryLimit.conf [Service] MemoryLimit=1073741824
実際のクラウドサービス等では、サービスメニューに応じて様々な帯域制限が設けられているのが一般的です。Linuxのcgroupを使えば、クラウド基盤において、特定のユーザーがネットワーク帯域を使い切らないように帯域制限をかけることができます。利用できるネットワーク帯域幅による重量課金制のクラウド基盤システムを構築する場合に有用です。
今回は、仮想化、コンテナ、そして、それらの環境で必要とされるcgroupによるリソース管理の基礎をご紹介しました。仮想化やコンテナは、単なるサーバー集約だけでなく、クラウド基盤でのサービス提供やDevOps環境で必要となる非常に重要な基礎技術です。CentOS 7には、クラウドコンピューティングに必要なこれらの機能が多く搭載されています。全てを紹介することはできませんが、少なくとも、本連載の手順を一通り試し、仮想化やクラウド基盤、開発環境のありかたを再考してみて下さい。
この連載が書籍になりました! | |
---|---|
古賀 政純 著 |
CentOS 7実践ガイド本書は、CentOS 7を取り巻く市場動向、CentOS 7が利用されるサーバーシステムの選定、CentOS 7の基礎、システム設計、OS管理やCentOS 7に対応したアプリケーションサーバーの構築手順などの勘所をご紹介します。連載では書ききれなかった本の内容、見どころが満載!
|