Sự cố
Lúc đó là 2 giờ sáng, và một đợt triển khai sản xuất định kỳ vừa bị đình trệ. Bạn đang cố gắng tạo template cho một tệp cấu hình dựa trên một địa chỉ IP cụ thể, nhưng Ansible đột ngột dừng lại. Playbook thất bại với một thông báo cộc lốc:
fatal: [web-server-01]: FAILED! => {"msg": "AnsibleError: dict object has no attribute 'eth0'"}
Lỗi này xảy ra khi template Jinja2 hoặc task của bạn cố gắng truy cập vào một key trong dictionary không tồn tại trong ngữ cảnh hiện tại. Khi làm việc với các network facts, điều này thường có nghĩa là tên interface mạng trên host đích khác đi hoặc các facts chưa được thu thập. Đây là một sự khó chịu phổ biến, nhưng cách khắc phục rất đơn giản.
Tại sao điều này xảy ra?
Có ba lý do chính khiến Ansible không thể tìm thấy 'eth0' hoặc các interface khác trong dictionary facts của nó:
- Predictable Interface Naming: Các bản phân phối Linux hiện đại như Ubuntu 22.04 hoặc RHEL 9 không còn mặc định là
eth0. VM cục bộ của bạn có thể dùngeth0, nhưng một instance AWS EC2 có thể dùngens5, và một máy khách VMWare có thể dùngens160. - Disabled Fact Gathering: Nếu phần header của playbook có chứa
gather_facts: false, dictionaryansible_factssẽ trống rỗng. Ansible sẽ không biết bất cứ điều gì về phần cứng của host. - Hostvars Scope: Bạn có thể đang cố gắng lấy
hostvars['db_server']['ansible_eth0']. Nếudb_serverchưa được xử lý trong lượt chạy hiện tại, các facts của nó vẫn chưa được lưu trữ vào bộ nhớ.
Cách khắc phục
1. Cách tiếp cận phòng thủ: Sử dụng filter 'default'
Tránh việc giả định rằng một fact luôn tồn tại. Cách an toàn nhất để xử lý một thuộc tính dictionary có thể bị thiếu là sử dụng filter default của Jinja2. Điều này ngăn chặn việc bị crash bằng cách cung cấp một giá trị dự phòng nếu key bị thiếu.
# Thay vì tham chiếu trực tiếp:
# ip_address: "{{ ansible_facts['eth0']['ipv4']['address'] }}"
# Sử dụng một giá trị dự phòng:
ip_address: "{{ ansible_facts['eth0']['ipv4']['address'] | default('127.0.0.1') }}"
2. Cách tiếp cận linh hoạt: Sử dụng Default IPv4 Facts
Việc hardcoding eth0 cuối cùng sẽ làm hỏng hệ thống tự động hóa của bạn. Ansible cung cấp một fact có sẵn gọi là ansible_default_ipv4. Biến này tự động trỏ đến interface được sử dụng bởi bảng định tuyến mặc định của hệ thống. Nó đáng tin cậy hơn đáng kể trên các nhà cung cấp cloud và phiên bản OS khác nhau.
# Cách này hoạt động trên AWS, Azure và bare metal bất kể tên interface là gì
my_ip: "{{ ansible_default_ipv4.address }}"
3. Kiểm tra việc thu thập Fact
Kiểm tra phần đầu playbook của bạn nếu ansible_facts hoàn toàn trống. Đảm bảo rằng bạn không tắt tính năng thu thập fact trên toàn cục. Nếu bạn cần dữ liệu mạng cụ thể một cách nhanh chóng mà không cần quét toàn bộ, hãy sử dụng module setup với một filter.
- name: Thu thập các network facts cụ thể
setup:
filter: ansible_eth*
- name: Hiển thị IP nếu nó tồn tại
debug:
msg: "IP is {{ ansible_eth0.ipv4.address }}"
when: ansible_eth0 is defined
4. Kiểm tra Dictionary
Khi bạn không chắc chắn những key nào có sẵn, hãy chạy một lệnh ad-hoc nhanh. Đây là cách nhanh nhất để xem chính xác những gì host từ xa báo cáo lại cho Ansible.
ansible all -m setup -a "filter=ansible_interfaces"
Ngoài ra, hãy thêm một task debug vào playbook của bạn để trích xuất cấu trúc networking:
- name: Debug tất cả network facts
debug:
var: ansible_facts.networking
Giải quyết các vấn đề về Hostvars
Nếu bạn cần IP của một database server trong khi đang cấu hình một web server, bạn có thể thấy lỗi này vì host database chưa được "ghé thăm". Ansible chỉ nạp dữ liệu vào hostvars cho các host có trong luồng thực thi hiện tại.
Hãy ép buộc thu thập fact cho toàn bộ inventory ở đầu playbook để giải quyết vấn đề này:
- name: Đảm bảo tất cả host facts đều có sẵn
hosts: all
gather_facts: true
- name: Cấu hình Web Server
hosts: webservers
tasks:
- name: Lấy IP DB từ hostvars
set_fact:
db_ip: "{{ hostvars['db-server-01']['ansible_default_ipv4']['address'] }}"
Phòng ngừa và các thực hành tốt nhất
Để giữ cho các playbook của bạn ổn định, hãy tuân theo các nguyên tắc sau:
- Ưu tiên
ansible_default_ipv4thay vì các tên cụ thể nhưeth0hoặcenp0s3. - Xác thực các biến bằng cách sử dụng module
assert. Điều này giúp playbook thất bại với một thông báo lỗi hữu ích thay vì một vết truy dấu Jinja2 khó hiểu. - Áp dụng filter
| defaultbất cứ khi nào bạn truy cập vào các key lồng nhau trong một dictionary.
Việc đối phó với các môi trường cũ lộn xộn thường dẫn đến các facts không đáng tin cậy. Trong những trường hợp này, tôi sử dụng một Công cụ tính Subnet để xác minh các khối CIDR. Điều này đảm bảo các giá trị dự phòng tĩnh của tôi thực sự có ý nghĩa trước khi tôi hardcode chúng vào host_vars.
Xác minh
Xác nhận việc khắc phục bằng cách chạy playbook của bạn với flag --check. Nếu bạn đã triển khai filter default() chính xác, task sẽ bỏ qua hoặc sử dụng giá trị dự phòng thay vì bị crash.
ansible-playbook site.yml --limit web-server-01 --check
Nếu chế độ check vượt qua thành công, cấu hình của bạn giờ đây đã đủ linh hoạt để xử lý các interface mạng khác nhau.

