The Inside Playbook
Ansible Tower におけるワークフローの収束 (コンバージェンス) 機能
2019年7月28日(日本語)、寄稿者:Chris Meyers (クリス・メイヤーズ)

Red Hat Ansible Tower 3.1 で、ワークフローという機能が新しく追加されました。この機能により、ジョブテンプレートを任意のグラフツリーとして作成することが実質的に可能になりました。ユーザーが作成するシンプルなワークフローの例としては、下図のような直線的なパイプラインが挙げられます。

ワークフロー機能は分岐にも対応しており、各ブランチは並列的に処理されます。

しかし、まだ足りない機能もありました。それは、並列処理されている別ブランチの完了まで待機してから次の処理に進む機能です。この機能があれば、上図のワークフローは下図のように単純化できます。

Red Hat Ansible Tower 3.4 では、新しく追加されたワークフローの収束 (コンバージェンス) 機能により、このようなワークフローの作成が可能になりました。
これは、コンピュータサイエンス分野の表現でいえば、ツリー型のワークフローだけでなく、有向非巡回グラフ (DAG) のワークフローを作成できるようになったということです。要するに 2 つのノードを下流の同じノードに収束させる機能なので、我々はシンプルに「収束 (コンバージェンス)」と呼んでいます。概念については、例を見ていただくのが一番わかりやすいと思います。上の図は、3 つのノードを持つワークフローです。最初の 2 つのジョブテンプレートは並列的に処理され、両方の処理が完了すると、下流にある 3 つめの収束ノードがトリガーされます。
この記事では、ワークフローが失敗した場合のシナリオの変更点、ワークフローノードの失敗と成功が伝播される方法、失敗や成功がランタイムグラフに与える影響、親ノードのいずれかではなくすべてが成功した場合にのみトリガーされるワークフローの作成方法について説明します。この記事を読むことで、ワークフロー機能がグラフの実行可否を判定する方法と、ユーティリティ Playbook を使用して「いずれかが成功」のシナリオと「すべてが成功」のシナリオを作り分ける方法がわかります。
ワークフロー実行シナリオ
Ansible Tower 3.4 がリリースされるまでは、ワークフローの成功や失敗は最終ノードの結果ステータスで判定されていました。下図のワークフローは、いずれかの「Install APP」ジョブが失敗すると失敗になります。

現在では、ワークフローの成功と失敗は例外処理に近い形になりました。ワークフローで生成されたジョブのいずれかが失敗し、かつ失敗ハンドラーがなければ、そのワークフローは失敗となり、それ以外の場合は成功となります。失敗ハンドラーとは、常時または失敗したときに経由する、ノードから出ているパスです。以下の図は、よくある失敗シナリオがワークフローではどのように処理されるかを示したものです。

このワークフローは EC2 インスタンスを作成し、アプリケーションコードをデプロイします。この 2 つのジョブのうちどちらかが失敗すると、EC2 インスタンスは「Delete EC2 Instance」ジョブによってクリーンアップされます。「Delete EC2 Instance」ジョブが失敗ハンドラーであるため、ワークフロージョブは失敗となりません。しかし、「Delete EC2 Instance」ジョブが失敗した場合は、ワークフローは失敗となります。これにより、ユーザーはクリーンアップ処理が失敗した原因の調査と、結果的に親プロセスがなくなった EC2 インスタンスの削除を、手動で実行する必要があることがわかります。

では、もう少し複雑な例を見てみましょう。上記のワークフローの意図は、インスタンス作成ジョブが両方とも成功した場合に限り「Install App」ジョブテンプレートを実行することです。しかし、ワークフローの動作はそうではありません。親ジョブのどちらか一方が成功すれば、「Install App」ジョブテンプレートは実行されます。次の項で、このような OR の動作ではなく、意図したとおりに AND の動作をさせる方法を説明します。
AND とOR
先ほど説明した内容を、もう少し一般的なレベルで見てみることにしましょう。子ノードに複数の親ノードがあり、成功したらトリガーされる状態になっているとします。親ノードのいずれかが成功すれば、子ノードは実行されます。このような動作が必要な場合もありますが、そうではなく、すべての親が成功したときにのみ子ノードを実行したい場合もあります。では、どうすればよいでしょうか。このような動作を実現するには、ユーティリティ・ジョブテンプレートを作成し、親がすべて成功したときにだけ実行したいコンバージェンスノード (上の例では「Install App」ジョブ) の前に配置します。
AND ユーティリティ・ジョブテンプレート

さて、もう一度確認すると、私たちが望む動作は「Create GCE Instance」と「Create EC2 Instance」の両方が成功した場合にのみ「Install App」を実行することです。このような動作を実現するため、「Utility All」という名の新しいジョブテンプレートを作成し、「Install App」の代わりにこの「Utility All」を収束ノードとします。「Utility All」を使用した Playbook の例を以下に示します。この Playbook は、親ジョブ群を取得してループ処理し、親ジョブのいずれか 1 つでも失敗したら Playbook 自体が失敗となります。これで、すべての親ノードが成功したときにのみ「Install App」を実行するという、意図したとおりの動作が実現できます。
# and_util.py
---
- hosts: localhost
gather_facts: false
vars:
this_playbook_should_fail: false
job_id: "{{ lookup('env', 'JOB_ID') }}"
tower_base_url: "https://{{ lookup('env', 'TOWER_HOST') }}/api/v2"
tower_username: "{{ lookup('env', 'TOWER_USERNAME') }}"
tower_password: "{{ lookup('env', 'TOWER_PASSWORD') }}"
tower_verify_ssl: "{{ lookup('env', 'TOWER_VERIFY_SSL') }}"
tasks:
- name: "Get Workflow job id for which this job belongs"
shell: tower-cli job get {{ job_id }} -f json | jq ".related.source_workflow_job" | sed 's/\/"$//' | sed 's/.*\///'
register: workflow_job_id
- name: "Get Workflow node id for this job"
uri:
url: "{{ tower_base_url }}/workflow_job_nodes/?job_id={{ job_id }}"
validate_certs: "{{ tower_verify_ssl }}"
force_basic_auth: true
user: "{{ tower_username }}"
password: "{{ tower_password }}"
register: result
- name: "Get parent workflow nodes for this workflow node"
uri:
url: "{{ tower_base_url }}/workflow_job_nodes/?success_nodes={{ result.json.results[0].id }}"
validate_certs: "{{ tower_verify_ssl }}"
force_basic_auth: true
user: "{{ tower_username }}"
password: "{{ tower_password }}"
register: result
- name: "Fail this playbook if a parent node failed"
fail:
msg: "Parent workflow node {{ item }} failed"
when: "item.summary_fields.job.status == 'failed'"
loop: "{{ result.json.results }}"

まとめ
私たちが実施したテストでは、このワークフロー収束機能を使用すると、より実際の状況に適した形での動作が可能になることがわかりました。皆さんにとっても同じように有益な機能となっていることを願います。このブログ記事では、収束ノード機能の追加でワークフロー失敗のシナリオがどのように変わったのかと、ランタイムグラフの作成方法、デフォルトのワークフロー収束メソッドの使い方と変更方法について説明しました。Ansible Tower ではこの他にもワークフロー機能の強化が行われています。ぜひご確認ください。