はじめに

Ansibleでtagsを指定してplaybookを実行するとき、依存しているroleを自動で呼び出すようにしたい。

version: ansible 2.4

Subversionのinstallを例にとる

例えば、Subversionをinstallするとき、フロントにApacheをおいてユーザ認証にBasic認証を利用するとする。
Subversionのroleの各taskに設定されているtagsがsvnで、Apacheのroleのtagsがapacheとする。
tagsを指定して実行する場合、通常であればansible-playbook -i hosts.yml setup_middleware.yml --tags apache,svnのように二つ指定しなければならない。また汎用的なroleにするためにApache用の変数設定とSubversion用の変数設定を通常であれば用意すると思うので、SubversionとApacheのそれぞれの変数設定もしなければならない。
しかし達成したい本質はSubversionのinstallであって、Apacheの部分はあまり細かく変数設定せずにほとんど固定値で問題ないし、tagsを指定するときにapacheを入れるのではなく、svnを入れるだけでSubversionが必要としている設定が全て自動で入ってほしい。

このようなときはdependenciesを使って--tags svnだけでApacheも自動でsetupされるようにするのがいい。

dependenciesについて書く前に、ApacheとSubversionのroleの一部を抜粋する。

Apacheのrole

confファイルをtemplateから作成したりしている。

$ cat roles/apache/tasks/main.yml 
---
(略)

- name: copy ssl.conf
  tags: apache
  template:
    src: ssl.conf.j2
    dest: /etc/httpd/conf.d/ssl.conf

- name: create vhost.conf for https
  tags: apache
  template:
    src: vhost-https.conf.j2
    dest: /etc/httpd/conf.d/sites.d/{{ item.server_name }}-{{ item.listen }}-https.conf
  when: item.enable_https == true
  with_items: "{{ apache.sites }}"

(略)

Subversionのrole

単一Repositoryと複数Repositoryの両方を構築できるようにしている。変数svn.multi_repository.usetrueを設定し、svn.multi_repository.repositoriesでリポジトリ名を列挙すれば、その分だけリポジトリが作成される。

複数のコンポーネントを作成しなければいけないプロジェクトにおいて、単一Repositoryと複数Repositoryのどちらを採用すればいいかは会社やチーム次第だが、こちらが参考になる。

Subversionのリポジトリ構成より

  1. 単一リポジトリ単一trunk型
  2. 単一リポジトリ複数trunk型
  3. 複数リポジトリ型

Subversionサーバを構築して渡す身としては、複数Repositoryを選ぶとRepositoryが増えるたびにApacheとの連携の設定をしなければいけないので、単一Repositoryを採用して、中のRepositoryを自由に使ってもらいたい。

$ cat roles/svn/tasks/main.yml
---
- name: create svn directory
  tags: svn
  file:
    path: "{{ svn.top_dir }}"
    state: directory
    owner: apache
    group: apache
    mode: 0755

- name: create repository only once
  tags: svn
  shell:
    svnadmin create "{{ svn.top_dir }}"
  args:
    creates: "{{ svn.top_dir }}/db"
  when: not svn.multi_repository.use

- name: create repositories only once
  tags: svn
  shell:
    svnadmin create "{{ svn.top_dir }}/{{ item }}"
  args:
    creates: "{{ svn.top_dir }}/{{ item }}/db"
  when: svn.multi_repository.use
  with_items:
    - "{{ svn.multi_repository.repositories | default() }}"

- name: set permission to repositories
  tags: svn
  file:
    path: "{{ svn.top_path }}"
    state: directory
    owner: apache
    group: apache
    recurse: yes

## setting for svn + https
- name: set httpd conf file
  tags: svn
  template:
    src: subversion.conf.j2
    # VirtualHost定義のある{{ item.server_name }}-{{ item.listen }}-https.confから
    # `Include conf.d/sites.d/subversion*.conf`でIncludeされようにする
    dest: "/etc/httpd/conf.d/sites.d/subversion.conf"
  when: not svn.multi_repository.use
  notify: restart apache

