Ansible:さらにPlaybookをきわめる

2016年7月12日(火)
安久 隼人
前回に続いて、Ansibleのより実際的なPlaybookの記述方法を紹介します

本連載の第4回、第5回では「Ansible応用編」と題して、Ansibleの応用的な使い方やTipsについて詳しく解説していきます。前回(第4回)は、Ansibleのベストプラクティスに沿ってPlaybookを再整理する方法を紹介しました。今回は、Ansibleの持つ各種機能を活かしたPlaybookの効率的な記述方法やつまずきやすいポイントの回避方法を紹介します。

Playbookを効率的に書く

同種の処理をまとめる

同じ処理を何度も実行する際には、Loop構文を使うことで処理をまとめて記述できます。処理をまとめることで記述を簡潔にし、Playbookのメンテナンス性を高めることができます。

基本的なLoop構文

基本的なLoopの記述方法は、以下のようになります。「with_items」アトリビュートに、YAML記法のシーケンス(配列に相当)形式で定義しておくと、実行時に各要素が順次展開され実行されます。

リスト1:Loopの記述例

- name: echo tasks
  command: echo "{{ item }}"
  with_items:
    - foo
    - bar
    - baz

また、yumモジュールやaptモジュールは、「with_items」アトリビュートで指定した複数パッケージを1トランザクションにまとめて適用するため、処理の高速化にも寄与します。

さらに、以下のようにYAML記法のマッピング(ハッシュや連想配列に相当)として定義し、各要素にアクセスすることも可能です。

リスト2:マッピングとして定義した要素へのアクセス

- name: add user
  user: name={{ item.name }} groups={{ item.groups }}
  with_items:
    - { name: 'user1', groups: 'wheel' }
    - { name: 'user2', groups: 'root' }

以下のように、変数を用いて処理することも可能です。

リスト3:変数を用いたアクセス

- hosts: all

  vars:
    users:
      satoh:
        directory: /home/satoh
        shell: /bin/bash
        groups: developers
      tanaka:
        directory: /home/tanaka
        shell: /bin/zsh
        groups: developers
  become: yes
  tasks:
  - name: "user add"
    user: name={{ item.key }} shell={{ item.value.shell }} groups={{item.value.groups}}
    with_dict: "{{ users }}"

コマンド結果を使った Loop 構文

以下のようにコマンドの実行結果を元にLoop処理を行うことで、動的なリストに対して処理を行うことも可能です。例示したPlaybookは、「/opt以下にある拡張子が『.dat』のファイルのパーミッションを全て『600』の状態にする」ものです。「.dat」ファイルが増減した場合にも、状態が管理され続ける動的なPlaybookとなっています。

リスト4:動的な処理の実例

- hosts: all

  tasks:
  - name: Assign command output as variable
    shell: find /opt -type f -name "*.dat"
    register: command_result

  - name: Change permission each "*.dat" files
    file: path={{ item }} mode=600
    with_items: "{{ command_result.stdout_lines }}"

本稿で挙げた以外にも、Ansibleでは様々なLoop処理の記述が可能です。その他のLoop構文に関しては Ansible 公式ドキュメントの「Loops」の項を参照ください。

条件に応じて処理を切り替える

様々な条件に応じて処理を切り替えることで、より複雑な処理を記述できるようになります。

変数による条件分岐

管理対象の状態や変数の内容を条件に、次に実行するタスクを制御する際には、「when」アトリビュートを利用します。以下の例は、管理対象がCentOSの場合はyumを、Debianの場合はaptを使ってパッケージ管理を行う例です。

リスト5:変数による分岐の例

- hosts: all

  tasks:
  - name: install package by yum
    yum: name=vim state=latest
    when: ansible_os_family == 'CentOS'

  - name: install package by apt
    apt: name=vim state=latest
    when: ansible_os_family == 'Debian'

コマンド結果による条件分岐

「変数による条件分岐」の応用として、コマンドの実行結果を条件に、その結果に応じて次に実行するタスクを制御することができます。

以下の例では最初のコマンド実行が成功すると「success」かつ「changed」の結果となり、実行に失敗すると「failed」の結果に遷移します。また遷移しないステータスは「skipped」としてパスされます。ステータス以外にも、標準出力の結果を条件とすることも可能です。

リスト6:コマンド結果による分岐の例

