akishin999の日記

調べた事などを書いて行きます。

Ansible の Playbook を使ってみる

構成管理ツール Ansible を使ってみる の続きです。
今回は Playbook について調べた事をまとめてみます。

Playbook は Ansible のモジュールを使用した処理の組合せにより対象サーバの構成を記述したものです。

例えば Apache を使用して Web サーバを構築する場合、単に yumApache をインストールして完了、という事はなく、その後 httpd.conf の内容を適切に設定したり、iptables でポートを開けたり、といった一連の作業がありますが、そういったサーバ構築のための作業をまとめて Playbook として定義しておく事で、「実行できる手順書」として残しておく事が出来ます。
Puppet で言うところのマニフェスト、Chef でのレシピに相当するものになります。

Ansible の Playbook はフォーマットが YAML なので、気軽に書き始める事ができます。

簡単な Playbook

まずは単純に yum から MySQL をインストールするだけの Playbook を作成してみます。

---
- hosts: all
  user: root
  tasks:
    - name: install mysql
      yum: name=mysql state=installed

作成した Playbook を実行するには、ansible コマンドの時と同様に対象ホストを記述したファイルを用意して ansible-playbook コマンドを実行します。

$ ansible-playbook mysql.yml -i hosts -k

これで yum モジュールを使用して対象ホストに mysql パッケージがインストールされます。

yaml ファイルの先頭付近、hosts には対象ホストのホスト名または対象ホストのグループ名を指定します。
user には ansible が SSH ログインする際のユーザ名を指定します。

tasks

Tasks list
http://docs.ansible.com/ansible/latest/playbooks_intro.html#tasks-list

実際に実行する処理は tasks 以下に定義していきます。

name にそのタスクの判り易い名前を指定し、その下に使用するモジュールとオプションを指定します。

先ほどの例では一つのパッケージのみインストールしているので ansible コマンドを直接実行するのとあまり変わりませんが、Playbook では以下のように tasks の下に複数の処理を定義することが出来ます。

---
- hosts: all
  user: root
  tasks:
    - name: install mysql
      yum: name=mysql state=installed

    - name: install mysql-server
      yum: name=mysql-server state=installed

    - name: install mysql-devel
      yum: name=mysql-devel state=installed

上記を実行すると対象サーバに mysql, mysql-server, mysql-devel の三つのパッケージがインストールされます。
ちなみに上記は with_items を使用して以下のように書く事が出来ます。

---
- hosts: all
  user: root
  tasks:
    - name: install mysql packages
      yum: name=$item state=installed
      with_items:
        - mysql
        - mysql-server
        - mysql-devel

with_items に記述した要素が一つずつ yum モジュールの name に指定した item 変数に渡され、インストールされます。
with_items による繰り返し実行は他のモジュールでも使用できるので覚えておくと便利です。

vars

Variables
http://docs.ansible.com/ansible/latest/playbooks_variables.html

Playbook 内では変数を使用する事もできます。
基本的には vars セクションで定義し、tasks セクション内などで値を参照する事が出来ます。
(with_items での item や register で作成する変数もあります。)

プログラムのバージョンなどのように頻繁に変わる値や、複数のタスクで共通に参照する値などは変数として定義しておいた方が変更が楽になります。

---
- hosts: all
  user: root
  vars:
    mysql_port: 3306
  tasks:
    - name: install mysql packages
      yum: name=$item state=installed
      with_items:
        - mysql
        - mysql-server
        - mysql-devel

    - name: insert iptables rule
      lineinfile: dest=/etc/sysconfig/iptables state=present regexp="{{ mysql_port }}"
                  insertafter="^:OUTPUT " line="-A INPUT -p tcp --dport {{ mysql_port }} -j ACCEPT"

上記の例では vars セクションで MySQL のポート番号を mysql_port という変数に定義し、「insert iptables rule」タスクで定義した変数を参照しています。
変数は以下のような書き方で参照することが出来ます。

  • $var
  • ${var}
  • {{ var }}

handlers

Handlers: Running Operations On Change
http://docs.ansible.com/ansible/latest/playbooks_intro.html#handlers-running-operations-on-change

task の実行により何か変更が発生した場合のみ実行したい処理を定義するために用意されているのが handler です。
例えば、設定ファイルの変更が発生した場合のみ、サービスを再起動したい、といった事を実現するための仕組みです。

handler は通常の task と定義方法は同じですが、handlers セクションに定義します。

