envsubstを使ってDockerで設定ファイルに環境変数を埋め込めこむ汎用的なパターン

# はじめに
Docker使ってるとできるだけイメージは共通化して、設定ファイルはマウントで差し込み、環境ごとの設定差分は環境変数で埋め込む方向になってきます。
しかしながら、設定ファイルに環境変数を直接埋め込めるかは対象のソフトウェアの実装次第で、例えばfluentdは設定ファイルで環境変数を直接埋め込めるけど、nginxは環境変数を直接埋め込めなかったりします。

で、よくやるのは `entrypoint.sh` みたいなのを作って起動時にsedするという原始的な方法もあるけど、もうちょっとスマートな方法として、 `envsubst` という軽量なテンプレートエンジンを使うことで、設定ファイルに汎用的に環境変数を埋め込むことができて便利なので紹介したい。

# インストール
nginx:alpineには既にenvsubstコマンド入ってるので、そのまま使える。

他のイメージで入ってなければ以下の方法でインストールできる。

## alpine:3.5
以下で雑にインストールはできる。

```bash
$ apk --no-cache add gettext
```

もうちょっとサイズを切り詰めたい場合は、こんなかんじ。

```Dockefile
FROM alpine:3.5

RUN apk --no-cache add libintl && \
      apk --no-cache add --virtual .gettext gettext && \
      cp /usr/bin/envsubst /usr/local/bin/envsubst && \
      apk del .gettext
```

サイズとしてはこれぐらいで、envsubstは賞味111KBぐらい。めっちゃ軽い。

```
$ docker build -t envsubst .
$ docker images
REPOSITORY                      TAG                 IMAGE ID            CREATED             SIZE
envsubst                        latest              1bab5f1656d7        6 seconds ago       4.095 MB
alpine                          3.5                 88e169ea8f46        5 weeks ago         3.984 MB
```

## debian:jessie

```bash
$ apt-get install gettext-base
```

## 使い方

簡単な例として以下のような設定ファイルがあったとして、

```bash
$ cat test.conf.template
hoge=$HOGE
```

こんなかんじで設定ファルをマウントしつつ、環境変数を差し込んで、envsubstに食わせると埋め込んでくれます。

```bash
$ docker run --rm \
    -v $(pwd)/test.conf.template:/tmp/test.conf.template \
    -e HOGE=aaa \
    nginx:alpine \
    /bin/sh -c "envsubst < /tmp/test.conf.template > /tmp/test.conf && cat /tmp/test.conf"
hoge=aaa
```

なんにでも使えそうですね。

ただ、nginx.confのように、設定ファイルの中で `$` 記号が使える場合は、埋め込む環境変数名を明示する必要があります。

たとえばこんなかんじのnginx.confがあったとして `${NGINX_SERVER_NAME}`, `${NGINX_UPSTREAM_HOST}`, `${NGINX_UPSTREAM_PORT}` は置換したいけど、 `$remote_addr`, `$host` とかは置換してほしくないみたいな。

```nginx.conf
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
	worker_connections  1024;
}

http {
	include       /etc/nginx/mime.types;
	default_type  application/octet-stream;

	log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
		'$status $body_bytes_sent "$http_referer" '
		'"$http_user_agent" "$http_x_forwarded_for"';

	access_log  /var/log/nginx/access.log  main;

	sendfile        on;
	#tcp_nopush     on;

	keepalive_timeout  65;

	#gzip  on;

	resolver           127.0.0.11 valid=5s;

	server {
		listen 80;
		set_real_ip_from   '10.0.0.0/8';
		real_ip_header     X-Forwarded-For;
		proxy_set_header   Host $host;
		proxy_set_header   X-Forwarded-Proto https;

		server_name ${NGINX_SERVER_NAME};

		set $upstream ${NGINX_UPSTREAM_HOST};

		location / {
			proxy_pass http://$upstream:${NGINX_UPSTREAM_PORT};
		}
	}
	include /etc/nginx/conf.d/*.conf;
}
```

この場合ははenvsubstに置換対象の環境変数名を指定します。
引数が長いのでdocker-compose.ymlにしてます。(2017/02/05: 改行のバックスラッシュいらないという指摘があったので修正)

```docker-compose.yml
version: '2'
services:
  nginx:
    image: nginx:alpine
    command: >
      /bin/sh -c
      "envsubst '
      $$NGINX_SERVER_NAME
      $$NGINX_UPSTREAM_HOST
      $$NGINX_UPSTREAM_PORT
      '< /etc/nginx/nginx.conf.template
      > /etc/nginx/nginx.conf
      && nginx -g 'daemon off;'"
    volumes:
      - ./nginx.conf.template:/etc/nginx/nginx.conf.template
    ports:
      - 8080:80
    environment:
      NGINX_SERVER_NAME: "test.example.com"
      NGINX_UPSTREAM_HOST: "hoge"
      NGINX_UPSTREAM_PORT: "80"
```

起動して設定ファイルの中身を覗いてみます。

```bash
$ docker-compose up -d
Starting 20170204_nginx_1

$ docker exec -it $(docker ps -l -q) cat /etc/nginx/nginx.conf
```

ちゃんと指定したところだけ環境変数が埋め込まれてます。

```nginx.conf
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
        worker_connections  1024;
}

http {
        include       /etc/nginx/mime.types;
        default_type  application/octet-stream;

        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" '
                '"$http_user_agent" "$http_x_forwarded_for"';

        access_log  /var/log/nginx/access.log  main;

        sendfile        on;
        #tcp_nopush     on;

        keepalive_timeout  65;

        #gzip  on;

        resolver           127.0.0.11 valid=5s;

        server {
                listen 80;
                set_real_ip_from   '10.0.0.0/8';
                real_ip_header     X-Forwarded-For;
                proxy_set_header   Host $host;
                proxy_set_header   X-Forwarded-Proto https;

                server_name test.example.com;

                set $upstream hoge;

                location / {
                        proxy_pass http://$upstream:80;
                }
        }
        include /etc/nginx/conf.d/*.conf;
}
```

# まとめ
envsubst使うとDockerで設定ファイルに環境変数を埋め込めこむ汎用的なパターンとしていろいろ便利そうです。