- hosts: all

  tasks:
  - name: exec command
  command: /bin/true
  register: result
  ignore_errors: True

  - name: exec success
    debug: msg="echo success"
    when: result|success

  - name: exec changed
  debug: msg="echo changed"
  when: result|changed

  - name: exec skipped
  debug: msg="echo skipped"
  when: result|skipped

  - name: exec failed
  debug: msg="echo failed"
  when: result|failed"

Taskの終了状態による条件分岐

Taskの結果が「Changed」になることを条件として、実行される後処理を入れたい場合には、以下の例のように「notify」と「handler」を組み合わせて記述します。

Taskが「Changed」で終了すると、一連のPlaybookの最後にnotifyで指定した名前のTaskが実行されます。その際、同じhandlerタスクが複数回呼ばれたとしても、全てまとめて1度だけ実行されます。例えば、設定反映のための再起動をnotifyで指定したTaskが複数回実行さても、実際に再起動が行われるのは1度だけ、という制御が可能になります。

また、Playbookが途中で終了した場合、デフォルトではhandlerタスクは実行されませんが、

  • コマンドに「--force-handlers」オプションをつけて実行
  • Playbook内で、「force_handlers: True」と指定
  • ansible.cfg設定ファイル内で「force_handlers = True」と設定

上記いずれかを実施しておくと、Playbookが途中で異常終了しても、その時点までにnotifyで指定を受けたhandlerタスクだけは、最後に実行できます。

リスト7:Taskの終了状態による分岐の例

- hosts: all

  tasks:
  - name: exec command(always change)
  command: /bin/true
  notify: handler

  handlers:
  - name: handler
  debug: msg="handler"

グルーピングした処理の終了状態による条件分岐

Block構文を使うことで、個別のTaskをひとまとめのグループとして扱うことができます。グループ化したTask内のいずれかでエラーが発生した場合に実行される処理を「rescue」内に、Block内Taskの成否に関わらず常に実行される後処理を「always」内に記述することができます。

リスト8:Block構文の使用例

- hosts: all

  tasks:
  - block:
    - name: install tomcat7
    yum: pkg={{ item }} state=installed
    with_items:
      - java-1.7.0-openjdk
      - tomcat7

    - name: configure tomcat settings
      template: src=tomcat7.conf.j2 dest=/etc/tomcat7/tomcat7.conf

    - name: start tomcat7
      service: name=tomcat7 state=started enabled=yes

    rescue:
      - name: exec when error happened in block.
        debug: msg="error happened."

    always:
      - name: exec always.
        debug: msg="always execute."

管理対象を効率的に管理する

管理対象の増加や改廃などによって、inventory fileの見通しが悪くなり、管理性が低下します。inventory fileも以下のようにすることで記述を簡潔にし、管理性を高めることが可能となります。

ルールに基づいた管理

IPアドレスや、ホスト名が英数字の連番で構成されている場合には以下のように[begin:end:step]の書式でまとめて記述することが可能です。「begin」には開始の数字又は英字を、「end」には終了を、そして「step」にはカウントアップの単位を指定します。「step」は省略可能で、その場合は1ずつカウントアップされることになります。

リスト9:ルールによる管理の例

[webservers]
web-[1-9:1]
[apservers]
app-[01:99:1] 192.168.[0:4:1].[1:30:1]
[dbservers]
db-[a-Z:1]

外部ソースに基づいた管理

管理対照の情報を外部で管理している場合、Dynamic Inventory機能を利用することで外部ソースの情報を元にした動的なinventory情報を利用できます。

以下は、OpenStackをデータソースにしたDynamic Inventoryの例です。まず必要なソフトウェアの導入と管理対象の設定を行います。今回は管理対象にTagを振ることで、Playbookの適用先を特定する環境を想定します。

まず、必要なソフトウェアの導入と事前準備を行います。

リスト10:外部ソースによる管理の例(準備)

# 環境準備
$ sudo yum install -y python-pip python-devel gcc
$ sudo pip install os_client_config shade
$ curl -O https://raw.githubusercontent.com/ansible/ansible/devel/contrib/inventory/openstack.py
$ chmod +x openstack.py

# Tagの付与
# (実際にはインスタンス作成時やHeat Template等に自動でTagが振られるようにしておきます)

