Sửa lỗi Ansible 'The loop variable item is already in use' trong vòng lặp lồng nhau

intermediate🔧 Ansible2026-05-07| Ansible 2.5+, mọi hệ điều hành (Linux/macOS), thường xảy ra bên trong roles khi dùng include_tasks hoặc cấu trúc vòng lặp lồng nhau

Error Message

The loop variable 'item' is already in use. You should set the `loop_var` value in the `loop_control` option for the task to something different to avoid this error.
#ansible#loop#loop_control#loop_var#role

Tình huống

Playbook triển khai của bạn chạy ổn định trong nhiều tuần. Bạn thêm một role mới có vòng lặp qua danh sách users — chuyện bình thường. Rồi toàn bộ play bị crash giữa chừng với lỗi:

The loop variable 'item' is already in use. You should set the `loop_var` value in the `loop_control` option for the task to something different to avoid this error.

Không có gì thay đổi trong playbook bên ngoài. Role trông có vẻ ổn. Vậy nguyên nhân là gì?

Nguyên nhân

Ansible mặc định dùng item làm biến vòng lặp cho mọi directive loop. Hai task không thể dùng chung tên đó cùng lúc.

Khi một task đang lặp gọi include_tasks (hoặc một role bên trong cũng có vòng lặp), task bên ngoài lẫn bên trong đều tranh nhau dùng item. Ansible 2.5+ phát hiện xung đột này và dừng lại thay vì âm thầm ghi đè giá trị vòng ngoài — điều đó sẽ tạo ra kết quả sai mà không có thông báo lỗi nào để truy nguyên.

Cấu hình hay kích hoạt lỗi này nhất:

  • Playbook bên ngoài lặp qua danh sách environments hoặc users
  • Vòng lặp đó gọi include_tasks hoặc include_role
  • File hoặc role được include cũng chứa một loop
# playbook.yml — vòng lặp ngoài
- name: Configure users
  include_tasks: setup_user.yml
  loop: "{{ users }}"

# setup_user.yml — vòng lặp trong (XUNG ĐỘT)
- name: Add SSH keys for user
  authorized_key:
    user: "{{ item.name }}"
    key: "{{ item.key }}"
  loop: "{{ item.ssh_keys }}"  # 'item' đã bị vòng lặp ngoài chiếm!

Ansible thấy item đã bị vòng lặp ngoài giữ, cố tái sử dụng nó ở vòng lặp trong, và dừng lại.

Cách sửa nhanh

Thêm loop_control với loop_var tùy chỉnh vào vòng lặp ngoài, vòng lặp trong, hoặc cả hai. Thường thì đổi tên biến ở vòng lặp ngoài sẽ gọn hơn — bạn trực tiếp kiểm soát task đó và có thể đặt tên mô tả rõ ràng.

Cách 1: Đổi tên biến vòng lặp ngoài

# playbook.yml
- name: Configure users
  include_tasks: setup_user.yml
  loop: "{{ users }}"
  loop_control:
    loop_var: user_item

# setup_user.yml — 'item' giờ đã rảnh cho vòng lặp trong
- name: Add SSH keys for user
  authorized_key:
    user: "{{ user_item.name }}"
    key: "{{ key }}"
  loop: "{{ user_item.ssh_keys }}"
  loop_control:
    loop_var: key

Cách 2: Đổi tên biến vòng lặp trong

Nếu bạn không kiểm soát vòng lặp ngoài — ví dụ nó nằm trong một role của bên thứ ba — thì hãy đổi tên ở vòng lặp trong:

- name: Add SSH keys for user
  authorized_key:
    user: "{{ item.name }}"
    key: "{{ ssh_key }}"
  loop: "{{ item.ssh_keys }}"
  loop_control:
    loop_var: ssh_key

Sửa triệt để cho Role