---
- hosts: all
  user: root
  vars:
    mysql_port: 3306
  handlers:
    - name: restart iptables
      service: name=iptables state=restarted
  tasks:
    - name: install mysql packages
      yum: name=$item state=installed
      with_items:
        - mysql
        - mysql-server
        - mysql-devel

    - name: insert iptables rule
      lineinfile: dest=/etc/sysconfig/iptables state=present regexp="{{ mysql_port }}"
                  insertafter="^:OUTPUT " line="-A INPUT -p tcp --dport {{ mysql_port }} -j ACCEPT"
      notify: restart iptables

handler の実行のトリガとなる task には notify を定義しておきます。
これによりその task により何かが変更された場合(changed になった場合)に notify に指定した名前を持つ handler が実行されます。
二度目の実行などでその task による変更が発生しなかった場合は実行されません。

handler が実行されるタイミングは対象の task 実行後ではなく、 Playbook の最後になります。
また、複数の task から同じ handler が notify されていた場合でも一度だけしか実行されません。

task の include

Including and Importing
http://docs.ansible.com/ansible/latest/playbooks_reuse_includes.html

tasks, vars, handlers が分かれば後はモジュールのマニュアルを見ながら大抵の処理は書けますが、複数のミドルウェアから構成されるアプリケーションのインストール処理や、複数台のサーバで構成されるシステムの構築手順を Playbook にする場合、どうしても YAML ファイルが肥大化してしまいます。

そのため、Ansible では別のファイルに定義した task を include する事により、複数ファイルから構成される Playbook を作成する事が出来るようになっています。

  • site.yml(一部)
- hosts: all
  user: root
  tasks:
    - name: install python-selinux
      yum: pkg=libselinux-python state=latest

    - name: disable selinux
      selinux: state=disabled

    - include: redis.yml
      vars:
        libunwind: http://dl.fedoraproject.org/pub/epel/6/x86_64/libunwind-1.1-2.el6.x86_64.rpm
        libunwind_devel: http://dl.fedoraproject.org/pub/epel/6/x86_64/libunwind-devel-1.1-2.el6.x86_64.rpm
        gperftools_libs: http://dl.fedoraproject.org/pub/epel/6/x86_64/gperftools-libs-2.0-11.el6.3.x86_64.rpm
        redis: http://rpms.famillecollet.com/enterprise/6/remi/x86_64/redis-2.6.13-1.el6.remi.x86_64.rpm
    ・
    ・
    ・
  • redis.yml
- name: install redis packages
  action: yum name=$item state=installed
  with_items:
    - ${libunwind}
    - ${libunwind_devel}
    - ${gperftools_libs}
    - ${redis}

- name: start redis
  action: service name=redis state=started enabled=yes

この例ではメインとなる site.yml の中から Redis インストール用の redis.yml を include しています。
include される側となる redis.yml には通常 tasks セクション内に書く task のみ記述し、その他のセクションについては記述しません。
変数については include 時に vars を使って渡す事が出来ます。

Playbook の include

task の include 以外にも、 name と同じレベルで include を使う事で Playbook 自体を include することも出来ます。

  • dbserver.yml
---
- name: setup database server
  hosts: all
  user: root
  vars:
    mysql_port: 3306
  handlers:
    - name: restart iptables
      service: name=iptables state=restarted
  tasks:
    - name: insert iptables rule
      lineinfile: dest=/etc/sysconfig/iptables state=present regexp="{{ mysql_port }}"
                  insertafter="^:OUTPUT " line="-A INPUT -p tcp --dport {{ mysql_port }} -j ACCEPT"
      notify: restart iptables

- include: mysql.yml
- include: redis.yml
---
- name: setup mysql
  hosts: all
  user: root
  tasks:
    - name: install mysql packages
      yum: name=$item state=installed
      with_items:
        - mysql
        - mysql-server
        - mysql-devel
  • redis.yml
---
- name: setup redis
  hosts: all
  user: root
  vars:
    libunwind: http://dl.fedoraproject.org/pub/epel/6/x86_64/libunwind-1.1-2.el6.x86_64.rpm
    libunwind_devel: http://dl.fedoraproject.org/pub/epel/6/x86_64/libunwind-devel-1.1-2.el6.x86_64.rpm
    gperftools_libs: http://dl.fedoraproject.org/pub/epel/6/x86_64/gperftools-libs-2.0-11.el6.3.x86_64.rpm
    redis: http://rpms.famillecollet.com/enterprise/6/remi/x86_64/redis-2.6.13-1.el6.remi.x86_64.rpm
  tasks:
    - name: install redis packages
      action: yum name=$item state=installed
      with_items:
        - ${libunwind}
        - ${libunwind_devel}
        - ${gperftools_libs}
        - ${redis}

    - name: start redis
      action: service name=redis state=started enabled=yes