$ openstack server list -c ID -c Name -c Properties --long
+--------------------------------------+---------------------+------------+
| ID                                   | Name                | Properties |
+--------------------------------------+---------------------+------------+
| 949639ae-4ab0-447d-b559-f484040353b8 | db-instance-01      |            |
| f17af9bf-50cf-4243-b2c1-150024d2be3b | web-instance-01     |            |
| 435f8643-d0ff-4708-b5e7-6c0c9b0bfb85 | app-instance-01     |            |
| 1fbf91a4-b9a9-42d5-a350-231509058e81 | bastion-instance-01 |            |
+--------------------------------------+---------------------+------------+

$ openstack server set --property tags=db 949639ae-4ab0-447d-b559-f484040353b8
$ openstack server set --property tags=web f17af9bf-50cf-4243-b2c1-150024d2be3b
$ openstack server set --property tags=app 435f8643-d0ff-4708-b5e7-6c0c9b0bfb85
$ openstack server set --property tags=app 1fbf91a4-b9a9-42d5-a350-231509058e81

$ openstack server list -c ID -c Name -c Properties --long
+--------------------------------------+---------------------+---------------+
| ID                                   | Name                | Properties    |
+--------------------------------------+---------------------+---------------+
| 949639ae-4ab0-447d-b559-f484040353b8 | db-instance-01      | tags='db'     |
| f17af9bf-50cf-4243-b2c1-150024d2be3b | web-instance-01     | tags='web'    |
| 435f8643-d0ff-4708-b5e7-6c0c9b0bfb85 | app-instance-01     | tags='app'    |
| 1fbf91a4-b9a9-42d5-a350-231509058e81 | app-instance-02     | tags='app'    |
+--------------------------------------+---------------------+---------------+

ソフトウェアの導入結果は、以下のコマンドで確認します。

リスト11:実効結果の確認

$ ./openstack.py --list

準備が整ったら、以下のように実行します。

リスト12:外部ソースによる管理の実行例

# 全体に実行する場合
$ ansible -i openstack.py all -m ping
# tags=appの対象にのみ実行する場合
$ ansible -i openstack.py meta-tags_app -m ping

今回例示したOpenStack以外にも、AWSやCobblerに対応したDynamic Inventory用スクリプトも公開されています。詳細はAnsible公式ドキュメントの「Dynamic Inventory」の項を参照してください。また、必要に応じて独自に作成することで、対応データソースの拡充も可能です。

このように、Dynamic Inventory機能を利用することで、管理対照の情報を多重管理する必要がなくなりますし、管理対象の頻繁な改廃にも容易に対応可能となります。

デバッグを効率的に行う

Playbook実行前のチェック

事前に各種チェックを行うことで、Playbookを実行する前に不具合に気付けるようになります。

構文チェック

ansibleコマンドに「--syntax-check」オプションをつけて以下のように実行すると、Playbookの構文をチェックできます(チェックのみで、Playbookの実行はされません)。Playbookを新規作成した際や修正を行った際には、必ずチェックすることをお勧めします。

リスト13:Playbookの構文をチェックするオプション

ansible-playbook -i <hosts> <playbook.yml> --syntax-check

Task一覧のチェック

ansible コマンドに「--list-tasks」オプションをつけて以下のように実行すると、実行されるTaskとTagの一覧が表示されます。事前にTask一覧を確認し、想定するTaskがリストアップされているか、想定外のTaskが実行されることがないか、あらかじめ確認しておきます。

リスト14:Task一覧を確認するオプション

ansible-playbook -i <hosts> <playbook.yml> --list-tasks

実行時影響のチェック(Dry Run)

ansibleコマンドに「--check」オプションをつけて以下のように実行すると、対象への変更は行わずに、Playbookを適用した結果起きる変化を確認できます。

さらに併せて「--diff」オプションも指定すると、テンプレートによってリモートファイルにどのような変更が行われるかも事前に確認できるようになります。

ただし、コマンドは実際には実行されませんので、対象の状態変化を前提にした後続タスクや、結果を変数に格納しての処理は正常に実施されない点には注意が必要です。

リスト15:Playbookを適用した結果を確認するオプション

ansible-playbook -i <hosts> <playbook.yml> --check --diff

Playbook実行中のチェックを行う

debug モジュールを使ったチェック

debugモジュールを使うことで、以下のようにPlaybook中で任意のメッセージを表示したり、変数に代入された値を確認したりできます。

リスト16:debugモジュール

- hosts: all

  tasks:
    - name: exec uptime
      command: /usr/bin/uptime
      register: result

    - debug: var=result

    - debug: msg="debug message"

