CentOS6.5でDockerを動かしてみる(2)
Dockerイメージのビルドはもちろん手作業でやってもいいのですが、Dockerfileを使うのが良いと思いますので、ここからDockerfileを使ったDockerイメージのビルドについて説明します。
Dockerfileとは
Dockderfileとは、元となるイメージをプルして、ソフトウェアのインストールやコンフィグの変更を行うなどの一連の操作をファイル(Dockerfile)に順番に記載しておき、ファイルを元にイメージのビルドを自動実行するための仕組みです。これは次のようなメリットがあります。
- 事前に構築手順を確認できるので、手作業でのビルドよりもミスを減らせる。
- Dockerfile自体が引き継ぎドキュメントになり得る。
- 異なるDockerホストで同一のDockerfileを使ったイメージのビルドを行えば、同一のイメージが出来上がる。
Dockerfileに書くこと
私のようにDockerfileを使ってとりあえずイメージのビルドをしてみようというレベルで必要となるDockerfileの記載事項を説明してみます。
FROM
元となるイメージを指定します。
RUN
実行する処理を指定します。これは複数書けるので、RUN yum -y updateして次にRUN yum -y install openssh-serverして、……といったように、Dockerfileのメインとなる部分です。
EXPOSE
コンテナに外部ネットワークからアクセスさせるときに使用するポートを記載します。ただ、この記載、本当に必要か分かりません。ここでついでにコンテナのネットワークの扱いについて説明します。
Dockerコンテナのネットワーク
Dockerコンテナにはそれぞれ仮想のネットワークインターフェイスがありますが、実体はホストOSに仮想インターフェイスが作られ、これがコンテナ用に割り当てられているようです。そのため、仮想インターフェイスからホストOSの実インターフェイスにNAPTすることで、コンテナが外部ネットワークとやりとりできる仕組みです。
ホストOSのルーティングテーブルを見てあげると、コンテナ用仮想インターフェイスと経路情報が分かります。
$ route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 10.1.0.0 * 255.255.255.0 U 0 0 0 eth0 link-local * 255.255.0.0 U 1002 0 0 eth0 172.17.0.0 * 255.255.0.0 U 0 0 0 docker0 default 10.1.0.254 0.0.0.0 UG 0 0 0 eth0
上記の例では仮想インターフェイス名がdocker0で、172.17.0.0/16がコンテナに割り当てられるネットワークとなっています。
NAPTを使っているので、当然ホストOSで仮想インターフェイスからの送信パケット、仮想インターフェイスへの受信パケットについて、転送(FORWARD)を許可してあげないと、コンテナが外部ネットワークと通信できずに嵌ります。ホストOSのiptablesの設定は忘れずに。
ではNAPTでホストOSに届いたパケットをコンテナが受け取れるようにするにはどうすればいいかというと、docker runするときに次の通り引数を与えます。
$ docker run -i -t -p 10022:22 centos:centos6 /bin/bash
これでホストOSのTCP10022番宛てのパケットは、コンテナのTCP22番ポートへ転送されます。
ここで話が戻るのですが、DockerfileにEXPOSEで10022:22のような書き方が出来ません。そのうえ、EXPOSEに書かないでビルドしたイメージでも、上記のようにdocker runするときにポートを指定してあげればちゃんとパケットは転送されます。ですので、あまりEXPOSEの記載には意味がないのではと思っています。
CMD
docker runしたときに必ず実行させたいプロセスがあれば、CMDに記載します。例えばCMD ["/usr/sbin/httpd -D FOREGROUND]と記載すれば、docker runしたときに何も指定しなくてもapacheをフォアグラウンドで実行させることができます。
Dockerfileを用いたイメージのビルド
上記の内容でDockerfileというファイル名でテキストファイルを任意のディレクトリに作り、これを指定してdocker buildコマンドを実行します。
$ docker build -t takedah:test2 .
最後のピリオドがDockerfileを指します。カレントディレクトリにないDockerfileからビルドするなら、ピリオドの前にパスを書いてください。
これで作ったDockerfileを基にしたイメージが出来上がります。とても手軽です。
Dockerfile嵌りどころのチェックポイント
今回私が勉強会用に作ったDockerfileは、GitHubにアップしました。このDockerfileは、sshdとRuby on Railsをセットアップし、sshdはsupervisorで実行するようにしたイメージをビルドするものです。
これを基に以下嵌ってしまったポイントを説明してみます。
yumリポジトリは固定した方が早い
ビルドの際にyumを使う場合、デフォルトではyumfastestmirrorプラグインが近いミラーサイトを探すと思いますが、Dockerイメージでyumをすると毎回決まって海外鯖に繋ぎにいくような気がします。そのため、yumリポジトリは国内鯖に固定した方が無難な気がします。以下リポジトリをIIJに固定する例。
RUN sed -i -e 's/^#baseurl/baseurl/' /etc/yum.repos.d/CentOS-Base.repo RUN sed -i -e 's/mirror\.centos\.org/ftp\.iij\.ad\.jp\/pub\/linux/' /etc/yum.repos.d/CentOS-Base.repo RUN sed -i -e 's/^mirrorlist/#mirrorlist/' /etc/yum.repos.d/CentOS-Base.repo
Dockerコンテナで複数プロセスを実行するには
基本的にDockerコンテナは起動時に1プロセスしか実行できないため、supervisorからデーモンとしてプロセスを複数実行させる必要があります。以下supervisorをyumでインストールし、sshdをsupervisorから実行させる設定。
RUN yum -y --enablerepo=epel install supervisor RUN sed -i -e 's/^nodaemon=false/nodaemon=true/' /etc/supervisord.conf RUN echo '[program:sshd]' >> /etc/supervisord.conf RUN echo 'command=/usr/sbin/sshd -D' >> /etc/supervisord.conf
sshでログインできない
設定合ってるのにログインできなくて悩んでたら、解決方法がありました。/etc/pam.d/sshdの設定変更が必要です。
RUN sed -i -e 's/^session *required *pam_loginuid\.so/session optional pam_loginuid/' /etc/pam.d/sshd
openssl-develがcentosplusリポジトリにある
標準リポジトリにopenssl-develがなくcentosplusリポジトリにあるため、リポジトリを指定してインストールする必要があります。
RUN yum --enablerepo=centosplus -y install openssl-devel
Dockerfileの行が変わると環境変数の設定が初期化されちゃう
例えばビルド中に/etc/profileの内容を反映させるとします。しかしDockerfileの次の行に処理が移ると、これが初期化されてしまいます。そのため、RUN source /etc/profile && 次のコマンド、というように、一行にまとめないとうまくいきません。
最後に
上記のDockerfileでビルドすれば、sshdとRuby on Railsが動くCentOS6コンテナができますが、なぜかwebrickを起動するときにexecJSのライブラリが無い的なエラーが出て起動してくれません。
解決策は次の通りです。
- rails newしてプロジェクトを作成した後、Gemfileに"gem 'therubyracer'"を追記する。
- bundle installする。
- rails serverすればwebrickがちゃんと起動します。
勉強会では、上記のDockerfileからコンテナをビルドして実行し、railsのwebrickを起動してブラウザに表示させるところをデモとして見せたかったのですが、ど緊張して解決策の2番目を忘れてしまい、うまく行きませんでした……。
とりあえず勉強会で発表した内容をブログにまとめてみました。本当はこんなことを伝えたかったんだなと思っていただければ幸いです……。
今回主催の@smaguchiさん、発表の機会を与えていただきありがとうございました。また、勉強会の際に私がKVM on Dockerは出来るけどKVM on KVMはできないと話したんですが、後日@plansetさんから可能にする方法を教えていただきました。参考→KVM on KVM(nested KVM)。ありがとうございました!