状況深夜2時、あなたの playbook がこのエラーで落ちました:
fatal: [web01]: FAILED! => {
'changed': false,
'cmd': 'some_nonexistent_command',
'msg': '[Errno 2] No such file or directory: b\'some_nonexistent_command\'',
'rc': 2
}
SSH でホストに接続し、コマンドを手動で実行すると — 問題なく動きます。では、なぜ Ansible は失敗するのでしょうか?
PATH の問題です。Ansible は SSH 経由で接続し、非インタラクティブ・非ログインシェルを起動します。そのシェルは ~/.bashrc と /etc/profile を完全にスキップします。nvm、pyenv、rbenv、またはカスタムスクリプト経由でインストールされたツールは、バイナリをユーザー固有のディレクトリに配置します — これらはどれも Ansible の簡略化された PATH には含まれていません。
デバッグの流れ### ステップ 1:インストール不足ではなく PATH の問題であることを確認するまず、コマンドがリモートホストに存在するかどうか、どこにあるかを確認します:
- name: コマンドの場所を確認する
command: which some_command
register: cmd_path
ignore_errors: true
- name: Ansible シェル環境の PATH を表示する
command: env
register: env_output
- debug:
var: env_output.stdout_lines
出力を確認してください。Ansible の PATH は通常 /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin の 6 エントリです。インタラクティブな SSH セッションでは 12 以上あるかもしれません。不足しているディレクトリはたいてい一目でわかります。
ステップ 2:コマンドがインストールされているか確認する```
ansible web01 -m command -a "which some_command" ansible web01 -m command -a "ls /usr/local/bin/some_command"
`which` から出力がない場合?コマンドがインストールされていません — 解決策 4 に進んでください。パスが返ってきた場合?インストールされているが Ansible から見えていません。それは PATH の問題です。
### ステップ 3:Ansible が取得する PATH を正確に確認する```
ansible web01 -m command -a "echo $PATH"
Ansible の PATH は通常このようになります:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
インタラクティブな SSH セッションで同じ echo $PATH を実行してください。両者の差異が何が不足しているかを正確に教えてくれます。
解決策### 解決策 1:絶対パスを使用するバイナリの場所がわかっている場合の最も素早い修正方法:
- name: フルパスでコマンドを実行する
command: /usr/local/bin/some_command --arg value
パスがわからない場合は、まず動的に解決します:
- name: フルパスを取得する
command: which some_command
register: cmd_path
- name: 解決したパスを使用する
command: "{{ cmd_path.stdout }} --arg value"
解決策 2:タスクに PATH を注入するenvironment パラメーターを使用すると、ホスト上の他の設定に触れることなく、タスクごとに PATH を拡張できます:
- name: カスタム PATH で実行する
command: some_command --arg value
environment:
PATH: "/opt/custom/bin:/usr/local/bin:/usr/bin:/bin:{{ ansible_env.PATH }}"
プレイ全体で必要な場合は、プレイレベルで一度設定します:
- hosts: webservers
environment:
PATH: "/opt/custom/bin:/usr/local/bin:{{ ansible_env.PATH }}"
tasks:
- name: このタスクは拡張された PATH を参照できます
command: some_command
解決策 3:実行前にシェルプロファイルを読み込むnvm、pyenv、rbenv などのツールは、動作する前に初期化スクリプトを読み込む必要があります。command モジュールから shell に切り替え、プロファイルを明示的に読み込みます:
- name: プロファイルを読み込んで実行する
shell: "source ~/.bashrc && some_command --arg value"
args:
executable: /bin/bash
/etc/profile と ~/.bash_profile を読み込むには、bash をログインシェルとして直接呼び出します:
- name: ログインシェルで実行する
shell: bash -l -c 'some_command --arg value'
**重要:**これは shell モジュールでのみ機能します。command モジュールはバイナリを直接実行します — シェルなし、プロファイルなし、.bashrc からの PATH 展開なし。
解決策 4:不足しているコマンドを先にインストールするコマンドがホストに本当に存在しない場合、それを必要とするタスクの前にインストールステップを追加します:
- name: 必要なツールがインストールされていることを確認する
package:
name: some-package
state: present
become: true
- name: コマンドを実行する
command: some_command --arg value
pip ベースのツールの場合:
- name: Python ツールをインストールする
pip:
name: some-tool
state: present
executable: pip3
- name: インストールしたツールを使用する
command: some-tool --version
解決策 5:バージョンマネージャー(nvm、pyenv、rbenv)のパスを固定するバージョンマネージャーは ~/.nvm/versions/node/v20.11.0/bin/ のようなユーザー固有のディレクトリにバイナリをインストールします。完全なバージョンパスをハードコードする — これが確実に機能する唯一のアプローチです:
- name: 固定した nvm パスで node を実行する
command: /home/deploy/.nvm/versions/node/v20.11.0/bin/node -e "console.log('ok')"
become: true
become_user: deploy
バージョンパスは、タスク全体に散在させるのではなく、プレイブックの先頭の変数に保存してください。Node を v20 から v22 にアップグレードするとき、10 か所ではなく 1 か所だけ変更すれば済みます。
確認修正が有効であることを確認するため、詳細出力で実行します:
ansible-playbook your_playbook.yml --tags your_task_tag -v
rc: 0 と FAILED エントリがないことを確認してください。実際のタスクを実行する前に明示的なサニティチェックを行うには、簡単なプローブを追加します:
- name: PATH が正しいことを確認する
command: which some_command
register: check
- debug:
msg: "コマンドの場所: {{ check.stdout }}"
- name: 実際のコマンドを実行する
command: "{{ check.stdout }} --arg value"
Sudo / become を使った場合関連する失敗が多くの人を驚かせます。deploy ユーザーには存在するが root には存在しないコマンド — またはその逆:
fatal: [host]: FAILED! => {
'msg': '[Errno 2] No such file or directory: b\'some_command\'',
'rc': 2
}
become: true を使用すると、タスクは root として実行されます。root は通常、一般ユーザーよりも短く制限された PATH を持っています。become と一緒に PATH を明示的に渡します:
- name: フル PATH で root として実行する
command: some_command
become: true
environment:
PATH: "/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin"

