Ansibleのモジュール開発(テスト編)

2016.05.25

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは、藤本です。

過去3エントリでAnsibleモジュールを開発する上でのルールや開発に便利なユーティリティライブラリ、実際にAnsibleモジュールを開発する方法をご紹介しました。

今回はAnsibleモジュール開発において、予期せぬエラー時に失敗を返すことができますが、何が起きたのか、何が原因で失敗したのかトレースに手こずりました。そこで今回はデバッギングに役立ったロギング、Ansibleが提供するテストツールをご紹介します。

ロギング

プログラム開発におけるログ出力は適切な処理フローであるかのトラッキングや、トラブルの切り分けに必要なものとなります。AnsibleもAnsibleModuleクラスのロギングメソッドを利用することでモジュール内でロギングが可能となっています。ロギングメソッドはOSのSyslogを利用するため、ログファイルや外部ホストへ出力することでモジュールの実行情報を取得することができます。
またロギングメソッドはdebuglogの2つのレベルの出力が可能となっています。debugはAnsibleの設定ファイルのgeneralセクションにdebug = True、もしくは環境変数にANSIBLE_DEBUG=Trueを指定した場合のみログ出力が実行されます。

それではソースコードでロギング機能を確認しましょう。

library/logging/logging.py
#!/usr/bin/env python
from ansible.module_utils.basic import *

module = AnsibleModule({})
module.log("info log")
module.debug("debug log")

module.exit_json()
# ansible -m logging centos6
centos6 | SUCCESS => {
    "changed": false
}

# ssh -F ssh_config centos6 "sudo tail /var/log/messages"
:
May 22 03:01:02 localhost ansible-logging: Invoked
May 22 03:01:02 localhost ansible-logging: info log

# ANSIBLE_DEBUG=True ansible -m logging centos6
 92194 1463886212.64124: starting run
 92194 1463886212.83540: in VariableManager get_vars()
 92194 1463886212.83563: done with get_vars()
 92194 1463886212.85060: getting the remaining hosts for this loop
:
centos6 | SUCCESS => {
    "changed": false
}
:
 92194 1463886213.98306: checking for any_errors_fatal
 92194 1463886213.98313: done checking for any_errors_fatal
 92194 1463886213.98321: running handlers
 92194 1463886213.98344: RUNNING CLEANUP

# ssh -F ssh_config centos6 "sudo tail /var/log/messages"
:
May 22 03:01:02 localhost ansible-logging: Invoked
May 22 03:01:02 localhost ansible-logging: info log
May 22 03:02:20 localhost ansible-logging: Invoked
May 22 03:02:20 localhost ansible-logging: info log
May 22 03:02:20 localhost ansible-logging: debug log

このようにSyslogによりCentOSの場合、/var/log/messagesにメッセージが出力されました。またdebugメソッドを利用したメッセージはdebugオプション指定した場合のみ出力されます。debugオプションが作用するのはモジュールだけに限らず、Ansibleの処理に対しても有効となり、多くの情報を取得する事が可能となります。

モジュールテストツール

AnsibleはGithubリポジトリ内でモジュールのテストツールを提供しています。Ansibleのモジュール開発の公式ドキュメントにて紹介されています。

テストツールはローカルホストに対して、モジュールを実行し、可読性の高い形式による実行結果を取得したり、Pythonデバッガによるデバッグモードでモジュールを実行したりすることができます。

公式ドキュメントの手順でインストール、セットアップを行います。

