CUBE SUGAR CONTAINER

技術系のこと書きます。

Docker コンテナの動作に必要な設定を起動時に渡す

今回は Docker コンテナを起動するタイミングで、コンテナの動作に必要な設定を受け渡す方法について書く。 やり方としては、大まかに分けて「環境変数を通して渡す」と「コマンドライン引数を通して渡す」という二つがある。 どちらの場合も docker run で実行するコマンドの中に設定を含めることになる。

使った環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.12.6
BuildVersion:   16G1212
$ docker version
Client:
 Version:   18.01.0-ce
 API version:   1.35
 Go version:    go1.9.2
 Git commit:    03596f5
 Built: unknown-buildtime
 OS/Arch:   darwin/amd64
 Experimental:  false
 Orchestrator:  swarm

Server:
 Engine:
  Version:  18.01.0-ce
  API version:  1.35 (minimum version 1.12)
  Go version:   go1.9.2
  Git commit:   03596f5
  Built:    Wed Jan 10 20:13:12 2018
  OS/Arch:  linux/amd64
  Experimental: false

環境変数を通して渡す

まずは環境変数を通して渡す一般的なやり方から。

最初は動作確認のために、起動時に環境変数の一覧を表示する Docker イメージを作ることにする。 その Dockerfile が次の通り。

$ cat << 'EOF' > Dockerfile
FROM alpine

CMD env
EOF

Alpine Linux をベースイメージにして、起動時に実行するコマンドを CMD 命令で指定している。 env コマンドは環境変数を一覧で表示する。 これ以上ないくらいシンプル。

上記の Docker ファイルをビルドして Docker イメージを作る。

$ docker build -t example/env .
...
Successfully tagged example/env:latest

上記でビルドした Docker イメージからコンテナを起動してみよう。 すると、コンテナ内で設定されている環境変数が出力される。

$ docker run -t example/env
HOSTNAME=4df5d62c61a1
SHLVL=1
HOME=/root
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/

続いてコンテナに環境変数を設定してみる。 これには docker run コマンドで -e (--env) オプションを指定する。 例えば、よくありがちなバインドするアドレスやポートっぽい値を設定してみよう。

$ docker run -e BIND_ADDRESS=127.0.0.1 -e BIND_PORT=8080 -t example/env
BIND_ADDRESS=127.0.0.1
HOSTNAME=2fb165c97c2a
SHLVL=1
HOME=/root
BIND_PORT=8080
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/

ちゃんと環境変数に BIND_ADDRESSBIND_PORT が設定されたことが分かる。

ちなみに、環境変数はファイル経由で渡すこともできる。 渡す変数の数が多いときは、こちらを使った方が良い。

まずは環境変数を羅列したファイルを用意する。

$ cat << 'EOF' > envfile.txt
BIND_ADDRESS=127.0.0.1
BIND_PORT=8080
EOF

あとはコンテナを起動するときに --env-file オプションでファイルを指定する。

$ docker run --env-file envfile.txt -t example/env
HOSTNAME=4ed1b4a9a45c
BIND_ADDRESS=127.0.0.1
SHLVL=1
BIND_PORT=8080
HOME=/root
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/

デフォルト値を設定する (Dockerfile)

上記でアプリケーションの設定として環境変数を使う下地が整った。 ただ、設定には一般的にデフォルト値がほしくなる。

そんなときは Dockerfile で ENV 命令を使うことができる。 先ほどの Dockerfile に ENV 命令を加えたものを用意しよう。

$ cat << 'EOF' > Dockerfile
FROM alpine

ENV BIND_ADDRESS 127.0.0.1
ENV BIND_PORT 8080

CMD env
EOF

上記のファイルをビルドする。

$ docker build -t example/env .
...
Successfully tagged example/env:latest

ビルドしたイメージからコンテナを起動してみよう。 特に -e (--env) オプションを使わなくても環境変数が出力されていることが分かる。

$ docker run -t example/env
BIND_ADDRESS=127.0.0.1
HOSTNAME=4c795ff84a32
SHLVL=1
HOME=/root
BIND_PORT=8080
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/

上記の値は -e (--env) オプションを指定することで上書きできる。

$ docker run -e BIND_ADDRESS=0.0.0.0 -e BIND_PORT=80 -t example/env
BIND_ADDRESS=0.0.0.0
HOSTNAME=de360066a866
SHLVL=1
HOME=/root
BIND_PORT=80
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/

デフォルト値を設定する (シェルスクリプト)

環境変数のデフォルト値を設定するには Dockerfile で ENV 命令を使う以外のやり方もある。 具体的には、コンテナが起動するタイミングでシェルスクリプトを実行して、その中で環境変数を操作する。

以下の Dockerfile では docker-entrypoint.sh というファイルをイメージに転送している。 そして ENTRYPOINT 命令を使うことで、コンテナの起動時にそのシェルスクリプトを実行するように設定されている。

$ cat << 'EOF' > Dockerfile 
FROM alpine

