てくなべ (tekunabe)

ansible / network / automation

[Ansible] 「つまずき Ansible 【Part19】モジュールのコードをデバッグしたい」ふりかえり

はじめに

2020/10/17 に、YouTube Live で「つまずき Ansible 【Part19】モジュールのコードをデバッグしたい」という配信をしました。

実際に作業しながらエラーと戦って進めるシリーズです。

tekunabe.connpass.com

今回は、ANSIBLE_KEEP_REMOTE_FILES の設定を有効にして Playbook を実行して残った Python のコードをもとにしてデバッグをしてみます。

やったことや、わかったことをふりかえります。

  • 環境

動画

youtu.be

チャプターもつけてます。


■ やったこと

ANSIBLE_KEEP_REMOTE_FILES を有効にして実行

利用した Playbook は以下のとおりです。

NetBox にサイトというオブジェクトを作成する APIuri モジュールで叩きます。

本当は専用のモジュールがあるのですが、ここではよりメジャーなモジュールとして uri を取り上げました。

---
- hosts: netbox01
  gather_facts: false
  connection: local

  vars:
    url: http://192.168.1.145:32769/api/dcim/sites/
    
  tasks:
    - name: 
      uri:
        url: "{{ url }}"
        method: POST
        headers:
          Authorization: "Token {{ token }}"
        # body: "{{ lookup('file', 'body.json') }}"
        body:
          name: test1
          slug: test1
        status_code:
          - 200
          - 201
        body_format: json

実行コマンド

ANSIBLE_KEEP_REMOTE_FILES=1 ansible-playbook -i inventory.ini uri.yml 

この場合は、一回限りの環境変数としての指定指定です。

他の設定と同様に、ansible.cfg にも定義できます。詳細は 公式ドキュメントを参照してください。

残ったファイルの確認

先程のように ANSIBLE_KEEP_REMOTE_FILES を有効にして Playbook を実行すると、ターゲットノードの ~/.ansible/tmp ディレクトリ配下の、実処理を実行するための Python ファイルが残されます。

通常であれば、このファイルは実行後に削除されるのですが、それを残すのが ANSIBLE_KEEP_REMOTE_FILES の設定ということです。

今回は、connection: local なので、Ansible コントロールノード自身の ~/.ansible/tmp ディレクトリ配下に残ります。

(a2100) [root@centos7 tmp]# ls -al
合計 0
drwx------. 3 root root 66 10月 17 20:10 .
drwx------. 5 root root 66 10月  7 19:48 ..
(a2100) [root@centos7 ansible-tmp-1602933024.2020504-20448-161437222207769]# ls -al
合計 156
drwx------. 3 root root     47 10月 17 20:14 .
drwx------. 3 root root     66 10月 17 20:10 ..
-rwxr--r--. 1 root root 157816 10月 17 20:10 AnsiballZ_uri.py

AnsiballZ_モジュール名.py というファイル名で残りリます。

残ったファイルを普通に実行

AnsiballZ_uri.py は、Ansible は不要で、通情の Python ファイルとして実行できます。

(a2100) [root@centos7 ansible-tmp-1602933024.2020504-20448-161437222207769]# python AnsiballZ_uri.py

