Tình huống xảy ra2 giờ sáng và playbook của bạn vừa chết với thông báo sau:
fatal: [web01]: FAILED! => {
'changed': false,
'cmd': 'some_nonexistent_command',
'msg': '[Errno 2] No such file or directory: b\'some_nonexistent_command\'',
'rc': 2
}
Bạn SSH vào host, chạy lệnh thủ công — hoàn toàn bình thường. Vậy tại sao Ansible lại thất bại?
PATH. Ansible kết nối qua SSH và khởi tạo một shell không tương tác, không phải login shell. Shell đó bỏ qua hoàn toàn ~/.bashrc và /etc/profile. Các công cụ cài qua nvm, pyenv, rbenv, hoặc script tùy chỉnh đặt binary vào các thư mục riêng của từng user — không có thư mục nào trong số đó xuất hiện trong PATH rút gọn của Ansible.
Quy trình Debug### Bước 1: Xác nhận đây là vấn đề PATH, không phải thiếu cài đặtBắt đầu bằng cách kiểm tra xem lệnh có tồn tại trên remote host không và nó nằm ở đâu:
- name: Tìm vị trí của lệnh
command: which some_command
register: cmd_path
ignore_errors: true
- name: Hiển thị PATH trong môi trường shell của Ansible
command: env
register: env_output
- debug:
var: env_output.stdout_lines
Kiểm tra kết quả đầu ra. PATH của Ansible thường là /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin — sáu mục. Phiên SSH tương tác của bạn có thể có 12 mục hoặc hơn. Thư mục bị thiếu thường lộ ra ngay lập tức.
Bước 2: Kiểm tra xem lệnh có được cài đặt không```
ansible web01 -m command -a "which some_command" ansible web01 -m command -a "ls /usr/local/bin/some_command"
Không có kết quả từ `which`? Lệnh chưa được cài đặt — bỏ qua đến Giải pháp 4. Có đường dẫn trả về? Lệnh đã được cài nhưng Ansible không thấy. Đó là vấn đề PATH.
### Bước 3: Xem chính xác PATH mà Ansible nhận được```
ansible web01 -m command -a "echo $PATH"
PATH thông thường của Ansible trông như sau:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Chạy lệnh echo $PATH tương tự trong phiên SSH tương tác của bạn. Sự chênh lệch giữa hai kết quả cho bạn biết chính xác những gì đang bị thiếu.
Các Giải pháp### Giải pháp 1: Dùng đường dẫn tuyệt đối đầy đủCách sửa nhanh nhất khi bạn biết binary nằm ở đâu:
- name: Chạy lệnh với đường dẫn đầy đủ
command: /usr/local/bin/some_command --arg value
Không biết đường dẫn? Xác định động trước:
- name: Lấy đường dẫn đầy đủ
command: which some_command
register: cmd_path
- name: Dùng đường dẫn đã xác định
command: "{{ cmd_path.stdout }} --arg value"
Giải pháp 2: Inject PATH cho taskTham số environment cho phép bạn mở rộng PATH cho từng task mà không ảnh hưởng gì khác trên host:
- name: Chạy với PATH tùy chỉnh
command: some_command --arg value
environment:
PATH: "/opt/custom/bin:/usr/local/bin:/usr/bin:/bin:{{ ansible_env.PATH }}"
Cần áp dụng cho toàn bộ play? Đặt một lần ở cấp play:
- hosts: webservers
environment:
PATH: "/opt/custom/bin:/usr/local/bin:{{ ansible_env.PATH }}"
tasks:
- name: Task này giờ thấy được PATH mở rộng
command: some_command
Giải pháp 3: Source shell profile trước khi chạyCác công cụ như nvm, pyenv và rbenv cần được source script khởi tạo trước khi hoạt động. Chuyển từ module command sang shell và source profile một cách tường minh:
- name: Chạy sau khi source profile
shell: "source ~/.bashrc && some_command --arg value"
args:
executable: /bin/bash
Để tải /etc/profile và ~/.bash_profile, gọi bash trực tiếp dưới dạng login shell:
- name: Chạy trong login shell
shell: bash -l -c 'some_command --arg value'
Lưu ý quan trọng: Cách này chỉ hoạt động với module shell. Module command thực thi binary trực tiếp — không có shell, không có profile, không có mở rộng PATH từ .bashrc.
Giải pháp 4: Cài đặt lệnh còn thiếu trướcKhi lệnh thực sự chưa có trên host, thêm bước cài đặt trước task cần dùng nó:
- name: Đảm bảo công cụ cần thiết đã được cài
package:
name: some-package
state: present
become: true
- name: Giờ chạy lệnh
command: some_command --arg value
Với các công cụ dựa trên pip:
- name: Cài đặt công cụ Python
pip:
name: some-tool
state: present
executable: pip3
- name: Dùng công cụ vừa cài
command: some-tool --version
Giải pháp 5: Ghim đường dẫn chính xác cho version manager (nvm, pyenv, rbenv)Các version manager cài binary vào thư mục riêng của user như ~/.nvm/versions/node/v20.11.0/bin/. Hardcode đường dẫn version đầy đủ — đây là cách duy nhất hoạt động đáng tin cậy:
- name: Chạy node qua đường dẫn nvm cố định
command: /home/deploy/.nvm/versions/node/v20.11.0/bin/node -e "console.log('ok')"
become: true
become_user: deploy
Lưu đường dẫn version vào biến playbook ở đầu file, không rải rác khắp các task. Khi bạn nâng cấp Node từ v20 lên v22, bạn chỉ cần thay đổi một dòng thay vì mười dòng.
Kiểm tra kết quảChạy với verbose output để xác nhận bản sửa đã ổn định:
ansible-playbook your_playbook.yml --tags your_task_tag -v
Kiểm tra rc: 0 và không có mục FAILED nào. Nếu bạn muốn kiểm tra rõ ràng trước khi chạy task thực, thêm một probe nhanh:
- name: Xác nhận PATH chính xác
command: which some_command
register: check
- debug:
msg: "Tìm thấy lệnh tại: {{ check.stdout }}"
- name: Chạy lệnh thực sự
command: "{{ check.stdout }} --arg value"
Biến thể với Sudo / becomeMột lỗi liên quan hay khiến nhiều người bất ngờ. Lệnh tồn tại với deploy user nhưng không có với root — hoặc ngược lại:
fatal: [host]: FAILED! => {
'msg': '[Errno 2] No such file or directory: b\'some_command\'',
'rc': 2
}
Với become: true, task chạy dưới quyền root. Root thường có PATH ngắn hơn và bị hạn chế hơn so với user thông thường. Truyền PATH tường minh cùng với become:
- name: Chạy với quyền root và PATH đầy đủ
command: some_command
become: true
environment:
PATH: "/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin"