- name: set httpd conf files
  tags: svn
  template:
    src: subversion_repo.conf.j2
    # VirtualHost定義のある{{ item.server_name }}-{{ item.listen }}-https.confから
    # `Include conf.d/sites.d/subversion*.conf`でIncludeされようにする
    dest: "/etc/httpd/conf.d/sites.d/subversion_{{ item }}.conf"
  when: svn.multi_repository.use
  with_items:
    - "{{ svn.multi_repository.repositories | default() }}"
  notify: restart apache

(略)

SubversionのApache用confのtemplateも掲載する。

単一Repository用

$ cat roles/svn/templates/subversion.conf.j2 
# below are loaded in conf.modules.conf/10-subversion.conf
#LoadModule dav_svn_module     modules/mod_dav_svn.so
#LoadModule authz_svn_module   modules/mod_authz_svn.so
#LoadModule dontdothat_module  modules/mod_dontdothat.so

<Location /svn>
  DAV svn
  SVNPath {{ svn.top_dir }}
  AuthType Basic
  AuthName "Authorization"
  AuthUserFile {{ svn.top_dir }}/.htpasswd
{% if svn.authz %}
  AuthzSVNAccessFile {{ svn.top_dir }}/conf/authz
{% endif %}
  Require valid-user
  SSLRequireSSL
</Location>

複数Repository用

$ cat roles/svn/templates/subversion_repo.conf.j2 
# below are loaded in conf.modules.conf/10-subversion.conf
#LoadModule dav_svn_module     modules/mod_dav_svn.so
#LoadModule authz_svn_module   modules/mod_authz_svn.so
#LoadModule dontdothat_module  modules/mod_dontdothat.so

<Location /svn/{{ item }}>
  DAV svn
  SVNPath {{ svn.top_dir }}/{{ item }}
  AuthType Basic
  AuthName "Authorization"
  AuthUserFile {{ svn.top_dir }}/{{ item }}/.htpasswd
{% if svn.authz %}
  AuthzSVNAccessFile {{ svn.top_dir }}/{{ item }}/conf/authz
{% endif %}
  Require valid-user
  SSLRequireSSL
</Location>

変数設定

dependenciesを使わない場合

もしdependenciesを使わないと、変数設定においてSubversionだけでなく、汎用的に作ってあるApacheについても詳細に決めなくてはいけなくなる。

以下のような変数設定になる。

$ cat inventories/development/hosts.yml
---
(略)

svn_srv:
  hosts:
    - svn0001
  vars:
    svn:
      top_dir: /DISK01/svn
      multi_repository:
        use: false
        repositories: 
#          - api
#          - batch
    apache:
      sites:
        - server_name: svn.example.com
          listen: 443
          document_root: /var/www/svn
          enable_https: true
          svn: true

(略)

dependenciesを使う場合

FQDNとなるserver_name以外は決めうちでいいと考え、server_nameだけを設定できるようにする。またapache.server_nameで設定させるのではなく、svn.server_nameで設定できるようにする。

$ cat inventories/development/hosts.yml
---
(略)

svn_srv:
  hosts:
    - svn0001
  vars:
    svn:
      top_dir: /DISK01/svn
      authz: true
      server_name: svn.example.com
      multi_repository:
        use: false
        repositories: 
#          - api
#          - batch

(略)

dependenciesの書き方

inventoryのhosts.ymlにおいて、先に述べたような書き方をできるようにするために、またplaybook実行時に--tags svnだけでApacheのroleまで実行できるようにするために、dependenciesを導入する必要がある。

dependenciesは、Subversionのroleディレクトリ配下のmeta/main.ymlに書く。

$ cd roles/svn
$ mkdir meta
$ vi main.yml
$ cat main.yml
---
dependencies:
  - { role: apache, apache: {sites: [{server_name: '{{ svn.server_name }}', listen: 443, document_root: '/var/www/svn', enable_https: true, svn: true}]}, tags: 'svn' }

依存するroleとその変数の指定

dependenciesでは依存するroleが指定できる。ここではApacheを指定する。

