Hiểu về lỗi này
Bạn đang chạy một playbook để triển khai 15 Nginx virtual host, và mọi thứ có vẻ ổn cho đến khi task gặp trở ngại. Ansible dừng đột ngột và thông báo lỗi cụ thể này:
Invalid data passed to 'loop', it requires a list, got <class 'dict'>
Ansible đang thông báo rất chính xác. Từ khóa loop, vốn trở thành tiêu chuẩn từ phiên bản 2.5, yêu cầu nghiêm ngặt một danh sách YAML (một mảng). Nếu bạn truyền vào một dictionary (từ điển) — một tập hợp các cặp key-value nằm trong dấu ngoặc nhọn — task sẽ thất bại ngay lập tức. Nó giống như việc cố gắng nhét một khối vuông vào một cái lỗ tròn vậy.
Tại sao điều này xảy ra
Phần lớn thời gian, trở ngại này xảy ra do cách dữ liệu được cấu trúc hoặc thu thập. Dưới đây là ba nguyên nhân phổ biến nhất:
- Sử dụng Dictionary trực tiếp: Bạn đã định nghĩa một biến dưới dạng dictionary nhưng lại cố gắng lặp qua nó như thể đó là một danh sách đơn giản.
- Các biến đã đăng ký (Registered Variables): Bạn đã thu thập đầu ra của một module như
find, nhưng bạn lại cố gắng lặp qua toàn bộ đối tượng kết quả thay vì danh sách các tệp bên trong nó. - Lỗi cú pháp YAML: Việc thiếu một dấu gạch ngang (
-) trong tệp biến có thể vô tình biến một danh sách thành một dictionary.
Các bước khắc phục
Kịch bản 1: Chuyển đổi Dictionary ngay lập tức
Giả sử bạn có một danh sách người dùng được định nghĩa trong vars/main.yml như sau:
# vars/main.yml
users:
alice:
uid: 1001
shell: /bin/bash
bob:
uid: 1002
shell: /bin/zsh
Nếu bạn cố gắng sử dụng loop: "{{ users }}", Ansible sẽ thấy một dictionary và báo lỗi. Cách khắc phục là sử dụng filter dict2items. Nó sẽ định dạng lại dictionary của bạn thành một danh sách mà Ansible có thể xử lý được.
# Task đã được sửa lại
- name: Tạo người dùng từ một dictionary
ansible.builtin.user:
name: "{{ item.key }}"
uid: "{{ item.value.uid }}"
shell: "{{ item.value.shell }}"
loop: "{{ users | dict2items }}"
Kịch bản 2: Điều hướng các biến đã đăng ký
Đây là một cái bẫy kinh điển khi sử dụng các module find hoặc stat. Các module này trả về một dictionary khổng lồ chứa metadata, dấu thời gian và mã trạng thái. Danh sách thực tế mà bạn muốn thường nằm sâu hơn một cấp.
# Cách làm SAI
- name: Tìm các tệp log cũ
ansible.builtin.find:
paths: /var/log/nginx
patterns: "*.log.gz"
register: found_logs
- name: Dọn dẹp log
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ found_logs }}" # Lệnh này sẽ lỗi!
Cách khắc phục: Bạn phải trỏ vòng lặp vào thuộc tính files. Đó là nơi chứa danh sách thực sự.
# Cách làm ĐÚNG
- name: Dọn dẹp log
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ found_logs.files }}" # Trỏ vào danh sách cụ thể
Kịch bản 3: Phát hiện lỗi định dạng YAML
YAML rất nhạy cảm. Nếu bạn quên một dấu gạch ngang, cấu trúc dữ liệu của bạn sẽ thay đổi hoàn toàn. Hãy xem sự so sánh này:
# Sai: Đây là một Dictionary
my_packages:
git: present
vim: present
# Đúng: Đây là một Danh sách
my_packages:
- git
- vim
Kiểm tra các tệp biến của bạn. Nếu loop của bạn trỏ đến my_packages, hãy đảm bảo mọi mục đều bắt đầu bằng một dấu gạch ngang. Nếu không có những dấu gạch ngang đó, Ansible sẽ hiểu đó là một dictionary và kích hoạt lỗi.
Các bước xác minh
Bạn không chắc mình đang xử lý kiểu dữ liệu nào? Hãy sử dụng module debug và filter type_debug để kiểm tra các biến của bạn ngay khi đang chạy. Nó giúp tiết kiệm rất nhiều công sức đoán mò.
- name: Kiểm tra kiểu dữ liệu
ansible.builtin.debug:
msg: "Kiểu biến là {{ users | type_debug }}"
- name: Xác minh việc chuyển đổi
ansible.builtin.debug:
msg: "Sau khi dùng filter, kiểu là {{ (users | dict2items) | type_debug }}"
Nếu đầu ra hiển thị list, bạn đã sẵn sàng.
Mẹo thực tế để thành công
Việc hình dung YAML có thể khó khăn khi các playbook ngày càng lớn. Khi gặp bế tắc, tôi thường sử dụng một công cụ chuyển đổi YAML sang JSON. JSON chặt chẽ hơn nhiều với các dấu ngoặc vuông và ngoặc nhọn, giúp bạn dễ dàng nhận thấy mình đã tạo ra một Object (Dictionary) hay một Array (List).
Một mẹo cuối cùng: hãy chạy playbook của bạn với tham số -v. Ansible sẽ in cấu trúc JSON đầy đủ của các biến đã đăng ký. Điều này cho phép bạn thấy chính xác key nào đang giữ danh sách bạn cần cho vòng lặp mà không cần phải đoán tên thuộc tính.