上記のPlaybookを実行すると以下のように結果が表示され、変数に代入された値が確認できます。

リスト17:debugモジュールの使用例

PLAY [all] *********************************************************************

TASK [exec uptime] *************************************************************
changed: [127.0.0.1]

TASK [debug] *******************************************************************
ok: [127.0.0.1] => {
    "result": {
        "changed": true, "cmd": [
            "/usr/bin/uptime"
        ],
        "delta": "0:00:00.001574",
        "end": "2016-06-07 06:56:29.955837",
        "rc": 0,
        "start": "2016-06-07 06:56:29.954263",
        "stderr": "",
        "stdout": " 06:56:29 up 5 days, 4:45, 1 user, load average: 0.00, 0.01, 0.05",
        "stdout_lines": [
            " 06:56:29 up 5 days, 4:45, 1 user, load average: 0.00, 0.01, 0.05"
        ],
        "warnings": []
    }
}

TASK [debug] *******************************************************************
ok: [127.0.0.1] => {
    "msg": "debug message"
}

PLAY RECAP *********************************************************************
127.0.0.1       : ok=3  changed=1       unreachable=0   failed=0"

verboseモードを使ったチェック

Playbook実行時に「-v」オプションをつけることで、管理対象の状態やPlaybookの実行状況をより詳細に確認することができます。さらに「-vvv」オプションをつけると、より詳細にSSH接続の状況なども確認できるようになります。

リスト18:「-v」オプションをつけた実行例

PLAY [all] *********************************************************************

TASK [exec uptime] *************************************************************

