はじめに
Ansible Tower / AWX では、Playbook の実行ログがサーバー内に自動的に保存されます。 いつだれが実行してどうなったかを追跡するのに便利です。
そのログを、ファイルとしてダウンロードすることもできます。

数個ログだったらよいのですが、ワークフロー内にいくつもジョブがあり、ひとつひとつ実行結果画面を開いてダウンロード、開いてダウンロード、を繰り返すのは少しつらいかもしれません。
この記事では、ワークフロー実行ID を指定すると、そのワークフロー内の一連のジョブの実行ログをファイルとして保存する Playbook を紹介します。
ただし、試作レベルです。たとえば、ワークフローからワークフローを呼ぶケースには対応していません。
- 検証環境
- AWX 11.0
- Ansible 2.9.6
Playbook
さっそくですが、Playbook は以下のとおりです。
実現できそうな tower モジュールがなかったので、uri モジュールを利用します。
--- - hosts: tower gather_facts: false connection: local vars: wfjid: 600 # 対象のワークフロージョブID tower_username: testuser tower_password: testpass # 必要に応じて暗号化などする validate_certs: no tasks: # (1) ワークフロー内のノードを取得 - name: get workflow nodes uri: url: "https://{{ ansible_host }}/api/v2/workflow_jobs/{{ wfjid }}/workflow_nodes/" method: GET url_username: "{{ tower_username }}" url_password: "{{ tower_password }}" force_basic_auth: true validate_certs: false register: res_wf_nodes # (2) ワークフロージョブ内の各ジョブの実行ログを取得 - name: get job stdout uri: url: "https://{{ ansible_host }}/api/v2/jobs/{{ item.job }}/stdout/?format=txt" method: GET return_content: true url_username: "{{ tower_username }}" url_password: "{{ tower_password }}" force_basic_auth: true validate_certs: false register: res_job_stdouts loop: "{{ res_wf_nodes.json.results }}" loop_control: label: "{{ item.job }} - {{ item.summary_fields.unified_job_template.name }}" when: - item.job != None # 実行されなかったジョブはスキップ - item.summary_fields.job.type == 'job' # 他の workflow_approval などはスキップ # (3) ワークフロージョブIDからワークフロー名を取得(ディレクトリ名に利用) - name: get workflow name by id uri: url: "https://{{ ansible_host }}/api/v2/workflow_jobs/{{ wfjid }}/" method: GET url_username: "{{ tower_username }}" url_password: "{{ tower_password }}" force_basic_auth: true validate_certs: false register: res_wfj # (4) ワークフロージョブID ごとのログファイル保存用ディレクトリを作成 - name: make directory file: path: "log/wf_{{ wfjid }}_{{ res_wfj.json.name }}" state: directory register: res_dir # (5) ワークフロージョブ内の各ジョブの実行ログをファイルとして保存 - name: save job log to file copy: content: "{{ item.content }}" dest: "{{ res_dir.path }}/job_{{ item.item.job }}_{{ jt_name }}_{{ summary.job.status }}.txt" # vars: summary: "{{ item.item.summary_fields }}" jt_name: "{{ summary.unified_job_template.name }}" loop: "{{ res_job_stdouts.results }}" loop_control: label: "{{ item.item.job }} - {{ jt_name }}" when: - item.item.job != None # 実行されなかったジョブはスキップ - item.item.summary_fields.job.type == 'job' # 他の workflow_approval などはスキップ
解説
Playbook 内の各タスクを簡単に解説します。
vars 部分
wfjid: 600 で対象のワークフロージョブIDを指定しています。
今回は埋め込んでしまっていますが、可変にするために vars_prompt や、extra_vars を利用するといいかもしれません。
(1) ワークフロー内のノードを取得
uri モジュールで、指定されたワークフロージョブ内のノードを取得します。
- APIリファレンス
GET /api/v2/workflow_jobs/{id}/workflow_nodes/ List Workflow Job Nodes for a Workflow Job
(2) ワークフロー内の各ジョブの実行ログを取得
各ジョブの実行ログを取得します。GUI で実行ログをダウンロードする場合は format=txt_download ですが、ここでは format=txt とします。
- APIリファレンス
GET /api/v2/jobs/{id}/stdout/ Retrieve Job Stdout
(3) ワークフロージョブIDからワークフロー名を取得(ディレクトリ名に利用)
あとでワークフロージョブIDごとにディレクトリを作成しますが、その際にワークフロー名もつけたかったので、このタイミングで取得します。
- APIリファレンス
GET /api/v2/workflow_jobs/{id}/ Retrieve a Workflow Job
(4) ワークフロージョブID ごとのログファイル保存用ディレクトリを作成
わかりやすいように、log/ワークフローID_ワークフロー名 でディレクトリを作成します。
(5) ワークフロージョブ内の各ジョブの実行ログをファイルとして保存
実際に実行ログをファイルとして保存します。
GUI で実行ログをダウンロードすると、job_ジョブID.txt というファイル名になりますが、ここでは、job_ジョブID_ジョブ名_ステータス.txt とします。
ステータスには、success や canceled など、そのジョブの状態が入ります。
■ 実行
今回対象のワークフロージョブは以下のものです。中央の jt_show9 は jt_show1 が失敗したときのみ実行するジョブなのでスキップされてます。

