我想要将Ansible执行结果输出到默认输出 + JSON文件中

我想要以JSON格式处理Ansible的执行结果!但是控制台输出不太理想!

一般情况下,Ansible会将执行结果逐步输出到控制台,如下所示:

$ ansible-playbook -i hosts playbook.yml

PLAY [all] ******************************************************************************************************

TASK [Gathering Facts] ******************************************************************************************
ok: [web_server]
ok: [db_server]

....

PLAY RECAP ******************************************************************************************************
db_server                  : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web_server                 : ok=8    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

如果想要在其他工具中处理此执行结果,我认为会需要结构化数据(如JSON)。(或者,您可以尝试直接解析控制台输出,但会相当困难。)

如果出现这种情况,您只需要将 ANSIBLE_STDOUT_CALLBACK=json 作为环境变量提供即可,但是在执行播放书的最后,会一下子输出一大段长长的JSON。

$ ANSIBLE_STDOUT_CALLBACK=json ansible-playbook -i hosts playbook.yml
{
    "custom_stats": {},
    "global_custom_stats": {},
    "plays": [
        {
            "play": {
                "duration": {
                    "end": "2022-05-26T04:22:39.631342Z",
                    "start": "2022-05-26T04:22:37.698472Z"
                },

... (4200行ののち)

    "stats": {
        "db_server": {
            "changed": 0,
            "failures": 0,
            "ignored": 0,
            "ok": 5,
            "rescued": 0,
            "skipped": 0,
            "unreachable": 0
        },
        "web_server": {
            "changed": 0,
            "failures": 0,
            "ignored": 0,
            "ok": 8,
            "rescued": 0,
            "skipped": 0,
            "unreachable": 0
        }
    }
}
# 上記が ansible-playbook が実行終了のタイミングでドバっと一気に出る

现在无法确定处理进行到什么地方。而且,简直是一眼望不清。

并非如此,在将JSON输出到文件的同时,不会有一种方便的方法将标准输出设置为默认输出方法。

如果希望自由地输出Ansible的执行结果,只需自己创建一个自定义的回调插件即可。

要解决这个问题,你需要自己创建一个定制的回调插件来自定义Ansible的控制台输出。

Ansible是使用Python编写的,您可以通过编写自己的Python脚本来自由地向Ansible添加插件。

对于这个项目而言,可以按照如下方式创建一个专用插件(假设为json_export)。

[defaults]
# ./callback_plugins/json_export.py に自作callbackプラグインを置く
# (ansible-playbook に与える引数のプレイブックからの相対パスになる)
callback_plugins = ./callback_plugins
callbacks_enabled = json_export

# デフォルトのcallbackプラグインの指定
# 別に ANSIBLE_STDOUT_CALLBACK=json_export のように実行時に環境変数として指定してもいい
stdout_callback = json_export
import os
import json
from ansible.parsing.ajson import AnsibleJSONEncoder
from ansible.plugins.callback.default import CallbackModule as Base

DOCUMENTATION = '''
    name: json_export
    type: stdout
    short_description: Also export results into a JSON file if specified.
    version_added: historical
    description:
        - Export results into a JSON file (specified at JSON_EXPORT_PATH).
    extends_documentation_fragment:
      - default_callback
    requirements:
      - set as stdout in configuration
'''

# 基本的にはデフォルトの出力が欲しいので、デフォルトのcallbackモジュールを拡張したものを作成する
class CallbackModule(Base):

    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'stdout'
    CALLBACK_NAME = 'json_export'

    def __init__(self):
        super(CallbackModule, self).__init__()
        self._results = []
        self._servers = {}
        self._output_to = os.getenv('JSON_EXPORT_PATH', '')

    # v2_* メソッドをオーバーライドしてデフォルトの動作に処理を追加していく

    def v2_playbook_on_task_start(self, task, is_conditional):
        super(CallbackModule, self).v2_playbook_on_task_start(task, is_conditional)
        self._results.append({ 'name': task.get_name(), 'items': [] })
    
    def v2_runner_on_ok(self, result):
        super(CallbackModule, self).v2_runner_on_ok(result)
        self._append_result('ok', result)

    def v2_runner_on_failed(self, result, ignore_errors=False):
        super(CallbackModule, self).v2_runner_on_failed(result)
        self._append_result('failed', result)

    def v2_runner_on_skipped(self, result):
        super(CallbackModule, self).v2_runner_on_skipped(result)
        self._append_result('skipped', result)

    def v2_runner_on_unreachable(self, result):
        super(CallbackModule, self).v2_runner_on_skipped(result)
        self._append_result('unreachable', result)

    def _append_result(self, result_type, result):
        lastResults = self._results[-1]['items']
        host_name = result._host.get_name()

        rawResult = result._result
        essentialResult = {}
        for key in ["changed", "start", "end", "stdout", "stdout_lines", "stderr", "stderr_lines"]:
            if key in rawResult:
                essentialResult[key] = rawResult[key]

        lastResults.append({
            'host_name': host_name,
            'type': result_type,
            'result': essentialResult
        })
        self._servers[host_name] = True

    # 実行結果出力時(実行の最後)にJSONファイルを出力する
    def v2_playbook_on_stats(self, stats):
        super(CallbackModule, self).v2_playbook_on_stats(stats)
        if self._output_to:
            with open(self._output_to, "w") as f:
                json.dump({
                    'results': self._results,
                    'host_names': list(self._servers.keys())
                }, f, cls=AnsibleJSONEncoder, indent=2, ensure_ascii=False, sort_keys=True)

只需要在ansible.cfg中添加插件的搜索路径和允许的插件名称,然后将其配置为运行时要使用的插件。

如果您在上述自制插件中提供了所需的JSON_EXPORT_PATH,那么在执行结束时会生成一个JSON文件。

$ JSON_EXPORT_PATH=ansible_result.json ansible-playbook -i hosts playbook.yml
PLAY [all] *************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************
ok: [web_server]
ok: [db_server]

...

PLAY RECAP *************************************************************************************************************************
db_server                  : ok=5    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
web_server                 : ok=8    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
{
  "host_names": [
    "db_server",
    "web_server"
  ],
  "results": [
    {
      "items": [
        {
          "host_name": "db_server",
          "result": {
            "changed": false
          },
          "type": "ok"
        },
...

总结

如果您想要自由定制化执行结果的输出,您可以创建一个回调插件来实现任何功能。

可以利用这个方法,例如,可以将Ansible的所有执行结果强制性地上传到审计用的存储库中。

广告
将在 10 秒后关闭
bannerAds