changed: [127.0.0.1] => {"changed": true, "cmd": ["/usr/bin/uptime"], "delta": "0:00:00.001550", "end":

TASK [debug] *******************************************************************
ok: [127.0.0.1] => {
    "result": {
        "changed": true, "cmd": [
            "/usr/bin/uptime"
        ],
        "delta": "0:00:00.001550",
        "end": "2016-06-07 07:04:14.418680",
        "rc": 0,
        "start": "2016-06-07 07:04:14.417130",
        "stderr": "",
        "stdout": " 07:04:14 up 5 days, 4:52, 1 user, load average: 0.00, 0.01, 0.05",
        "stdout_lines": [
            " 07:04:14 up 5 days, 4:52, 1 user, load average: 0.00, 0.01, 0.05"
        ],
        "warnings": []
    }
}

TASK [debug] *******************************************************************
ok: [127.0.0.1] => {
    "msg": "debug message"
}

PLAY RECAP *********************************************************************
127.0.0.1       : ok=3  changed=1       unreachable=0   failed=0

リスト19:「-vvv」オプションをつけた実行例

PLAYBOOK: debug_module.yml *****************************************************
1 plays in debug_module.yml

PLAY [all] *********************************************************************

TASK [exec uptime] *************************************************************
task path: /home/centos/debug_module.yml:4
<127.0.0.1> ESTABLISH SSH CONNECTION FOR USER: None
<127.0.0.1> SSH: EXEC ssh -C -q -o ControlMaster=auto -o ControlPersist=3600s –o StrictHostKeyCheckin changed: [127.0.0.1] => {"changed": true, "cmd": ["/usr/bin/uptime"], "delta": "0:00:00.001628", "end":

TASK [debug] *******************************************************************
task path: /home/centos/debug_module.yml:8
ok: [127.0.0.1] => {
    "result": {
        "changed": true, "cmd": [
            "/usr/bin/uptime"
        ],
        "delta": "0:00:00.001628",
        "end": "2016-06-07 07:05:57.062392",
        "rc": 0,
        "start": "2016-06-07 07:05:57.060764",
        "stderr": "",
        "stdout": " 07:05:57 up 5 days, 4:54, 1 user, load average: 0.00, 0.01, 0.05"
        "stdout_lines": [
            " 07:05:57 up 5 days, 4:54, 1 user, load average: 0.00, 0.01, 0.05"
        ],
        "warnings": []
    }
}

TASK [debug] *******************************************************************
task path: /home/centos/debug_module.yml:10
ok: [127.0.0.1] => {
    "msg": "debug message"
}

PLAY RECAP *********************************************************************
127.0.0.1       : ok=3  changed=1       unreachable=0   failed=0"

指定タスクの実行

あるTaskの実行を試したいときには、「--tags」オプションを使い下記のように実行することで、特定のtagのついたタスクのみの実行が可能です。

リスト20:特定のTaskのみを指定して実行

ansible-playbook -i <hosts> <playbook.yml> --tags <tag_name>

tagは以下のように各タスクや、Blockに対して「tags」アトリビュートを使い任意の数だけ設定することができます。

リスト21:複数のTaskも設定可能

- hosts: all

  tasks:
  - block:
    - name: install tomcat7
      yum: pkg={{ item }} state=installed
      with_items:
        - java-1.7.0-openjdk
        - tomcat7
      tags:
        - setup

    - name: configure tomcat settings
      template: src=tomcat7.conf.j2 dest=/etc/tomcat7/tomcat7.conf
      tags:
        - configure

    - name: start tomcat7
      service: name=tomcat7 state=started enabled=yes
      tags:
        - setup
  tags:
    - tomcat

  rescue:
    - name: exec when error happened in block.
      debug: msg="error happened."

  always:
    - name: exec always.
      debug: msg="always execute."

指定タスク以降の実行

Playbook実行中のある特定Taskの処理が失敗した場合には、「--start-at」オプションを使って指定したTask以降の全てを再実行できます。

リスト22:あるタスク以降を再実行するオプション

ansible-playbook -i <hosts> <playbook.yml> --start-at ’<task_name>’

もし、一連のタスク内の一部だけをピックアップして実行したい場合には「--step」オプションも併せて実行し、不要なタスクはスキップさせるという使い方も可能です。

その他のTips

Proxy経由でのネットワーク接続を行う

実行環境によっては、インターネットアクセスのためにProxy設定が必要な場合があります。Ansibleでは「environment」アトリビュートで環境変数が指定できるため、下記の例のようにすることでProxyの設定ができます。

環境によってProxy設定の有無を使い分けたい場合は、proxy_envに空白を指定しておけばProxy設定なしで接続されます。

リスト23:Proxyを経由したネットワーク接続

- hosts: all

  vars:
    proxy_env:
      http_proxy: http://proxy.example.com:8080
      https_proxy: https://proxy.example.com:8080

  tasks:
    - yum: name=httpd state=latest
      environment: "{{proxy_env}}"

一部タスクを別ホストで実行する

「delegate_to」アトリビュートを使うことで、指定したタスクを別ホスト上で実行できます。この機能は、一連の処理の中に特定ホストのみでしか実行できないTaskがある場合などに有効です。以下の例は、OpenStackのLBaaS配下のサーバーをアップデートするもので、OpenStackサーバーへの通信やコマンド実行が特定のホストでのみ可能なケースを想定し、次の手順を実行しています。

  • LBの負荷分散グループから一部のサーバーを離脱
  • Webサーバーのアップデート
  • アップデート完了後負荷分散グループへ再度追加

リスト24:一部タスクの分散実行

- hosts: webservers
  serial: "30%"

  vars:
    ops-cli-server: 10.0.0.1
    pool-name: "demo-pool01"
    protocol-port: 8080

  tasks:
  - name: take out of balancing pool
    command: neutron lb-member-delete {{ ansible_default_ipv4.address }} delegate_to: {{ ops-cli-server }}

  - name: update server
    yum: name=* state=latest

  - name: add to balancing member
    command: neutron lb-member-create --address {{ ansible_default_ipv4.address }} --protocol-port delegate_to: {{ ops-cli-server }}

また今回の例のように、負荷分散グループやクラスタ内の一部を順にアップデートしていく処理などの場合、一度に全台を更新するのではなく一部を順にアップデートすることで、サービスダウンを防ぎつつ処理を進められます。「serial」アトリビュートを指定することで同時に処理する台数や、割合を指定して実行することが可能となります。Task実行先に自サーバーを指定する際は「local_action」アトリビュートで代替することも可能です。より詳しい情報を知りたい場合はAnsible公式ドキュメント、「Delegation」の項を参照ください。

再起動からの復帰が完了するまで待機する

変更処理のなかには、Kernelのアップデートなどのように、管理対象が再起動されるまで反映されないものがあります。後続のタスクが、反映後の状態を前提とする場合には管理対象の再起動を行ったうえで復帰を待ち、後続タスクを再開する必要があります。こうした場合には、以下の例のようにローカルモードと「wait_for」モジュールを組み合わせることで管理対象の復帰状況を定期的に確認し、管理対象の再起動を待って後続のタスクを継続させることが容易になります。

リスト25:再起動が必要な処理の実行例

- hosts: all

  tasks:
  - name: reboot server
    command: shutdown -r now
    become: true
    async: 0
    poll: 0

  - name: wait for shutdown
    local_action: |
      wait_for host={{ inventory_hostname }}
      state=stopped

  - name: wait for start
    local_action: |
      wait_for host={{ inventory_hostname }}
      state=started"

機密情報を暗号化する

Playbookを書いていると、認証情報やAPIキーなど平文で保存したくない機密情報の扱いに困ることがあります。Ansibleでは「Vault」機能を使うことで、指定のファイルを暗号化し保存できます。ファイルを暗号化するには、以下のようにansible-vaultコマンドを実行します。

リスト26:情報の暗号化

ansible-vault create secret.yml
Vault password:
Confirm Vault password:
Encryption successful

このように暗号化しておくことで、機密情報も含めてソースコード管理システムで管理できるようになるといったメリットも得られます。

暗号化したファイルを使うには、以下のように実行時にパスワードを入力します。

リスト27:暗号化したファイルを使用する

ansible-playbook -i hosts site.yml --ask-vault-pass Vault password:

また、暗号化したファイルを再度編集するには、一度復号して修正したのち再度暗号化する必要があります。当然のことながら、暗号化するとどんな変数が定義されているのか復号するまで分からなくなります。Vaultはファイル単位で暗号化するので、暗号化したい内容と暗号化不要な内容はファイルを分けて管理するのがお勧めです。

第4回で紹介したベストプラクティスに則った構成をとると、inventoryファイルに対応したディレクトリ配下の全ファイルをvarsファイルとして読み込むため、以下のようにファイルを分けて配置しておけば、public_settings.ymlは暗号化不要な内容を平文で記述し、private_settings.ymlには機密情報を記述し暗号化するという使い分けができます。

  • group_vars/<group_name>/public_settings.yml
  • group_vars/<group_name>/private_settings.yml

実行を高速化する

初期設定のままAnsibleを利用していると、Playbookの大規模化や管理対象の増加に伴いPlaybookの実行にかかる時間も長くなってしまいます。Ansibleでは各種設定を工夫することで、処理時間を短縮することが可能です。

並列実行数の設定(forks)

Ansibleでは、同じグループに対する処理を並列実行します。初期状態では5並列となっていますが、これを増やすことで並列度を増やすことができます。並列度の設定は、以下の箇所で指定可能です

  • ANSIBLE_CONFIG(環境変数)
  • ansible.cfg(カレントディレクトリ)
  • .ansible.cfg(ホームディレクトリ)
  • /etc/ansible/ansible.cfg
  • 実行時のコマンド引数

また、並列実行は同じグループ内にのみ適用される点には注意が必要です。例えば以下のようなinventoryファイルを利用すると、webserversに対する処理が3並列で実行されたのち、dbserversに対する処理が2並列で実行されるという挙動になります。

リスト28:並列実行は同グループ内のみに適用される

[webservers]
192.168.0.10
192.168.0.11
192.168.0.12
[dbservers]
192.168.0.20
192.168.0.21

SSH接続の高速化

Ansibleでは、SSHを利用してタスクごとに管理対象に接続し処理を行います。そのため、SSH接続のオーバーヘッド低減は高速化に非常に有効です。

・ControlMaster

OpenSSHは、最初に接続した1つのセッションを複数セッション間で共有できる「ControlMaster」という機能を持っています。この機能を使うことで1度確立されたセッションを再利用し、使いまわすことができます。Ansibleではタスク実行ごとにSSH接続を行いますが、この機能を使うことでセッション確立のためのオーバーヘッドを低減しPlaybook実行全体での処理時間を削減することができます。

この機能を使うには、ansible.cfg設定ファイルの[ssh_connection]ディレクティブに、以下の設定を追加します。

リスト29:ControlMaster機能利用時の設定

ssh_args = -o ControlMaster=auto -o ControlPersist=3600s

ControlPersistには、セッションを維持し続ける時間を指定します。

・Pipelining

Ansibleでは通常、「一時ディレクトリに各タスク用の実行スクリプトを生成し、それを管理対象へsftp又はscpで転送したのちに、再度SSH接続してそのスクリプトを実行する」という処理がタスクごとに行われています。

Pipeliningを有効化すると、スクリプトファイルの転送処理は行わず、実行するスクリプトの内容を標準入力としてSSHでリモート実行するようになります。こうすることで、SSH接続処理とファイル転送処理の時間の分処理時間を削減できます。この機能を使うには、ansible.cfg設定ファイルの[ssh_connection]ディレクティブに、以下の設定を追加して、有効化します。

リスト30:Pipelining機能利用時の設定

pipelining = True

Gathering factsの省略

Ansibleでは、Playbook実行前にまずは管理対象の情報を収集しています。これをGathering facts処理と呼びますが、ここにも時間がかかっています。

もし、収集した情報が不要であればこの処理自体を止めることで高速化できます。ansible.cfg設定ファイルに以下の設定を追加することで、情報収集を無効化できます。

リスト31:Gathering facts処理を無効化する際の設定

gathering = explicit

ただし、無効化すると管理対象サーバーの情報を利用できなくなりますので、適用するPlaybookがansibleの取得した情報を利用していないかどうか確認のうえ、無効化するようにしてください。

対話処理を自動化する

自動化が難しい処理に、対話型の入力が必要なコマンドやインストーラーの実行があります。アプリケーションによっては、コマンドライン引数による指定やサイレントモードといった代替手段が提供されず、文字列をリダイレクトで渡してもうまく動作しないものがあります。そのような場合でも、Ansibleでは、「expect」モジュールを使うことで簡単に自動化できます。

以下では、MariaDBインストール後にセキュリティ対策実行する「mysql_secure_installation」コマンドを自動実行する方法を例に挙げて説明していきます。MariaDB Server 5.5にて動作を確認しています。また、管理対象サーバー側に「pexpect」python moduleの導入が必要となります。

リスト32:対話処理の自動化

- hosts: dbservers
  vars:
    password: "password"
  tasks:
    - name:"/usr/bin/mysql_secure_installation"
      expect:
        command: /usr/bin/mysql_secure_installation
        responses:
          "Enter current password for root \\(enter for none\\): " : ""
          "Set root password\\? \\[Y\\/n\\] " : "Y"
          "New password: " : "{{ password }}"
          "Re-enter new password: " : "{{ password }}"
          "Remove anonymous users\\?" : "y"
          "Disallow root login remotely\\?" : "y"
          "Remove test database and access to it\\?" : "y"
          "Reload privilege tables now\\?" : "y"

上記の例では、6 行目に「expect」モジュールの利用を宣言し、続く「command」アトリビュートで実行コマンドを指定しています。その後の「responses」ブロックでは

<対話型の待ち受けメッセージ(正規表現)> : <入力文字列>

の形で入力文字列を指定することで、対話型の待ち受けメッセージに応じた入力をエミュレートし、自動で処理が進みます。

expectによる自動化は、対話処理を自動化する非常に強力で便利なツールですが、コマンド文字列の応答タイミングや期待する応答内容が少し変更されるだけで、動作しなくなってしまう可能性があります。対話型の入力を必要とするコマンドと等価な処理を記述することなども検討したうえで、最終手段として使うことをお勧めします。

まとめと次回予告

今回は、AnsibleのPlaybookを記述するうえで、知っていると便利な実践的なTipsについて解説してまいりました。

Ansibleの持つ機能を活かすことで、様々な処理を効率的に記述できることが理解いただけたかと思います。次回は、Ansibleのテストにおける考え方や方法論を解説していきます。

新日鉄住金ソリューションズ
技術本部 システム研究開発センター システム基盤技術研究部 所属。クラウドサービスの開発やマルチクラウド時代のシステム構築・運用自動化に関する研究開発、OSSクラウド技術の評価検証等に従事。OSSコンソーシアム クラウド部会などでも活動中。

連載バックナンバー

運用・管理技術解説
第10回

Ansible Towerのクラウド連携機能による効率化を目指す

2017/9/26
Ansible Towerが備えるクラウド連携機能を用いることで、Playbookやユーザーの管理を効率良く行えることを紹介する。
運用・管理技術解説
第9回

Ansible Towerによる権限の管理

2017/7/27
複数のユーザーによる使用を前提としたAnsible Towerでの権限管理の方法を紹介する。
運用・管理
第8回

Ansibleの機能を拡張するAnsible Tower

2017/4/18
好評の連載「注目の構成管理ツールAnsibleを徹底活用する」の追加コンテンツとして、Ansible Towerを取り上げる。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています