文系プログラマによるTIPSブログ

文系プログラマ脳の私が開発現場で学んだ事やプログラミングのTIPSをまとめています。

ansibleでshellモジュール実行時に環境変数(.bash_profile)が反映されない問題

久しぶりにansibleをやっているわけですが、.bash_profileに書いたPATHが反映されないなあ、と思って色々調べてみました。


f:id:treeapps:20160219001058p:plain

どういう事が起きたか

今回rbenvのplaybookを書いていて気づきました。

大まかにrbenvのインストールは以下の流れになります。

  • rbenvをgit cloneする。
  • ruby-buildをgit cloneする。
  • ~/.bash_profileにrbenvのバイナリへのパスとrbenvの初期化コマンドを追記する。
  • rbenv install 2.2.2する。
  • rbenv global 2.2.2する。

という感じで、playbookには以下のように記述しました。

- name: install ruby
  shell: rbenv install {{ ruby_version }}
  sudo: no

すると、以下のエラーが起きました。

TASK: [rbenv | install ruby] **************************************************
failed: [192.168.33.21] => {"changed": true, "cmd": "rbenv install 2.2.2", "delta": "0:00:00.002410", "end": "2015-05-16 06:23:09.890315", "rc": 127, "start": "2015-05-16 06:23:09.887905", "warnings": []}
stderr: /bin/sh: rbenv: command not found

FATAL: all hosts have already failed -- aborting

rbenv: command not foundだそうです。
rbenvバイナリへのパスは、.bash_profileに以下のように記述しています。

export PATH="$HOME/.rbenv/bin:$PATH"
eval "$(rbenv init -)"

どうやらshellモジュール実行時に.bash_profileの設定が読み込まれない事が解りました。

ansibleのドキュメントを見てみる

The shell module takes the command name followed by a list of space-delimited arguments. It is almost exactly like the command module but runs the command through a shell (/bin/sh) on the remote node.

http://docs.ansible.com/shell_module.html#synopsis

こう書いてあるのとエラーログの内容を加味すると、shellモジュール実行時のシェルは「/bin/sh」であり、ログインシェルは実行していない(.bash_profile等を読み込まない)事が解ります。

2通りの解決策

もっといい方法があるかもしれませんが、私が色々試して思いついた解決策は以下の2種類でした。

shellモジュールを使う時は必ずログインシェルとして実行する

- name: install ruby
  shell: bash -lc "rbenv install {{ ruby_version }}"

このように「-l」オプションを付けてbashコマンドを実行する事で、ログインシェルとして実行する事で、.bash_profile等を読み込む事ができ、rbenvコマンドが参照できるようになります。

メリット

必要な時だけログインシェルとして実行して各種設定ファイルを読み込むので、shellモジュール実行時のオーバーヘッドが最小限になる。

デメリット

.bash_profile等に記述した環境変数上にあるバイナリを実行する際は、毎回「/bin/bash -l」等と書かなくてはならず、面倒だし冗長になる。

また、ダブルクォートで挟むので、sed等のコマンドでシングル・ダブルクォートが、壊れないよう注意する必要がある。

ansible.cfgにexecutableを追加してしまう

ansible.cfgに以下のように記述してしまう事で、shellコマンド実行時は必ずログインシェルとして実行するようにしてしまいます。

[defaults]
executable = /bin/bash -l

Configuring Ansible — Ansible Documentation

メリット

shellモジュール実行毎に「/bin/bash -l」を書かなくて済むのでplaybookがシンプルになる。

デメリット

shellモジュール実行時はログインシェルを都度読み込んでしまい、オーバーヘッドが発生する。(shellだけでなくexecutables属性があるモジュール全てにいえるかもしれません)

雑感

個人的には ansible.cfgにexecutableに書いてしまってもいいかなあと思っています。パフォーマンス面等で考えると都度/bin/bash -lした方がいいんでしょうが、そもそもパフォーマンス面を考えるとansibleではなくchef serverの方がいいでしょうから、割りきってansible.cfgにexecutableを書く方が楽だし冗長化も防げるかと思います。

Ansible実践ガイド 第2版 (impress top gear)

Ansible実践ガイド 第2版 (impress top gear)