COPY docker-entrypoint.sh /usr/local/bin

ENTRYPOINT ["docker-entrypoint.sh"]
EOF

続いて、上記の Dockerfile で使っている docker-entrypoint.sh を用意する。 やっていることは単純で、環境変数がないときはデフォルト値を扱うように ${環境変数名:-デフォルト値} という記法を使うだけ。

$ cat << 'EOF' > docker-entrypoint.sh 
#!/bin/sh

echo ${BIND_ADDRESS:-127.0.0.1}
echo ${BIND_PORT:-8080}
EOF
$ chmod +x docker-entrypoint.sh 

上記で用意したファイル群から Docker イメージをビルドする。

$ docker build -t example/env .
...
Successfully tagged example/env:latest

イメージからコンテナを起動する。 特にオプションを指定のないときはシェルスクリプトで指定したデフォルト値が使われる。 また、先ほどと同じように -e (--env) オプションを指定することで値を上書きできる。

$ docker run -t example/env
127.0.0.1
8080
$ docker run -e BIND_ADDRESS=0.0.0.0 -e BIND_PORT=80 -t example/env
0.0.0.0
80

もちろんシェルスクリプトの中では受け渡された環境変数の内容をバリデーションしたりもできる。

コマンドライン引数を通して渡す

続いてはもう一つのやり方、コマンドライン引数を使う方法について。 このやり方は、基本的には前述したシェルスクリプトを使う方法の応用になっている。

まずは、先ほどと同じように起動時にシェルスクリプトを実行するような Docker ファイルを用意する。 ここでシェルスクリプトを実行するのに ENTRYPOINT 命令を使っているのがポイントになる。 これが CMD 命令だと上手くいかない。

$ cat << 'EOF' > Dockerfile 
FROM alpine

COPY docker-entrypoint.sh /usr/local/bin

ENTRYPOINT ["docker-entrypoint.sh"]
EOF

実行されるシェルスクリプトは、全てのコマンドライン引数を参照できる $@ 変数を echo コマンドで出力する。

$ cat << 'EOF' > docker-entrypoint.sh 
#!/bin/sh

echo $@
EOF
$ chmod +x docker-entrypoint.sh

上記をビルドしよう。

$ docker build -t example/opt .   
...
Successfully tagged example/opt:latest

上記のイメージからコンテナを起動するタイミングで、普段なら起動するコマンドを渡すところに適当な文字列を入れてみよう。

$ docker run -t example/opt foo bar baz                          
foo bar baz

すると、入力した文字列がそのまま出力された。 これはシェルスクリプトがコマンドライン引数を出力するようにした echo $@ による結果となる。 つまり、コマンドライン引数をシェルスクリプトに渡すことができたというわけ。

コマンドライン引数を解析する (getopts)

シェルスクリプトにコマンドライン引数さえ渡せてしまえば、あとはどうとでもなる。 一例として、ここでは getopts を使って引数を解析してみることにした。

起動するシェルスクリプトで getopts を使って受け取った引数を解析する。 そして、最終的には解析した変数を出力している。 ここでは -a オプションで渡した値が BIND_ADDRESS に、-p オプションで渡したあたいが BIND_PORT に格納される。

$ cat << 'EOF' > docker-entrypoint.sh 
#!/bin/sh

usage() {
  echo "Usage: $0 [-a bind-address] [-p bind-port]" 1>&2
  exit 1
}

while getopts a:p:h OPT
do
  case $OPT in
    a)  BIND_ADDRESS=$OPTARG
        ;;
    p)  BIND_PORT=$OPTARG
        ;;
    h)  usage
        ;;
  esac
done

echo ${BIND_ADDRESS:-127.0.0.1}
echo ${BIND_PORT:-8080}
EOF
$ chmod +x docker-entrypoint.sh 

上記を元にイメージをビルドしよう。

$ docker build -t example/opt .             
...
Successfully tagged example/opt:latest

そしてイメージからコンテナを起動する。 特に何も指定しないときはデフォルト値が表示され、オプションをコマンドライン引数で指定したときはその値が表示される。

$ docker run -t example/opt            
127.0.0.1
8080
$ docker run -t example/opt -a 0.0.0.0 -p 80
0.0.0.0
80

これだと、例えば -h を渡したときは usage を表示して終了みたいなことも簡単にできる。

$ docker run -t example/opt -h
Usage: /usr/local/bin/docker-entrypoint.sh [-a bind-address] [-p bind-port]

ちなみに上記のやり方を取るとデバッグしたいときにどうするんだって話になる。 起動時のパラメータの最後に bin/bash とか付けるだけではシェルスクリプトの起動が上書きできないので。 そんなときは --entrypoint オプションを使えば ENTRYPOINT 命令の内容を上書きできる。

$ docker run --entrypoint sh -it example/opt
/ # uname -a
Linux a6c3524f68a5 4.4.111-boot2docker #1 SMP Thu Jan 11 16:25:31 UTC 2018 x86_64 Linux

ばっちり。