初級情報インフラ管理者の技術メモ

ほぼ自分用の技術メモです。

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は、sshdRuby 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でビルドすれば、sshdRuby on Railsが動くCentOS6コンテナができますが、なぜかwebrickを起動するときにexecJSのライブラリが無い的なエラーが出て起動してくれません。

解決策は次の通りです。

  1. rails newしてプロジェクトを作成した後、Gemfileに"gem 'therubyracer'"を追記する。
  2. bundle installする。
  3. rails serverすればwebrickがちゃんと起動します。

勉強会では、上記のDockerfileからコンテナをビルドして実行し、railswebrickを起動してブラウザに表示させるところをデモとして見せたかったのですが、ど緊張して解決策の2番目を忘れてしまい、うまく行きませんでした……。

とりあえず勉強会で発表した内容をブログにまとめてみました。本当はこんなことを伝えたかったんだなと思っていただければ幸いです……。

今回主催の@smaguchiさん、発表の機会を与えていただきありがとうございました。また、勉強会の際に私がKVM on Dockerは出来るけどKVM on KVMはできないと話したんですが、後日@plansetさんから可能にする方法を教えていただきました。参考→KVM on KVM(nested KVM)。ありがとうございました!