はじめてのAnsible #4 template編

前回はタスクが終了した際に後続処理を実行する方法についてご紹介しました。今回は設定ファイルなどをローカルから管理対象のサーバへコピーする際に、単純なコピーではなくもう少し複雑なことができる「テンプレート」について取り上げます。

設定ファイルをコピーする

単純なコピー

ローカルにあるファイルを、管理対象のサーバに転送するだけであればcopyモジュールを使えば手軽に実現できます。

- hosts: servers
  remote_user: ansibleman
  become: yes
  tasks:
    - name: copy Apache vhost config file
      copy:
        src: conf/httpd/vhost.conf
        dest: /etc/httpd/conf.d/vhost.conf

ファイルの構造は以下の通りです。

~/📁 ansible1st
    📒 ansible.cfg
    📒 playbook.yml
   📁 inventory
      📒 production
   📁 conf
       📁 httpd
          📒 vhost.conf  ★NEW

実行すると転送されてるのが確認できますね。お手軽です。

$ ansible-playbook playbook.yml 
$ ssh ansibleman@example.com

[example.com] $ ls -1 /etc/httpd/conf.d | grep vhost
vhost.conf

このように予め設定ファイルを用意しておけばコピーするだけで済むのですが、今回はAnsible上で動的に設定ファイルを作成する方法に挑戦してみます。

設定ファイルを動的に作成する

Ansibleにはtemplateモジュールが用意されており、Python用のテンプレートエンジンであるJinja2を利用できます。これらはAnsibleに最初から入ってますので別途インストールや設定の必要はありません。

では単純に特定の箇所を置換するシンプルな方法から試してみます。

playbook.yml

まずはPlaybookです。

- hosts: servers
  remote_user: ansibleman
  become: yes
  vars:
    #------------------------------------------#
    # テンプレートに渡す変数
    #------------------------------------------#
    hostname: example.com
    document_root: /var/www/example.com/htdocs
  tasks:
    #------------------------------------------#
    # Apahceの設定ファイルを動的に作成して転送
    #------------------------------------------#
    - name: generate Apache vhost config file
      template:
        src: template/conf/httpd/vhost.conf.j2
        dest: /etc/httpd/conf.d/vhost.conf

Jinja2へ渡すための変数をvarsに定義します。もちろんvars_filesで外部のファイルを読み込んでも大丈夫です。

タスクの方にはtemplateモジュールを書いてやります。オプションとしてsrcにテンプレートファイルを、descに転送先のパスを書いてあげます。テンプレートファイルの拡張子は通常.j2とするのが慣習となっています。

vhost.conf.j2

こちらはテンプレートです。 テンプレート内で{{ 変数名 }}と記述すると、Playbookで定義した変数をそのまま埋め込むことが可能です。

<VirtualHost *:80>
  ServerName {{ hostname }}
  DocumentRoot {{ document_root }}
</VirtualHost>

現在のファイル構造

以下の通りです。

~/📁 ansible1st
    📒 ansible.cfg
    📒 playbook.yml
   📁 inventory
      📒 production
   📁 template
       📁 conf
           📁 httpd
              📒 vhost.conf.j2  ★NEW

実行してみる

実際にPlaybookを実行すると、{{ 変数名 }}の部分が置換されているのがわかります。

$ ansible-playbook playbook.yml 
$ ssh ansibleman@example.com

[example.com] $ cat /etc/httpd/conf.d/vhost.conf 
<VirtualHost *:80>
  ServerName example.com
  DocumentRoot /var/www/example.com/htdocs
</VirtualHost>

設定ファイルを「ループ」して作成する

単純な置換だけでもかなり便利に使えるのですが、今度は複数のVirtualHostを同時に定義してみます。

パッと思いつく方法としては以下のように頭に接頭詞(prefix)を付けて区別するやり方ですが、数が多くなってくると人間が対応するのは現実的ではなくなってきます。ミスを無くして楽をするためにAnsibleを導入するハズが本末転倒ですねw

  vars:
    a_hostname: example.com
    b_hostname: example.net
    c_hostname: example.org
<VirtualHost *:80>
  ServerName {{ a_hostname }}
</VirtualHost>
<VirtualHost *:80>
  ServerName {{ b_hostname }}
</VirtualHost>
<VirtualHost *:80>
  ServerName {{ c_hostname }}
</VirtualHost>

こういった時のために、Jinja2にはループ機能が備わっています。

playbook.yml

Playbookのvarsにはハッシュの配列を用意してやります。

- hosts: servers
  remote_user: ansibleman
  become: yes
  vars:
    vhosts:
      - { name: example.com, docroot: /var/www/example.com/htdocs }
      - { name: example.net, docroot: /var/www/example.net/htdocs }
      - { name: example.org, docroot: /var/www/example.org/htdocs }
  tasks:
    - name: generate Apache vhost config file
      template:
        src: template/conf/httpd/vhost.conf.j2
        dest: /etc/httpd/conf.d/vhost.conf

vhost.conf.j2

