基本的にAnsible側に制御を委ねることで冪等性の保持やエラーハンドリングが成されるので、やりたいtaskに対してまずはmoduleを探すべき(sed
を使わずにlineinfile
モジュールを使えないかとか)ではあるのだが、どうしてもmoduleで実現できない場合にシェルコマンド直書きになってしまう場合はある。 てかコマンド書いちゃうならAnsibleじゃなくてシェルスクリプトでいいじゃんって話でさ。
※追記 2017-04-05 17:00
記事執筆時点から時を経た現在の自分の見解としては、shell, command両モジュールの活用には以下の面から否定的であり、なるべくコマンド実行は行わないべきと考えています。
- 冪等性の保証が困難になる。
- Ansible Playbookは実現されるべきシステムの「状態」を示した「実行可能なドキュメント」という側面があるが、コマンドが記載されている場合は実現される「状態」が判別しづらくなる。
- (冗談めかして書いてはみたが)コマンドによるデプロイを行いたいのであればシェルスクリプトでよい。Ansibleを何のために使っているのか?
commandではなくshellを使う
シェルコマンド直書きする場合によく話題になることとして、command
とshell
のどちらのモジュールを使うべきかというのがあるが、shell
を使う方が間違いない場合が多い。相違点としては、公式ドキュメント上でcommand
が"It will not be processed through the shell"と表現されている通り、シェルを通すのでシェルの機能が使える点。command
が環境変数を読めない、&
やパイプ、リダイレクトが使えないのに対し、shell
ではこれらが使用できる。
シェルを通さないのでcommand
には環境に左右されず実行できるという利点はあるが、あまりそれが必要になる場面も浮かばないので、迷うようならshell
だけ使っていても良いのではないかと。
※追記 2016-05-19 08:45
Ansible Docsには以下の記述があるので、汎用性や安全性を高めるのであればcommand
の方が望ましい。あくまでパイプ等のシェル機能がどうしても必要な場合、あるいは楽をしたい場合はshell
という位置付けの方が良さそうです。
The command module is much more secure as it’s not affected by the user’s environment.
エラーハンドリング
Ansibleはリターンコードが0以外の場合にfailed
と判断するが、シェルコマンドを実行させる際にこの条件だと困ることがある。ignore_error: True
を使ってエラーを完全に無視させることも可能だが、register:
とfailed_when:
を使った方がより細かく制御できる。
- name: check default ruby version
shell: /home/{{ user_name }}/.rbenv/bin/rbenv version | grep {{ ruby_version }}
register: ruby_version_check
failed_when: ruby_version_check.rc not in [0, 1]
register:
は指定した名前の変数にコマンドの実行結果をregisterする。公式ドキュメント上での記載はおそらくここになると思うものの、あまり詳しくなくてモンニョリするのだが、キーバリュー形式でいくつかの値が収まっており、.hoge
と記述することで値を取り出すことができる。
- rc => リターンコード
- stdout => 標準出力
- stderr => 標準エラー出力
- stdout_lines => 標準出力を1行ずつ出力する(with_items等と組み合わせて使う模様)
上記のyamlではリターンコードを取り出して、0か1以外の場合はfailed
となるよう指定している。grep
のリターンコードは0と1に限られるので事実上ignore_error:
と変わらない指定になっているが、例えば0のときにfailed
にもできるし、stdout
の内容を確認してfailed
させることもできるので、failed_when:
を使う習慣を身に付けて良さそうに思う。
またregisterした変数はその後のtaskでも引き続き使えるので、コマンド実行結果をwhen:
で条件分岐として利用して、taskの実行要否を判断させることができる。上述のyamlでやろうとしているのがまさにそれで、rbenvで指定しているデフォルトのRubyバージョンが想定と異なる場合に限りrbenv global
を実行させるよう、下記のtaskを続かせている。
- name: set default ruby version
shell: /home/{{ user_name }}/.rbenv/bin/rbenv global {{ ruby_version }}
when: ruby_version_check.rc == 1
becomeやsudoを使う場合の注意
taskをsudo実行させるbecome: yes
(あるいはdeprecatedになっているsudo: yes
)を指定してshell
モジュールを実行する場合、当然ながらコマンド実行ユーザーがroot(もしくは指定されたbecomeユーザー)になるので留意する。例えば新しくファイルやディレクトリがそのコマンドの中で作られると所有者がrootになってしまったり、予期せぬ構成となることがままありうる。become: yes
をズボラにplaybook内で指定しておけば全taskがsudo実行されるのでエラーが起こらなくなるが、shell
モジュールを含んでいる場合はtask単位できちんとbecome: yes
の要否を見極めた方が良い。