Kiến Trúc Ansible: Control Node, Inventory, Module và SSH

K
Kai··5 min read

Bài 0 nói Ansible "agentless, qua SSH, idempotent". Bài này mổ xẻ chính xác điều đó: các thành phần của Ansible, và — quan trọng nhất — chuyện gì thực sự xảy ra khi bạn chạy một lệnh. Hiểu cơ chế này thì ba tính chất kia (agentless, idempotent, viết module được) không còn là điều bí ẩn.

Các thành phần

Ansible có một sự bất đối xứng quan trọng: mọi thứ nằm ở control node, máy đích gần như không cần gì.

   Control node (máy bạn)                  Managed node (host đích)
   ┌──────────────────────────┐            ┌─────────────────────────┐
   │ • Ansible (đã cài)        │            │ • sshd (có sẵn)         │
   │ • thư viện module         │            │ • python3 (có sẵn)      │
   │ • inventory (danh sách host)│   SSH    │                         │
   │ • playbook (YAML)         │ ─────────► │ KHÔNG cài agent gì cả   │
   └──────────────────────────┘            └─────────────────────────┘
  • Control node — máy cài Ansible, nơi bạn chạy lệnh. Nó giữ thư viện module, inventory, playbook. (Chỉ chạy trên Linux/macOS; Windows dùng WSL.)
  • Managed node (host) — máy đích. Yêu cầu duy nhất: SSH vào được và có Python. Không cài Ansible, không agent.
  • Inventory — danh sách host + cách nhóm (Bài 3).
  • Module — đoạn code (thường Python) thực hiện một việc trên host: ping, apt, copy, service... Ansible có sẵn hàng nghìn module.
  • Plugin — mở rộng bản thân Ansible (connection, filter, lookup, callback... — Bài 12).

Chuyện gì xảy ra khi chạy một lệnh

Đây là phần deep-dive. Chạy lệnh ad-hoc đơn giản nhất với cờ -vvv (rất chi tiết) để nhìn vào trong:

ansible all -m ping -vvv

Lọc các dòng quan trọng, ta thấy đúng trình tự:

<203.0.113.10> ESTABLISH SSH CONNECTION FOR USER: ec2-user
<203.0.113.10> SSH: EXEC ssh ... '/bin/sh -c ... mkdir -p ".../.ansible/tmp/ansible-tmp-..."'
Using module file .../ansible/modules/ping.py
<203.0.113.10> PUT /tmp/.../tmpXXXX TO /home/ec2-user/.ansible/tmp/.../AnsiballZ_ping.py
<203.0.113.10> SSH: EXEC sftp ... put ... AnsiballZ_ping.py
<203.0.113.10> SSH: EXEC ssh ... chmod u+rwx .../AnsiballZ_ping.py
... python3 .../AnsiballZ_ping.py  → JSON về
... rm -rf .../ansible-tmp-...

Diễn giải từng bước — đây chính là "vòng đời" của một task:

   1. SSH CONNECT          mở kết nối SSH tới host (dùng key, user ec2-user)
   2. MKDIR TEMP           tạo thư mục tạm ~/.ansible/tmp/ansible-tmp-... trên HOST
   3. SHIP MODULE          đóng gói module thành "AnsiballZ_ping.py", PUT (sftp) lên host
   4. EXECUTE              chạy: python3 AnsiballZ_ping.py  (BẰNG Python CỦA HOST)
   5. RETURN JSON          module in kết quả ra stdout dạng JSON → Ansible đọc về
   6. CLEANUP              xóa thư mục tạm trên host

Điểm cốt lõi: Ansible không "điều khiển từ xa" mà CHUYỂN code tới host rồi chạy tại chỗ. Module được đóng gói (cùng thư viện cần thiết) thành một file Python tự chứa gọi là AnsiballZ, ship lên host qua SSH, thực thi bằng Python của host, trả về JSON rồi tự xóa.

Vì sao cơ chế này giải thích mọi thứ

Ba đặc điểm ở Bài 0 giờ trở nên hiển nhiên:

  • Agentless: không cần agent vì Ansible mang code tới mỗi lần chạy, dùng SSH + Python vốn đã có trên host. Yêu cầu duy nhất với host là "SSH vào được + có Python". Không có dịch vụ nền nào cài lên host.
  • Idempotent: module (chạy trên host) tự kiểm tra trạng thái hiện tại trước khi hành động, rồi trả JSON có trường changed: true/false. Nó là code thật, không phải lệnh mù — nên biết "đã đúng trạng thái chưa" mà không làm thừa (Bài 5).
  • Viết module được: vì module chỉ là một chương trình nhận tham số (JSON vào) và trả JSON ra, bạn tự viết một cái bằng Python là xong (Bài 11).

Vài chi tiết đáng để ý trong trace

Nhìn kỹ output -vvv còn thấy hai thứ Ansible làm để chạy nhanh:

  • ControlMaster=auto ControlPersist=60s — Ansible tái dùng một kết nối SSH cho nhiều task (giữ kết nối 60s), thay vì mở SSH mới mỗi task. Đây là tối ưu quan trọng khi playbook có nhiều task.
  • StrictHostKeyChecking=no (do ta đặt trong ansible.cfg) — bỏ qua hỏi xác nhận host key, tiện cho lab. Trong production nên quản lý known_hosts đàng hoàng.

Có một tùy chọn tăng tốc nữa là pipelining (gửi module qua stdin của SSH thay vì sftp PUT, giảm số vòng SSH) — ta nhắc lại ở Bài 13 (tối ưu).

Push model: control node chủ động

Khác các công cụ agent-based (Chef/Puppet) theo pull model (agent trên host định kỳ tự kéo cấu hình về), Ansible theo push model: control node chủ động đẩy thay đổi xuống host khi bạn chạy lệnh. Hệ quả: thay đổi xảy ra ngay và có kiểm soát (bạn biết chính xác lúc nào áp), nhưng host không tự "sửa mình" giữa các lần chạy — muốn đảm bảo trạng thái thì chạy lại playbook (định kỳ hoặc qua CI).

Tổng kết

Ansible đặt mọi thứ ở control node (Ansible + module + inventory + playbook); managed node chỉ cần SSH + Python, không agent. Khi chạy một task, Ansible mở SSH, ship module (AnsiballZ) lên host, chạy bằng Python của host, nhận JSON về, rồi dọn — quan sát được bằng -vvv. Chính cơ chế "mang code tới chạy tại chỗ" này giải thích vì sao Ansible agentless (không cần cài gì), idempotent (module tự kiểm trạng thái), và mở rộng được (module chỉ là chương trình JSON-in/JSON-out). Đây là push model: control node chủ động đẩy thay đổi.

Bài 2 xắn tay vào: cài Ansible làm control node, dựng một EC2 làm host, và chạy lệnh ad-hoc đầu tiên — chính là ping ta vừa mổ xẻ.