使えばきっと一目置かれるAnsibleのちょっと高度な機能


こちらは [Ansible Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ansible) の14日目の記事です。

# はじめに
普段から構成管理にはAnsibleを使用していて、他にも作業の自動化でもガッツリ使っていますが、その際に使用して重宝したちょっと高度な機能と簡単な例を紹介したいと思います。

# 概要
以下の機能を紹介します。そんなの知ってるぜというかたはすっ飛ばしてください。
1. [debuggerでデバッグ](#debuggerでタスクのデバッグ)
2. [block,rescueでエラーハンドリング](#block,rescueディレクティブでエラーハンドリングを実装)
3. [untilでリトライ機能の実装](#untilでリトライ機能の実装)
4. [run_once,delegate_toでローカル実行](#run_once,delegate_toでローカル実行)


# `debugger`でタスクのデバッグ
`debugger`で任意のタスクをデバッグすることができます。(Ansible2.5以降)

```
---
- name: debug test
  hosts: localhost
  connection: local
  debugger: on_failed
  tasks:
    - name: task1
      vars:
        var1: value1
      shell: |
        true

    - name: task2
      vars:
        var2: value2
      shell: |
        false
```

実際に実行した結果がこちら。

```
PLAY [debug test] **********************************************************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************************************************
Sunday 09 December 2018  20:18:57 +0900 (0:00:00.148)       0:00:00.148 *******
ok: [localhost]

TASK [task1] ***************************************************************************************************************************************************************************
Sunday 09 December 2018  20:18:58 +0900 (0:00:01.064)       0:00:01.213 *******
changed: [localhost]

TASK [task2] ***************************************************************************************************************************************************************************
Sunday 09 December 2018  20:18:59 +0900 (0:00:00.435)       0:00:01.649 *******
fatal: [localhost]: FAILED! => {"changed": true, "cmd": "false", "delta": "0:00:00.010900", "end": "2018-12-09 20:18:59.635267", "msg": "non-zero return code", "rc": 1, "start": "2018-12-09 20:18:59.624367", "stderr": "", "stderr_lines": [], "stdout": "", "stdout_lines": []}
[localhost] TASK: task2 (debug)> p task
TASK: task2
[localhost] TASK: task2 (debug)> p task.args
{'_ansible_check_mode': False,
 '_ansible_debug': False,
 '_ansible_diff': False,
 '_ansible_keep_remote_files': False,
 '_ansible_module_name': 'command',
 '_ansible_no_log': False,
 '_ansible_remote_tmp': u'~/.ansible/tmp',
 '_ansible_selinux_special_fs': ['fuse', 'nfs', 'vboxsf', 'ramfs', '9p'],
 '_ansible_shell_executable': u'/bin/sh',
 '_ansible_socket': None,
 '_ansible_syslog_facility': u'LOG_USER',
 '_ansible_tmpdir': None,
 '_ansible_verbosity': 0,
 '_ansible_version': '2.7.4',
 u'_raw_params': u'false',
 '_uses_shell': True,
 'warn': True}
[localhost] TASK: task2 (debug)> p task_vars['var2']
u'value2'
[localhost] TASK: task2 (debug)> c

PLAY RECAP *****************************************************************************************************************************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=1

Sunday 09 December 2018  20:19:41 +0900 (0:00:42.096)       0:00:43.745 *******
===============================================================================
shell ------------------------------------------------------------------ 42.53s
setup ------------------------------------------------------------------- 1.06s
total ------------------------------------------------------------------ 43.60s
```

`task2`でfailedになったところで`debugger`が起動しています。
また`debugger`には以下の機能があります。

- `p task`でデバッグ対象のタスクを出力
- `p task_args`で引数を出力
- `p task_vars[]`で変数の値を出力
- `r`でtaskを再実行
- `c`で後続タスクを続行
- `q`で終了


# `block`,`rescue`ディレクティブでエラーハンドリング
通常作業用のtaskを`block`で定義し、その中で失敗した場合にのみ実行したいｔaskを`rescue`で定義します。
この場合、`migrate task1`,`migrate task2`のいずれかが失敗した場合にのみ`failback task`が実行されます。
すべて成功した場合は`failback task`は実行されません。
`always`ディレクティブを使えば結果にかかわらず実行させるタスクを定義できます。

またblock単位で`become`や`when`ができるのでコード全体の見通しも良くなります。

```
---
- name: example block,rescue
  block:
    - name: migrate task1
      shell: |
        echo "exec migrate task1"

    - name: migrate task2
      shell: |
        echo "exec migrate task2"
  become: yes
  when: ansible_distribution == "centos"

  rescue:
    - name: failback task
      shell: |
        echo "exec failback task"

  always:
    - name: always task
      shell: |
        echo "exec always task"
```

# `prompt`モジュールで入力待ち
作業は自動化したい。でも作業途中の確認は目視で確認したい、実行のタイミングを計りたい場合ってありますよね。問題なければ`yes`を入力、みたいな。
`prompt`モジュールを使えばプロンプトを出して標準入力を受け取れます。

```
---
- name: wait for prompt
  pause: prompt="please input y"
  register: yn
- name: exit, if not y
  fail: msg="Aborted!!"
  when: yn.user_input != 'y'
  run_once: true
```

# `until`でリトライ機能の実装
`until`で条件をつければ条件がTrueになるまでリトライさせることが出来ます。`retries`はリトライする回数、`delay`はリトライのインターバルです。
また`register`で取得した実行結果をそのまま条件に使えます。
この場合は`show slave status`の結果でレプリケーションの遅延やエラーがなくなるまで1秒おき3回までリトライします。

```
---
- name: show slave status
  mysql_replication:
    mode: getslave
    login_host: 127.0.0.1
    login_port: 3306
    login_user: root
  register: result_getslave_before
  until:
    - result_getslave_before.Slave_IO_Running == "Yes"
    - result_getslave_before.Slave_SQL_Running == "Yes"
    - result_getslave_before.Last_IO_Errno == 0
    - result_getslave_before.Last_SQL_Errno == 0
    - result_getslave_before.Slave_IO_State == "Waiting for master to send event"
    - '"Slave has read all relay log; waiting for" in result_getslave_before.Slave_SQL_Running_State'
  retries: 3
  delay: 1
      
```

# `run_once`,`delegate_to`でローカル実行
`hosts`で複数のサーバやグループを指定しているけどローカルで1回だけ実行したいタスクがある場合に有効です。
この場合は複数のアプリケーションサーバでDBへのログインチェックを実行しますが、その前の`vault`からのパスワード取得はローカルで1回のみ実行しています。
注意点として`become`も継承されるので必要に応じて変更する必要があります。

```
---
- name: db login check
  hosts: "{{ env }}-app-servers"
  become: yes
  vars_files: ["./{{ env }}-vars.yml"]
  tasks:
    - name: get password form hashi_vault
      debug:
        msg: "{{ lookup('hashi_vault', 'secret=secret/{{vault_secret}}: url=http://127.0.0.1:8200')}}"
      register: result_hashi_vault
      delegate_to: 127.0.0.1
      become: no
      run_once: true
      no_log: yes

    - name: check db connect from app-servers
      shell: |
        mysql -u user database -p'{{ result_hashi_vault.msg.user }}' -h 127.0.0.1 -P 3306 -e "show variables like 'version'"
      register: result_mysql_app_to_cloudsql
```


#　おわりに
自分が実際に使って重宝した機能を書いてみましたが、実はここで紹介した機能は全て[Advanced Playbooks Features](https://docs.ansible.com/ansible/2.6/user_guide/playbooks_special_topics.html)で紹介されています。
特に`debugger`はこの記事を書くときに初めて知ったのですが、引数、変数のダンプが個人的にはめちゃめちゃ便利で開発が捗りそうだと思いました。
使える機能はしっかり使って効率よく開発したいですね！