こんにちは、cloudpack の Sebastian です。
はじめに
Ansibleのmoduleは基本的にはpythonで記述を行います。
実はmodule自体は所定の形式でレスポンスを返せばpythopnで無くても記載は可能なのですが、公式が pythonで書かれている事と、ansibleがpythonの使用を前提に記載されている事からpythonを用いないと公式にcommitは出来ません。
また、pythonの方が相性は良いので今回はそのままpythonで書きます。
moduleが実行されるまで
ansibleのmoduleを書く前に、まずansibleのmoduleがどのように実行されるのかを知らねば話になりません。
そういう訳でmoduleがどの様に実行されていくのかを追いかけてみます。
ansibleの導入後、サーバーに対してpingモジュールを用いてremote serverへの疎通応答を求めて見ます。-vvvv
はデバッグ出力用のオプションです。
ansible hogehoge -m ping -vvvv
するとレスポンスが以下の様に返ります。
ESTABLISH CONNECTION FOR USER: huga REMOTE_MODULE ping EXEC ['ssh', '-C', '-tt', '-vvv', '-o', 'ControlMaster=auto', '-o', 'ControlPersist=60s', '-o', 'ControlPath=/Users/foo/.ansible/cp/ansible-ssh-%h-%p-%r', '-o', 'StrictHostKeyChecking=no', '-o', 'Port=22', '-o', 'IdentityFile="/etc/ansible/key/bar.pem"', '-o', 'KbdInteractiveAuthentication=no', '-o', 'PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey', '-o', 'PasswordAuthentication=no', '-o', 'User=huga', '-o', 'ConnectTimeout=10', 'xxx.xxx.xxx.xxx', "/bin/sh -c 'mkdir -p $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901 && chmod a+rx $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901 && echo $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901'"] PUT /var/folders/0x/g4lkrc4528x_rqpzr47d2k0r0000gp/T/tmpxqYhf1 TO /home/huga/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901/ping EXEC ['ssh', '-C', '-tt', '-vvv', '-o', 'ControlMaster=auto', '-o', 'ControlPersist=60s', '-o', 'ControlPath=/Users/foo/.ansible/cp/ansible-ssh-%h-%p-%r', '-o', 'StrictHostKeyChecking=no', '-o', 'Port=22', '-o', 'IdentityFile="/etc/ansible/key/bar.pem"', '-o', 'KbdInteractiveAuthentication=no', '-o', 'PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey', '-o', 'PasswordAuthentication=no', '-o', 'User=huga', '-o', 'ConnectTimeout=10', 'xxx.xxx.xxx.xxx', u"/bin/sh -c 'LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/huga/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901/ping; rm -rf /home/huga/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901/ >/dev/null 2>&1'"] hogehuga | success >> { "changed": false, "ping": "pong" }
1行目の
はxxx.xxx.xxx.xxxに接続しhugaユーザーにてログインを行う事を明示しています。
2行目の
はpingモジュールを用いる事を明示しています。
問題は3行目以降です。
EXEC ['ssh', '-C', '-tt', '-vvv', '-o', 'ControlMaster=auto', '-o', 'ControlPersist=60s', '-o', 'ControlPath=/Users/foo/.ansible/cp/ansible-ssh-%h-%p-%r','-o', 'StrictHostKeyChecking=no', '-o', 'Port=22', '-o', 'IdentityFile="/etc/ansible/key/bar.pem"', '-o', 'KbdInteractiveAuthentication=no', '-o','PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey', '-o', 'PasswordAuthentication=no', '-o', 'User=huga', '-o', 'ConnectTimeout=10','xxx.xxx.xxx.xxx', "/bin/sh -c 'mkdir -p $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901 && chmod a+rx $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901 && echo $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901'"]
EXEC
と書いてあるからには何らかのexecuteだと判断出来ます。
[]で囲まれている範囲を見てみるとssh
にて何らかの実行を行っています。
見やすく整えると以下のようになります。
ssh -C -tt -vvv -o ControlMaster=auto -o ControlPersist=60s -o ControlPath=/Users/foo/.ansible/cp/ansible-ssh-%h-%p-%r -o StrictHostKeyChecking=no -o Port=22 -o IdentityFile="/etc/ansible/key/bar.pem" -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=huga -o ConnectTimeout=10 xxx.xxx.xxx.xxx /bin/sh -c 'mkdir -p $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901 && chmod a+rx $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901 && echo $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901'
幾つかのsshのオプションが並び最後に/bin/sh
があります。
つまりssh経由でリモートホストに対してcommandを投げている訳です。
内容は以下の様になります。
mkdir -p $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901 chmod a+rx $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901 echo $HOME/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901
見るとユーザーホーム以下に一時directoryを作成しています。
4行目は
となっています。PUT
と在るようにremote server内の先ほど作成した一時directory内にping
をputしています。
ここまでで分かると思いますが、ansibleのmoduleはremote serverにmodule自身が転送されてremote server上で実行されます。
実際に5行目にはEXEC
の指定で実行commandが記載されています。
u"/bin/sh -c 'LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 /usr/bin/python /home/huga/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901/ping; rm -rf /home/huga/.ansible/tmp/ansible-tmp-1419504192.56-262299774391901/ >/dev/null 2>&1'"
内容を追うと、言語環境が英語環境で文字コードがutf-8にてpythonを用いて先ほどのpingを実行しています。
また、その後rm
にてpingファイルの削除が実行されています。
最後にjsonにて文字列が返って来ています。
hogehuga | success >> { "changed": false, "ping": "pong" }
これはansibleのmoduleが吐き出すレスポンスの内容です。
纏
- ansibleはmoduleをremote serverに転送して実行を行う
- 転送先は一時directoryとして日付付きで作成される
- us-US.UTF-8が指定されたpythonにて実行される
- moduleは実行後、速やかに削除される
- moduleのresponseはjsonで返却される
※ この為、ansibleはsimple json moduleが必須になる
補足
当然ながらこれらの挙動はansibleの設定次第で変更されるものです。
転送先の一時directoryはansible.cfgに定義されるremote_tmpによります。
EXECにて用いられるshellはexecute設定で変更されるのでzshやcshに変更する事も可能でしょう。
inventoryのhost指定でansible_*_interpreterの指定を行えばpythonでは無くruby等の実行も可能ですが基本はpythonで行うことが好ましいです。
ansible module作る
tutorial
では、ansible module実際に作ってみます。
ansibleのTutorialを見るとexampleが書いてあります。
#!/usr/bin/python import datetime import json date = str(datetime.datetime.now()) print json.dumps({ "time" : date })
はい、かなりシンプルですね。
jsonを用いてdateを返すだけのmoduleです。
実行してみると以下の様に返ります。
ansible hogehuga -m time -v hogehuga | success >> { "time": "2014-12-25 12:02:59.370913" }
ただのpythonファイルなので単純に実行してみても構いません
chmod +x time ./time {"time": "2014-12-25 21:04:14.466017"}
ansible_module class library
tutorialではjsonを自分で記載していましたが、実は最初からansible用のlibraryが用意されています。
このlibraryを用いると引数を処理したりjsonにてresponseを返したりはそのまま行えます。
試しに、必須引数message
を指定すると指定した引数message
の内容をそのまま返却するだけのmoduleを作成してみます。
#!/usr/bin/python # -*- coding: utf-8 -*- from ansible.module_utils.basic import * DOCUMENTATION = ''' --- module: echo short_description: This is a sample echo module description : - This module is module which just returns the message just as it is. version_added: "1.0" options: message: description: - echo message. required: true default: null ''' EXAMPLES = ''' - action: echo message=arg1 ''' ############################################################################### # # ansible module main function # ############################################################################### def main(): module = AnsibleModule( argument_spec=dict( message=dict(required=True) , ) , supports_check_mode=True ) try : # # 受け取ったパラメータのチェック msg_param = module.params['message'] if module.check_mode: module.exit_json(changed=False , msg=msg_param , mode='check mode') else : module.exit_json(changed=False , msg=msg_param) except Exception as e: module.fail_json(msg=str(e)) ############################################################################### # # call main # ############################################################################### main()
はい、こんな感じです。
これをechoとして保存しmoduleとして実行してみます。
ansible hogehuga -m echo -a "message='hello ansible world'" hogehuga | success >> { "changed": false, "msg": "hello ansible world" }
引数の処理 arguments_spec
内部でinstance化しているClass AnsibleModuleがansibleのmoduleを記載する為に用いるclassになります。
module = AnsibleModule( argument_spec=dict( message=dict(required=True) , ) )
arguments_spec
にてdictにて指定されているmessageが引数名を表しています。
更に、messageにイコールで指定されるdictは引数messageに関しての詳細を示しています。
ここではrequired
にTrue
が指定されているため、必須引数である旨を定義しています。
以下に引数の制御に用いれる内容の一覧を示します。
指定内容 | 意味 | 例 |
---|---|---|
require | 必須指定 | required=True |
default | 指定引数が無指定の場合に用いる値を指定します | default=None |
choices | 配列にて指定して指定配列の中から値を指定させる様に指定します | choices=[‘start’ . ‘stop’ , ‘restart’] |
type | 引数の型を指定します | type=’int’ |
aliases | 引数名のエイリアスを作成します | aliases[‘name’] |
responseの処理
AnsibleModule classはレスポンスはdict指定で連想配列を渡すと自動的にjsonで返してくれます。
echoスクリプトで指定されているmodule.exit_json(changed=False , msg=msg_param)
が実際にresponseを返している行です。
changedはmoduleが実行された結果、変更されたか否かの指定を行います。
特に何も指定しない場合はFalseが返されるようになっています。
ただし、何らかの要因により処理に失敗した場合は別です。
exit_jsonは出力結果をsuccessとして返すため、failの際は別途処理が必用です。
echoスクリプト内ではmodule.fail_json(msg=str(e))
がfailでの返却を行うmethodの指定です。
check modeとsupports_check_mode
実はansibleにはcheck mode、つまりdry runモードがあります。
しかし、module側でcheck modeに対応を行い、check modeが指定された際にcheckのみを行い、変更を行わない処理の実装を行わなければなりません。
check_modeに対応するためにはまず、AnsibleModuleの引数にてsupports_check_mode=True
の指定を行うつ様はあります。
Falseが指定されていたり指定がない場合は以下の様に返ります。
ansible hogehuga -m echo -a "message='hello ansible world'" --check hogehuga | skipped
また、実際にsupports_check_mode=True
の指定があってもそのまま実行しては変更されてしまいます。
よって、check modeに対応する際はAnsibleModuleのcheck_mode
methodにて実行時にcheck modeであるかの確認を行います。
テスト用のスクリプトではcheck modeであった場合はresponseのmodeに「check mode」と返る様にしています。
よって、check modeで実行すると以下の様に返ります。
ansible hogehuga -m echo -a "message='hello ansible world'" --check hogehuga | success >> { "changed": false, "mode": "check mode", "msg": "hello ansible world" }
documentation
ansibleにはansible-docと言うcommandがあります。
このcommandを用いるとansibleでは各moduleのdocumentが表示されるようになっています。
折角なのでdocumentも書いておきます。
因みに、ここでmoduleにdocumentが書かれていないと漏れ無く、ansible-doc
でエラーが吐かれます。
ansibleのdocumentationはそのままpythonのご作法に従って書かれているのでDOCUMENTATION = ''' 〜〜〜〜 '''
で書けばOKです。
注意点としてはansible上でのpythonの実行環境はあくまで英語環境のutf-8が指定されているので日本語での指定はNGです。
DOCUMENTATION = ''' --- module: echo short_description: This is a sample echo module description : - This module is module which just returns the message just as it is. version_added: "1.0" options: message: description: - echo message. required: true default: null '''
以上の様に記載しておき、ansible-doc
にて表示を行うと整形されたdocumentが出力されます。
ansible-doc echo > ECHO This module is module which just returns the message just as it is. Options (= is mandatory): = message echo message. [Default: None] - action: echo message=arg1
元記事はこちらです。
「ansibleのmoduleの作成」