role名とともにApacheのroleで必要とする変数をここで設定することができる。Apacheのroleで使う変数はapache.sitesの配列になっている。上記ではYAMLをフロースタイルの一行で書いてしまったが、ちょっと複雑なのでブロックスタイルで複数行にわたって書いた方がよかったかもしれない。

server_name以外の変数は決めうちでいいという考えなので、document_root等は固定値を渡す。server_nameはinventoryで設定したsvn.server_nameを採用したい。'{{変数}}'で変数参照と変数設定ができるため、server_name: '{{ svn.server_name }}'としている。

ちなみにSubversionの前段にいるApacheの設定について「変数は固定値、決めうちで十分」というスタンスで書いているが、inventoryを書く労力を削減するだけでなく、Apacheを使うときにHTTPSを強制させることができていることも評価したい。Basic認証というパスワードが平文でネットワークを流れてしまう認証方式を用いている以上、SSL/TLSで通信自体を暗号化しなければいけない。
もしこのAnsibleをチームに展開して、案件ごとに各メンバにApacheの変数設定を全て記載してもらうようにすると、listen:80, enable_https: falseと設定する人が出ないとも限らない。

dependenciesのtagsのつけ方

tagsもここで指定することができる。Apacheのroleの各taskでtags指定がなければ不要だが、現在のroleでは全taskにapacheというtagをつけている。もしtagsをここで指定しないと、ansible-playbook -i hosts.yml setup_middleware.yml --tags svnのように実行するとき、dependencies機能でApacheのroleを呼び出してもApacheのroleのtags: apacheにマッチせず、Apacheのtaskは一つも実行されない。
SubversionのdependenciesからApacheを呼び出す場合は、Apacheのroleにtags: svnをつけてあげることで、--tags svnで実行してもApacheのroleのタグにもマッチするようになる。

はじめよく理解せずに以下のようにtags: apacheとしてしまっていた。ansible-playbook -i hosts.yml setup_middleware.yml --tags svnで実行したとしても、dependenciesから呼び出すときは--tags svn, apacheというようにapacheタグを補う役割をすると誤解してしまっていた。ansible-playbookコマンド実行時の--tagsを補うのではなく、dependenciesで呼び出しているrole全体に対してtagsを付与しているということになる。

dependencies:
  - { role: apache, apache: {sites: [{server_name: '{{ svn.server_name }}', listen: 443, document_root: '/var/www/svn', enable_https: true}]}, tags: 'apache' }

playbookでroleを読み込む場合にtagsを指定でき、role全体にそのtagsを付与することができる。dependenciesでのtagsも全く同じ考え方なので、上記のような誤解は普通はしないと思うが、「--tagsを自動で補ってほしい」という考えに頭がなってしまっていたのでdependenciesでのtagsのつけ方を勘違いしてしまったようだ。

非専用サーバでの問題点

サーバをSubversion専用として使う想定であればこれで問題ないが、Apacheで他のサイトも動かすときに問題が発生する。

Subversion以外でもApacheを使用するためapache.sitesには他のサイトの情報を記載し、apache roleが実行される。その後にsvn roleが実行されたとき、dependenciesにはsvnのapache.sites情報しか書いていないため、apache roleの作り次第でapache roleで設定している内容を上書きしてしまう。

これの対策として、Apacheを他の用途で動かさない場合のみdependenciesが動くようにwhen: "'apache' not in group_names"と条件を付ける。他の用途でApacheを使うときにはapacheグループに属していて、Subversion専用の場合はsvnグループのみに属しているという前提のものである。

$ cd roles/svn
$ mkdir meta
$ vi main.yml
$ cat main.yml
---
dependencies:
  - { role: apache,   apache: {sites: [{server_name: '{{ svn.server_name }}', listen: 443, document_root: '/var/www/svn', access_log: svn.access_log, error_log: svn.error_log, enable_https: true, svn: true}]}, tags: 'svn', when: "'apache' not in group_names" }

SubversionとApacheを例に挙げたが、このような問題があるために依存関係をつくるためのroleとしては不適切だったかもしれない。

例えば証明書の配布タスクのようなものであれば、それぞれの実行結果が互いに影響を与えないため、依存関係をつくるのに適切だろう。