Ansibleのcommand系が取ってる戦略とは
Ansibleのcommand系で任意のコマンドが実行される場合、Ansibleで事前にそれを予測して何かの対策を立てることはできない。
任意のコマンドなので、どんなコマンドが実行されるのか分からないからだ。
それで、Ansibleではcommand系について二つの戦略を取っている。
- コマンドを実行するタスクは常にchangedになる。
- コマンドの実行結果が0以外のものはfailedになる。
これはAnsibleだけではない、Chefも同様である。
とてもシンプルだが、全てのコマンドに対応ができるベストの戦略とも言える。
冪等性を保つ為には
command系は使わない方がいいだろう。
yumやgitなどのmoduleが使える場合はそれらを使った方がいいのは当たり前だ。
しかし、moduleが提供されてない場合はcommand系を使うしかない。
また、場合によってはcommand系(特にshell)を使った方がplaybookがもっとシンプルになる。
こんな時は、command系を使うべきだ。
command系を使っても冪等性を保つ方法はある。
Ansibleが自動でやってくれないだけだ。
command系で冪等性を保つ方法としてよく使うのがregisterとwhenの組み合わせである。
僕の環境で使っている次のサンプルを見よう。
1 - name: set up vagrant
2 shell: vagrant init
3 args:
4 creates: ~/.vagrant
5
6 - name: check if plugin is installed
7 shell: vagrant plugin list | awk '{print $1}'
8 register: plugin_list
9 changed_when: false
10 failed_when: false
11
12 - debug: msg={{ plugin_list.stdout.split('\n') }}
13
14 - name: install vagrant's plugins
15 shell: vagrant plugin install {{ item }}
16 when: not item in (plugin_list.stdout.split('\n'))
17 with_items:
18 - "{{ vagrant_plugins }}"
vagrantの環境を構築するplaybookである。
homebrewでvagrantを設置するタスクは他のplaybookにある。
4つのタスクで構成されている。
各タスクについて説明する。
- set up vagrant
vagrantを初期化して環境を構築する。しかし、~/.vagrantのディレクトリが存在する場合は、vagrantが初期化されたと判断してこのタスクは実行しない。
~/.vagrantのディレクトリはvagrant initを実行するとき生成されるものだ。
createsはcommandやshellを使うとき冪等性を保つ為によく使われる。
- check if plugin is installed
vagrantにはいくつか有用なpluginが存在する。
Ansibleでvagrantのpluginを設置する為には今のところcommand系を使うしかない。
その時の冪等性を保つ為にvagrantのpluginの一覧を取得して変数に保存して置く。
Ansibleのタスクは変更があったときchangedになり、changedの場合はタスクが実行される。
変更がなければ、okになりタスクは実行されない。
またタスクが失敗した場合はfailedになり、playbookの実行を止まる。
changed_whenとfailed_whenはchangedとfailedの条件を上書きするものだ。
両方falseになっているので、changedにならないしタスクが失敗しない。
冪等性を保つためのregisterで変数を定義する時に定番ように使われる。
- debug
予想通りのデータが保存されているのか確認する。
jinja2の中ではpythonを使える。
plugin_list.stdoutはワンラインでstringの結果を返す。
各値が改行文字(\n)で繋がっている文字列になる。
次のようにdebugコードを追加して確認して見よう。
+ 12 - debug: msg={{ plugin_list }}
+ 13 - debug: msg={{ plugin_list.stdout }}
14 - debug: msg={{ plugin_list.stdout.split('\n') }}
playbookを実行すると次のような結果が表示される。
TASK [mac : debug] ******************************************************************************************************************************************************************
ok: [localhost] => {
"msg": {
"changed": false,
"cmd": "vagrant plugin list | awk '{print $1}'",
"delta": "0:00:00.804493",
"end": "2017-10-08 23:33:40.952168",
"failed": false,
"failed_when_result": false,
"rc": 0,
"start": "2017-10-08 23:33:40.147675",
"stderr": "",
"stderr_lines": [],
"stdout": "sahara\nvagrant-cachier\nvagrant-share",
"stdout_lines": [
"sahara",
"vagrant-cachier",
"vagrant-share"
]
}
}
TASK [mac : debug] ******************************************************************************************************************************************************************
ok: [localhost] => {
"msg": "sahara\nvagrant-cachier\nvagrant-share"
}
TASK [mac : debug] ******************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
"sahara",
"vagrant-cachier",
"vagrant-share"
]
}
12行のdebugの結果からstdoutに注目する。
13行のdebugの結果がそれだ。
14行のdebugではsplitを用いて改行文字('\n')で割っている。
実はplugin_list.stdout.split('\n')の代わりにplugin_list.stdout_linesを使えばいい。
この記事の作成中に気づいたが、jinja2の中ではpythonを使えることを見せるため、そのままにした。
pythonの文法をそのまま使うのが、filterを使うことより使い勝手がいい。
- install vagrant's plugins
変数で設定されたvagrantのpluginをloopで回しながら、設置するタスクである。
次はvagrant_plguinsという変数に設定されたvagrantのpluginのリストだ。
vagrant-shareはデフォルトで設置されるpluginなので、ここには定義しない。
vagrant_plguins:
- sahara
- vagrant-cachier
whenは条件がtrueの場合はタスクを実行するが、falseの場合はタスクをskipする。
vagrant plugin listの中にvagrant_pluginsに指定したpluginが無い場合はタスクが実行される。
もう一つのサンプルも見よう。
1 - name: check if services are running with homebrew
2 shell: brew services list | awk 'NR>1 {print $1, $2}'
3 register: service_list
4 changed_when: false
5 failed_when: false
6
7 - debug: msg={{ service_list.stdout_lines }}
8
9 - debug: msg={{ item + ' started' }}
10 with_items:
11 - "{{ homebrew_services}}"
12
13 - name: run services with homebrew
14 shell: brew services start {{ item }}
15 when: not (item + ' started') in service_list.stdout_lines
16 with_items:
17 - "{{ homebrew_services}}"
homebrew_services:
- mariadb
- mongodb
- postgresql
homebrewのサービス一覧を確認してhomebrew_servicesにて指定されたサービスが起動してない場合は、該当サービスを起動するplaybookである。
まず、サービスの状態を確認する必要がある。
brew services list
Name Status User Plist
postgresql started devtopia /Users/devtopia/Library/LaunchAgents/homebrew.mxcl.postgresql.plist
mariadb started devtopia /Users/devtopia/Library/LaunchAgents/homebrew.mxcl.mariadb.plist
mongodb started devtopia /Users/devtopia/Library/LaunchAgents/homebrew.mxcl.mongodb.plist
この一覧から必要な情報は実際のサービス名とその状態(started、stopped)だけだ。
カラム名は要らないから排除し、サービス名とその状態だけを取得するコマンドで組み合わせる。
brew services list | awk 'NR>1 {print $1, $2}'
postgresql started
mariadb started
mongodb started
サービス名(空白)startedの場合はサービスが起動されているので、このタスクをskipする。
要点
- ターゲットノードに影響を及ぼすタスクでcommand系を使う場合は、whenを用いて実行可否判定をする。
- そのwhenで使う変数をcommand系とregisterを組み合わせて用意する。
- この場合、条件式で使うための変数を作ることだけなので、ターゲットノードに影響はない。
- ターゲットノードに影響が無い変数保存用のタスクがcommand系を使ったとしてchanged、failedになるのは意味がない。
- それで、changed_whenとfailed_whenをfalseで上書きする。