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


こちらは Ansible Advent Calendar 2018 の14日目の記事です。

はじめに

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

概要

以下の機能を紹介します。そんなの知ってるぜというかたはすっ飛ばしてください。
1. debuggerでデバッグ
2. block,rescueでエラーハンドリング
3. untilでリトライ機能の実装
4. 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で定義し、その中で失敗した場合にのみ実行したいtaskをrescueで定義します。
この場合、migrate task1,migrate task2のいずれかが失敗した場合にのみfailback taskが実行されます。
すべて成功した場合はfailback taskは実行されません。
alwaysディレクティブを使えば結果にかかわらず実行させるタスクを定義できます。

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

---
- 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で紹介されています。
特にdebuggerはこの記事を書くときに初めて知ったのですが、引数、変数のダンプが個人的にはめちゃめちゃ便利で開発が捗りそうだと思いました。
使える機能はしっかり使って効率よく開発したいですね!