Ansible

Ansible Moduleの開発

Ansibleでは独自のモジュールを組み込み実行することができる。
pythonでプログラムが書ければ、以下のようにtaskの中で作成したモジュールを使用することができる。他にもfilterやlookup pluginsなど、module以外にも独自の拡張ができる。

- my_module:
    param_a: hello
    paramb_b: world

module作成は、公式のドキュメントやansibleのmodulesディレクトリ下のデフォルトで用意されているmoduleを見ると良いだろう。

参考

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のパラメータタイプ

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

サンプル fileparam_test.tar.gz

参考

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

fail_json

moduleのロード

Ansibleがデフォルトでmoduleを探索するパスに設置する、または、ansible.cfgでmoduleのパスを指定しすればよい。そうすればAnsibleがプレイブックを実行時にロードしてくれる。

ansible.cfgに指定

ansible.cfglibraryというパラメタに自作した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

サンプル fileansible_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

サンプルfileplaybooks_test.tar.gz

実験 特定の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

サンプル fileroles_test.tar.gz

参考

参考リンク


トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
目次
ダブルクリックで閉じるTOP | 閉じる
GO TO TOP