ansible の PATH 通っていない問題

ansible を使ったことがある人ならおそらくみんなハマったであろう PATH 問題についてまとめる。

こんなやつ

- shell: echo $PATH

EXEC /bin/sh -c 'sudo -H -S -n -u root /bin/sh -c '"'"'echo BECOME-SUCCESS-lzymqihsegohzfxrfdinmivkxocjkfsb; /usr/bin/python'"'"' && sleep 0'
changed: [centos] => {
    (中略)
    "stdout": "/sbin:/bin:/usr/sbin:/usr/bin",
    "stdout_lines": [
        "/sbin:/bin:/usr/sbin:/usr/bin"
    ]
}

あれ?となって確認した。

$ sudo su -
# echo $PATH
/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin

何が起きているか?

これは ansible の問題ではなく、 sudo, su の仕様の話。

  • デフォルトの実行コマンドがログインシェルを使っていないので .bash_profile, .bashrc などの設定ファイルは読み込まない
  • sudo で参照する先が root の PATH ではない。
    /etc/sudoers というファイルに
    Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
    という記述があり、 セキュリティ確保のために最低限の PATH のみを参照するようになっている。
    visudo コマンドを使って /etc/sudoers を編集すれば解決する。

(参考) 実行コマンドと PATH の違い

$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/vagrant/.local/bin:/home/vagrant/bin

$ printenv
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/vagrant/.local/bin:/home/vagrant/bin
********************************************************************************
$ sudo su
# echo $PATH
/sbin:/bin:/usr/sbin:/usr/bin

$ sudo su
# printenv
/sbin:/bin:/usr/sbin:/usr/bin
********************************************************************************
$ sudo su -
# echo $PATH
/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin

$ sudo su -
# printenv
/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
********************************************************************************
$ sudo echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/vagrant/.local/bin:/home/vagrant/bin

$ sudo printenv
/sbin:/bin:/usr/sbin:/usr/bin
********************************************************************************
$ sudo -E echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/vagrant/.local/bin:/home/vagrant/bin

$ sudo -E printenv
/sbin:/bin:/usr/sbin:/usr/bin
********************************************************************************
$ sudo -i echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/vagrant/.local/bin:/home/vagrant/bin

$ sudo -i printenv
/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin
********************************************************************************
$ sudo -s echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/vagrant/.local/bin:/home/vagrant/bin

$ sudo -s printenv
/sbin:/bin:/usr/sbin:/usr/bin

echo $PATHprintenv の挙動が違うのがなんだろう、気になる

解決方法

.bash_profile, .bashrc などを読み込ませるにはログインシェルで実行する必要がある

[解決策①] ansible 全体でログインシェルを使うよう記述する

ansible.cfg に以下のように記述する

[defaults]
executable = /bin/bash -l
- shell: echo $PATH

<centos> EXEC /bin/bash -l -c 'sudo -H -S -n -u root /bin/bash -l -c '"'"'echo BECOME-SUCCESS-oqlyupjtmruqutxhwwikvwvdzyhkqhzi; /usr/bin/python'"'"' && sleep 0'

"stdout": "/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin"

[解決策②] コマンド自体にログインシェルを使うよう記述する

- shell: /bin/bash -l -c 'echo $PATH'

EXEC /bin/sh -c 'sudo -H -S -n -u root /bin/sh -c '"'"'echo BECOME-SUCCESS-orthrhxlvhyzidtypbrafkgurhiziccv; /usr/bin/python'"'"' && sleep 0'
"stdout": "/usr/local/sbin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin"

[解決策③] 自分で PATH に追加する

- shell: echo $PATH
  environment:
    PATH: "/opt:{{ ansible_env.PATH }}"

EXEC /bin/sh -c 'sudo -H -S -n -u root /bin/sh -c '"'"'echo BECOME-SUCCESS-dqterilpwfhgbmbdsizvyanxvetoxonl; PATH=/opt:/sbin:/bin:/usr/sbin:/usr/bin /usr/bin/python'"'"' && sleep 0'
"stdout": "/opt:/sbin:/bin:/usr/sbin:/usr/bin"

色々と調べたけどコントリビューターの人が↓のように言っていた。

While I understand the underlying issues here Ansible is intended to be a centrally managed automation system.
The login shell of the user is not to be something that is required for managing the environment.

https://github.com/ansible/ansible/issues/4854#issuecomment-37657751

確かに複数サーバーを同時に扱ったりするからユーザーが好き勝手に変えれる部分を鵜呑みにするとアレかなーと思ったりした。
ってことで解決策③を使うのが ansible の思想に沿っているっぽい。 sudoers も編集しなくて良いし。

失敗例

[失敗例①]

- shell: echo hello
  args:
    executable: /bin/bash -l

EXEC /bin/sh -c 'sudo -H -S -n -u root /bin/sh -c '"'"'echo BECOME-SUCCESS-diybjbffvistlrsppnykceegqswzygxp; /usr/bin/python'"'"' && sleep 0'

同じ executable だからいけるのでは!?と思ったけどダメだった。
オプションまで含めて1つのコマンドとして扱われるらしく、 [Errno 2] No such file or directory というエラーになる。
実行時のコマンドには変化がないので、実行ファイル内で呼び出される部分が変わるっぽい?

$ /bin/bash echo hello
/usr/bin/echo: /usr/bin/echo: cannot execute binary file
$ /bin/bash -c "echo hello"
hello
$ "/bin/bash" -c "echo hello"
hello
$ "/bin/bash -l" -c "echo hello" 👈 これ
-bash: /bin/bash -l: No such file or directory

[失敗例②]

- shell: source ~/.bashrc && echo $PATH

EXEC /bin/sh -c 'sudo -H -S -n -u root /bin/sh -c '"'"'echo BECOME-SUCCESS-ouzvwbrjctdmvcgmpgrkqfhresukbaqr; /usr/bin/python'"'"' && sleep 0'
"stdout": "/sbin:/bin:/usr/sbin:/usr/bin"

参考URL