koh’s blog

Sys Admin who loves automation

Ansibleのcopyモジュールとtemplateモジュールの違いと注意点

For English

Ansibleにはcopyモジュールとtemplateモジュールが存在します。
どちらも基本的にはローカルにあるファイルをリモートホストへ転送する用途のモジュールであり、使い方も似ていますが細かい挙動が異なります。
ここではその違いと注意点を記載していきます。
※なお使用しているAnsibleのバージョン及び参照しているドキュメントのバージョンは2.8.1です。

copyモジュール

docs.ansible.com

主な特徴

  • ファイル内にvariableを記載しても展開されない
  • srcで指定するファイルのデフォルトのパスがroles/{{ rolename }}/files配下
  • remote_srcパラメータによりリモートからリモートの別パスへのコピーができる
  • srcの代わりにcontentを指定することで実ファイルを用意せずに直接ファイルの内容を記載できる

templateモジュール

docs.ansible.com

主な特徴

  • Jinja2によりテンプレートファイル内でifやforなどのプログラムっぽい記載ができる
  • ファイル内にvariableを記載すると展開してくれる
  • srcで指定するファイルのデフォルトのパスがroles/{{ rolename }}/templates配下

Jinja2について

上記で述べたJinja2についてどんなものなのか簡単に解説します。
jinja.pocoo.org

Jinja2 is a modern and designer-friendly templating language for Python, modelled after Django’s templates.

Jinja2はモダンでデザイナーに優しいPython製のテンプレート言語であり、Djangoのテンプレートをモデルとしています。

よく使うと思われるif文とfor文の簡単な例を下記に記載します。

[koh@kohs-MBP] ~/vag_test
% cat test.j2
{% for item in ["foo","bar","baz"] %}
{{ item }}
{% endfor %}

{% if inventory_hostname == "Vag2" %}
hostname is Vag2
{% else %}
hostname is not Vag2
{% endif %}
[koh@kohs-MBP] ~/vag_test
%

簡単に解説すると
上の方はfor文でfoo,bar,bazと記載させています。
下の方はinventory_hostnameがVag2であればhostname is Vag2, そうでなければhostname is not Vag2と記載させています。

実行結果が下記になります。

[koh@kohs-MBP] ~/vag_test
% ansible Vag1 -m template -a "src=test.j2 dest=."
Vag1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "c2383df182afda3f4de83ce7171885ce7fd4839a",
    "dest": "././test.j2",
    "gid": 1000,
    "group": "vagrant",
    "md5sum": "54a03d50ba7a7751f6d647afdeb80e02",
    "mode": "0644",
    "owner": "vagrant",
    "secontext": "unconfined_u:object_r:user_home_t:s0",
    "size": 34,
    "src": "/home/vagrant/.ansible/tmp/ansible-tmp-1562164682.016716-96505856164811/source",
    "state": "file",
    "uid": 1000
}
[koh@kohs-MBP] ~/vag_test
% ssh Vag1 "cat test.j2"
foo
bar
baz

hostname is not Vag2
[koh@kohs-MBP] ~/vag_test
%

上記のJinja2や変数展開をうまく活用すると下記のような用途にも対応でき、実運用しやすいPlaybookになると思います。

  • HAproxyやNginxを使用したリバースプロキシのバックエンドの設定をfor文を使用して簡略化
  • 本番/ステージングのconfigファイルに変数やif文を活用することで設定差分をなくす

注意点

一見するとtemplateモジュールは便利に思えますが、モジュール内で元のファイルを加工してサーバに配置するため当然設定ミスの可能性があります。

Jinja2の記載ミスはよくありますが、意図していない箇所がJinja2のフォーマットとして認識されてしまい処理されてしまう可能性もあります。

私が実際に遭遇した例だとapacheのconfigのLogformatのタイムスタンプが誤認識されました。
下記のようなファイルを用意し、templateを実施してみます。

[koh@kohs-MBP] ~/vag_test
% cat logformat.j2
LogFormat "%{%d/%b/%Y %T}t.%{msec_frac}t %{%z}t"
[koh@kohs-MBP] ~/vag_test
%
[koh@kohs-MBP] ~/vag_test
% ansible Vag1 -m template -a "src=logformat.j2 dest=."
Vag1 | FAILED! => {
    "changed": false,
    "msg": "AnsibleError: template error while templating string: Encountered unknown tag 'd'.. String: LogFormat \"%{%d/%b/%Y %T}t.%{msec_frac}t %{%z}t\"\n"
}
zsh: exit 2     ansible Vag1 -m template -a "src=logformat.j2 dest=."
[koh@kohs-MBP] ~/vag_test
%

テンプレート内の {%dの部分がJinja2のtag扱いされてしまいエラーになってしまいます。 このようにエラーになってくれればまだ検知できるのですが、そのまま通ってしまったりすると気づけないこともあるかもしれません。

対策

上記のような事故を防ぐために下記のような方法が効果的です。

Jinja2テンプレートや変数展開を使用しないファイルはcopyモジュールを使用する

copyモジュールを使用すればsrcのファイルがAnsibleによって書き換えられることはないので、きちんとcopyモジュールとtemplateモジュールをPlaybookの中で使い分けることが効果的です。

validateパラメータを使用する

templateモジュールにはvalidateというパラメータがあり、ファイル配置の際に任意のコマンドを発行することでファイルの正当性を確認できます。
特にsshdやsudoなど設定ミスしたときのリスクが大きい設定に効果的です。
ちなみにcopyモジュールでもvalidateは使えるため、copyでも積極的に使用して良いと思います。

- name: Copy a new sudoers file into place, after passing validation with visudo
  template:
    src: /mine/sudoers
    dest: /etc/sudoers
    validate: /usr/sbin/visudo -cf %s

checkモードで確認

Ansibleにはcheckモード(-C)があり、実際にPlaybookの内容を適用せずに挙動だけを確認できるオプションがあります。
またDiff(-D)オプションと組み合わせることでより正確に確認できます。

[koh@kohs-MBP] ~/vag_test
% ansible Vag1 -m template -a "src=test.j2 dest=." -D -C
--- before: ./test.j2
+++ after: /Users/koh/.ansible/tmp/ansible-local-90545ps884h5s/tmpuvs9nt1z/test.j2
@@ -1,4 +1,4 @@
-foo
+fooooo
 bar
 baz


Vag1 | CHANGED => {
    "changed": true
}
[koh@kohs-MBP] ~/vag_test
%

まとめ

うまくtemplateモジュールを活用できるとPlaybookの運用がしやすくなると思うのでこの記事がお役に立てば嬉しいです。
またシンプルにかけることが売りのAnsibleですが、templateは数少ない黒魔術化しがちなポイントでもあるので容量用法に気をつけていただければと。