AWX のジョブのログを YAML 形式で表示させる

はじめに

Ansible でよくある困りごとのひとつとして、タスクのログに複数行の文字列が含まれるとき、以下のように人間の眼にはやさしくない表示になることが挙げられます。改行がエスケープシーケンスとして表示されるためです。

...
TASK [Referring undefined variable] ********************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an unde
fined variable. The error was: 'undefined_variable' is undefined. 'undefined_var
iable' is undefined\n\nThe error appears to be in '/runner/project/demo.yaml': l
ine 20, column 7, but may\nbe elsewhere in the file depending on the exact synta
x problem.\n\nThe offending line appears to be:\n\n\n    - name: Referring undef
ined variable\n      ^ here\n"}
...

この対策として、改行が改行として表示されるように 出力を YAML 形式にする 方法がよく知られています。本エントリ公開時点では、次の二つの方法が一般的です。

  • Ansible の組み込みの機能を利用する(Ansible 2.13 以降のみ)
    • ansible.cfgcallback_result_format(または環境変数 ANSIBLE_CALLBACK_RESULT_FORMAT)に yaml を指定する
  • コレクション community.genral のコールバックプラグイン yaml を利用する
    • ansible.cfgstdout_callback(または環境変数 ANSIBLE_STDOUT_CALLBACK)に community.general.yaml を指定する

一方で、同じ困りごとは AWX でも発生しますが、AWX で同じ対策を試みても意外と素直にいきません

先日、これに関する質問が Ansible Community Forum に寄せられ ました。フォーラムでもぼくから回答済みですが、本エントリでは、AWX でジョブのログを YAML 形式で出力させる方法を改めて日本語で簡単に紹介します。

基本的な考え方

うまく構成できれば 前述のどちらの対策も AWX で利用できます が、いずれの場合でも次の点が重要です。

  • 利用する EE のイメージ が YAML 形式をサポートできるように正しく構成されていること
  • 場合によっては AWX のコントロールプレーンの EE も変更が必要なこと
  • ジョブ用の 環境変数または ansible.cfg を正しく構成する こと

手順

以下、前述の点を順に紹介します。

(1) 利用する EE イメージを準備する

AWX では、すべてのジョブは例外なく EE 内で処理されます。したがって、そもそもの EE のイメージが YAML 形式での出力に対応している必要があります。

技術的な背景は後述しますが、実施する対策ごとの要件は次の通りです。

  • callback_result_format を変更したい場合
    • Ansible 2.13 以降が導入されていること
    • Ansible Runner に含まれるコールバックプラグイン awx_display がオプション callback_result_format を扱えること
  • stdout_callback を変更したい場合
    • コレクション community.general がインストールされていること

Ansible Builder を使ってていねいにビルドしてももちろんよいですが、次のような Dockerfile をビルドすれば、既成の EE イメージを簡単に修正できます。ここでは quay.io/ansible/awx-ee:latest をベースに必要な変更を加えています。

FROM quay.io/ansible/awx-ee:latest

# 必須
USER root

# callback_result_format を変更したい場合
## プラグイン awx_display が callback_result_format を扱えるようにソースコードを修正する
RUN sed -i 's/      - default_callback/      - default_callback\n      - result_format_callback/g' /usr/local/lib/python3.9/site-packages/ansible_runner/display_callback/callback/awx_display.py

# stdout_callback を変更したい場合
## コレクション community.general をインストールする
RUN ansible-galaxy collection install -p /usr/share/ansible/collections/ansible_collections community.general

# 必須
USER 1000

ビルドしたら、コンテナレジストリにプッシュします。

(2) コントロールプレーンの EE を変更する

AWX のコントロールプレーン(*-task の Pod)では、プロジェクトの同期などのジョブを実行する EE として *-ee コンテナが動作しています。このイメージにはデフォルトで quay.io/ansible/awx-ee:latest が使われますが、以下の場合にはこの変更も必要です。

  • プロジェクトの同期のログも YAML 形式にしたい場合
  • YAML 形式にする設定を AWX の Extra Environment Variables で行う場合(後述)

とくに、community.general.yaml を利用したい場合は、コントロールプレーンの EE を変更しないとプロジェクトの同期がエラーで失敗するようになるため注意が必要です。

