Ansibleのモジュール開発(テスト編)
はじめに
こんにちは、藤本です。
過去3エントリでAnsibleモジュールを開発する上でのルールや開発に便利なユーティリティライブラリ、実際にAnsibleモジュールを開発する方法をご紹介しました。
今回はAnsibleモジュール開発において、予期せぬエラー時に失敗を返すことができますが、何が起きたのか、何が原因で失敗したのかトレースに手こずりました。そこで今回はデバッギングに役立ったロギング、Ansibleが提供するテストツールをご紹介します。
ロギング
プログラム開発におけるログ出力は適切な処理フローであるかのトラッキングや、トラブルの切り分けに必要なものとなります。AnsibleもAnsibleModuleクラスのロギングメソッドを利用することでモジュール内でロギングが可能となっています。ロギングメソッドはOSのSyslogを利用するため、ログファイルや外部ホストへ出力することでモジュールの実行情報を取得することができます。
またロギングメソッドはdebug
、log
の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の既存の仕組みを利用すれば、簡単にモジュールを開発できるんだということが伝われば幸いです。