Fix AnsibleUndefinedVariable: 'variable_name' is undefined

beginner🔧 Ansible2026-03-18| Ansible 2.9+, bất kỳ control node nào (Linux/macOS), Jinja2 templating

Error Message

AnsibleUndefinedVariable: 'variable_name' is undefined
#ansible#variable#jinja2#template

Mô tả lỗi

Playbook của bạn dừng đột ngột với thông báo:

fatal: [host]: FAILED! => {
    "msg": "The task includes an option with an undefined variable. The error was: 'variable_name' is undefined"
}
AnsibleUndefinedVariable: 'variable_name' is undefined

Ansible gặp phải một template Jinja2 tham chiếu đến một biến mà nó không tìm thấy. Có thể biến đó chưa được định nghĩa ở đâu cả. Có thể nó được định nghĩa sai phạm vi. Hoặc đơn giản là tên biến bị gõ sai.

Nguyên nhân

  • Biến chưa được định nghĩa ở bất kỳ đâu — không có vars:, không có defaults/main.yml, không có inventory, không có flag -e
  • Sai phạm vi: biến được định nghĩa bên trong một block hoặc trong host-specific vars không áp dụng cho task này
  • Gõ sai tên (db_port so với db_ports — rất dễ bỏ qua)
  • Một key lồng nhau không tồn tại (result.output khi result không có key output)
  • Biến được register nhưng dùng trước khi task của nó chạy, hoặc khi task đó bị bỏ qua

Cách sửa từng bước

Bước 1 — Tìm nơi biến cần được định nghĩa

Bắt đầu với -v để lấy thêm chi tiết trong đầu ra lỗi:

ansible-playbook site.yml -v

Sau đó tìm kiếm biến trên toàn bộ project:

grep -r "variable_name" roles/ group_vars/ host_vars/ inventory/

Không có kết quả? Đó là câu trả lời — biến chưa được định nghĩa ở đâu cả.

Bước 2 — Định nghĩa biến đúng chỗ

Chọn vị trí phù hợp với phạm vi áp dụng của biến:

Trực tiếp trong playbook (phạm vi task-level):

- name: Deploy app
  hosts: webservers
  vars:
    app_port: 8080
    app_user: deploy
  tasks:
    - name: Start app
      ansible.builtin.systemd:
        name: myapp
      environment:
        PORT: "{{ app_port }}"

Trong group_vars (áp dụng cho mọi host trong nhóm đó):

# group_vars/webservers.yml
app_port: 8080
app_user: deploy

Trong role defaults (ưu tiên thấp nhất — dễ bị ghi đè bởi bất kỳ thứ gì khác):

# roles/myapp/defaults/main.yml
app_port: 8080
app_user: deploy

Khi chạy với -e (ưu tiên cao nhất — ghi đè tất cả):

ansible-playbook site.yml -e "app_port=8080 app_user=deploy"

Bước 3 — Xử lý biến tùy chọn bằng giá trị mặc định

Không phải biến nào cũng cần bắt buộc có. Dùng filter default() của Jinja2 để cung cấp giá trị dự phòng thay vì để playbook bị crash:

- name: Cấu hình timeout
  ansible.builtin.template:
    src: config.j2
    dest: /etc/myapp/config.conf
  vars:
    timeout: "{{ connection_timeout | default(30) }}"
    debug_mode: "{{ enable_debug | default(false) }}"

Muốn bỏ qua tham số hoàn toàn khi biến không được đặt? Dùng default(omit):

- name: Tạo user
  ansible.builtin.user:
    name: "{{ username }}"
    password: "{{ user_password | default(omit) }}"

Bước 4 — Sửa lỗi truy cập biến lồng nhau

Truy cập một key bên trong dict? Key đó có thể không tồn tại. Hãy bảo vệ nó:

# Không an toàn — lỗi nếu result không có key stdout
- debug:
    msg: "{{ result.stdout }}"

# An toàn — trả về chuỗi rỗng nếu stdout không tồn tại
- debug:
    msg: "{{ result.stdout | default('') }}"

# Hoặc kiểm tra trước
- debug:
    msg: "{{ result.stdout }}"
  when: result.stdout is defined

Bước 5 — Sửa biến register dùng sau task bị bỏ qua

Lỗi kinh điển: một task bị bỏ qua, biến mà nó đáng ra phải register không bao giờ tồn tại, rồi một task sau đó cố dùng nó.

# Vấn đề: nếu task này bị bỏ qua, check_result sẽ undefined
- name: Kiểm tra service
  ansible.builtin.command: systemctl status myapp
  register: check_result
  when: run_checks | default(false)

# Sửa: bảo vệ phần sử dụng
- name: Hiển thị kết quả
  debug:
    msg: "{{ check_result.stdout }}"
  when: check_result is defined and check_result.stdout is defined

Kiểm tra sau khi sửa

Chạy lại playbook. Task sẽ pass thay vì FAILED. Trước khi chạy thật, xác nhận Ansible có thể thấy biến của bạn:

# Kiểm tra một biến cụ thể cho tất cả host trong một nhóm
ansible -m debug -a "var=app_port" webservers

# Dump toàn bộ biến cho một host cụ thể
ansible -m debug -a "var=hostvars[inventory_hostname]" webservers --limit web01

Chạy thử (dry-run) để phát hiện vấn đề mà không thực hiện bất kỳ thay đổi nào:

ansible-playbook site.yml --check --diff

Mẹo nhanh

  • Thứ tự ưu tiên biến: Ansible có 22 cấp độ ưu tiên. Nhận được giá trị không như mong đợi? Kiểm tra thứ tự ưu tiên. Extra vars (-e) luôn thắng; role defaults luôn thua.
  • Chế độ strict: Đặt error_on_undefined_vars = True trong ansible.cfg để phát hiện biến undefined sớm hơn — trước khi chúng âm thầm tạo ra output sai thay vì báo lỗi rõ ràng.
  • Dump tất cả biến giữa chừng: Thêm một task debug tạm thời để xem chính xác những gì đang có tại thời điểm đó trong play:
  • name: Debug tất cả biến debug: var: vars
  
  - **Biến trần trong `when:`**: `when: my_var` sẽ lỗi nếu `my_var` undefined. Thay vào đó hãy viết `when: my_var is defined and my_var`.

Related Error Notes