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_taskshoặcinclude_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.

