DockerとLinux OSのリソース共有状況の調査
ディスク容量の制限をテストしてみる
上限を超える大容量ファイルの作成
各ディストリビューションのDocker上にコンテナを起動し、そのコンテナ上で1つのファイルをエラーで停止するまで拡張する実験を行った。実験に使用したテスト用の簡易プログラム(disk_full.c)を、以下に示す。
#include <stdio.h> #include <stdlib.h> #include <string.h> #define LOOP_COUNT 10 #define WRITE_BUF_SIZE 1024 #define WRITE_COUNT 4 int main( int argc, char **argv ) { FILE *fptr = NULL; int ret = 0; int cnt = 0; int max_cnt = LOOP_COUNT; int print_term = 1; char write_buf[WRITE_BUF_SIZE]; if( argc > 1 ){ max_cnt = atol( argv[1] ); } if( max_cnt > 10 ){ print_term = (int)( (double)max_cnt * 0.1 ); } memset( write_buf, '@', WRITE_BUF_SIZE ); fptr = fopen( "dummy.txt", "w+" ); if( fptr == NULL ){ printf( "fopen error\n" ); return( EXIT_FAILURE ); } for( cnt = 1; cnt <= max_cnt; cnt++ ){ /* 4KBずつ書込み */ ret = fwrite( write_buf, WRITE_BUF_SIZE, WRITE_COUNT, fptr ); if( ret < WRITE_COUNT ){ printf( "fwrite [%d]回目でエラー rc = [%d]\n", cnt, ret ); return( EXIT_FAILURE ); } if( ( cnt % print_term ) == 0 ){ printf( "fopen [%d]回目成功\n", cnt ); } } fclose( fptr ); return( EXIT_SUCCESS ); }
ディストリビューション | ストレージドライバ | 発生した事象 |
---|---|---|
CentOS 6.6 x86_64 CentOS 7.0 x86_64 | device-mapper | コンテナに割り当てられた10GBを上限に、ファイルの生成が停止する。ベースOS側でもSparseファイルの実際の使用量が10GB増加する |
Ubuntu Server 14.04 x86_64 | aufs | ベースOSの限界容量(空き容量+空きSWAPサイズ)までファイルが生成される。ベースOS側もDisk Fullとなり、ファイル書き込み不能となり全体障害状態となる |
ストレージドライバとしてdevice-mapperを使用した場合、コンテナに割り当てられる仮想ディスクは1つのデバイスとして提供される。そのため、ディスク容量自体にも上限が存在する。コンテナ内で大容量ファイルを生成した場合でも、その限界を超えることはできない。
ところが、aufsを使用した場合は、ベースOSのファイルシステムのフォルダがそのまま提供されるので、コンテナごとの容量制限は行えない。そのため、ベースOSの空きディスク容量を完全に枯渇させるまでファイルサイズを拡大できてしまう。Disk Quotaでフォルダのサイズ制限を行うか、ストレージドライバをdevice-mapperに変更するといった対策が必要と考えられる。
ベースOSと共用のファイル
Dockerコンテナのファイルの大部分は、前述のコンテナ専用のディスク領域に格納されている。ただしコンテナ内の/etc以下に存在する「hosts」「hostname」「resolf.conf」の3つのファイルは、ベースOS上のファイルが1個ずつマウントされている。それらのファイルが置かれている場所を、表5に示す。
ディストリビューション | ストレージドライバ | 格納フォルダ |
---|---|---|
CentOS 6.6 x86_64 CentOS 7.0 x86_64 | device-mapper | /var/lib/docker/containers/[コンテナID] |
Ubuntu Server 14.04 x86_64 | aufs | /var/lib/docker/aufs/diff/[コンテナID]-init/etc |
これらのファイル自体は単体でマウントされているため、削除もファイル名の変更も行えないが、上書きだけは行える。そのため、これらのファイルに大きなデータを追記すれば、ベースOSの領域を圧迫することも可能である。実際に/etc/hostsのファイルにデータを追記するテストを行った結果は以下となる。
(1)コンテナ内の/etc/hostsを1Gに拡大
# ls -lh /etc/hosts -rw-r--r-- 1 root root 1.0G Mar 20 06:02 /etc/hosts
(2)ベースOSの"/"の空き容量が1G減少
# df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/vg_kernelcentos6-lv_root 45G 12G 31G 27% /
(3)コンテナ内の/etc/hostsを元に戻す
(4)ベースOSの"/"の空き容量が元に戻る(1G増える)
# df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/vg_kernelcentos6-lv_root 45G 11G 32G 25% /
実際にこのような操作がされることはないと考えられるが、マルチテナントでDocker環境を提供するような場合は、これらの共用ファイルは読み取り専用で提供し、管理者が変更を行うなどの運用が必要になると考えられる。
追加検証:device-mapperの仮想ディスクイメージ用のSparseファイルの上限
ストレージドライバにdevice-mapperを選択した場合、標準インストールでは仮想ディスク領域として100GBのSparseファイルが作成される。Sparse(疎)ファイルであるため、lsコマンドで確認するとファイルサイズは100GBと表示されるが、実際に使用されている領域はもっと少ない。下記の例を見ると、ファイルサイズ(data)は100GBと表示されているが、実際のディスク使用量の表示であるtotalには7.6Gと表示されている。
# cd /var/lib/docker/devicemapper/devicemapper/ # ls -lh total 7.6G -rw------- 1 root root 100G Apr 15 18:36 data -rw------- 1 root root 2.0G Apr 9 16:16 metadata
Dockerコンテナは1個あたり10GBの上限が存在するが、コンテナを複数個起動し、合計サイズが100GBを上回った場合の挙動を検証してみた。結果としては、「コンテナ上の領域は空いているように見える(dfコマンドでディスク使用率が100%となっていない)が、書き込みエラーが発生する」状態となった。
Sparseファイル自体は、その割り当てサイズの空き容量が存在しなくても作成できてしまう。今回検証に使用した環境は、KVM上で50GB程度のハードディスク容量を割り当ててベースOSを起動している。その状態でも、100GBの容量のSparseファイルを作成可能である。そして、その環境で検証を行った場合もベースOSのディスク使用率が100%になった時点で、まったく同じ状況が発生した。
追加検証:Sparseファイルの拡張
ベースOSのディスクに空き容量が存在する状態で、Spaceファイルの上限に到達してしまった場合、より大きなサイズのSparseファイルを再作成する必要が発生する。ただしSparseファイルを再作成する際に、中のコンテナの情報を消失してしまう可能性があるため、全コンテナをexportするなどの作業が非常に煩雑である。コンテナの情報を失うことなく拡張するためには、以下の手順で操作すればいい。
(1)dockerのサービスを停止
# /bin/systemctl stop docker.service
(2)念のため、Sparseファイルをバックアップしておく(推奨)
# cp –p /var/lib/docker/devicemapper/devicemapper/data /tmp/data.backup
(3)ddコマンドでSparseファイルのサイズを変更する
# dd if=/dev/zero of=data bs=1G count=0 seek=200
※例では200GBに拡張している。count=0以外だと実際に0x00が書き込まれるので注意が必要。
(4)dockerのサービスを起動
# /bin/systemctl start docker.service
追加検証:検証中のDisk I/O負荷
大容量ファイルを作成する際、ディスクに対してかなりの負荷をかけることとなった。その負荷状況を参考情報として取得した。ディスクI/O速度については、双方の検証環境や検証実行時のコンディションが同一ではないため、この数字での評価は行わない。
(1)CentOS 7.0(device-mapper)
ディスクI/O負荷が急上昇し、ベースOS側も%utilが高まり、スローダウンが発生していると推察される。
avg-cpu: %user %nice %system %iowait %steal %idle 1.34 0.00 56.86 41.81 0.00 0.00 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util vda 0.00 2.34 1.00 168.23 20.07 79743.14 942.66 74.15 407.64 72.33 409.64 4.81 81.44 dm-0 0.00 0.00 1.34 1626.42 5.35 103824.75 127.57 57.00 35.02 233.00 34.85 0.43 70.74 dm-1 0.00 0.00 1.00 1897.32 4.01 121107.69 127.60 1388.45 776.05 855.33 776.01 0.53 100.03 dm-2 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
(2)Ubuntu 14.04(aufs)
こちらも同様である。
vg-cpu: %user %nice %system %iowait %steal %idle 0.00 0.00 1.57 79.50 0.00 18.93 Device: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util fd0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00 sda 0.00 1.40 0.60 95.00 2.40 47310.40 989.81 129.39 1345.36 134.67 1353.01 10.46 100.00
実際に行ったのは、1つのファイルに連続してデータを書き込むだけの処理である。現状のDockerの機能では、コンテナごとのディスクI/Oの速度の制限は行えない。そのため、1つのコンテナが連続で大量のディスクI/Oを行うと、物理サーバ全体がスローダウンする事態が発生する。
ディスクI/Oについて制限は、CPUやメモリの使用量制限にも利用されているcgroupで設定が可能だ。
cgroupの設定ファイルとパラメータ
ディスクI/Oの制限を行う設定ファイルは、CentOS7の場合、/sys/fs/cgroup/blkio/system.slice/ [デバイス名].scope以下に存在する。デバイス名はブロックデバイス情報を参照するlsblkコマンドで確認できる。
# lsblk -o NAME loop0 └─docker-253:1-134489996-pool ├─docker-253:1-134489996-a29fe7f14c40f295db44e78cf57806a5148971823ebc381dfb1bc0e2076cc └─a29fe7f14c40_vol
ディスク書き込み速度の制限を行うblkio.throttle.write_bps_deviceのパラメータで制限を実施してみた。
※その他のパラメータについては下記の表6を参照してほしい。
10MB/secに制限するため、ファイルに "253:1 10485760"を書き込む必要がある。
blkio.throttle.write_bps_device | デバイスが実行できる書き込み操作数の上限をデータ量で指定する(単位はバイト/秒) |
実際に行ったところ、コンテナを1個だけを動作させている状態では制限がかかっており、書き込み速度を10MB/secに制限できることがわかった。(特にマルチテナント環境での)コンテナを複数実行する環境では、コンテナごとのディスクI/O速度の制限が必要になると考えられる。各パラメータの設定や複数コンテナ実行時の挙動などは、今後検証してみたい。
ファイル名 | 機能 | |
---|---|---|
1 | blkio.io_merged | cgroupにより、I/O操作要求にマージされた、BIOS要求数をレポート |
2 | blkio.io_queued | grouptにより、I/O操作のキューに入れられた要求の数をレポート |
3 | blkio.io_service_bytes | CFQスケジューラーに認識されるように、cgroupにより特定のデバイスとの間で転送されたバイト数をレポート |
4 | blkio.io_service_time | CFQスケジューラーに認識されるように、cgroupにより特定のデバイス上で行われるI/O操作の要求がディスパッチされてから完了するまでの合計時間をレポート |
5 | blkio.io_serviced | CFSスケジューラーに認識されるように、cgroupにより特定のデバイス上で実行されたI/O操作の回数をレポート |
6 | blkio.io_wait_time | スケジューラーキュー内のサービスを待つのに費した、cgroupによる 特定のデバイス上のI/O操作の合計時間をレポート |
7 | blkio.reset_stats | その他の疑似ファイルに記録されている統計情報をリセットする |
8 | blkio.sectors | cgroupにより、特定のデバイスとの間で転送されたセクターの数をレポート |
9 | blkio.throttle.io_service_bytes | スロットリングのポリシーに認識されるように、特定のデバイス上でcgroupにより実行されたI/O操作の回数をレポート |
10 | blkio.throttle.io_serviced | cgroupにより、特定のデバイスとの間で転送されたバイト数をレポート |
11 | blkio.throttle.read_bps_device | デバイスが実行できる読み取り操作の上限をデータ量で指定する(単位はバイト/秒) |
12 | blkio.throttle.read_iops_device | デバイスが実行できる読み取り操作数の上限を毎秒の操作数で指定する |
13 | blkio.throttle.write_bps_device | デバイスが実行できる書き込み操作の上限をデータ量で指定する(単位はバイト/秒) |
14 | blkio.throttle.write_iops_device | デバイスが実行できる書き込み操作数の上限を毎秒の操作数で指定する |
15 | blkio.time | cgroupが特定のデバイスにI/Oアクセスを行った時間をレポート |
16 | blkio.weight | デフォルトでcgroupに提供されるブロックI/Oアクセスの相対的比率(ウェイト)を100から1000の範囲内で指定する |
17 | blkio.weight_device | cgroupに提供される特定のデバイス上のI/Oアクセスの相対的比率(ウェイト)を100から1000の範囲内で指定する |
各ファイルの詳細はレッドハットの以下のサイトで公開されている。
第3章 サブシステムと調整可能なパラメーター
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- CentOS 7の仮想化、Docker、リソース管理(後編)
- Dockerの導入前に知っておくべきこと
- コンテナ関連技術の現状を確認しておく
- Dockerコンテナのパフォーマンス劣化とチューニング
- Dockerの誤解と神話。識者が語るDockerの使いどころとは? Docker座談会(前編)
- ベアメタル環境とDockerコンテナ環境の性能比較
- CoreOS&Docker環境においてOracle Database 11g Release 2をインストールするためのポイント
- Dockerコンテナ環境のバックアップツール「Convoy」を使う
- Dockerの導入前の設計、CentOS 7.xとDockerのインストール
- Docker向けの軽量Linux OS 主要3種を比較する