てくなべ (tekunabe)

ansible / network automation / 学習メモ

[Ansible] Playbook ならぬ Rulebook、イベントドリブンな ansible-rulebook を試してみた

はじめに

先日 Ansible の公式ブログで、Event-Driven Ansible という言葉が含まれるブログが次々と投稿されました。

また、AnsibleFest 2022 の 10/19 の Key Note でも Event-Driven Ansible が紹介されていました。

動画 (40:13 ころから)

www.youtube.com

また、個人的には GitHubAnsible Organizationをフォローしてるのですが、たしかに最近それっぽいリポジトリがいくつかパブリックになりました。なのでそのうち何か発表があるかなと楽しみにしていました。

Event-Driven Ansible というのは、外部からのイベントを検出し、条件にあえば特定のアクションを実行する仕組みのようです。これまでは基本的に人がPlaybookをキックしますが、それとはまた別で、人が介在しないアプローチ。まっさきに IFTTT や StackStorm を連想しました。

Event-Driven Ansible を実現するツールとして ansible-rulebookがあります。まだ developer preview という扱いのようです。

まだあまり情報が多くないですが、簡単なものでためしてみたのでまとめます。まだまだ、環境含めて全体的に手探りです・・。

  • 環境
    • ansible-rulebook 0.9.4
    • ansible.eda コレクション 1.3.2
    • ansible-core 2.13.5
    • Python 3.9.7 (3.8 以上が要件)
    • RHEL 8.6

Event-Driven Ansible を実現する要素

Event-Driven Ansible を理解するために重要そうな4の要素があります。

Rulebook: どういうときに何をさせたいかの定義ファイル

検出したいイベントの発生源、条件、実行したいアクションを YAML で定義するファイルです。遠目で見ると Playbook のように見えます。

以降紹介する、 source、condition(rule)、action の定義を含みます。以下の関係です。

  • rulebook
    • source
    • condition (rule)
    • action

source: イベントの発生源

イベントの発生源です。例えば Webhook や Kafka、Azure Event Bus などです。

拡張性のためか、ansible collection によって追加できる仕組みのようです。

現状、イベントソースを含むコレクションとしては ansible.eda (eda は Event-Driven Ansible の略)があるようです。後でこのコレクションを利用して試します。

ansible.eda コレクション 0.9.4 には、例えばイベントソースが含まれています(詳細はこちら)。

  • webhook
  • alertmanager
  • file
  • kafka
  • azure_service_bus
  • url_check

これらを見ると、他システムとの連携イメージが湧きそうです。また、「ansible-rulebook は何に対応しているか?」という問いには「どのイベントソースがあるか」という調べ方になるケースが多そうです。

StackStorm でいうと Sensor のようなものかなと思います。

condition(rule): 条件

何かを実行するための条件です。主に source からのイベントに含まれる情報を元にします。例えば「飛んできた Webhook に含まれるペイロード内の messagesakana だったら」のような条件です。

condition の例(Rulebook 抜粋)

      condition: event.payload.message == "sakana"

複数の条件も指定できます。

AND 条件:

      condition:
        all:
          - event.payload.message == "sakana"
          - event.payload.user == "taro"

OR 条件:

      condition:
        any:
          - event.payload.message == "sakana"
          - event.payload.message == "fish"

なお、Rulebook の書式上は conditions という書式を利用しますが、仕組みの説明上は rule と表現されることもあるようです。

action: 実行させたい処理

condition にマッチしたときに実行させたい処理です。以下は一例です。

  • run_playbook: 指定した Playbook を実行する (あとで扱う)
  • run_module: 指定した モジュールのを実行する
  • debug: デバッグ情報を表示する

すべての action 一覧はこちらです

ここまでの source と condition と action をとても雑に表現すると、以下のようなイメージでしょうか。

# 雑なイメージ
if (sourceを元にしたcondition):
  action

環境の準備

ここから、ansible-rulebook を試すための環境を準備します。

インストール・設定

インストール方法に関する公式ドキュメントはこちらです。

github.com

ここでは私の環境で私が試した方法を掲載します。上記ドキュメントは後で見つけたため、方法が微妙に異なるのでご了承ください。一次ソースとしてはドキュメントを参照してください。

本体

本体を pip でインストールします。

pip install ansible-rulebook

この時点で ansible-runner も入りました(ansible-core はない)。

