【無料ツール】画像をPDFに変換するPhotoPDF Appを公開しました!

【Ansible応用講座 第4回】標準機能で足りないなら。PythonによるCustom Module開発入門

「shellモジュールで頑張る」のは、もう終わりにしよう。

こんにちは!「LINUX工房」管理人の「リナックス先生」です。
前回は、Ansibleのパフォーマンスを極限まで引き出すチューニング術について解説しました。

さて、実務でAnsibleを使っていると、どうしても「標準モジュールでは手が届かない」場面に出くわします。
社内独自のAPI連携、特殊なフォーマットの設定ファイル操作、複雑な条件分岐…。
そんな時、多くのエンジニアは安易に shell モジュールで長いワンライナーやシェルスクリプトを実行してしまいがちです。

コウ君

先生、ギクッとしちゃいました。
実は今、社内の古い在庫管理システムと連携しなきゃいけないんですけど、curl コマンドでAPI叩いて、grepawk で結果を切り出して…ってやってます。
「冪等性? 何それ?」状態だし、エラーハンドリングもボロボロで、正直メンテしたくないです…。

リナックス先生

それは典型的な「技術的負債」の作り方ね。
AnsibleはPythonで書かれているわ。つまり、Pythonが書ければ、自分だけのモジュール(Custom Module)を作れるということ。
shell モジュールを卒業して、冪等性もエラー処理も完璧な「本物のモジュール」を作る方法を教えるわ!

本記事では、AlmaLinux 9 をベースに、Pythonを使ったカスタムモジュールの作成方法、引数の設計、冪等性の実装、そしてプロしか知らないデバッグテクニックまでを徹底解説します。

🚀 Ansible応用講座(全8回)

現在地:【第4回】標準機能で足りないなら。PythonによるCustom Module開発入門


第1章:なぜカスタムモジュールを作るのか?

「Ansibleには数千のモジュールがあるのに、わざわざ自作する必要があるの?」
そう思うかもしれません。しかし、現場では以下のようなケースで自作が必要になります。

カスタムモジュールが必要になる3つの壁

  1. 社内独自のAPI/DB操作:
    世の中に公開されていない社内システム(レガシーな在庫管理、独自仕様の認証サーバーなど)を操作する場合。uri モジュールでは複雑すぎる認証フローなどに対応できないことがあります。
  2. 複雑なロジックと計算:
    「現在のメモリ使用量とディスク空き容量から、最適なJVMヒープサイズを計算して設定ファイルに書く」といったロジックをPlaybook(YAML)で書くと、可読性が死にます。Pythonなら数行です。
  3. パフォーマンスと再利用性:
    shell モジュールで10個のコマンドを叩くより、1つのPythonモジュール内で処理した方が高速です。また、Roleとして配布すればチーム全員が簡単に使えます。

💡 プロの経験談
私が過去に作成したのは、「社内のIPアドレス台帳(Excel管理されていたものをDB化したもの)」から空きIPを取得し、DNSに登録するモジュールです。
これをPlaybookで書こうとすると地獄ですが、モジュール化することで my_company_ip: state=present の1行で完結するようになりました。


第2章:モジュールの解剖学(仕組みを知る)

Ansibleのモジュールは、魔法の箱ではありません。
その実体は、「標準入力からJSONを受け取り、標準出力にJSONを返すだけのスクリプト」です。

実行フロー

  1. Ansible本体: Playbookの引数(args)をJSON化し、モジュールファイルと一緒にターゲットノードへ転送する。
  2. ターゲットノード: モジュール(Pythonスクリプト)を実行する。
  3. モジュール: 引数を読み込み、処理を行い、結果(成功/失敗、変更有無、メッセージ)をJSONで標準出力に書き出す。
  4. Ansible本体: 出力されたJSONを解析し、okchanged を判定する。

つまり、Pythonに限らず、RubyでもBashでもGoでもモジュールは作れますが、Ansibleが提供する便利なライブラリ(AnsibleModule クラス)を使えるPythonが圧倒的に楽です。


第3章:【実践】Hello Worldモジュールの作成

それでは、実際にモジュールを作ってみましょう。
まずは「名前を受け取って挨拶を返す」だけのシンプルなモジュールです。

1. ディレクトリの準備