コントロールプレーンの EE イメージを変更するには、AWX Operator でデプロイするカスタムリソース AWXspeccontrol_plane_ee_image を追加します。

...
spec:
  ...
  control_plane_ee_image: registry.example.com/ansible/awx-ee:latest-yaml
  ...

(3) 目的のオプションを変更する

最後に、実際に callback_result_format または stdout_callback を変更します。なお、callback_result_format は環境変数と ansible.cfg のどちらでも変更できます が、stdout_callback は環境変数でしか変更できません

環境変数は Settings > Job settingsExtra Environment Variables で指定できますが、グローバルに変更される点に注意が必要です。細かく制御したい場合はコンテナグループを作って Pod に env を持たせるほうがよいでしょう。

{
  # callback_result_format  を変更したい場合
  "ANSIBLE_CALLBACK_RESULT_FORMAT": "yaml"

  # stdout_callback を変更したい場合
  "ANSIBLE_STDOUT_CALLBACK": "community.general.yaml"
}

callback_result_format は、プロジェクトのルートに次の ansible.cfg を配置することでも変更できます。

[defaults]
callback_result_format = yaml

結果

AWX でもログが YAML 形式で表示されるようになり、読みやすくなります。

...
TASK [Referring undefined variable] ********************************************
fatal: [localhost]: FAILED! => 
    msg: |-
        The task includes an option with an undefined variable. The error was: 
        'undefined_variable' is undefined. 'undefined_variable' is undefined

        The error appears to be in '/runner/project/demo.yaml': line 20, column 
        7, but may be elsewhere in the file depending on the exact syntax
        problem.

        The offending line appears to be:
            - name: Referring undefined variable
              ^ here
...

技術的な背景

細かい余談です。

callback_result_format のこと

callback_result_format は、Ansible 2.13 以降でコールバックプラグイン default に組み込まれたオプションです。AWX のデフォルトの EE イメージには Ansible 2.15 が導入されているため、素直に考えればこのオプションも使えそうに思えますが、実際には(少なくとも本エントリ公開時点の実装では)前述のようにソースコードをいじらなければ使えません。これを理解するには、以下の点が重要です。

  • AWX のジョブは、実際には Ansible Runner 経由で実行されること
  • Ansible Runner でプレイブックを実行すると、stdout コールバックプラグインが強制的に awx_display に変更されること
  • 本エントリ公開時点の awx_display(Ansible Runner 2.3.4 に同梱のもの)は、オプション callback_result_format に対応していないこと

実際、オプション callback_result_formatyaml を指定した状態で次のタスクを AWX で実行して設定を確認すると、callback_result_format はあくまでコールバックプラグイン default の設定を変更するだけで、awx_display に対しては何ら影響を与えられていないことがわかります。

- name: Dump stdout configuration
  ansible.builtin.debug:
    var: configuration
  vars:
    configuration:
      01_stdout_callback__________: "{{ lookup('config', 'DEFAULT_STDOUT_CALLBACK', show_origin=true, errors='ignore') }}"
      02_result_format_default____: "{{ lookup('config', 'result_format', plugin_type='callback', plugin_name='default', show_origin=true, errors='ignore') }}"
      03_result_format_awx_display: "{{ lookup('config', 'result_format', plugin_type='callback', plugin_name='awx_display', show_origin=true, errors='ignore') }}"
TASK [Dump stdout configuration] ***********************************************
ok: [localhost] => {
    "configuration": {
        "01_stdout_callback__________": "awx_display"
        "02_result_format_default____": "yaml",
        "03_result_format_awx_display": "",
    }
}

プラグインごとの有効なオプションは、そのプラグインの DOCUMENTATION の定義で決定されます。awx_display の実装ではフラグメント default_callback の取り込みが指定されている ため、一見、コールバックプラグイン default のオプションである callback_result_format も読み込まれそうです。

DOCUMENTATION = '''
    callback: awx_display
    short_description: Playbook event dispatcher for ansible-runner
    version_added: "2.0"
    description:
        - This callback is necessary for ansible-runner to work
    type: stdout
    extends_documentation_fragment:
      - default_callback
    requirements:
      - Set as stdout in config
'''