# git clone git://github.com/ansible/ansible.git --recursive
Cloning into 'ansible'...
remote: Counting objects: 120047, done.
remote: Compressing objects: 100% (17/17), done.
remote: Total 120047 (delta 8), reused 0 (delta 0), pack-reused 120030
Receiving objects: 100% (120047/120047), 35.91 MiB | 1.10 MiB/s, done.
Resolving deltas: 100% (74211/74211), done.
Submodule 'lib/ansible/modules/core' (https://github.com/ansible/ansible-modules-core) registered for path 'lib/ansible/modules/core'
Submodule 'lib/ansible/modules/extras' (https://github.com/ansible/ansible-modules-extras) registered for path 'lib/ansible/modules/extras'
Cloning into 'lib/ansible/modules/core'...
remote: Counting objects: 35511, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 35511 (delta 6), reused 2 (delta 2), pack-reused 35496
Receiving objects: 100% (35511/35511), 8.38 MiB | 847.00 KiB/s, done.
Resolving deltas: 100% (23377/23377), done.
Submodule path 'lib/ansible/modules/core': checked out 'a64d72d7bc1d1e37afca3159628e933dcf52abf8'
Cloning into 'lib/ansible/modules/extras'...
remote: Counting objects: 33051, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 33051 (delta 0), reused 0 (delta 0), pack-reused 33049
Receiving objects: 100% (33051/33051), 7.18 MiB | 358.00 KiB/s, done.
Resolving deltas: 100% (22006/22006), done.
Submodule path 'lib/ansible/modules/extras': checked out 'd2900e856b7f8a27631638dd4024c22b947dc27a'

# source ansible/hacking/env-setup
running egg_info
creating lib/ansible.egg-info
writing requirements to lib/ansible.egg-info/requires.txt
writing lib/ansible.egg-info/PKG-INFO
writing top-level names to lib/ansible.egg-info/top_level.txt
writing dependency_links to lib/ansible.egg-info/dependency_links.txt
writing manifest file 'lib/ansible.egg-info/SOURCES.txt'
reading manifest file 'lib/ansible.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
no previously-included directories found matching 'v2'
no previously-included directories found matching 'docsite'
no previously-included directories found matching 'ticket_stubs'
no previously-included directories found matching 'packaging'
no previously-included directories found matching 'test'
no previously-included directories found matching 'hacking'
no previously-included directories found matching 'lib/ansible/modules/core/.git'
no previously-included directories found matching 'lib/ansible/modules/extras/.git'
writing manifest file 'lib/ansible.egg-info/SOURCES.txt'

Setting up Ansible to run out of checkout...

PATH=/root/ansible/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
PYTHONPATH=/root/ansible/lib:
MANPATH=/root/ansible/docs/man:

Remember, you may wish to specify your host file with -i

Done!

# chmod +x ansible/hacking/test-module

次にAnsibleのモジュール開発(実践編)で実装したSwapモジュールをテストツールにより実行します。

# cd ansible/hacking/
# ./test-module -m ~/swap.py -a filepath=/swap
* including generated source, if any, saving to: /root/.ansible_module_generated
* ziploader module detected; extracted module source to: /root/debug_dir
***********************************
RAW OUTPUT

{"invocation": {"module_args": {"filepath": "/swap", "size": null}}, "changed": true}


***********************************
PARSED OUTPUT
{
    "changed": true,
    "invocation": {
        "module_args": {
            "filepath": "/swap",
            "size": null
        }
    }
}

このようにAnsibleモジュールの実行結果を受け取ることができます。また標準出力にある.ansible_module_generatedはリモート実行用スクリプト、debug_dirにはリモート実行で必要最小限のファイル群が生成されます。

デバッグモード

デバッグモードは--debuggerオプションでPythonのデバッガーツール(pdbなど)を利用することで対話型のデバッグモードでモジュールを実行することが可能です。

# ./test-module -m ~/swap.py -a filepath=/swap -D /usr/lib64/python2.6/pdb.py
* including generated source, if any, saving to: /root/.ansible_module_generated
* ziploader module detected; extracted module source to: /root/debug_dir
> /root/debug_dir/ansible_module_swap.py(37)<module>()
-> '''
(Pdb) help

Documented commands (type help <topic>):
========================================
EOF    bt         cont      enable  jump  pp       run      unt
a      c          continue  exit    l     q        s        until
alias  cl         d         h       list  quit     step     up
args   clear      debug     help    n     r        tbreak   w
b      commands   disable   ignore  next  restart  u        whatis
break  condition  down      j       p     return   unalias  where

Miscellaneous help topics:
==========================
exec  pdb

Undocumented commands:
======================
retval  rv

(Pdb) where
  /usr/lib64/python2.6/bdb.py(372)run()
-> exec cmd in globals, locals
  <string>(1)<module>()
> /root/debug_dir/ansible_module_swap.py(37)<module>()
-> '''

(Pdb) list 84
 79  	        self.filepath = module.params['filepath']
 80
 81  	        if not self.size:
 82  	            self.size = self._get_memsize()
 83
 84  	    def create_swapfile(self):
 85  	        if os.path.exists(self.filepath):
 86  	            return False
 87
 88  	        cmd = [self.CREATE_SWAPFILE_CMD, '-l', self.size, self.filepath]
 89  	        rc, out, err = self.module.run_command(cmd)

(Pdb) break 85
Breakpoint 1 at /root/debug_dir/ansible_module_swap.py:85

(Pdb) continue
> /root/debug_dir/ansible_module_swap.py(85)create_swapfile()
-> if os.path.exists(self.filepath):

(Pdb) where
  /usr/lib64/python2.6/bdb.py(372)run()
-> exec cmd in globals, locals
  <string>(1)<module>()
  /root/debug_dir/ansible_module_swap.py(232)<module>()
-> main()
  /root/debug_dir/ansible_module_swap.py(221)main()
-> if swap.create_swapfile():
> /root/debug_dir/ansible_module_swap.py(85)create_swapfile()
-> if os.path.exists(self.filepath):

(Pdb) print self.filepath
/swap

(Pdb) q

pdbを利用したことがない方には分かりづらいですが、ブレークポイントを設定して、ブレークポイントまで処理を進めて、変数をインスペクションといったことを実施しています。その結果、引数で渡したfilepathの値/swapを取得できていることがわかります。

まとめ

いかがでしたでしょうか?
Ansibleの機能を利用したAnsibleモジュールのロギング、テストツールによるデバッギング方法をご紹介しました。
私個人の見解ですが、ロギング、テストツールによるデバッグによりトラブルの解析や単発の動作確認は可能ですが、ロギングも、テストツールも試験対象OSにログインして確認、実行する必要があり、継続的な開発で利用する試験ツールとしては物足りません。モジュールの動作対象OS、対象OSバージョンの拡充、OS、ミドルウェアの新バージョンへの対応を追従しているとCI、特に継続的な試験環境が必要となります。
継続的な試験を行うためには、リモートから設定状況を確認できるツール(ServerSpecなど)や、ロギングによるSyslogを外部に集約し、ロギングによるログメッセージを解析する仕組みなどと併用することがベストだと考えています。

4回に渡って、Ansibleのモジュール開発についてご紹介しました。Ansibleの既存の仕組みを利用すれば、簡単にモジュールを開発できるんだということが伝われば幸いです。