上記の例では dbserver.yml を実行すると include されている mysql.yml と redis.yml も実行されます。

Role

Roles
http://docs.ansible.com/ansible/latest/playbooks_reuse_roles.html

include を更に便利にしたような仕組みとして Role というものが用意されています。
Role ではサーバの役割毎にディレクトリを分け、更にその下にセクション毎にディレクトリを分けた構造で Playbook を管理します。

シンプルな例だと以下のような感じ。

mysql
├─hosts
├─site.yml
└─roles
    └─mysql
        ├─handlers
        │  └─main.yml
        ├─tasks
        │  └─main.yml
        ├─templates
        │  └─my.cnf.j2
        └─vars
            └─main.yml

Role を使用すると以下のようなメリットがあります。

  • roles/x/tasks/main.yml が存在すれば自動的に読み込まれる
  • roles/x/handlers/main.yml が存在すれば自動的に読み込まれる
  • roles/x/vars/main.yml が存在すれば自動的に読み込まれる
  • copy タスクで roles/x/files/ 以下のファイルはパスを指定せずに参照可能
  • script タスクで roles/x/files/ 以下のファイルはパスを指定せずに参照可能
  • template タスクで roles/x/templates/ 以下のファイルはパスを指定せずに参照可能

要するに、推奨されている規約に従ってディレクトリ構造を作成しておけば自動的にファイルが include される仕組みです。

ansible-examples が Role を使った構造になっているので、実際に使用する際には大変参考になります。

ansible/ansible-examples
https://github.com/ansible/ansible-examples

Playbook のデバッグ方法

Playbook が上手く動作してくれない、という時に原因を調べる方法は以下のようにいくつかあります。

「--syntax-check」オプションを指定して実行してみる

YAML ファイルの文法チェックができます。

$ ansible-playbook dbserver.yml --syntax-check
Playbook Syntax is fine
「-C, --check」オプション付きで実行してみる

いわゆる dry run です。
実際には対象サーバは変更されません。
ただ、/var/log/messages にはログは出力されます。

$ ansible-playbook dbserver.yml -i hosts -k -C
「-vvv」オプションを指定する

ansible コマンドの時と同じで、コンソールに実行時の詳細なメッセージが表示されます。

$ ansible-playbook dbserver.yml -i hosts -k -vvv
対象サーバの /var/log/messages を確認する

ansible によって何が実行されているかがログに出力されています。

# tail -f /var/log/messages
Aug 13 11:05:12 centos6 ansible-setup: Invoked with filter=*
Aug 13 11:05:12 centos6 ansible-yum: Invoked with name=mysql,mysql-server,mysql-devel list=None disable_gpg_check=False conf_file=None state=installed disablerepo=None enablerepo=None
Aug 13 11:05:30 centos6 yum[12936]: Installed: mysql-5.1.69-1.el6_4.x86_64
Aug 13 11:05:42 centos6 yum[13057]: Installed: mysql-server-5.1.69-1.el6_4.x86_64
Aug 13 11:05:51 centos6 yum[13185]: Installed: mysql-devel-5.1.69-1.el6_4.x86_64
「--step」オプション付きで実行してみる

task を一つずつステップ実行することが出来ます。

$ ansible-playbook dbserver.yml -i hosts -k --step
debug モジュールを使う

debug - Print statements during execution
http://docs.ansible.com/ansible/latest/debug_module.html

以下のような感じで使います。
任意のメッセージや変数の値をコンソールに出力して確認することが出来ます。

---
- hosts: all
  user: root
  tasks:
    - command: cat /etc/redhat-release
      register: rhel_version
    - debug: msg="{{ rhel_version.stdout }}"

まとめ

Ansible の Playbook にはこの他にもまだまだ沢山の便利な機能があります。
実際に作成する際には以下のドキュメントが非常に役に立ちました。

Playbooks
http://docs.ansible.com/ansible/latest/playbooks.html

Playbooks: Special Topics
http://docs.ansible.com/ansible/latest/playbooks_special_topics.html

また、どう書くか迷った時には ansible-examples にある Playbook や、その他にも GitHub で Playbook を公開されている方が結構いるので、それらを参考にさせて貰う事で大体なんとかなります。

というわけで、私も以下で自作の Playbook の公開を始めてみました。

akishin/ansible-playbooks
https://github.com/akishin/ansible-playbooks

まだまだ数も少ないですが、今後少しずつ増やしていきたいと思います。
実際に Playbook を作成される際に少しでも参考になれば嬉しいです。