環境変数JAVA_HOME` が必要なので、設定します。私の環境の場合は以下の通り。

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-17.0.5.0.8-2.el8_6.x86_64

JAVA_HOME を設定しないと ansible-rulebook --version すらエラーになります。

関連パッケージなど

後で分かったのですが、別途 Ansible 環境が必要だったので ansible-core もインストールします。

pip install ansible-core

今回使うイベントソースが入っている ansible.eda コレクションをインストールします。

ansible-galaxy collection install ansible.eda

ほか、これも後でエラーが起きてから気づいたのですが、今回使う ansible.eda コレクション内の webhook イベントソースが aiohttp を使うのでインストールします。

pip install aiohttp

何が必要になるかは、利用するイベントソースによって異なります。

おためし

環境ができたので試します。手軽さを重視して、すべてローカルホストで完結する内容です。

Rulebook の準備

お手軽にそれらしことを体験できそうな Webhook をイベントソースとする Rulebook を試してみます。

test-rulebook.yml

---
- name: Hello Events
  hosts: localhost

  sources:
    - ansible.eda.webhook:
        host: 0.0.0.0
        port: 5000

  rules:
    - name: Say Hello
      condition: event.payload.message == "sakana"
      action:
        run_playbook:
          name: test_playbook.yml

この Rulebook の定義内容は以下の通りです。

  • Sensor: Webhook の受け口として、Web サーバー を起動してポート番号 5000 (デフォルト通り)で待ち受ける
  • Condition: こに対して messagesakana というペイロードの Webhook が飛んできたら・・
  • Action: test_playbook.yml (あとでrulebookと同じディレクトリに作成)という Playbook を実行する

インベントリファイルの準備

ansible-rulebook の実行にはインベントリが必要です。

今回は必要最低限の以下のファイルを用意しました。

inventory.yml

---
all:
  hosts:
    localhost:

Playbook の準備

Rulebook 内の条件がマッチしたときに実行する Playbook を準備します。

test_playbook.yml

---
- name: debug msg play
  hosts: localhost
  connection: local
  gather_facts: false

  tasks:
    - name: debug msg
      ansible.builtin.debug:
        msg: HELLO!!

実行

まず待受側として Rulebook を実行します。コマンドは ansible-rulebook です。

ansible-rulebook -i inventory.yml --rulebook test_rulebook.yml --verbose

ここで指定している ansible-rulebook コマンドの主なオプションは以下のとおりです。

  • -i: インベントリファイル名を指定。--inventory でも可
  • --rulebook : Rulebook ファイル名、またはコレクション内の Rulebook を指定。単にファイル名だけ指定するのも可
  • --verbose : 詳細なログを画面上に表示する(任意)。-v のように省略できないので注意

ansible-rulebook 側はフォアグラウンドで待ち受け状態になります(--verbose 付きなのでログが多めです)。

$ ansible-rulebook -i inventory.yml --rulebook test_rulebook.yml --verbose
INFO:ansible_rulebook.app:Starting sources
INFO:ansible_rulebook.app:Starting rules
INFO:ansible_rulebook.engine:run_ruleset
INFO:ansible_rulebook.engine:ruleset define: {"name": "Hello Events", "hosts": ["localhost"], "sources": [{"EventSource": {"name": "ansible.eda.webhook", "source_name": "ansible.eda.webhook", "source_args": {"host": "0.0.0.0", "port": 5000}, "source_filters": []}}], "rules": [{"Rule": {"name": "Say Hello", "condition": {"AllCondition": [{"EqualsExpression": {"lhs": {"Event": "payload.message"}, "rhs": {"String": "sakana"}}}]}, "action": {"Action": {"action": "run_playbook", "action_args": {"name": "test_playbook.yml"}}}, "enabled": true}}]}
INFO:ansible_rulebook.engine:load source
INFO:ansible_rulebook.engine:load source filters
INFO:ansible_rulebook.engine:Calling main in ansible.eda.webhook
INFO:ansible_rulebook.engine:Waiting for event from Hello Events
(ここで待機)

条件にマッチする Webhook リクエス

続いて、Webhook を再現するために、同じホストから以下の curl を叩きます。条件にマッチするように messagesakanaペイロードを仕込んでいます。endpointansible-rulebook 側の仕様で、固定のようです。

curl -H 'Content-Type: application/json' -d "{\"message\": \"sakana\"}" 127.0.0.1:5000/endpoint

すると、待ち受け状態の ansible-rilebook 側で、以下のログが続きました。

INFO:aiohttp.access:127.0.0.1 [21/10月/2022:06:21:57 +0000] "POST /endpoint HTTP/1.1" 200 158 "-" "curl/7.61.1"
INFO:ansible_rulebook.rule_generator:calling Say Hello
INFO:ansible_rulebook.engine:call_action run_playbook
INFO:ansible_rulebook.engine:substitute_variables [{'name': 'test_playbook.yml'}] [{'event': {'payload': {'message': 'sakana'}, 'meta': {'headers': {'Accept': '*/*', 'User-Agent': 'curl/7.61.1', 'Host': '127.0.0.1:5000', 'Content-Length': '21', 'Content-Type': 'application/json'}, 'endpoint': 'endpoint'}}, 'fact': {'payload': {'message': 'sakana'}, 'meta': {'headers': {'Accept': '*/*', 'User-Agent': 'curl/7.61.1', 'Host': '127.0.0.1:5000', 'Content-Length': '21', 'Content-Type': 'application/json'}, 'endpoint': 'endpoint'}}}]
INFO:ansible_rulebook.engine:action args: {'name': 'test_playbook.yml'}
INFO:ansible_rulebook.builtin:running Ansible playbook: test_playbook.yml
INFO:ansible_rulebook.builtin:ruleset: Hello Events, rule: Say Hello
INFO:ansible_rulebook.builtin:Calling Ansible runner

PLAY [debug msg play] **********************************************************

TASK [debug msg] ***************************************************************
ok: [localhost] => {
    "msg": "HELLO!!"
}

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

無事に条件にマッチし、run_playbook で指定した Playbook が実行されました。

動画です(次に説明するマッチしないパターンも含みます)。

条件にマッチしない Webhook リクエス

条件にマッチしない以下のリクエストの場合は、Playbook は実行されませんでした。

$ curl -H 'Content-Type: application/json' -d "{\"message: \"fish\"}" 127.0.0.1:5000/endpoint

来たリクエストの概要だけログに表示されました。--verbose による挙動です。

INFO:aiohttp.access:127.0.0.1 [21/10月/2022:06:22:49 +0000] "POST /endpoint HTTP/1.1" 200 158 "-" "curl/7.61.1"

なるほどなるほど。

Webhook (イベントソース)の内容を Playbook 内で参照する

先ほどの Playbook は、べた書きの文字列を表示するだけでした。せっかくなので、イベントソースの内容、今回では飛んできた Webhook のペイロードの値も扱いたいと思って調べました。

action として debug を指定すると、イベントソースの色々な情報が表示されました。

Rulebook は以下の通りです。

- name: Hello Events
  hosts: localhost

  sources:
    - ansible.eda.webhook:
        host: 0.0.0.0
        port: 5000

  rules:
    - name: Say Hello
      condition: event.payload.message == "sakana"
      action:
        debug:        # デバッグの指定

上記 Rulebook を実行中に、条件にマッチする Webhook をリクスエストをすると、以下のようにイベントソースの内容が色々表示されました。

INFO:ansible_rulebook.engine:call_action debug
INFO:ansible_rulebook.engine:substitute_variables [{}] [{'event': {'payload': {'message': 'sakana'}, 'meta': {'headers': {'Accept': '*/*', 'User-Agent': 'curl/7.61.1', 'Host': '127.0.0.1:5000', 'Content-Length': '21', 'Content-Type': 'application/json'}, 'endpoint': 'endpoint'}}, 'fact': {'payload': {'message': 'sakana'}, 'meta': {'headers': {'Accept': '*/*', 'User-Agent': 'curl/7.61.1', 'Host': '127.0.0.1:5000', 'Content-Length': '21', 'Content-Type': 'application/json'}, 'endpoint': 'endpoint'}}}]
INFO:ansible_rulebook.engine:action args: {}
=============================================================================================
kwargs:
{'facts': {},
 'hosts': ['localhost'],
 'inventory': {'all': {'hosts': {'localhost': None}}},
 'project_data_file': None,
 'ruleset': 'Hello Events',
 'source_rule_name': 'Say Hello',
 'source_ruleset_name': 'Hello Events',
 'variables': {'event': {'meta': {'endpoint': 'endpoint',
                                  'headers': {'Accept': '*/*',
                                              'Content-Length': '21',
                                              'Content-Type': 'application/json',
                                              'Host': '127.0.0.1:5000',
                                              'User-Agent': 'curl/7.61.1'}},
                         'payload': {'message': 'sakana'}},
               'fact': {'meta': {'endpoint': 'endpoint',
                                 'headers': {'Accept': '*/*',
                                             'Content-Length': '21',
                                             'Content-Type': 'application/json',
                                             'Host': '127.0.0.1:5000',
                                             'User-Agent': 'curl/7.61.1'}},
                        'payload': {'message': 'sakana'}}}}
===================

上記の内容のうち、event.payload.message を Playbook に埋め込んでみます。

---
- name: debug msg play
  hosts: localhost
  connection: local
  gather_facts: false

  tasks:
    - name: debug msg
      ansible.builtin.debug:
        msg: "{{ event.payload.message }}"    # イベントソースの情報を利用

Rulebook 内の action を debug から run_playbook に戻します。

# ...(略)...
      action:
        run_playbook:
          name: test_playbook.yml

上記 Rulebook を実行中に、条件にマッチする Webhook をリクスエストをすると、以下のように event.payload.message の値(sakana)が表示されました。

INFO:aiohttp.access:127.0.0.1 [21/10月/2022:06:33:42 +0000] "POST /endpoint HTTP/1.1" 200 158 "-" "curl/7.61.1"
INFO:ansible_rulebook.rule_generator:calling Say Hello
INFO:ansible_rulebook.engine:call_action run_playbook
INFO:ansible_rulebook.engine:substitute_variables [{'name': 'test_playbook.yml'}] [{'event': {'payload': {'message': 'sakana'}, 'meta': {'headers': {'Accept': '*/*', 'User-Agent': 'curl/7.61.1', 'Host': '127.0.0.1:5000', 'Content-Length': '21', 'Content-Type': 'application/json'}, 'endpoint': 'endpoint'}}, 'fact': {'payload': {'message': 'sakana'}, 'meta': {'headers': {'Accept': '*/*', 'User-Agent': 'curl/7.61.1', 'Host': '127.0.0.1:5000', 'Content-Length': '21', 'Content-Type': 'application/json'}, 'endpoint': 'endpoint'}}}]
INFO:ansible_rulebook.engine:action args: {'name': 'test_playbook.yml'}
INFO:ansible_rulebook.builtin:running Ansible playbook: test_playbook.yml
INFO:ansible_rulebook.builtin:ruleset: Hello Events, rule: Say Hello
INFO:ansible_rulebook.builtin:Calling Ansible runner

PLAY [debug msg play] **********************************************************

TASK [debug msg] ***************************************************************
ok: [localhost] => {
    "msg": "sakana"
}

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

無事に、実行された Playbook 内でイベントソースの内容を扱えました。

おわりに

Event-Driven Ansible の中心となるツール ansible-rulebook を試してみました。

今後は対応するイベントソースが増えていくと更に可能性が広がりそうです。 イベントソースは collection に含められるようなので、すでに配布やインストールの仕組みがすでに整ってるのは良い条件に思います。

また、こちらの記事によると、AAP のコンポーネントになることが計画されているそうです。

Event-Driven Ansible is planned to become a component of Red Hat Ansible Automation Platform.

今回は、ローカルホストで完結するよう簡単なサンプルでした。まだ、分かっていない仕組み、扱っていないイベントソースや action が色々あります。

今後の動向もチェックしていきたいと思います。

まだ理解していないこと

現時点ではまだ以下の点がまだ理解しきれていません。またあとで調べたいと思います。まだ少し触っただけですので、そもそも認識が及んでいない領域もあります。

  • ansible-rulebook と一緒にインストールされる ansible-runner の使われ方
    • docker を止めてる状態でも正常に Playbook が実行した(明示的にインストールした ansible-core によるものの模様)
  • インベントリと 各 hosts: の関係
    • 今回はすべて lcoalhost で完結する内容だったが、ansible-rulebook-i で指定したイベントリファイルと Rulebook 冒頭の hosts: 、Playbook 冒頭の hosts: がどういう関係なのか
  • 内部で Drools がどのように使わているか
    • 今回 JAVA_HOME を設定したが、使われてるのかよくわかっていない
    • この記事によると「Event-Driven Ansible contains a decision framework that was built using Drools」とある

参考

Event-Driven Ansible 総合情報

https://www.ansible.com/event-driven

Event-Driven Ansible 関連の公式ブログの記事たち

https://www.ansible.com/blog/topic/event-driven-automation

Event-Driven Ansible を含むハンズオンのラボ環境

https://www.ansible.com/products/ansible-training

ansible-rulebook リポジトリ

https://github.com/ansible/ansible-rulebook

ansible-rulebook ドキュメント

https://ansible-rulebook.readthedocs.io/en/latest/

直近の関連ウェビナー

https://www.redhat.com/en/events/webinar/event-driven-ansible-office-hours-november

https://www.redhat.com/en/events/webinar/event-driven-ansible-office-hours-december

関連動画

雰囲気がわかりやすいです

www.youtube.com

Git Ops とからめる

www.youtube.com