{"redirected": false, "url": "http://192.168.1.145:32769/api/dcim/sites/", "status": 201, "server": "nginx", "date": "Sat, 17 Oct 2020 11:13:19 GMT", "content_type": "application/json", "content_length": "467", "connection": "close", "location": "http://192.168.1.145:32769/api/dcim/sites/9/", "vary": "Accept, Cookie, Origin", "allow": "GET, POST, HEAD, OPTIONS", "api_version": "2.9", "x_content_type_options": "nosniff", "referrer_policy": "same-origin", "x_frame_options": "SAMEORIGIN", "p3p": "CP=\"ALL DSP COR PSAa PSDa OUR NOR ONL UNI COM NAV\"", "cookies_string": "", "cookies": {}, "msg": "OK (467 bytes)", "elapsed": 0, "changed": false, "json": {"id": 9, "url": "http://192.168.1.145:32769/api/dcim/sites/9/", "name": "test1", "slug": "test1", "status": {"value": "active", "label": "Active"}, "region": null, "tenant": null, "facility": "", "asn": null, "time_zone": null, "description": "", "physical_address": "", "shipping_address": "", "latitude": null, "longitude": null, "contact_name": "", "contact_phone": "", "contact_email": "", "comments": "", "tags": [], "custom_fields": {}, "created": "2020-10-17", "last_updated": "2020-10-17T11:13:19.879794Z"}, "invocation": {"module_args": {"url": "http://192.168.1.145:32769/api/dcim/sites/", "method": "POST", "headers": {"Authorization": "Token 0123456789abcdef0123456789abcdef01234567", "Content-Type": "application/json"}, "body": {"name": "test1", "slug": "test1"}, "status_code": [200, 201], "body_format": "json", "force": false, "http_agent": "ansible-httpget", "use_proxy": true, "validate_certs": true, "force_basic_auth": false, "return_content": false, "follow_redirects": "safe", "timeout": 30, "remote_src": false, "unsafe_writes": false, "url_username": null, "url_password": null, "client_cert": null, "client_key": null, "dest": null, "src": null, "creates": null, "removes": null, "unix_socket": null, "mode": null, "owner": null, "group": null, "seuser": null, "serole": null, "selevel": null, "setype": null, "attributes": null}}}

ただし、中身の大半がエンコードされているためデバッグがまともにできません。

AnsiballZ_uri.py を普通に実行するのはた、ただの Playbook のタスクの再現用という感じです。

explode て展開する

AnsiballZ_uri.py 内の、エンコードされた部分を展開するのが explode オプションです。

(a2100) [root@centos7 ansible-tmp-1602933024.2020504-20448-161437222207769]# python AnsiballZ_uri.py explode
Module expanded into:
/root/.ansible/tmp/ansible-tmp-1602933024.2020504-20448-161437222207769/debug_dir

debug_dir の中に、タスクのオプションを JSON 化した arg や、モジュールのコードの本体や、それが利用するファイル一式が含まれます。

(a2100) [root@centos7 ansible-tmp-1602933024.2020504-20448-161437222207769]# tree debug_dir/
debug_dir/
├── ansible
│   ├── __init__.py
│   ├── __pycache__
│   │   └── __init__.cpython-36.pyc
│   ├── module_utils
│   │   ├── __init__.py
│   │   ├── __pycache__
│   │   │   ├── __init__.cpython-36.pyc
│   │   │   ├── _text.cpython-36.pyc
│   │   │   ├── basic.cpython-36.pyc
│   │   │   ├── pycompat24.cpython-36.pyc
│   │   │   └── urls.cpython-36.pyc
│   │   ├── _text.py
│   │   ├── basic.py
│   │   ├── common
│   │   │   ├── __init__.py
│   │   │   ├── __pycache__
│   │   │   │   ├── __init__.cpython-36.pyc
│   │   │   │   ├── _collections_compat.cpython-36.pyc
│   │   │   │   ├── _json_compat.cpython-36.pyc
│   │   │   │   ├── _utils.cpython-36.pyc
│   │   │   │   ├── collections.cpython-36.pyc
│   │   │   │   ├── file.cpython-36.pyc
│   │   │   │   ├── parameters.cpython-36.pyc
│   │   │   │   ├── process.cpython-36.pyc
│   │   │   │   ├── sys_info.cpython-36.pyc
│   │   │   │   ├── validation.cpython-36.pyc
│   │   │   │   └── warnings.cpython-36.pyc
│   │   │   ├── _collections_compat.py
│   │   │   ├── _json_compat.py
│   │   │   ├── _utils.py
│   │   │   ├── collections.py
│   │   │   ├── file.py
│   │   │   ├── parameters.py
│   │   │   ├── process.py
│   │   │   ├── sys_info.py
│   │   │   ├── text
│   │   │   │   ├── __init__.py
│   │   │   │   ├── __pycache__
│   │   │   │   │   ├── __init__.cpython-36.pyc
│   │   │   │   │   ├── converters.cpython-36.pyc
│   │   │   │   │   └── formatters.cpython-36.pyc
│   │   │   │   ├── converters.py
│   │   │   │   └── formatters.py
│   │   │   ├── validation.py
│   │   │   └── warnings.py
│   │   ├── compat
│   │   │   ├── __init__.py
│   │   │   ├── __pycache__
│   │   │   │   ├── __init__.cpython-36.pyc
│   │   │   │   └── selectors.cpython-36.pyc
│   │   │   ├── _selectors2.py
│   │   │   └── selectors.py
│   │   ├── distro
│   │   │   ├── __init__.py
│   │   │   ├── __pycache__
│   │   │   │   ├── __init__.cpython-36.pyc
│   │   │   │   └── _distro.cpython-36.pyc
│   │   │   └── _distro.py
│   │   ├── parsing
│   │   │   ├── __init__.py
│   │   │   ├── __pycache__
│   │   │   │   ├── __init__.cpython-36.pyc
│   │   │   │   └── convert_bool.cpython-36.pyc
│   │   │   └── convert_bool.py
│   │   ├── pycompat24.py
│   │   ├── six
│   │   │   ├── __init__.py
│   │   │   └── __pycache__
│   │   │       └── __init__.cpython-36.pyc
│   │   └── urls.py
│   └── modules
│       ├── __init__.py
│       ├── __pycache__
│       │   ├── __init__.cpython-36.pyc
│       │   └── uri.cpython-36.pyc
│       └── uri.py
└── args