Playbook を実行します。
$ ansible-playbook -i inventory.ini log_downlod.yml PLAY [tower] ****************************************************************************************************** TASK [get workflow nodes] ***************************************************************************************** ok: [awx1] TASK [get job stdout] ********************************************************************************************* ok: [awx1] => (item=601 - jt_show1) ok: [awx1] => (item=606 - jt_show4) ok: [awx1] => (item=603 - jt_show2) skipping: [awx1] => (item= - jt_show9) ok: [awx1] => (item=604 - jt_show3) TASK [get workflow name by id] ************************************************************************************ ok: [awx1] TASK [make directory] ********************************************************************************************* changed: [awx1] TASK [save job log to file] *************************************************************************************** changed: [awx1] => (item=601 - jt_show1) changed: [awx1] => (item=606 - jt_show4) changed: [awx1] => (item=603 - jt_show2) skipping: [awx1] => (item= - jt_show9) changed: [awx1] => (item=604 - jt_show3) PLAY RECAP ******************************************************************************************************** awx1 : ok=5 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
確認
以下のように実行ログが保存されます。
$ tree log
log
└── wf_600_wf_show
├── job_601_jt_show1_successful.txt
├── job_603_jt_show2_successful.txt
├── job_604_jt_show3_successful.txt
└── job_606_jt_show4_successful.txt
結果から以下のことが分かります。
- 今回の対象のワークフロージョブID
600のワークフロー名はwf_show - ワークフロー内には以下のジョブが含まれ、すべて成功した
jt_show9のジョブは実行されずにスキップされた- 条件的に実行されないノードのため
代表して一つ、ログの中身を確認します。見慣れた Playbook 実行ログです。
$ cat log/wf_600_wf_show/job_601_jt_show1_successful.txt
SSH ********:
PLAY [ios] *********************************************************************
TASK [show verion] *************************************************************
ok: [iosao_latest]
ok: [iosao]
ok: [iosao_latest] => {
"msg": {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": false,
"failed": false,
"stdout": [
"Cisco IOS XE Software, Version 16.11.01a\\nCisco IOS Software [Gibraltar], ...(略)..."
],
"stdout_lines": [
[
"Cisco IOS XE Software, Version 16.11.01a",
...(略)...
"",
"Configuration register is 0x2102"
]
]
}
}
ok: [iosao] => {
...(略)...
}
}
iosao : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
iosao_latest : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
■ おわりに
少々手間ではありますが、専用のモジュールがなくても、API 経由でやりたいことができました。