Jinja2 — データから設定ファイルへ
一言でいうと
テンプレートは、「環境ごとに異なる設定ファイル」を、1つのひな形と環境ごとのデータに分解します。
なぜ必要なのか
nginxの設定をdev・stage・prodそれぞれに置くと、3つのファイルが互いに違っていきます。バックエンドサーバーが増えるたびに3か所を直さなければならず、1か所を忘れると、その環境だけにトラフィックが集中します。テンプレートはサーバーの一覧をデータとして持ち、ひな形を1回だけ書けばよいようにします。バックエンドが増えても、データが1行増えるだけです。
どう動くのか
Jinja2で使う構文は3つだけです。
{{ 값 }}: 値の出力(プレースホルダーは値です){% 제어 %}: ifやforのような制御構文(プレースホルダーは制御です){# 주석 #}: レンダリングされないコメント(プレースホルダーはコメントです)
これにフィルターが付きます。{{ name | upper }}、{{ timeout | default(30) }}、{{ data | to_nice_json }}のように、パイプでつなげて書きます。defaultフィルターは特に重要です。定義されていないかもしれない値に安全な代替値を与え、変数がないときに<no value>やエラーが出力されるのを防ぎます。
最もよく問題になるのは空白です。{% for %}のような制御構文はそれ自体が1行を占めるので、レンダリング結果に空行が残ります。YAMLの設定なら、この空行がファイルを壊すこともあります。解決策は2つです。構文に{%-/-%}を使って前後の空白を消すか、templateモジュールでtrim_blocks/lstrip_blocksを有効にすることです。
templateモジュールの2つのオプションも知っておくと、事故を防げます。validateは、レンダリング結果を指定したコマンドで検査し、通過したときだけ配置します。backup: trueは、上書きする前にコピーを残します。設定ファイルを自動でデプロイした瞬間、誤った設定がサービスを落とす経路が開きます。validateは、その経路を塞ぐ最も安い仕掛けです。
現場での姿
1つ目は、レンダリング結果が毎回変わるテンプレートです。タイムスタンプをコメントに入れると、ファイルが毎回変わってハンドラーが毎回動き、サービスが毎回再起動されます。この1行のせいで無停止デプロイが崩れる事例はよくあります。
2つ目は、ファクトを使うテンプレートです。ansible_factsの値をそのまま使うと、サーバーごとに異なる設定が自動的に出力されます。ただし、特殊な環境ではファクトが予想外の値を返すので、defaultで防御線を張ります。
3つ目は、テンプレートにロジックを入れすぎることです。ifが5重に積み重なったら、それはテンプレートではなくプログラムです。そうした分岐は変数の計算段階に引き上げて、テンプレートは単純に保つほうが、保守に向いています。
テンプレートでよく使うフィルター
Jinja2の価値は、フィルターにあります。これらだけ知っていれば、たいていのことはできます。
{{ port | default(8080) }} 값이 없으면 기본값
{{ name | mandatory }} 없으면 에러 — 조용한 빈 값을 막는다
{{ items | join(',') }} 목록을 문자열로
{{ config | to_nice_yaml(indent=2) }} 딕셔너리를 YAML 블록으로
{{ secret | b64encode }} 쿠버네티스 시크릿용
{{ path | basename }} 경로 조각
{{ hosts | map(attribute='ip') | list }} 목록에서 필드만 뽑기
mandatoryを使う習慣が重要です。変数がないと、Jinja2はデフォルトで空文字列を入れます。するとlisten ;のような壊れた設定が作られ、問題はサービスが再起動するときに表面化します。
ansible.cfgに次のように書いておくと、定義されていない変数を即座にエラーにします。
[defaults]
error_on_undefined_vars = True
空白の扱い方
生成されたファイルに空行がたくさん入るのは、ほとんどが制御構造のせいです。
{% for h in hosts %}
server {{ h }};
{% endfor %}
このように書くと、{% %}の行ごとに改行が残ります。ハイフンを付けて取り除きます。
{% for h in hosts -%}
server {{ h }};
{% endfor -%}
Ansibleのtemplateモジュールはデフォルトでtrim_blocksを有効にしないので、必要ならテンプレートの最初の行で指示します。
#jinja2: trim_blocks: True, lstrip_blocks: True
テンプレートをテストする方法
デプロイする前に、結果を確認します。
# 렌더링 결과만 보기 (파일을 쓰지 않는다)
ansible -i inv web -m template -a "src=nginx.conf.j2 dest=/tmp/out.conf" --check --diff
# 문법 검사
ansible-playbook site.yml --syntax-check
# 실제로 무엇이 바뀌는지
ansible-playbook site.yml --check --diff
--check --diffの組み合わせが最も役に立ちます。変更せずに、何が変わるかを見せてくれます。本番のデプロイ前にこれを実行しないと、設定1行がサービスを止めます。
そして、生成された設定を、そのプログラムの検査ツールで確認する段階を入れます。
- name: nginx 설정
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
validate: 'nginx -t -c %s' # ← 실패하면 파일을 바꾸지 않는다
notify: reload nginx
validateは、一時ファイルで検査したあと、通過したときだけ移動します。壊れた設定がディスクに届きません。
次のラボですること
基本のレンダリングから始めて、フィルター・繰り返し・条件を順に使い、空白の制御で結果をきれいにします。ファクトとinventory_hostnameをテンプレートで使い、validateで誤った設定が配置されないようにします。最後に、グループ変数のバックエンド一覧から、nginxの設定をまるごと生成します。