Role được tạo ra để tái sử dụng. Bất kỳ role nào dùng item mặc định đều sẽ xung đột với caller cũng có vòng lặp — đó là lỗi tiềm ẩn chờ ngày phát sinh.

Cách khắc phục: luôn đặt tên biến vòng lặp trong role theo tên riêng của role đó. Quy ước phổ biến là đặt tiền tố theo tên role hoặc tên viết tắt — vhost thay vì item, pkg thay vì item, v.v.

# roles/nginx_vhost/tasks/main.yml
- name: Create vhost configs
  template:
    src: vhost.conf.j2
    dest: "/etc/nginx/sites-available/{{ vhost.name }}.conf"
  loop: "{{ nginx_vhosts }}"
  loop_control:
    loop_var: vhost        # đặc thù cho role, không bị trùng
    label: "{{ vhost.name }}"

Trường label là một tiện ích hữu ích kèm theo. Nếu không có nó, Ansible in toàn bộ dict ở mỗi vòng lặp — thường là một đống JSON. Với label, bạn chỉ thấy những gì bạn định nghĩa, như vhost.name. Dễ đọc hơn nhiều trong log CI.

Xử lý include_role bên trong vòng lặp

Lặp qua include_role hoạt động tương tự. Thêm loop_control vào chính task include_role:

- name: Deploy application per environment
  include_role:
    name: deploy_app
  loop: "{{ environments }}"
  loop_control:
    loop_var: env
    label: "{{ env.name }}"

Sau đó kiểm tra bên trong deploy_app — không task nào trong đó được dùng env làm biến vòng lặp riêng, không thì bạn lại quay về vạch xuất phát.

Ba cấp lồng nhau

Ba vòng lặp lồng nhau là hiếm, nhưng các role cấu hình phức tạp đôi khi cần đến. Mỗi cấp cần một tên biến riêng biệt — không có ngoại lệ:

# Cấp 1
- include_tasks: per_region.yml
  loop: "{{ regions }}"
  loop_control:
    loop_var: region

# per_region.yml — Cấp 2
- include_tasks: per_host.yml
  loop: "{{ region.hosts }}"
  loop_control:
    loop_var: host

# per_host.yml — Cấp 3
- name: Apply firewall rules
  ufw:
    rule: allow
    port: "{{ port }}"
  loop: "{{ host.allowed_ports }}"
  loop_control:
    loop_var: port

Xác nhận đã sửa xong

Trước khi chạy trên host thật, hãy kiểm tra cú pháp và chạy thử:

# Phát hiện xung đột tên biến trong phân tích tĩnh
ansible-playbook playbook.yml --syntax-check

# Chạy thử — output chi tiết cho thấy tên biến vòng lặp của bạn đang hoạt động
ansible-playbook playbook.yml --check -v

Với output chi tiết, nhãn task sẽ hiển thị biến vòng lặp tùy chỉnh thay vì item. Nếu bạn đặt label, chuỗi đó sẽ xuất hiện thay vì object thô. Một lần chạy thành công trông như sau:

TASK [Add SSH keys for user] ***
ok: [server1] => (item=deploy_key)
ok: [server1] => (item=backup_key)

Không còn lỗi xung đột, tên biến tùy chỉnh hiển thị rõ ràng — xong việc.

Tham chiếu nhanh

loop_control:
  loop_var: my_var              # đổi tên 'item' thành 'my_var'
  label: "{{ my_var.name }}"   # output log gọn hơn
  index_var: idx                # tùy chọn: chỉ số vòng lặp (0, 1, 2...)
  pause: 1                      # tùy chọn: giây chờ giữa các vòng lặp

Một quy tắc cần nhớ: mọi loop bên trong một role đều phải định nghĩa loop_var. Chỉ thêm một dòng. Nhưng đó là sự khác biệt giữa một playbook chạy suôn sẻ lúc trưa và một playbook thất bại lúc 2 giờ sáng mà không có nguyên nhân rõ ràng.

Related Error Notes