Playbookと同じ階層に library というディレクトリを作ると、Ansibleはそこにあるファイルをモジュールとして認識します。

mkdir library
touch library/my_hello.py

2. コードの記述 (library/my_hello.py)

以下のPythonコードを記述します。
AnsibleModule クラスを使うのがポイントです。

#!/usr/bin/python

from ansible.module_utils.basic import AnsibleModule

def run_module():
    # 1. 引数の定義
    module_args = dict(
        name=dict(type='str', required=True),
        loud=dict(type='bool', required=False, default=False)
    )

    # 2. モジュールオブジェクトの生成
    module = AnsibleModule(
        argument_spec=module_args,
        supports_check_mode=True
    )

    # 3. 処理の実行
    name = module.params['name']
    loud = module.params['loud']

    if loud:
        msg = f"HELLO, {name.upper()}!"
    else:
        msg = f"Hello, {name}."

    # 4. 結果の返却 (JSON出力)
    result = dict(
        message=msg,
        changed=False  # 今回は変更操作がないのでFalse
    )

    # 成功として終了
    module.exit_json(**result)

if __name__ == '__main__':
    run_module()

3. Playbookからの呼び出し

作成したモジュールは、dnfcopy と同じように使えます。

---
- hosts: localhost
  gather_facts: false
  tasks:
    - name: Test my custom module
      my_hello:
        name: "Ansible User"
        loud: true
      register: output

    - name: Show result
      debug:
        msg: "{{ output.message }}"

4. 実行

ansible-playbook test.yml

結果:

TASK [Show result] *************************************************************
ok: [localhost] => {
    "msg": "HELLO, ANSIBLE USER!"
}

自作モジュールが動きました!
library/ に置くだけで認識される手軽さがAnsibleの魅力です。


第4章:【応用】冪等性とCheck Modeの実装

モジュール開発で最も重要なのが「冪等性(Idempotency)」「Check Mode(Dry Run)」への対応です。
これがないと、Ansibleを使う意味が半減します。

シナリオ:ファイルの存在確認モジュール

「指定されたファイルが存在しなければ作成し、存在すれば何もしない(作成日時も更新しない)」というモジュールを作ってみます。

コード例 (library/my_touch.py)

#!/usr/bin/python
import os
from ansible.module_utils.basic import AnsibleModule

def run_module():
    module_args = dict(
        path=dict(type='str', required=True)
    )

    module = AnsibleModule(
        argument_spec=module_args,
        supports_check_mode=True  # Check Modeに対応する宣言
    )

    path = module.params['path']
    result = dict(changed=False)

    # 現状確認(ファイルの有無)
    if not os.path.exists(path):
        # Check Modeなら、実際には作らずに「作るつもりだ」と返して終了
        if module.check_mode:
            module.exit_json(changed=True)

        # 実際の処理
        try:
            with open(path, 'w') as f:
                f.write('')
            result['changed'] = True
        except Exception as e:
            # エラー時は fail_json で終了
            module.fail_json(msg=f"Failed to create file: {str(e)}")
    
    else:
        # ファイルがある場合は何もしない(changed=Falseのまま)
        result['msg'] = "File already exists."

    module.exit_json(**result)

if __name__ == '__main__':
    run_module()

ポイント解説

  1. supports_check_mode=True:
    AnsibleModule の初期化時にこれを指定します。
  2. module.check_mode:
    ansible-playbook --check で実行された場合、この値が True になります。
    この時は、「変更処理(書き込み、削除、API更新など)をスキップ」しつつ、「変更が発生するかどうか(changed=True/False)」を返す必要があります。
  3. module.fail_json:
    処理中に例外が発生した場合は、このメソッドでエラーメッセージとともに終了させます(Ansible側では failed 扱いになります)。

第5章:外部APIとの連携(requestsの利用)

実務で最も多いのが、「社内APIを叩く」ケースです。
Pythonの標準ライブラリ urllib でも可能ですが、可読性を高めるために requests ライブラリを使うのが一般的です。

※ターゲットノード側に pip install requests が必要になります。

API連携モジュールの骨子

try:
    import requests
    HAS_REQUESTS = True
except ImportError:
    HAS_REQUESTS = False