こちらはテンプレートです。ようはプログラミング言語で言うfor/foreach文が利用できます。

{% for host in vhosts %}
<VirtualHost *:80>
  ServerName {{ host.name }}
  DocumentRoot {{ host.docroot }}
</VirtualHost>
{% endfor %}

Jinja2では {% 制御構文 %} で囲むと制御構造として利用できます。 Playbookで定義した配列vhostsを1つずつhostに代入し、最後まで順番に処理をします。ハッシュの各要素にアクセスするときはドット(.)で区切ってハッシュのキーを指定するだけです。

実行してみる

実際にPlaybookを実行すると、期待通り繰り返し処理が置換されているのがわかります。

$ ansible-playbook playbook.yml 
$ ssh ansibleman@example.com

[example.com] $ cat /etc/httpd/conf.d/vhost.conf
<VirtualHost *:80>
  ServerName example.com
  DocumentRoot /var/www/example.com/htdocs
</VirtualHost>
<VirtualHost *:80>
  ServerName example.net
  DocumentRoot /var/www/example.net/htdocs
</VirtualHost>
<VirtualHost *:80>
  ServerName example.org
  DocumentRoot /var/www/example.org/htdocs
</VirtualHost>

設定ファイルを「条件分岐」して作成する

for/foreachが使えるとなると、if文も使いたくなるのが人の常という物ですねw これもJinja2の機能で利用することができます。

今回はhost.isauthの値がTrueならBASIC認証をかける設定ファイルを作成してみます。

playbook.yml

真ん中のexample.netだけTrueにしました。

- hosts: servers
  remote_user: ansibleman
  become: yes
  vars:
    vhosts:
      - { name: example.com, isauth: false, docroot: /var/www/example.com/htdocs }
      - { name: example.net, isauth: true,  docroot: /var/www/example.net/htdocs }
      - { name: example.org, isauth: false, docroot: /var/www/example.org/htdocs }
  tasks:
    - name: generate Apache vhost config file
      template:
        src: template/conf/httpd/vhost.conf.j2
        dest: /etc/httpd/conf.d/vhost2.conf

vhost.conf.j2

テンプレートの方はというと、こちらもループと同様に{% if %}でif文を定義することができます。

{% for host in vhosts %}
<VirtualHost *:80>
  ServerName {{ host.name }}
  DocumentRoot {{ host.docroot }}

  {% if host.isauth %}
    <Directory {{ host.docroot }}>
      AuthType Basic
      AuthName "Basic Auth"
      AuthUserfile /etc/httpd/conf.d/.htpasswd
      Require valid-user
    </Directory>
  {% endif %}
</VirtualHost>

{% endfor %}

条件演算子

一般的なプログラミング言語で使うようなものは一通り用意されています。

比較
==, !=, >, >=, <, <=
論理
and, or, not
その他
in

inは配列の中に指定した値があるかを判定する物です。

{% if "banana" in ["apple", "banana", "orange"] %}
   ...
{% endif %}

else if / else

else ifelseももちろん書けます。

{% if host.redirect == "blog" %}
  Redirect https://blog.example.com/
{% elif host.domain == "db" %}
  Redirect https://db.example.com/
{% else %}
  Redirect https://example.com/
{% endif %}

例は1行だけですが、各ブロックの中は複数行になっても大丈夫です。

実行してみる

では最後に実行します。無事に生成されましたがインデントがちょっと汚いですねw Apacheさんは気にせず実行してくれますが、人間が見るとちょっとウッとなるやつです。

$ ansible-playbook playbook.yml 
$ ssh ansibleman@example.com

[example.com] $ cat /etc/httpd/conf.d/vhost.conf
<VirtualHost *:80>
  ServerName example.com
  DocumentRoot /var/www/example.com/htdocs

  </VirtualHost>

<VirtualHost *:80>
  ServerName example.net
  DocumentRoot /var/www/example.net/htdocs

      <Directory /var/www/example.net/htdocs>
      AuthType Basic
      AuthName "Basic Auth"
      AuthUserfile /etc/httpd/conf.d/.htpasswd
      Require valid-user
    </Directory>
  </VirtualHost>

<VirtualHost *:80>
  ServerName example.org
  DocumentRoot /var/www/example.org/htdocs

  </VirtualHost>

この手のテンプレートエンジンあるあるですが、テンプレート側をキレイに書くか、出力ファイルをキレイにするか…悩ましいところですねw

Jinja2の機能で{%- 〜 -%}とパーセントの横にマイナスを書くことでホワイトキャラクター(改行やスペース)を削除することができますので、これで調整するのも手です。…が思ったとおりに一発で決まった試しがないので最初は動くことを最優先でテンプレートを書いて、余力あるときに見た目をキレイにするのが良いかと思います。(プログラミングで言う有る種のリファクタリングですね)

続き

blog.katsubemakito.net

参考ページ

Ansible実践ガイド 第3版 (impress top gear)
北⼭ 晋吾 佐藤 学 塚本 正隆 畠中幸司 横地 晃
インプレス (2019-10-18)
売り上げランキング: 17,753