Docker を使って開発してみてわかったこと
Ruby on Rails の開発に、Docker を使用してみてわかったこと2点とその対処方法をまとめてみた。
- boot2docker だとファイル数が多くなると非常に遅くなった
- Aufs のレイヤー数制限でエラーが出た
boot2docker だとファイル数が多くなると非常に遅くなった
Mac で Docker を使用したい場合は、boot2docker を使わないといけない。
boot2docker は、Dockerコンテナを動作させることを目的に作られた軽量な Linux ディストリビューションであり、VirtualBox を使うことで、Linux 以外の環境でも簡単に Docker を利用できるようになっている。
boot2docker は、共有フォルダとしてデフォルトでは VirtualBox の VirtualBox folder sharing を使用する。
VirtualBox SharedFolders (vboxsf) でもファイル数が少ないうちはそれほど気にならないが、ファイル数が多くなってくると非常に遅く感じた。
これについては、boot2docker の issues として上がっていて、様々な議論が展開されているようである。
nfs share / guest to host · Issue #64 · boot2docker/boot2docker
Volume sharing performance issues · Issue #593 · boot2docker/boot2docker
対応方法に関してはいくつかあるようだが、vboxsf ではなくて、NFS を利用するスクリプトを公開している人がいたのでそれを使ってみた。
元のスクリプトはこちら。
Script to mount /Users with nfs instead of vboxsf in boot2docker
そのまま使ってみたら、下記のエラーが出てうまくいかなかったので、
mount: RPC: Authentication error; why = Client credential too weak
ホストOSのIPアドレスを取得する部分(30行目あたり)と、/etc/exports
に記述する内容(44行目あたり)をすこし変更している。
#!/bin/bash
#
# This script will mount /Users in the boot2docker VM using NFS (instead of the
# default vboxsf). It's probably not a good idea to run it while there are
# Docker containers running in boot2docker.
#
# Usage: sudo ./boot2docker-use-nfs.sh
#
if [ "$USER" != "root" ]
then
echo "This script must be run with sudo: sudo ${0}"
exit -1
fi
# Run command as non root http://stackoverflow.com/a/10220200/96855
B2D_IP=$(sudo -u ${SUDO_USER} boot2docker ip &> /dev/null)
if [ "$?" != "0" ]
then
sudo -u ${SUDO_USER} boot2docker up
$(sudo -u ${SUDO_USER} boot2docker shellinit)
B2D_IP=$(sudo -u ${SUDO_USER} boot2docker ip &> /dev/null)
#echo "You need to start boot2docker first: boot2docker up && $(boot2docker shellinit) "
#exit -1
fi
#OSX_IP=$(ifconfig en0 | grep --word-regexp inet | awk '{print $2}')
VBOXNET=$(sudo -u ${SUDO_USER} VBoxManage showvminfo boot2docker-vm --machinereadable | grep hostonlyadapter | cut -d '"' -f 2)
OSX_IP=$(ifconfig $VBOXNET | grep --word-regexp inet | awk '{print $2}')
MAP_USER=${SUDO_USER}
MAP_GROUP=$(sudo -u ${SUDO_USER} id -n -g)
# Backup exports file
$(cp -n /etc/exports /etc/exports.bak) &&
echo "Backed up /etc/exports to /etc/exports.bak"
# Delete previously generated line if it exists
grep -v '^/Users ' /etc/exports > /etc/exports
# We are using the OS X IP because the b2d VM is behind NAT
# echo "/Users -mapall=${MAP_USER}:${MAP_GROUP} ${OSX_IP}"
echo "/Users -mapall=${MAP_USER}:${MAP_GROUP} --alldirs"
>> /etc/exports
nfsd restart
sudo -u ${SUDO_USER} boot2docker ssh << EOF
echo "Unmounting /Users"
sudo umount /Users 2> /dev/null
echo "Restarting nfs-client"
sudo /usr/local/etc/init.d/nfs-client restart 2> /dev/null
echo "Waiting 10s for nfsd and nfs-client to restart."
sleep 10
echo "Mounting /Users"
sudo mount $OSX_IP:/Users /Users -o rw,async,noatime,rsize=32768,wsize=32768,proto=tcp,nfsvers=3
echo "Mounted /Users:"
ls -al /Users
exit
EOF
実行方法については、スクリプト内に書かれている通り、下記コマンドを実行するだけ。
boot2docker が起動していなければ起動もしてくれる。
$ sudo ./boot2docker-use-nfs.sh
この対応で、vboxsf よりは、パフォーマンスについて体感的にはかなり改善できた。
Aufs のレイヤー数制限でエラーが出た
Docker で開発をしていたら、Cannot create container with more than 127 parents
というエラーが出た。
これは、Aufs のレイヤー数制限によるものらしい。
エラーが出た背景として
はじめは、各自がそれぞれの端末で、Docker Hub上のオフィシャルのリポジトリのrails
のイメージをベースにして、docker build
をして開発を行っていた。
ただ、それだとビルドするのに時間がかかる、という不満点が出てきた。
じゃあ、ビルド済みのイメージを Docker Hub に上げて置いて、それを基に各自が開発をする、という運用にしてみよう、ということになった。
何か変更があれば、Docker Hub に上がっているイメージに追加で変更を加えた上で、そのイメージを再度 Docker Hub に上げて共有する。
そういった運用を続けていたら、Cannot create container with more than 127 parents
というエラーが出てしまったのだ。
Docker Hub でビルド済みのイメージを、開発メンバー間で共有し始めたことでこの制限に気づいたのである。
Docker のレイヤー数制限
Docker Documentation によると、Docker では Union File System というものが使われており、下記の画像のように、変更を加えるごとに何層も read-only の層を重ねていき、最上位の層のみ read-write にする、ということを行う。
Docker では、127 以上のレイヤーを重ねることができないようになっている。
(もともと 42 だったようだ。それが今は、127 まで拡張されている)
Increase max image depth to 127 by crosbymichael · Pull Request #2897 · docker/docker
対処方法
では、どのような対応をすればこの制限に引っかからないのか、調べてみた。
主な対応として、下記の2つある。
- Dockerfile の
RUN
などのコマンドを実行する回数を減らす - export & import を実行する
Dockerfile の RUN
などで実行する回数を減らす
Docker では、Dockerfile に記述している RUN
や ADD
などが実行されるたびに、レイヤーが重なることになる。
そこで、コマンドを &&
でつないで実行する回数を減らす。
例えば下記のような Dockerfile があったとする。
FROM debian:wheezy
WORKDIR /tmp
RUN wget -nv http://www.example.com/foo/someutility-v1.0.0.tar.gz
RUN tar -xvf someutility-v1.0.0.tar.gz
RUN mv /tmp/someutility-v1.0.0/someutil /usr/bin/someutil
RUN rm -rf /tmp/someutility-v1.0.0
RUN rm /tmp/someutility-v1.0.0.tar.gz
これを下記のように、&&
でつなげることで、レイヤー数をかせげるとのこと。
FROM debian:wheezy
WORKDIR /tmp
RUN wget -nv http://www.example.com/foo/someutility-v1.0.0.tar.gz &&
tar -xvf someutility-v1.0.0.tar.gz &&
mv /tmp/someutility-v1.0.0/someutil /usr/bin/someutil &&
rm -rf /tmp/someutility-v1.0.0 &&
rm /tmp/someutility-v1.0.0.tar.gz
もしくは、コマンドをシェルスクリプトにまとめて実行する。
FROM debian:wheezy
WORKDIR /tmp
RUN script.sh
という具合に。
export & import を実行する
もう一つの対応方法として、docker export/import
を使う方法がある。docker export
をすると、すべてのファイルシステムの差分の履歴情報を保持せずに、ファイルに出力する。
そのため、履歴情報であるレイヤーを節約できるのである。
下記のように実行すれば docker export/import
ができる。
【docker export】
$ sudo docker export $container_id > tmp.tar
【docker import】
$ cat tmp.tar | sudo docker import - <REPOS:TAG>
ただ、気をつけないといけないのが、export/import をすると、履歴情報が消えてしまうため、環境変数などの設定が消えてしまうということだ。
例えば Dockerfile で、
ENV TESTENV aaabbbccc
と設定した TESTENV という環境変数が、
$ docker run -it test env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=d3e4c37dbfa6
TERM=xterm
TESTENV=aaabbbccc #この環境変数を設定
HOME=/root
docker export/import
を行うことで、
$ docker export d3e4c37dbfa6 > testenv.tar
$ cat testenv.tar | docker import - mmmsasaki/aufstest:testenv
4d4399606c802c31035eb79b729d3ff99d63edfe08916c5b8312b3bc5619f752
消えてしまう。
$ docker run -ti mmmsasaki/aufstest:testenv env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=094e28fe25e9
TERM=xterm
HOME=/root
そのため、環境変数が必要な場合は、export/import を行ったイメージを基に再度 Dockerfile で定義してビルドしてやる必要がある。
また、環境変数だけでなく、Dockerfile で設定している、CMD
や EXPOSE
なども保持していないので注意が必要である。
docker import
を行う際に、環境変数を設定したい場合は、--change "ENV DEBUG true"
というようにコマンドで渡してやると設定が可能。
前述の例で言うと、下記のようになる。
$ cat testenv.tar | docker import --change "ENV TESTENV aabbcc" - mmmsasaki/aufstest:testenv
まとめ
今回は、Docker を使ってみて気になったこと、エラーとその対応について、まとめてみた。
export/import の運用については、開発メンバーがもっと簡単に各自で行えるようにスクリプト化したいと思っている。
その方が楽だし、何よりミスがないから。
…そのあたりもここでまとめたかったけど、まだちょっと時間がかかりそうなので、今回は割愛。
Dockerを活用したサービス開発を御希望の企業様は、是非MMMにご相談下さいませ!
参考URL
Optimizing Docker Images | Century Link Labs
Dockerイメージの最適化 - ワザノバ | wazanova
気づいたらガジェ獣: docker exportとdocker importでdockerイメージのサイズを小さくする
Dockerfile の 127 行制限を試した - ようへいの日々精進 XP
Docker 管理コマンド - @//メモ
私の Docker TIPS - Qiita
第3回 コンテナ型仮想化の情報交換会@大阪 (コンテナ型VMや関連するカーネル等の技術が話題の勉強会)に参加した - @znz blog
Docker command line - Docker Documentation