ようへいの日々精進XP

よかろうもん

Dockerfile の書き方「私的」なベストプラクティス

はじめに

最近、検証環境を構築するにあたり Dockerfile を書き始めるところから入るカワハラです。おはようございます。で、結局、当初の検証目的を忘れて Dockerfile 書きに没頭してしまって色々と滞っております...つきましては、Dockerfile を書くにあたってのメモを残しておきたいと思います。あくまでも「私的」なベストプラクティスですのでご注意下さい...


参考

以下のサイトを参考にさせて頂きました。


「私的」なベストプラクティス

MAINTAINER は入れましょう

どこの誰が作ったかをハッキリした方が良いと思うので MAINTAINER は指定しましょう。

MAINTAINER YOHEI KAWAHARA inokappa

上記のような感じ。

ENV

RUN したい時にパスが通っていないと悲しいのでパスが通ってなさそうだなーと思ったら、とりあえず以下のように RUN の前あたりで ENV でパスをしてしてあげると大概の場合は RUN が通りました。特にディレクトリごとバイナリをコピーした時等。

ENV PATH $PATH:/usr/local/jruby/bin
RUN jgem install hoge

上記の例は PATH/usr/local/jruby/bin を追加している。この後に jgemjruby 等が使えるようになる。

EXPOSE

EXPOSE は直訳すると「晒す」。コンテナを起動した際に外部に公開するポートを指定することが出来る。docker run する時にオプションで指定する -p と同じ役割をする。例えば、Jenkins のデフォルトポート 8080 を晒す場合には以下のようにする。

EXPOSE 8080
CMD /usr/bin/java -jar /usr/share/jenkins/jenkins.war --webroot=/var/cache/jenkins/war --httpPort=8080 --ajp13Port=-1

こんな感じにしておく。ちなみに、EXPOSE 8080:12345 としておくとコンテナをホストしている localhost12345 にバインドさせることも出来る。(はずなんだけど、何度試しても 12345 だけがバインドされてしまうので引き続き調べる)

CMD とか ENTRYPOINT とか

まだ、ちゃんと整理しきれていない部分もあるが、CMD とか ENTRYPOINT で docker run した時の挙動が異なるのでざっくりと書いてみる。

それぞれ、以下のような Dockerfile を作成して実行してみる。

以下はパターン 1。

# test1
FROM inokappa/wheezy-7.2-basic
MAINTAINER YOHEI KAWAHARA inokappa
ENTRYPOINT echo

以下はパターン 2。

# test2
FROM inokappa/wheezy-7.2-basic
MAINTAINER YOHEI KAWAHARA inokappa
CMD echo

どちらも echo を実行するだけ。とりあえず docker build -t inokappa/test1 .docker build -t inokappa/test2 . で構築してから docker run してみる。

docker run inokappa/test1

以下のように表示される。

[なんにも表示されない]

同様に...

docker run inokappa/test2

以下のように表示される。

[なんにも表示されない]

これだけだとどちらも変わらない。「なんや、CMDENTRYPOINT は一緒やん...」と思ったが...

docker run inokappa/test1 a

以下のように表示される。

[なんにも表示されない]

では、docker run inokappa/test2 a とすると...

2013/12/28 03:06:58 Unable to locate a

というエラーが表示される。なるほど、なるほど、a がコマンドとして認識されてるっぽいぞ。ちなみに終了コードを比較してみる。

f:id:inokara:20131228121653p:plain

さらに docker inspect で確認してみる。(一部、抜粋)

// docker run inokappa/test1 a
{
  "Config": {
    "Entrypoint": [
      "/bin/sh",
      "-c",
      "echo"
    ],
    "Cmd": [
      "a"
    ]
  },
  "Args": [
    "-c",
    "echo",
    "a"
  ],
  "Path": "/bin/sh"
}

ほうほう、ENTRYPOINT で指定された場合に Path/bin/sh -c が補完されており、docker run で指定したコマンドの aCmd に設定されている。

// docker run inokappa/test2 a
{
  "Config": {
    "Entrypoint": null,
    "Cmd": [
      "a"
    ]
  },
  "Args": [],
  "Path": "a"
}

CMDPath には何も設定されず Cmddocker run で引数につけた a が設定されているので a のみが実行される状態にエラーとなる。

ということで、CMDENTRYPOINT の違いは以下のような点が挙げられるかなと。

  • CMD でコマンドを指定した場合にはそのコマンドの引数を docker run 時に渡したら別のコマンドとして認識される
  • CMDDockerfile 内で完結させる必要がある(完結してしまう)
  • ENTRYPOINT でコマンドを指定した場合にはそのコマンドの引数を docker run 時に渡すと引数として認識される
  • ENTRYPOINTCMD の合わせ技を用いることが出来る

なんかあった時の為にログイン出来るユーザーは作っておく

どうせコンテナなんだし上手く動かなければ捨てて Dockerfile を修正して作りなおせば良いし、docker attach も使えたりするのだが、コンテナにログイン出来る手順は複数あった方が良いというのが持論。ということで、以下のように sshd のインストールとログイン用のユーザーも作っておく。

RUN apt-get install openssh-server
RUN mkdir -p /var/run/sshd
RUN useradd -d /home/hogehoge -m -s /bin/bash hogehoge
RUN echo hogehoge:${your_pass} | chpasswd
RUN echo 'hogepass ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

docker run する時

docker run した後にすぐにコンテナの IP を確認したい時が多々あるので、以下のように docker ps -a してわざわざコンテナの ID を調べなくて便利だったりする。

con=`docker run -d ${container/image}`
docker inspect $con | jq '.[].NetworkSettings.IPAddress'

以下のような感じになる。(※.[].NetworkSettings.IPAddress でも .[]|.NetworkSettings.IPAddress でもイケてしまう)

f:id:inokara:20131228065120p:plain


最後に

  • Dockerfile を通して docker の動作について少しだけ理解を深めることが出来た(気がする)
  • CMDENTRYPOINT の違いについてはまだまだモヤっとしているので使いながらフォローアップしていく