Help us understand the problem. What is going on with this article?

Ansibleでシェルコマンドを実行させるときのノウハウ

More than 3 years have passed since last update.

基本的にAnsible側に制御を委ねることで冪等性の保持やエラーハンドリングが成されるので、やりたいtaskに対してまずはmoduleを探すべき(sedを使わずにlineinfileモジュールを使えないかとか)ではあるのだが、どうしてもmoduleで実現できない場合にシェルコマンド直書きになってしまう場合はある。 てかコマンド書いちゃうならAnsibleじゃなくてシェルスクリプトでいいじゃんって話でさ。

※追記 2017-04-05 17:00

記事執筆時点から時を経た現在の自分の見解としては、shell, command両モジュールの活用には以下の面から否定的であり、なるべくコマンド実行は行わないべきと考えています。

  • 冪等性の保証が困難になる。
  • Ansible Playbookは実現されるべきシステムの「状態」を示した「実行可能なドキュメント」という側面があるが、コマンドが記載されている場合は実現される「状態」が判別しづらくなる。
  • (冗談めかして書いてはみたが)コマンドによるデプロイを行いたいのであればシェルスクリプトでよい。Ansibleを何のために使っているのか?

commandではなくshellを使う

シェルコマンド直書きする場合によく話題になることとして、commandshellのどちらのモジュールを使うべきかというのがあるが、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の要否を見極めた方が良い。

chroju
Site Reliability Engineer loves Terraform, Go, AWS
https://chroju.dev
globis
グロービスは 1992 年の創業以来、社会人を対象とした MBA、人材育成の領域で Ed-Tech サービスを提供し、現在は日本 No.1 の実績があります。これらの資産と、さらに IT や AI を活用することで、アジア No.1 を目指しています。
http://www.globis.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした