設定ファイルを環境ごとに書き分けていると、ほとんど同じ内容のコピーが少しずつ増えていきます。
本番と検証でホスト名とポートだけが違う、その程度の差なのにファイルは2つに割れてしまう。
そこで手が伸びるのが sed の置換ですが、差し替える箇所が増えるほどコマンドは読みにくくなりました。
envsubst は、テンプレートの中の変数記法を環境変数の値へ差し替えるだけの小さなコマンドです。
gettext に同梱されているため、多くの Linux ディストリビューションでは追加インストールなしに動きました。
ここから先は、手元で実際に打って確かめた結果だけを並べていきます。
検証環境は GNU gettext-runtime 0.23.1・bash 5.2.37 でした。
まずは標準入力に流すだけ
使い方は拍子抜けするほど単純で、テンプレートを標準入力から流し込むだけ。
置き換えたい箇所は、シェル変数と同じ記法で書いておきます。
$ cat app.conf.tmpl
server_name ${APP_HOST};
listen ${APP_PORT};
root /var/www/${APP_NAME};
# 未定義の例: ${NOT_DEFINED}
あとは環境変数を渡して envsubst に食わせるだけ。
$ APP_HOST=example.com APP_PORT=8080 APP_NAME=myapp envsubst < app.conf.tmpl
server_name example.com;
listen 8080;
root /var/www/myapp;
# 未定義の例:
3つの変数が、それぞれの値に置き換わりました。
注目したいのは最後の行で、定義していない変数はエラーにならず、黙って空文字になる。
終了ステータスも 0 のままなので、渡し忘れに気づけないまま設定が壊れる恐れがあります。
中括弧つきの書き方も、括弧なしの書き方も、どちらも同じように置換対象になる点も押さえておきたいところ。
$ printf 'plain $APP_HOST and braced ${APP_HOST}\n' | APP_HOST=example.com envsubst
plain example.com and braced example.com
この「なんでも拾う」性質が、次に書く事故につながっていきます。
いちばんの事故は、消えてほしくない記法まで消えること
私が最初に踏んだのは、nginx の設定テンプレートでした。
nginx は設定ファイルの中で自前の変数を使うため、テンプレートにはこちらが触りたくない記法が最初から混ざっています。
$ cat nginx.tmpl
server_name ${APP_HOST};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
ここで素の envsubst を通すと、どうなるか。
$ APP_HOST=example.com envsubst < nginx.tmpl
server_name example.com;
proxy_set_header Host ;
proxy_set_header X-Real-IP ;
nginx へ渡すはずだった変数が、まるごと空になって消えました。
シェルの環境変数として定義されていない以上、envsubst にとっては「未定義=空文字」でしかない。
設定の構文としては通ってしまうため、reload したあとに転送先のヘッダが欠けて初めて気づくたぐいの壊れ方です。
展開する変数を名指しして守る
対策は単純で、envsubst に引数を1つ渡すだけ。
この引数は SHELL-FORMAT と呼ばれ、ここに書いた変数だけを置換対象にするという指定になります。
$ APP_HOST=example.com envsubst '${APP_HOST}' < nginx.tmpl
server_name example.com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
今度は nginx 側の記法がそのまま残りました。
テンプレートに他人の変数記法が混ざる場面では、SHELL-FORMAT を必ず付けるのが安全側の作法。
複数を渡したいときは、空白で区切って並べれば通ります。
$ APP_HOST=example.com APP_PORT=8080 APP_NAME=myapp \
envsubst '${APP_HOST} ${APP_PORT}' < app.conf.tmpl
server_name example.com;
listen 8080;
root /var/www/${APP_NAME};
# 未定義の例: ${NOT_DEFINED}
名指ししなかった APP_NAME は、値を渡していても展開されずに残っている。
裏を返せば、テンプレートを段階的に差し替えていく使い方もできるわけです。
-v で「何を渡せばいいか」を先に出す
SHELL-FORMAT を書くとき、そもそも何を並べるべきか迷う場面が出てきました。
そこで使えるのが -v で、渡した SHELL-FORMAT に含まれる変数名だけを一覧してくれます。
$ envsubst -v '${APP_HOST} ${APP_PORT} ${APP_NAME}'
APP_HOST
APP_PORT
APP_NAME
デプロイ前に「この環境で渡し忘れている変数はどれか」を機械的に突き合わせられるので、CI の一歩目に置くと効いてきます。
ひとつ注意したいのは、-v が SHELL-FORMAT を省略できない点。
引数なしで実行すると、テンプレートを読ませたつもりでもエラーになりました。
$ envsubst -v < app.conf.tmpl
envsubst: missing arguments
テンプレート側から変数名を自動で拾ってくれるわけではない、と覚えておくと無駄に悩まずに済みます。
最後に
envsubst は、sed の置換をテンプレート用途に絞って単純にした道具だと捉えると腑に落ちるのです。
覚える順番は、まず素の envsubst、次に SHELL-FORMAT、最後に -v の3段でじゅうぶんでした。
未定義が黙って空文字になる性質だけは、事故が静かに進む分きちんと頭へ置いておきたいところ。
詳しい仕様は GNU gettext マニュアル(envsubst Invocation) が一次情報になります。
そもそも環境変数がどこから渡ってくるのかは、PATH と export の話とあわせて整理しました。
以上です。










コメントを残す