CentOS 7の仮想化、Docker、リソース管理(後編)
この連載が、書籍『CentOS 7 実践ガイド』になりました!
IT技術者のための現場ノウハウ CentOS 7 実践ガイド
CentOS 7を取り巻く市場動向、サーバーシステムの選定、システム設計、構築手順など巷で話題のDockerとは?
最近、巷で注目を浴びているオープンソースの一つに「Docker」(ドッカー)があります。Dockerは、コンテナと呼ばれる隔離空間を管理するためのツールです。CentOS 7は、この「コンテナ」と呼ばれるアプリケーションとOSをパッケージ化した実行環境をサポートしています。一般的に、Linux OS上での隔離空間を「Linuxコンテナ」と呼びます。Linuxコンテナは、KVMやXenなどのハイパーバイザー型の仮想化技術に比べて、CPU、メモリ、ストレージ、ネットワーク等のハードウェア資源の消費やオーバーヘッドが小さいとう利点が挙げられます。Dockerは、Linuxコンテナの機能に加えて、API、イメージフォーマット、環境を配布する仕組みを持っています。しかし、このDockerが注目される理由は、ハードウェア資源の消費のオーバーヘッドが小さいだけではありません。Dockerが注目される背景には、アプリケーション開発者が最も嫌うであろう「ハードウェアの調達やメンテナンス」の隠蔽が挙げられます。ハードウェアに関する部分は、アプリケーション開発者にとってあまり重要ではありません。従来のKVM等による仮想化は、アプリケーション開発者にとってのメリットというよりもむしろ、システム管理者にとって大きなメリットがありました。一方、Dockerのような軽量なコンテナ技術の発達により、アプリケーション単位での隔離、開発環境の容易な作成と廃棄ができるようになり、アプリケーションのメンテナンスの簡素化がより一層進むことになります。これは、システム管理者よりもアプリケーション開発者にとってのメリットが非常に大きいことを意味します。また、Dockerの仕組みによって、アプリケーションの開発と実環境への素早い展開と運用の両立が見えてきました。これは、近年、開発者やIT部門の間で話題になっている「DevOps環境」の実現に他なりません。アプリケーション開発者やIT部門にとっては、アプリケーションの開発、運用、廃棄等を、ハードウェア資源を意識しない「雲」の上で迅速に行える環境がDockerを使って整備されつつあります。
CentOS 7でdockerを使う
以下では、CentOS 7で採用されているdockerのインストールと基本的な利用方法について説明します。CentOS 7では、dockerパッケージが標準で含まれています。yumコマンドでインストールします。
# yum install -y docker # rpm -qa |grep docker docker-1.3.2-4.el7.centos.x86_64
docker pull centosでイメージをインターネット経由で入手しますが、もし企業内においてプロキシーサーバー経由でインターネットに接続する環境では、以下のように、/etc/sysconfig/dockerファイルにプロキシーサーバーを指定し、dockerサービスを再起動してください。
# vi /etc/sysconfig/docker … http_proxy=http://proxy.jpn.hp.com:8080 https_proxy=http://proxy.jpn.hp.com:8080 … # systemctl restart docker # docker pull centos
dockerサービスを起動します。
# systemctl start docker # systemctl enable docker ln -s '/usr/lib/systemd/system/docker.service' '/etc/systemd/system/multi-user.target.wants/docker.service'
これでDockerのインストールは完了です。Dockerでは、様々なOSとアプリケーションがパッケージ化された環境がDockerイメージとしてDockerリポジトリに用意されています。イメージファイルを一から作成することも可能ですが、すでにDockerリポジトリに用意されているイメージファイルを利用することができますので、コンテナへのOSやアプリケーションのインストール等の煩雑な作業をスキップすることができます。以下では、Dockerリポジトリに用意されているイメージファイルをDocker上で起動させてみます。
イメージファイルをDockerリポジトリから入手する前にSELinuxをdisableにしておきます。
# setenforce 0 # getenforce Permissive
イメージファイルをインターネット経由でDockerリポジトリから入手します。
# docker pull centos:centos5 Pulling repository centos bac0c97c3010: Download complete 511136ea3c5a: Download complete 5b12ef8fd570: Download complete Status: Downloaded newer image for centos:centos5 # docker pull centos:centos6 Pulling repository centos 510cf09a7986: Download complete 511136ea3c5a: Download complete 5b12ef8fd570: Download complete Status: Downloaded newer image for centos:centos6 # docker pull centos:centos7 Pulling repository centos 8efe422e6104: Download complete 511136ea3c5a: Download complete 5b12ef8fd570: Download complete Status: Downloaded newer image for centos:centos7
Dockerリポジトリから入手し、ローカルのCentOS 7サーバー上に保管されているイメージファイル一覧を確認します。
# docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE centos 5 bac0c97c3010 7 days ago 466.9 MB centos centos5 bac0c97c3010 7 days ago 466.9 MB centos 6 510cf09a7986 7 days ago 215.8 MB centos centos6 510cf09a7986 7 days ago 215.8 MB centos latest 8efe422e6104 7 days ago 224 MB centos 7 8efe422e6104 7 days ago 224 MB centos centos7 8efe422e6104 7 days ago 224 MB
上記のイメージファイル群のうち、centos6という名前の付いたタグの付いたイメージファイルから、コンテナを生成、起動し、そのコンテナ内で作業できるようにしてみましょう。Dockerのイメージファイルからコンテナを起動するには、docker runを実行します。--nameオプションに、コンテナの名前を指定します。今回は、test01という名前のコンテナを起動するように指定しました。-iオプションは、コンテナの標準入力を開いた状態にします。-tオプションを付与することにより、Dockerは、仮想端末を割り当てて、コンテナの標準入力にアタッチします。centos:centos6は、入手したイメージファイルのタグ付きのリポジトリ名です。最後に、/bin/bashを指定することで、コンテナ上のシェルを起動します。
# docker run --name test01 -i -t centos:centos6 /bin/bash [root@d962dc3be270 /]#
コンテナにログインし、作業してみます。コンテナのOSのバージョンを確認します。
[root@d962dc3be270 /]# cat /etc/redhat-release CentOS release 6.6 (Final)
すると、CentOS 6.6であることがわかります。ホスト名を確認してみます。
[root@d962dc3be270 /]# hostname d962dc3be270
ホスト名は自動的に「d962dc3be270」という名前で割り振られていることがわかります。さらにIPアドレスを確認し、ホストOSや外部と通信できるかを確認します。dockerでは、標準で、インタフェースdocker0を割り当てます。
[root@d962dc3be270 /]# ifconfig eth0 |grep inet inet addr:172.17.0.5 Bcast:0.0.0.0 Mask:255.255.0.0 inet6 addr: fe80::42:acff:fe11:5/64 Scope:Link [root@d962dc3be270 /]# ping -c 3 172.16.1.1 PING 172.16.1.1 (172.16.1.1) 56(84) bytes of data. 64 bytes from 172.16.1.1: icmp_seq=1 ttl=63 time=0.690 ms 64 bytes from 172.16.1.1: icmp_seq=2 ttl=63 time=0.281 ms 64 bytes from 172.16.1.1: icmp_seq=3 ttl=63 time=0.239 ms --- 172.16.1.1 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2373ms rtt min/avg/max/mdev = 0.239/0.403/0.690/0.204 ms [root@d962dc3be270 /]# nslookup www.hp.com<http://www.hp.com>
dockerのイメージファイルの仕組みを理解するため、まず、試しいdockerコンテナー上の/rootディレクトリにファイルtestfileを作成しておきます。
[root@d962dc3be270 /]# touch /root/testfile [root@d962dc3be270 /]# ls /root/ testfile
dockerコンテナのOS環境から離脱します。
[root@d962dc3be270 /]# exit exit #
ホストOS上で、過去に起動したコンテナ一覧を確認します。
# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d962dc3be270 centos:6 "/bin/bash" 9 minutes ago Exited (0) About a minute ago test01
先程作業したコンテナを再利用できるように、コンテナのイメージ化を行います。コンテナをイメージ化するには、コンテナをコミットします。先程作業したコンテナのコンテナIDとイメージを指定してコミットを行います。
# docker commit d962dc3be270 centos:centos6 5468f0e1072fbd5cdf0042efc76e3d0d8c778f235faf212bcbf9b2105ebdf30f
再び、現在のイメージファイルの一覧を確認します。
# docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE centos centos6 5468f0e1072f 24 seconds ago 215.8 MB centos centos5 bac0c97c3010 7 days ago 466.9 MB centos 5 bac0c97c3010 7 days ago 466.9 MB centos 6 510cf09a7986 7 days ago 215.8 MB centos centos7 8efe422e6104 7 days ago 224 MB centos latest 8efe422e6104 7 days ago 224 MB centos 7 8efe422e6104 7 days ago 224 MB
先程コミットしたイメージファイル「centos:centos6」を使って、別のコンテナtest02を生成してみます。
# docker run --name test02 -i -t centos:centos6 /bin/bash [root@5b2ac9622d6f /]#
コンテナtest02上の/rootを確認してみます。
[root@5b2ac9622d6f /]# ls /root/ testfile [root@5b2ac9622d6f /]#
コンテナtest02で、/root/testfile2を作成します。
[root@5b2ac9622d6f /]# touch /root/testfile2 [root@5b2ac9622d6f /]# exit exit # docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5b2ac9622d6f centos:centos6 "/bin/bash" 2 minutes ago Exited (0) About a minute ago test02 d962dc3be270 centos:6 "/bin/bash" 20 minutes ago Exited (0) 12 minutes ago test01 # docker commit 5b2ac9622d6f centos:testfile2 4eac0fb23ec1d4a212b4aa289637f43da3049dd975d48a91fd9890ca6a368cdd # docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE centos testfile2 4eac0fb23ec1 8 seconds ago 215.8 MB centos centos6 5468f0e1072f 7 minutes ago 215.8 MB centos 5 bac0c97c3010 7 days ago 466.9 MB centos centos5 bac0c97c3010 7 days ago 466.9 MB centos 6 510cf09a7986 7 days ago 215.8 MB centos centos7 8efe422e6104 7 days ago 224 MB centos latest 8efe422e6104 7 days ago 224 MB centos 7 8efe422e6104 7 days ago 224 MB
上記より、作業を行ったコンテナをコミットすることでその作業内容を反映したイメージファイルが生成でき、さらに作成したイメージファイルを再利用して、新たなコンテナを生成できていることがわかります。開発者は、アプリケーション毎に異なるコンテナを複数作成し、コミットしておけば、そのアプリケーションに特化したイメージファイルを持つことができ、すぐにアプリケーションが実行可能な環境をコンテナで利用することができます。
コンテナに含まれるファイルの確認方法
現在起動しているDockerコンテナのファイルシステム上にあるファイル等をホストマシンから確認したい場合があります。起動中のコンテナのファイルシステムは、/var/lib/docker/devicemapper/metadataディレクトリ配下にあるコンテナID の名前が付いたメタデータの情報を元に、Device Mapperを使ってアクセスできます。以下は、CentOS 6.6のDockerイメージからコンテナを起動し、そのコンテナのファイルシステムに存在する/etc/redhat-releaseファイルに記載されたCentOSのバージョンをホストマシンから確認する手順です。まず、Dockerに現在登録されているイメージファイルを確認します。今回、CentOS 6.6のDockerイメージ「centos:centos6.6」は、事前にCentOS 6のDockerイメージを入手し、yum updateによりパッケージを最新に更新することにより作成しました。
# docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE centos centos6.6 6e80aa2dad3a 23 seconds ago 215.8 MB centos testfile2 4eac0fb23ec1 4 minutes ago 215.8 MB centos centos6 5468f0e1072f 12 minutes ago 215.8 MB centos 5 bac0c97c3010 7 days ago 466.9 MB centos centos5 bac0c97c3010 7 days ago 466.9 MB centos 6 510cf09a7986 7 days ago 215.8 MB centos centos7 8efe422e6104 7 days ago 224 MB centos 7 8efe422e6104 7 days ago 224 MB centos latest 8efe422e6104 7 days ago 224 MB
上記のDockerイメージ「centos:centos6.6」から、コンテナを起動します。コンテナ名はtest001としました。
# docker run --name test0001 -i -t centos:centos6.6 /bin/bash [root@098686019f0d /]#
ホストマシンの別の端末で、起動中のコンテナを確認します。
# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 098686019f0d centos:centos6.6 "/bin/bash" 12 minutes ago Up 12 minutes test001
以下は、起動中のコンテナ「098686019f0d」のファイルシステムをホストマシンから確認する手順です。まず、コンテナ「098686019f0d」のメタデータから、コンテナのデバイスIDとサイズを確認します。
# cd /var/lib/docker/devicemapper/metadata # cat 098686019f0dec240ec5b0473046b78b5e56cb7dd0651e821d9e9216cfa9e37e |python -mjson.tool { "device_id": 6, "initialized": false, "size": 10737418240, "transaction_id": 121 }
上記より、このコンテナのデバイスIDは6、サイズが、10737418240であることがわかります。次に、起動しているコンテナがマウントされているループバックデバイスを表示します。
# ls -l /dev/mapper/ 合計 0 crw-------. 1 root root 10, 236 12月 17 17:46 control lrwxrwxrwx. 1 root root 7 1月 13 11:23 docker-8:3-84171797-098686019f0dec240ec5b0473046b78b5e56cb7dd0651e821d9e9216cfa9e37e -> ../dm-3 lrwxrwxrwx. 1 root root 7 1月 13 08:33 docker-8:3-84171797-pool -> ../dm-1 lrwxrwxrwx. 1 root root 7 1月 13 05:28 encrypted001 -> ../dm-2 lrwxrwxrwx. 1 root root 7 1月 13 05:28 pool001-lvol001 -> ../dm-0
上記のdocker-8:3-84171797-poolと先述のデバイスID、サイズを使って、テスト用のボリュームを作成します。具体的には、コンテナのサイズ「10737418240」と、デバイスIDの「6」をdmsetupコマンドの--tableオプションで指定します。
# dmsetup create testvol01 --table "0 $((10737418240 / 512)) thin /dev/mapper/docker-8:3-84171797-pool 6"
テスト用のボリュームtestvol01は/dev/mapperディレクトリ以下に作成されていますので、これをマウントします。
# mount /dev/mapper/testvol01 /mnt
/mntにマウントしたコンテナのディレクトリツリーと/etc/redhat-releaseファイルを確認します。
# ls -la /mnt/rootfs/ 合計 176 drwxr-xr-x. 21 root root 4096 1月 13 08:07 . drwxr-xr-x. 4 root root 4096 9月 9 06:30 .. dr-xr-xr-x. 2 root root 4096 1月 2 23:23 bin drwxr-xr-x. 4 root root 90112 1月 2 23:23 dev drwxr-xr-x. 45 root root 4096 1月 2 23:23 etc drwxr-xr-x. 2 root root 4096 9月 23 2011 home dr-xr-xr-x. 7 root root 4096 1月 2 23:23 lib dr-xr-xr-x. 6 root root 12288 1月 2 23:23 lib64 drwx------. 2 root root 4096 1月 2 23:21 lost+found drwxr-xr-x. 2 root root 4096 9月 23 2011 media drwxr-xr-x. 2 root root 4096 9月 23 2011 mnt drwxr-xr-x. 2 root root 4096 9月 23 2011 opt drwxr-xr-x. 2 root root 4096 1月 2 23:21 proc dr-xr-x---. 2 root root 4096 1月 2 23:23 root dr-xr-xr-x. 2 root root 4096 1月 2 23:23 sbin drwxr-xr-x. 3 root root 4096 1月 2 23:23 selinux drwxr-xr-x. 2 root root 4096 9月 23 2011 srv drwxr-xr-x. 2 root root 4096 1月 2 23:21 sys drwxrwxrwt. 2 root root 4096 1月 2 23:23 tmp drwxr-xr-x. 13 root root 4096 1月 2 23:21 usr drwxr-xr-x. 17 root root 4096 1月 2 23:21 var # cat /mnt/rootfs/etc/redhat-release CentOS release 6.6 (Final)
コンテナが持つファイルシステムを確認し終えたら、マウントを解除し、テスト用のボリュームは削除しておきます。
# cd # umount /mnt # dmsetup remove /dev/mapper/testvol01
【注意】
dmsetupコマンドにより、コンテナのファイルシステムを確認するためのテスト用のボリュームを作成する際に、デバイスIDを間違えると、別のコンテナのファイルシステムの中身を見ることになりますので十分注意して下さい。
Dockerfileを使ったイメージファイルの構築
Dockerイメージの作成や、イメージファイルからコンテナを起動する一連の手順は、先述のdockerコマンドでコマンドラインから入力することで可能ですが、イメージ作成作業、アプリケーションのインストール等の複数の作業をまとめて行いたい場合があります。そのような場合は、Dockerfileを使うと便利です。Dockerファイルは、開発環境におけるMakefileのように、一連の作業をDockerで定義された書式に従って事前に記述し、それに基づいてイメージの作成を行います。以下では、CentOS 7で稼働するApache Webサーバーが起動するコンテナのイメージファイルの作成、コンテナの起動、コンテナへのアクセス方法を述べます。まず、Apacheが稼働するコンテナ用のDockerfileを用意します。今回は、/root/apacheディレクトリを作成し、その下にDockerfileを作成します。
# mkdir /root/apache # cd /root/apache # vi Dockerfile FROM centos:centos7 ←使用するDockerイメージ名を指定 MAINTAINER Masazumi Koga ENV container docker RUN yum swap -y fakesystemd systemd ←systemdをDockerイメージにインストール RUN yum install -y initscripts RUN yum install -y httpd ←Apache WebサーバーのhttpdパッケージをDockerイメージにインストール RUN echo "Hello Apache." > /var/www/html/index.html ←テスト用のHTMLファイルをDockerイメージに配置 RUN systemctl enable httpd ←コンテナ起動時にhttpdサービスが起動するように設定 EXPOSE 80 ←Dockerコンテナが外部に開放するポート番号を指定
Dockerfile内では、FROM行に利用するイメージの種類を記述します。イメージ名はコマンドラインから「docker images」で確認可能ですので、一覧に表示されているイメージ名を記述します。ここではCentOS 7のイメージ「ceentos:centos7」を指定しています。RUN行では、Dockerのイメージを作成する際に実行したいコマンドを記述します。今回、RUN行に、「yum swap -y fakesystemd systemd」が指定されています。Dockerリポジトリに標準で用意されているCentOS 7のDockerイメージは、デフォルトでsystemdを利用しないものが用意されています。しかし、多くのCentOS 7向けのアプリケーションやサービスがsystemdを使用するため、Dockerイメージにsystemdをインストールしたい場合があります。今回は、yumコマンドで、fakesystemdを削除し、systemdをインストールしています。さらに、RUN行の「yum install -y httpd」は、Apache Webサーバーのhttpdパッケージをインストールする記述です。今回使用するDockerイメージは、CentOS 7のsystemdを有効にしますので、RUN行で「systemctl enable httpd」を記述し、コンテナが起動した際に、自動的にApache Webサーバーが起動するようにします。Apache Webサーバーのコンテナが外部に開放するポート番号をEXPOSE行で指定します。Dockerfileを記述したら、Dockerイメージを生成します。
# pwd /root/apache # ls -l 合計 4 -rw-r--r--. 1 root root 263 11月 9 17:09 Dockerfile # docker build -t centos7_apache .
Apache Webサーバー入りのDockerイメージ「centos7_apache」が作成されているかを確認します。
# docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE centos7_apache latest ac8e9d882d7e About a minute ago 381.9 MB ... ...
作成したDockerイメージ「centos7_apache」を使ってDockerコンテナを起動します。今回起動するコンテナ名は、「apache001」としました。
# docker run --name apache001 --privileged -i -t -d -p 80:80 centos7_apache /sbin/init 3db4623293db8c6a2e516ac788bb10fa32a73175c46d08c19a6b441d148b6838
DockerイメージがCentOS 7ベースで、コンテナ内でsystemdを使用する場合は、コンテナの起動オプションに、「--privileged」オプションが必要になります。またコンテナの起動に指定するコマンドは、「/sbin/init」を指定します。コンテナが起動したかを確認します。
# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3db4623293db centos7_apache:latest "/sbin/init" 15 seconds ago Up 15 seconds 0.0.0.0:80->80/tcp apache001
コンテナ「apache001」が起動していることがわかります。ホストマシンからコンテナ「apache001」にログインします。
# nsenter -t $(docker inspect --format '{{.State.Pid}}' apache001) -i -m -n -p -u /bin/bash [root@3db4623293db /]#
上記のようにログインプロンプトが「[root@3db4623293db /]」になったことで、コンテナにログインできていることがわかります。コンテナにログインできていますので、コンテナ上でApache Webサービスが起動しているかを確認します。
[root@3db4623293db /]# systemctl status httpd
コンテナがhttpdサービスを正常に起動できていることを確認できたら、コンテナに割り振られたIPアドレスをコンテナ上で確認します。
[root@3db4623293db /]# ip a |grep inet ... inet 172.17.0.26/16 scope global eth0 ...
コンテナが外部にWebサービスを提供できているかをホストマシンや他のクライアントマシンから確認します。コマンドラインから確認するには、curlコマンドが有用です。
# curl 172.17.0.26/index.html Hello Apache.
【注意】
本連載では、CentOS 7のDockerイメージにsystemdをインストールする方法を紹介しましたが、Dockerコンテナ上でのsystemdの利用については、全てのアプリケーションやデーモンについて、十分なテストが行われているわけではありません。したがって、利用者の自己責任の元で十分な動作確認を行って上記手順を利用するように下さい。
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に対応したアプリケーションサーバーの構築手順などの勘所をご紹介します。連載では書ききれなかった本の内容、見どころが満載!
|