What is it, naokirin?

簡単なAnsibleのモジュールを作成する手順

Ansibleでは数多くのモジュールが提供されており、大抵はモジュールの簡単な組み合わせで解決することが多いかと思います。また特定の設定をまとめていく場合、まずはロールを利用すると思います。

一方で、既存のモジュールの単純な組み合わせでは対処しにくく、よりプログラム的な処理をしたい場合、シェルスクリプトで頑張って書くといったことも増えてしまいます。そういう場合は、Pythonスクリプトとして書くことができるモジュールで書いてしまうのも1つの手になると思います。

複雑なモジュールは一旦置いておき、今回はシンプルな1スクリプトファイルのモジュールを作成する手順を説明します。PythonPowerShellなどで書くことができますが、Pythonを今回は利用します。

かなり前にAnsibleのモジュールを書いたことがあるので、そちらの知識とドキュメントの内容をもとに記載しています。

かなり前に作ったモジュール
GitHub - naokirin/ansible-apt-cyg-module

ドキュメントは以下を参照してください。
docs.ansible.com

まずは、そもそもモジュールの開発が必要?

モジュールはある程度のルールさえわかれば比較的簡単に作れてしまいます。

ただし、Pythonコードになるのと、自前で開発することになるので、自前で保守したりする必要があり、また既存のモジュールの便利な恩恵などを受けられなくなります。実際に、Ansibleにも以下のような「Should you develop a module?(モジュールを開発する必要がありますか?)」というドキュメントがあったりします。

Should you develop a module? — Ansible Documentation

ここでは以下の4つを確認して、どれも「いいえ」ないなら、開発すべきとなっています

  1. すでに似たようなモジュールがあるか?
  2. モジュールの代わりに、アクションプラグインを用いる、または開発するべきか?
  3. モジュールの代わりに、ロールを使用するべきか?
  4. 単一のモジュールの代わりに、モジュールのコレクションを作るべきか?

例えばですが、あなたが何らかのプログラミング言語向けの新しいパッケージ管理ツールを作り、そのツールを利用してパッケージ管理するAnsibleの機能を作りたいというシチュエーションを考えましょう。

その場合、1. は新しく作ったばかりなら「いいえ」でしょう。次に、2. 、3. も「いいえ」でしょう。4. も既存のモジュールの組み合わせでは難しいことが予想されます。

このように、既存のモジュールがなく、また既存のモジュールの組み合わせでは担保できない、べき等性といった難しい要件に対処する必要がある場合、モジュールの開発をすることが妥当だと考えられます。

モジュールのスクリプトを書いてみる

早速ですが、モジュールのスクリプトを書いてみましょう。

モジュールのスクリプトの大まかな構成

モジュールのスクリプトは以下のようになります。

#!/usr/bin/env python

DOCUMENTATION = r'''
  ここにモジュールのドキュメントを記載する
'''

EXAMPLES= r'''
  ここにモジュールの利用例を記載する
'''

RETURN = r'''
  ここに戻り値の例を記載する
'''

# 以降にモジュールで実際に実行する際のコードを書く

def run_module():
    # ...

def main():
    run_module()

if __name__ == '__main__':
    main()

DOCUMENTATION はモジュールのドキュメントです。Ansibleでフォーマットが定められているため、こちらを守って書く必要があります。ちなみに、ansible-doc というコマンドでこのドキュメントが表示されるようになっています。

Module format and documentation — Ansible Documentation

EXAMPLES は実際にPlaybookなどで使用する際の例を記載します。
RETURN は、モジュールが返す値がある場合に、その値の例を記載します。

これらがなくても問題はありませんが、記載しておくほうが好ましいでしょう。

それでは、次から実際にモジュールで実行される処理を書いていきます。

モジュールの処理を実装してみる

モジュールの処理を実装してみます。

from ansible.module_utils.basic import AnsibleModule

def run_module:
    states = ['anything', 'latest']

    # AnsibleModuleが、モジュールの一般的な機能をまとめて提供してくれるので利用する
    module = AnsibleModule(
        # argument_spec でモジュールの引数を定義する
        argument_spec=dict(
            name=dict(default=None, required=True),
            state=dict(default='latest', choices=states)
        ),
        # チェックモードがある場合はTrueにする
        supports_check_mode=True
    )

    # モジュールに渡された引数を取得する
    name = module.params['name']
    state = module.params['state']

    # チェックモードか確認する
    if module.check_mode:
        # チェックモードなら変更ありで、メッセージで 'ok!' と返す
        module.exit_json(changed=True, msg='ok!')

    try:
        # 特定の処理
    except Exception as e:
        # モジュールの実行が失敗したことにする
        module.fail_json(msg=str(e))

    # 変更ありで、メッセージで 'ok!' と返す
    module.exit_json(changed=True, msg='ok!')

モジュール特有の機能を除き、一般的なモジュールの機能のほとんどが、 AnsibleModule によって提供されている機能で対応できます。そのため、上記のようなテンプレ的な処理にモジュール固有の実装をするだけでモジュール自体は実装することができます。

モジュールをとりあえず試してみる

モジュールを開発中にとりあえず試しで動かしてみたいとき、わざわざ専用のPlaybookなどを用意するのは手間だと思います。

そこでAnsible外でモジュールを実行できるツールが提供されています。
ansible/hacking at devel · ansible/ansible · GitHub

$ANSIBLE_HOME/hacking/test-module -m ./library/test-module -a "version=0.1.0 prefix=$HOME/bin"

上記のように呼び出すと開発中にモジュール単体を実行することができます。当たり前ですが、このとき、Pythonが実行できる環境である必要があります。

モジュールを手元で利用する

公開はさておき、実際にPlaybookで使おうと思った場合、どうすればよいでしょうか?

その場合、大きく4つのパターンになります。

  1. Ansibleのプロジェクトに library というディレクトリを作り、そこに配置する
  2. 環境変数ANSIBLE_LIBRARY に指定されているパスに配置する
  3. ansible.cfglibrary に指定されたパスに配置する
  4. ansible や ansible-playbookコマンドの--module-path` のオプションに指定されたパス

基本的には、プロジェクト固有なら 1. 、プロジェクト間で共有して利用するなら 2. か 3. となると思います。

まとめ

今回は、非常に簡単なAnsibleのモジュールのPythonによる作成の方法について説明してみました。

まずはモジュールの開発が必要かを考えたうえで、必要なら開発がよいでしょう。

AnsibleModuleについては必要最小限で、説明を省いたものも多いので、気になる方は以下を確認してみてください。
Ansible モジュールのアーキテクチャー — Ansible Documentation