18 directories, 60 files

args を編集して再実行する

args でタスクのオプションにあたる値を編集できます。今回は body の中を編集しました。

{
  "ANSIBLE_MODULE_ARGS": {
    "url": "http://192.168.1.145:32769/api/dcim/sites/",
    "method": "POST",
    "headers": {
      "Authorization": "Token 0123456789abcdef0123456789abcdef01234567"
    },
    "body": {
      "name": "test3",
      "slug": "test3"
    },
  ...(略)...
  }
}

編集した args など、debug_dir 配下のフィアルを利用して再実行するには、execute オプションを付けます。

(a2100) [root@centos7 ansible-tmp-1602933024.2020504-20448-161437222207769]# python AnsiballZ_uri.py execute
...(略)...

execute オプションを付けないと、AnsiballZ_uri.py 単体の実行となります。

ここまでが、Ansible 固有の仕様です。

VS Codeブレークポイントをはってステップ実行する

ここからは 一般的な Pythonデバッグの手法と同じです。

ここでは、モジュールの本体のファイルである debug_dir/ansible/modules/uri.pymain() 呼び出し箇所にブレークポイントをはります。

f:id:akira6592:20201017212749p:plain
ブレークポイントをはる

execute オプションを付けてデバッグを開始するために、~/.vscode/launch.json に追記します。

{
  // IntelliSense を使用して利用可能な属性を学べます。
  // 既存の属性の説明をホバーして表示します。
  // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python: Current File",
      "type": "python",
      "request": "launch",
      "program": "${file}",
      "console": "integratedTerminal"
    },
    // 以下追加
    {
      "name": "Python: Current File with execute",
      "type": "python",
      "request": "launch",
      "program": "${file}",
      "args": [
        "execute"
      ],
      "console": "integratedTerminal"
    }
  ]
}

AnsiballZ_uri.py を開いている状態で、先程 launch.json に定義した定義した Python: Current File with executeデバッグを始めます。

f:id:akira6592:20201017212833p:plain
デバッグの開始

処理が始まり、ブレークポイントをはった debug_dir/ansible/modules/uri.pymain() の呼び出し箇所で止まりました。

f:id:akira6592:20201017212912p:plain
ブレークポイントで止まった

ここからステップ実行ができます。

たとえば、モジュールのオプションで与えた値は、モジュールのコード内でこのように変数に格納されます。

f:id:akira6592:20201017213048p:plain
body オプションに与えた値

途中で変数の値を変更して、処理を続けることもできます。

f:id:akira6592:20201017213329p:plain
test3(変更前)

f:id:akira6592:20201017213557p:plain
test4 (変更後)
VS Code によるデバッグは、公式ドキュメント などを参照にしてください。

モジュールの処理を細かく追いいたいときに便利だと思います。


Part20 にむけて

以下のネタを検討中です。気が向いたものをやります。

  • Ansible 2.10 関連ほかにも
  • connection: local ななにか
  • Ansible Toewr / AWX をコマンドがら操作する
  • ansible.cfg
  • Jinja2、フィルター
  • Windows
  • ESXi で VM作成
  • parsee_cli モジュール(Part15 の続き)