dockerでmysqlをvolumeなしで動かしてみた。その実験の記録。
環境
- VirtualBox上のUbuntu 18.10
- docker 18.09
- mysql 8.0
動機
dockerでmysqlを動かす場合、必ずデータの格納先はホスト側になり、コンテナには残らない。理由はofficialなmysqlイメージにはvolume指定が入っているから。volume指定が入っている場合、実際にvolumeを作るか、hostのディレクトリにbindするかしかない。いずれにせよ通常ホスト側になる(例外はホストの外なので、コンテナ側ではない)。
では、mysqlイメージからvolume指定を抜くと、どうなるのか?これを調査する。
方法
dockerの公式ツールだけでは、イメージ内に含まれるvolume指定を簡単に削除することができない。なので、この処理にはdocker-copyedit1を使用する。volume指定のないイメージが出来たら、これを起動して終了を繰り返し、イメージの差分をhistoryで見ていく。比較のために、素のイメージでもbind指定で動作させて同じことを繰り返し、イメージの差分と、bind先のdu結果を合わせてどの程度の差異があるかを検証する。
実施
(1) volume指定を削除したmysqlイメージの作成
docker-copyedit1を取得し、実行権限を付けた状態で以下を実施。
$ docker pull mysql
$ ./docker-copyedit.py FROM mysql INTO mysql:novolumes REMOVE ALL VOLUMES
これでnovolumeタグの付いたvolume指定を削除したmysqlイメージが完成する。
ついでに元のイメージにもnormalタグを付けておく(後の区別のため)。
docker tag mysql mysql:normal
(2) テスト作業のスクリプト化
コマンドでもいいけど長いし繰り返すので、一応作っておく。
WAIT_FIRST=60
WAIT_SECOND=20
ORG_IMAGE=mysql
NOVOLUMES=novolumes
NORMAL=normal
COMMIT_IMAGE=my-mysql
CONTAINER=my-mysql
MAX_COUNT=5
MODE=${NOVOLUMES}
DATADIR=$PWD/data
DULOG=du.log
for MODE in ${NOVOLUMES} ${NORMAL}; do
COUNT=1
mkdir ${DATADIR}
if [ "${MODE}" == "${NORMAL}" ]; then
OPTIONS="--volume ${DATADIR}:/var/lib/mysql"
else
OPTIONS=""
fi
while [ ${COUNT} -le ${MAX_COUNT} ]; do
echo "${MODE}: START (${COUNT}/${MAX_COUNT})"
if [ ${COUNT} -eq 1 ]; then
IMAGE=${ORG_IMAGE}:${MODE}
WAIT=${WAIT_FIRST}
else
IMAGE=${COMMIT_IMAGE}:${MODE}
WAIT=${WAIT_SECOND}
fi
docker run --name ${CONTAINER} ${OPTIONS} -e MYSQL_ROOT_PASSWORD=my-secret-pw -d ${IMAGE}
echo "${MODE}: WAIT (${COUNT}/${MAX_COUNT}) - ${WAIT}SEC"
sleep ${WAIT}
echo "${MODE}: STOP (${COUNT}/${MAX_COUNT})"
docker stop ${CONTAINER}
echo "${MODE}: COMMIT (${COUNT}/${MAX_COUNT})"
docker commit ${CONTAINER} ${COMMIT_IMAGE}:${MODE}
echo "${MODE}: RM (${COUNT}/${MAX_COUNT})"
docker rm -v my-mysql
echo "${MODE}: TAG (${COUNT}/${MAX_COUNT})"
docker tag ${COMMIT_IMAGE}:${MODE} ${COMMIT_IMAGE}:${MODE}_${COUNT}
echo "[${MODE}${COUNT}]" >> "${DULOG}"
du -sh "$DATADIR" >> "${DULOG}"
COUNT=`expr ${COUNT} + 1`
done
done
echo "以下実施しないとゴミが残ります"
echo "docker images --filter reference=\"my-mysql*:novolumes*\" | awk '{print \$1 \":\" \$2;}' | tail -n +2 | while read i;do docker image rm \$i;done"
echo "docker images --filter reference=\"my-mysql*:normal*\" | awk '{print \$1 \":\" \$2;}' | tail -n +2 | while read i;do docker image rm \$i;done"
echo "sudo rm -rf ${DATADIR}"
echo "rm ${DULOG}"
ボリュームなしと公式の2パターンで、各5回起動/終了/commitを繰り返してるだけのスクリプト。
公式の場合は、./dataにバインドしており、そのディレクトリのduのログを取っている。
適当なのでエラーが出るが、問題なければ読み飛ばす。以下実行方法。
$ bash test.sh
最後に出てくる4行のメッセージはコピペしとくと後でゴミ捨てするときに楽。
結果
$ docker image ls --filter "reference=*mysql:novolumes*"
REPOSITORY TAG IMAGE ID CREATED SIZE
my-mysql novolumes d46afbc3333a 27 minutes ago 1.34GB
my-mysql novolumes_5 d46afbc3333a 27 minutes ago 1.34GB
my-mysql novolumes_4 81c354774d2b 27 minutes ago 1.17GB
my-mysql novolumes_3 55094b7c7c7a 28 minutes ago 994MB
my-mysql novolumes_2 19f5e8c34a2a 28 minutes ago 822MB
my-mysql novolumes_1 fe3d91edd6c0 29 minutes ago 649MB
mysql novolumes 77ae41e89139 7 days ago 477MB
$ docker image ls --filter "reference=*mysql:normal*"
REPOSITORY TAG IMAGE ID CREATED SIZE
my-mysql normal e75ca3a58778 23 minutes ago 477MB
my-mysql normal_5 e75ca3a58778 23 minutes ago 477MB
my-mysql normal_4 36baccd83d5d 23 minutes ago 477MB
my-mysql normal_3 b359772d641f 24 minutes ago 477MB
my-mysql normal_2 c59b670f1a75 24 minutes ago 477MB
my-mysql normal_1 102b6079086b 24 minutes ago 477MB
mysql normal 91dadee7afee 7 days ago 477MB
$ cat du.log
[novolumes1]
4.0K /home/user/python/docker/mysql/novolumes/data
[novolumes2]
4.0K /home/user/python/docker/mysql/novolumes/data
[novolumes3]
4.0K /home/user/python/docker/mysql/novolumes/data
[novolumes4]
4.0K /home/user/python/docker/mysql/novolumes/data
[novolumes5]
4.0K /home/user/python/docker/mysql/novolumes/data
[normal1]
164M /home/user/python/docker/mysql/novolumes/data
[normal2]
164M /home/user/python/docker/mysql/novolumes/data
[normal3]
164M /home/user/python/docker/mysql/novolumes/data
[normal4]
164M /home/user/python/docker/mysql/novolumes/data
[normal5]
164M /home/user/python/docker/mysql/novolumes/data
$ sudo du -sh ./data
165M ./data
つまり
ボリューム | 回数 | イメージサイズ[MB] | ボリュームサイズ[MB] | 合計[MB] |
---|---|---|---|---|
なし | 0 | 477 | 0 | 477 |
なし | 1 | 649 | 0 | 649 |
なし | 2 | 822 | 0 | 822 |
なし | 3 | 994 | 0 | 994 |
なし | 4 | 1170 | 0 | 1170 |
なし | 5 | 1340 | 0 | 1340 |
あり | 0 | 477 | 0 | 477 |
あり | 1 | 477 | 165 | 642 |
あり | 2 | 477 | 165 | 642 |
あり | 3 | 477 | 165 | 642 |
あり | 4 | 477 | 165 | 642 |
あり | 5 | 477 | 165 | 642 |
ということ。
考察
ボリュームなし時には、差分が毎回173MB程度発生し、追加レイヤに記録されている。
ボリュームあり時には、差分が検出されず、ボリュームに165MB程度のデータが格納されている。
ボリュームには差分が格納されないため、ボリュームあり時には1~4回目のデータを見ることができないが、ボリュームなし時には全ての履歴が残るも、毎回ほぼ全量分のデータが差分として抽出されているということになる。
これはレイヤを実装するOverlay2がファイル単位の差分を取っており、これらデータの大半を占めているデータがサイズの大きなファイルであることに起因していると考える。そこで実際にボリュームあり時の./dataでサイズの大きなファイルを抽出しみてた。
$ cd data
$ ls -lAFh ib_logfile* *.ibd undo_*
-rw-r----- 1 vboxadd nobody 48M 3月 13 10:10 ib_logfile0
-rw-r----- 1 vboxadd nobody 48M 3月 13 10:07 ib_logfile1
-rw-r----- 1 vboxadd nobody 30M 3月 13 10:09 mysql.ibd
-rw-r----- 1 vboxadd nobody 12M 3月 13 10:10 undo_001
-rw-r----- 1 vboxadd nobody 10M 3月 13 10:10 undo_002
これらのデータだけで142MB、サイズの大半をここで消費していることが分かる。見たところ、UNDO/REDOログとデータ本体ではないかと推測されるようなデータであり、起動終了時にこれらの一部が書き換わることは容易に想像できる。
結論
databaseのような大きなファイルを少しずつ変更するようなシステムをコンテナで動かす場合は、Overlay2でのレイヤに入れると、ストレージ効率が著しく低下する。なので、volumeやbind-mountなど、コンテナの外に出すか、レイヤの実装を変更するべきと考える。
-
docker-copyedit(python2なので注意!。イヤなら自分で属性をいじらないといけない) ↩