def run_module():
    # ... (省略) ...
    module = AnsibleModule(...)

    if not HAS_REQUESTS:
        module.fail_json(msg="requests library is required")

    url = module.params['api_url']
    token = module.params['api_token']

    # 現状確認 (GET)
    response = requests.get(url, headers={"Authorization": token})
    current_state = response.json()['status']

    if current_state != 'active':
        if not module.check_mode:
            # 変更処理 (POST)
            requests.post(url, json={"status": "active"})
        result['changed'] = True

    module.exit_json(**result)

このように、「GETで現状確認」→「期待値と異なればPOST/PUTで更新」というロジックを組むことで、API操作にも冪等性を持たせることができます。
curl コマンドを垂れ流すよりも遥かに安全でスマートです。


第6章:プロ直伝!デバッグの極意

カスタムモジュールの開発で一番苦労するのは「デバッグ」です。
Playbook経由で実行すると、エラーが起きても詳細が分からず、「An exception occurred during task execution…」といきなり言われて途方に暮れることがあります。

1. ANSIBLE_KEEP_REMOTE_FILES=1

通常、Ansibleは実行後に転送したモジュールファイルを削除します。
環境変数 ANSIBLE_KEEP_REMOTE_FILES=1 を設定して実行すると、ターゲットノードの ~/.ansible/tmp/... にPythonファイルが残ります。

ANSIBLE_KEEP_REMOTE_FILES=1 ansible-playbook test.yml -vvv

ログに表示されたパス(例: /home/user/.ansible/tmp/ansible-tmp-.../AnsiballZ_my_hello.py)に移動し、直接そのファイルをPythonで実行してデバッグできます。

2. args ファイルを使ったローカルデバッグ

実は、ターゲットノードに行かなくても、手元の開発環境でデバッグする方法があります。
AnsibleModule は、引数をJSONファイルから読み込むことができます。

まず、引数を書いたJSONファイル args.json を作ります。

{
  "ANSIBLE_MODULE_ARGS": {
    "name": "Debug User",
    "loud": true
  }
}

そして、Pythonスクリプトの末尾に少し細工をして、標準入力からこのJSONを流し込みます。

python library/my_hello.py < args.json

これで手元で簡単に動作確認ができます。
print() でデバッグ出力しても、Ansible経由だと見えませんが、この方法ならコンソールに表示されます。

⚠️ pdb (Python Debugger) の利用
ローカル実行であれば、import pdb; pdb.set_trace() をコードに埋め込んで、ステップ実行しながら変数の値を調べることも可能です。
これができるようになると、開発効率が爆上がりします。


まとめ:Ansibleは「プラットフォーム」である

お疲れ様でした!
これであなたは、Ansibleの標準機能の枠を超え、Pythonの力を借りてあらゆる操作を自動化できる力を手に入れました。

今回の重要ポイント:

  • shell モジュールは技術的負債。Pythonで書けば資産になる。
  • AnsibleModule クラスを使えば、入出力の処理は自動化される。
  • supports_check_mode=True でドライランに対応させるのがプロの仕事。
  • デバッグ時は ANSIBLE_KEEP_REMOTE_FILES=1 か、ローカルでのJSON流し込みを活用せよ。

カスタムモジュールが作れるようになると、Ansibleは単なる「設定ツール」から、インフラ操作の「共通プラットフォーム」へと進化します。
チームメンバーに「このモジュール使って」と渡すだけで、複雑なAPI操作を安全に実行してもらえるようになるのです。

さて、モジュールを作ったら、次はその品質を担保し続ける必要があります。
「コードを変えたら動かなくなった」を防ぐには、テストの自動化が不可欠です。

次回、応用講座 第5回は「品質を保証する。MoleculeによるRoleの自動テストとCI連携」です。
Ansibleのためのテストフレームワーク Molecule(モレキュール) を使い、Dockerコンテナを使ってPlaybookのテストを自動実行し、GitHub ActionsなどのCIツールと連携させる方法を学びます。
DevOpsエンジニアへの階段をさらに登りましょう。お楽しみに!

▼ PythonでAnsibleを拡張する ▼

モジュール開発環境を作る
「VPS」で自分専用環境

おすすめVPSを見る

開発×インフラの二刀流へ
「ITエンジニア転職」

転職エージェントを見る

コメント