Edited at

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

More than 1 year has passed since last update.


はじめに

Docker使ってるとできるだけイメージは共通化して、設定ファイルはマウントで差し込み、環境ごとの設定差分は環境変数で埋め込む方向になってきます。

しかしながら、設定ファイルに環境変数を直接埋め込めるかは対象のソフトウェアの実装次第で、例えばfluentdは設定ファイルで環境変数を直接埋め込めるけど、nginxは環境変数を直接埋め込めなかったりします。

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


インストール

nginx:alpineには既にenvsubstコマンド入ってるので、そのまま使える。

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


alpine:3.5

以下で雑にインストールはできる。

$ apk --no-cache add gettext

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

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

$ apt-get install gettext-base


使い方

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

$ cat test.conf.template

hoge=$HOGE

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

$ 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"

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

$ 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で設定ファイルに環境変数を埋め込めこむ汎用的なパターンとしていろいろ便利そうです。