Module và Idempotency
Idempotency (chạy lại an toàn) là tính chất làm Ansible khác script bash. Bài 4 đã thấy nó: chạy playbook lần hai cho changed=0. Bài này là deep-dive — module làm thế nào để đạt điều đó, và đó cũng là nền để tự viết module ở Bài 11.
Module: nhắc lại
Nhớ Bài 1: module là đoạn code (thường Python) Ansible ship lên host và chạy bằng Python của host, nhận tham số dạng JSON và trả kết quả dạng JSON. Mỗi task là một lần gọi module. Ansible có sẵn hàng nghìn module, nhóm theo loại việc: gói (dnf, apt), file (copy, template, file), dịch vụ (service, systemd), người dùng (user), v.v.
Idempotency hoạt động thế nào
Chìa khóa: module không chạy mù — nó kiểm tra trạng thái hiện tại trước khi hành động. Bạn khai báo trạng thái mong muốn (Bài 4: state: present), module lo phần so sánh và chỉ thay đổi nếu cần:
Task: "file X phải có nội dung Y" (trạng thái mong muốn)
│
Module chạy trên host:
├─ đọc trạng thái HIỆN TẠI của X
├─ so với MONG MUỐN (Y)
├─ giống nhau? ──► KHÔNG làm gì → trả "changed": false (ok)
└─ khác nhau? ──► sửa cho đúng Y → trả "changed": true (changed)
Đây là lý do (Bài 4):
- Lần 1: nginx chưa cài, service chưa chạy, file chưa có → module thấy "khác mong muốn" → hành động →
changed=3. - Lần 2: mọi thứ đã đúng → module thấy "đã khớp" → không làm gì →
changed=0.
Trường changed trong JSON trả về chính là cách Ansible biết task có thay đổi gì hay không, và in ok (vàng nếu changed) cho bạn.
Quan sát idempotency: check mode và diff
Bạn xem được module "nghĩ" gì mà không áp thật, bằng check mode (--check) cộng --diff. Thử: sửa nội dung file trong playbook rồi chạy check:
ansible-playbook site.yml --check --diff
TASK [Triển khai trang index] *********************
--- before: /usr/share/nginx/html/index.html
+++ after: /usr/share/nginx/html/index.html
@@ -1 +1 @@
-<h1>Trien khai bang Ansible</h1>
+<h1>Trien khai bang Ansible v2</h1>
changed: [lab]
PLAY RECAP ****************************************
lab : ok=4 changed=1 ...
Module copy đã: đọc nội dung hiện tại trên host, so với nội dung mong muốn (v2), thấy khác → báo sẽ changed kèm diff chính xác. Nhưng vì là check mode, nó không áp thật — kiểm chứng: curl ngay sau đó vẫn trả nội dung cũ (chưa có "v2"). Đây là sức mạnh của check mode: thấy trước playbook sẽ đổi gì trên production trước khi chạy thật (Bài 13).
Vì sao command và shell KHÔNG idempotent
Đây là điểm cực quan trọng. Module chuyên dụng (dnf, copy, service) hiểu trạng thái nên idempotent. Nhưng command/shell chỉ chạy một lệnh tùy ý — Ansible không biết lệnh đó làm gì, nên không thể so sánh trạng thái. Hệ quả: chúng luôn báo changed mỗi lần chạy (như đã thấy ở Bài 2).
ansible -m dnf -a "name=nginx state=present" → nginx có rồi: ok (idempotent)
ansible -m command -a "dnf install -y nginx" → LUÔN changed (Ansible mù)
Quy tắc vàng: dùng module chuyên dụng thay cho command/shell bất cứ khi nào có thể. Khi buộc phải dùng command/shell, làm chúng idempotent thủ công bằng:
creates:/removes:— "chỉ chạy nếu file này CHƯA/ĐÃ tồn tại". Ví dụcommand: ./install.sh creates=/opt/app→ bỏ qua nếu/opt/appđã có.changed_when:/failed_when:— tự định nghĩa khi nào coi là changed/failed dựa trên kết quả (Bài 7).
Tham số state: ngôn ngữ của trạng thái
Phần lớn module dùng tham số state để khai báo trạng thái mong muốn:
state: present thứ đó phải TỒN TẠI (cài gói, tạo file/user)
state: absent thứ đó phải KHÔNG tồn tại (gỡ/xóa)
state: started dịch vụ phải ĐANG CHẠY
state: stopped dịch vụ phải DỪNG
state: latest gói phải ở bản MỚI NHẤT
Để gỡ nginx, chỉ cần đổi state: present → state: absent — module lo phần còn lại, idempotent cả hai chiều.
Tour các module hay dùng
Vài module bạn sẽ gặp liên tục (tất cả thuộc ansible.builtin, idempotent):
Gói: dnf / apt / package cài/gỡ phần mềm (package = đa distro)
File: copy copy file/nội dung lên host
template như copy nhưng render Jinja2 trước (Bài 6)
file tạo/xóa file/thư mục, đặt quyền (state, mode)
lineinfile đảm bảo MỘT dòng có/không trong file
blockinfile đảm bảo một KHỐI text trong file
Dịch vụ: service / systemd start/stop/enable dịch vụ
Người dùng: user / group quản lý user/group (nhớ series Linux Bài 12)
Lấy code: git clone/checkout repo
Lệnh: command / shell chạy lệnh (KHÔNG idempotent — hạn chế dùng)
Tra tài liệu một module: ansible-doc <tên_module> (ví dụ ansible-doc copy) — in mô tả, tham số, ví dụ ngay trong terminal. Đây là cách tra nhanh thay vì mở web docs.ansible.com.
Tổng kết
Module đạt idempotency bằng cách đọc trạng thái hiện tại trên host, so với trạng thái mong muốn bạn khai báo (state:), chỉ thay đổi phần khác biệt, rồi trả changed: true/false. Vì vậy chạy lại playbook luôn an toàn — đó là lý do changed=3 (lần đầu) thành changed=0 (lần sau). --check --diff cho xem trước thay đổi mà không áp. command/shell KHÔNG idempotent (Ansible không hiểu lệnh tùy ý) — ưu tiên module chuyên dụng, hoặc thêm creates/removes/changed_when. ansible-doc <module> để tra cứu.
Tới giờ ta hard-code giá trị trong playbook. Bài 6 làm chúng linh hoạt: biến, facts (thông tin host), và template Jinja2 để sinh file cấu hình động.