Module và Idempotency

K
Kai··5 min read

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: presentstate: 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.