しかしながら、Ansible 側の実装では、オプション callback_result_format の定義は default_callback ではなく別のフラグメントである result_format_callback に含まれます。

したがって、本エントリで紹介した前述の手順では、sed により extends_documentation_fragmentresult_format_callback を追加しています(参考: ansible/ansible-runner#994)。

DOCUMENTATION = '''
    callback: awx_display
    short_description: Playbook event dispatcher for ansible-runner
    version_added: "2.0"
    description:
        - This callback is necessary for ansible-runner to work
    type: stdout
    extends_documentation_fragment:
      - default_callback
      - result_format_callback
    requirements:
      - Set as stdout in config
'''

これにより、awx_display のオプションとして callback_result_format が指定できるようになります。かつ、awx_display実体 はデフォルトで default(後述)なので、実際にオプションの通りに出力形式が変更されることになります。

TASK [Dump stdout configuration] ***********************************************
ok: [localhost] => 
    configuration:
        01_stdout_callback__________: awx_display
        02_result_format_default____: yaml
        03_result_format_awx_display: yaml

stdout_callback のこと

Ansible Runner は stdout コールバックプラグインとして強制的に awx_display を利用しますが、awx_display 自体は もともと指定されていたコールバックプラグインを継承 する形で読み込まれるようになっているため、もともとの指定が無視されるわけではありません

実装として、Ansible Runner は、環境変数 ANSIBLE_STDOUT_CALLBACK を強制的に awx_display に変更する に、もともとの値を ORIGINAL_STDOUT_CALLBACK に保存 しています(ただし、ansible.cfgstdout_callback ではなく環境変数 ANSIBLE_STDOUT_CALLBACK しか見ていない点は注意が必要です)。

        if self.env.get('ANSIBLE_STDOUT_CALLBACK'):
            self.env['ORIGINAL_STDOUT_CALLBACK'] = self.env.get('ANSIBLE_STDOUT_CALLBACK')
        self.env['ANSIBLE_STDOUT_CALLBACK'] = 'awx_display'

さらに awx_display は、環境変数 ORIGINAL_STDOUT_CALLBACK にしたがって、継承するベースクラスを動的に変更 しています。デフォルトは default です。

# Dynamically construct base classes for our callback module, to support custom stdout callbacks.
if os.getenv('ORIGINAL_STDOUT_CALLBACK'):
    default_stdout_callback = os.getenv('ORIGINAL_STDOUT_CALLBACK')
elif IS_ADHOC:
    default_stdout_callback = 'minimal'
else:
    default_stdout_callback = 'default'

DefaultCallbackModule = callback_loader.get(default_stdout_callback).__class__
class CallbackModule(DefaultCallbackModule):
    '''
    Callback module for logging ansible/ansible-playbook events.
    '''

    CALLBACK_NAME = 'awx_display'

これにより、ユーザは環境変数 ANSIBLE_STDOUT_CALLBACK を指定すれば awx_display の実体を変更できる ことになります。

ただし、callback_result_format の項目で紹介した通り、awx_display にはコールバックプラグイン default のオプションの一部しか実装されていないため、独自のオプションを持つコールバックプラグインを指定するとエラーになる 場合があります。強引にどうにかしたい場合は、callback_result_format を無理やり有効化したように、awx_display がそのオプションを持てるように修正が必要です。

おわりに

なんというか、ややこしいので、AWX で簡単に設定できるようにしたいですね。まずは Ansible Runner 側に手を入れないとですが。

@kurokobo

くろいです。ギターアンサンブルやら音響やらがフィールドの IT やさんなアルトギター弾き。たまこう 48 期ぎたさん、SFC '07 おんぞう、新日本ギターアンサンブル、Rubinetto。今は野良気味。

「AWX のジョブのログを YAML 形式で表示させる」への2件のフィードバック

  1. Thank for sharing. I tried your config and it works!
    Do you know how to handle job outputs truncate?
    For long job output, I saw it displays ‘…’

  2. Hi, to view truncated logs, not tested well but I think you can click the hostname on the truncated task to open details modal window, or simply download full logs by download button on the upper-right corner.
    Have fun!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です