Ansible Moduleの開発 †
Ansibleでは独自のモジュールを組み込み実行することができる。
pythonでプログラムが書ければ、以下のようにtaskの中で作成したモジュールを使用することができる。他にもfilterやlookup pluginsなど、module以外にも独自の拡張ができる。
- my_module: param_a: hello paramb_b: world
module作成は、公式のドキュメントやansibleのmodulesディレクトリ下のデフォルトで用意されているmoduleを見ると良いだろう。
参考
- https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html
- https://github.com/ansible/ansible/tree/devel/lib/ansible/modules
moduleの名前は、他のモジュール名と重複しないように注意する。
moduleの作成 †
まずはシンプルなmoduleを見てみる。
以下は、helloというモジュール名でechoで受け取ったメッセージを返すだけのモジュールである。
# Python2と3で同様の振る舞いをするようなおまじない from __future__ import (absolute_import, division, print_function) __metaclass__ = type # https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_documenting.html#ansible-metadata-block # Ansibleが使用するメタデータ ANSIBLE_METADATA = { # moduleのバージョンではない!metadata schemaのバージョン 'metadata_version': '1.1', # 以下のステータスはAnsible Core Teamのメンバによって判断される 'status': ['preview'], 'supported_by': 'community' } # ドキュメント生成に使用される # 以下でドキュメントを表示できる # $ ansible-doc -M /path/to/moduledir-t module hello DOCUMENTATION = r''' --- module: hello short_description: my hello requirements: description: - echo message options: seealso: author: - moritetu ''' EXAMPLES = r''' - hello: echo: hello world ''' RETURN = r''' message: description: echo message returned: always type: str sample: hello world ''' # AnsibleModuleの読み込み from ansible.module_utils.basic import AnsibleModule def main(): module = AnsibleModule( # moduleのパラメータ argument_spec=dict( echo=dict(type='str', required=True), ), supports_check_mode=True ) if module.check_mode: module.exit_json(**result) result = dict( changed=False, message=module.params['echo'], ) # moduleの出力 module.exit_json(**result) if __name__ == '__main__': main()
このmoduleは以下のような使用方法となる。
- hello: echo: hello world
ドキュメントを記述していれば、ansible-doc
で出力できる。
$ ansible-doc -M library -t module hello > HELLO (/test/ansible/module_test/playbooks_test/library/hello.py) echo message * This module is maintained by The Ansible Community AUTHOR: moritetu METADATA: status: - preview supported_by: community EXAMPLES: - hello: echo: hello world RETURN VALUES: message: description: echo message returned: always type: str sample: hello world
moduleのパラメータタイプ †
- str
- list
- dict
- bool
- int
- float
- path
param_path: $HOME
- raw
そのままの値 - jsonarg
- json
param_json: {"key":"value"}
- bytes
- サイズをhuman readableな指定で書く
- 例:1Mb、2KB、8Kbyte
param_bytes: 1MB #=> 1048576
- bits
- サイズをhuman readableな指定で書く
- 例:1Mb、2Kb、1024bit
param_bytes: 1Mb #=> 1048576
hello.py
import json from ansible.module_utils.basic import AnsibleModule def main(): module = AnsibleModule( argument_spec=dict( _path=dict(type='path', required=False), _raw=dict(type='raw', required=False), _jsonarg=dict(type='jsonarg', required=False), _json=dict(type='json', required=False), _bytes=dict(type='bytes', required=False), _bits=dict(type='bits', required=False), ), supports_check_mode=True ) module.exit_json( _path=module.params['_path'], _raw=module.params['_raw'], _jsonarg=json.loads(module.params['_jsonarg']), _json=json.loads(module.params['_json']), _bytes=module.params['_bytes'], _bits=module.params['_bits'], ) if __name__ == '__main__': main()
param.yaml
- hosts: localhost connection: local gather_facts: no tasks: - hello: _path: ${HOME} _raw: raw _jsonarg: {"hoge":[1,2,3]} _json: {"hoge":[1,2,3]} _bytes: 8Kbyte _bits: 16bit register: facts - debug: msg={{ facts }}
実行結果は以下のとおり。
# ansible-playbook params.yaml PLAY [localhost] **************************************************************************** TASK [hello] ******************************************************************************** ok: [localhost] TASK [debug] ******************************************************************************** ok: [localhost] => { "msg": { "_bits": 16, "_bytes": 8192, "_json": { "hoge": [ 1, 2, 3 ] }, "_jsonarg": { "hoge": [ 1, 2, 3 ] }, "_path": "/home/guest", "_raw": "raw", "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python" }, "changed": false, "failed": false } } PLAY RECAP ********************************************************************************** localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
サンプル param_test.tar.gz
参考
- https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#argument-spec
Argument spec †
required †
パラメータが必須か否かを指定する。
type †
moduleのパラメータで列挙した値を指定する。
default †
パラメータに指定がない場合のデフォルト値。デフォルトはNone
。
fallback †
パラメータが渡されないときにフォールバックされる。
例えば、以下はusername
が指定されないとき、環境変数のANSIBLE_NET_USERNAME
から値を探索する(os.environ
)。
username=dict(fallback=(env_fallback, ['ANSIBLE_NET_USERNAME']))
choices †
選択式のパラメータを提供する。
# str mode=dict(required=True, type='str', choices=['init', 'update']) # list mode=dict(required=True, type='list', choices=['init', 'update'])
# 使用例 # str - mymod: mode: init # list - mymod: mode: ['init']
no_log †
secret情報などに使用し、出力がマスクされる。
secret=dict(required=True, type='str', no_log=True)
# VALUE_SPECIFIED_IN_NO_LOG_PARAMETERという文字列でマスクされる - mymod: secret: password
elements †
type=list
と組み合わせて使用する。
パラメータで指定されたリストの要素をelements
で指定されたタイプとして解釈する。
elm=dict(required=True, type='list', elements='str')
# strとして解釈され変換される - mymod elm: [1,2,3]
aliases †
パラメータに別名を指定できる。
options †
サブパラメータ(階層的な)を持たせたい場合に使用する。
apply_defaults †
options
と一緒に使用する。サブパラメータのデフォルト値を指定する。
removed_in_version †
deprecatedであることを示す。
elm=dict(required=True, type='list', elements='str',aliases=['pkg'], removed_in_version=2.9),
[DEPRECATION WARNING]: Param 'elm' is deprecated. See the module docs for more information. This feature will be removed in version 2.9. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
moduleの終了 †
module実行の終了は、以下のメソッドを呼び出す。
exit_json †
- exit 0で終了
fail_json †
failed
がTrue
にセットされる。msg
の指定が必須である。- exit 1で終了
moduleのロード †
Ansibleがデフォルトでmoduleを探索するパスに設置する、または、ansible.cfgでmoduleのパスを指定しすればよい。そうすればAnsibleがプレイブックを実行時にロードしてくれる。
- 環境変数
ANSIBLE_LIBRARY
~/.ansible/plugins/modules/
/usr/share/ansible/plugins/modules/
ansible.cfgに指定 †
ansible.cfg
のlibrary
というパラメタに自作したmoduleのパスを指定する。
ディレクトリ構成は以下のとおり。
$ tree . ├── ansible.cfg ├── mod │ └── hello.py └── test.yaml 1 directory, 3 files
ansible.cfg
以下では、mod
というディレクトリを相対パスでしている。
[defaults] library = ./mod
以下では、ansible.cfg
があるディレクトリでplaybookを実行する。
$ ansible-playbook test.yaml -v Using /test/ansible/module_test/ansible_cfg_module_test/ansible.cfg as config file PLAY [localhost] ********************************************************************************** TASK [hello] ************************************************************************************** ok: [localhost] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "message": "world"} PLAY RECAP **************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
サンプル ansible_cfg_module_test.tar.gz
libraryのローカルロード †
playbook及び特定のroleに対しmoduleをロードさせることができる。
playbookや特定のroleの下にlibraryフォルダを設定することで可能である。
実際に自作moduleがロードされるか確認する。
テストで使用するソースは以下のとおり。
hello.py
from ansible.module_utils.basic import AnsibleModule def main(): module = AnsibleModule( argument_spec=dict( echo=dict(type='str', required=True), ), supports_check_mode=True ) if module.check_mode: module.exit_json(**result) result = dict( changed=False, message=module.params['echo'], ) module.exit_json(**result) if __name__ == '__main__': main()
実験 プレイブックに適用する †
playbookのあるパスにlibrary
というディレクトリを配置する。
ディレクトリ構成は以下。
$ tree . ├── library │ └── hello.py └── test.yaml 1 directory, 2 files
test.yaml
- hosts: localhost connection: local gather_facts: no tasks: - hello: echo=world
プレイブックを実行する。(簡単のためconnectionはlocal)
$ ls library test.yaml $ ansible-playbook test.yaml -v Using /etc/ansible/ansible.cfg as config file PLAY [localhost] ********************************************************************************** TASK [hello] ************************************************************************************** ok: [localhost] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "message": "world"} PLAY RECAP **************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
実験 特定のroleに適用する †
roleの下にlibrary
というディレクトリを配置する。
$ tree . ├── db.yaml ├── roles │ ├── db │ │ └── tasks │ │ └── main.yaml │ └── web │ ├── library │ │ └── hello.py │ └── tasks │ └── main.yaml └── web.yaml 6 directories, 5 files
web.yaml
- hosts: localhost connection: local gather_facts: no roles: - web
roles/web/tasks/main.yaml
- hello: echo=web
db.yaml
- hosts: localhost connection: local gather_facts: no roles: - db
roles/db/tasks/main.yaml
- hello: echo=db
プレイブックを実行する。
$ ansible-playbook web.yaml -v Using /etc/ansible/ansible.cfg as config file PLAY [localhost] ********************************************************************************** TASK [web : hello] ******************************************************************************** ok: [localhost] => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python"}, "changed": false, "message": "web"} PLAY RECAP **************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
roleがdbの場合はmoduleのロードに失敗しエラーとなる。
$ ansible-playbook db.yaml -v Using /etc/ansible/ansible.cfg as config file ERROR! couldn't resolve module/action 'hello'. This often indicates a misspelling, missing collection, or incorrect module path. The error appears to be in '/test/ansible/module_test/roles_test/roles/db/tasks/main.yaml': line 1, column 3, but may be elsewhere in the file depending on the exact syntax problem. The offending line appears to be: - hello: echo=db ^ here
サンプル roles_test.tar.gz
参考
- https://docs.ansible.com/ansible/latest/dev_guide/developing_locally.html#developing-locally
- https://docs.ansible.com/ansible/latest/reference_appendices/config.html
参考リンク †
- moduleの開発
- moduleの追加とロード
- Ansible Config
- debugger