Tối Ưu và Chiến Lược Thực Thi

K
Kai··5 min read

Trên một host, Ansible chạy đơn giản. Trên hàng trăm host, hai câu hỏi nảy sinh: làm sao chạy nhanh (song song), và làm sao an toàn (không sập cả fleet cùng lúc). Bài này là các kỹ thuật vận hành thực chiến để trả lời hai câu đó.

forks: chạy song song bao nhiêu host

Mặc định Ansible xử lý 5 host song song (forks = 5). Với fleet lớn, tăng lên để nhanh hơn:

# ansible.cfg
[defaults]
forks = 50

Hoặc ansible-playbook site.yml -f 50. forks là số kết nối SSH đồng thời control node mở. Tăng quá cao có thể nghẽn control node (CPU/RAM/mạng) — cân theo sức máy, thường 20–100.

strategy: linear vs free

Strategy plugin quyết định cách Ansible điều phối task qua các host:

   linear (mặc định):  mọi host chạy XONG task N rồi cả nhóm mới sang task N+1
                       → đồng bộ, dễ hiểu; nhưng host nhanh phải đợi host chậm

   free:               mỗi host chạy hết các task của mình ĐỘC LẬP, không đợi nhau
                       → nhanh hơn khi host không đồng đều; khó theo dõi hơn
- hosts: web
  strategy: free        # mỗi host chạy hết playbook của nó, không chờ

linear (mặc định) dễ suy luận và đủ cho phần lớn. free hữu ích khi các host có tốc độ rất khác nhau và task độc lập.

serial: rolling update không gián đoạn

Đây là kỹ thuật quan trọng nhất cho production. Mặc định Ansible áp thay đổi lên tất cả host cùng lúc — nguy hiểm: nếu thay đổi có lỗi, toàn bộ fleet sập đồng thời. serial chia host thành từng đợt:

- hosts: web
  serial: 2          # hoặc "25%"
  tasks:
    - name: Deploy phiên bản mới
      ...
   6 host, serial: 2 →
   Đợt 1: host1, host2   ── deploy + kiểm tra ──┐
   Đợt 2: host3, host4   ── chỉ chạy NẾU đợt 1 ok │ (rolling)
   Đợt 3: host5, host6   ──                       ┘

Nếu một đợt lỗi (vượt ngưỡng max_fail_percentage), Ansible dừng — các host còn lại chưa bị động, dịch vụ vẫn chạy trên chúng. Kết hợp với load balancer (series Mạng Bài 11): rút host khỏi LB → deploy → kiểm tra → đưa lại LB, từng đợt → cập nhật không gián đoạn (zero-downtime). Đây là cách Ansible làm rolling deploy.

delegate_to và run_once

delegate_to chạy một task trên host khác với host đang xử lý — hữu ích khi thao tác liên quan host hiện tại nhưng phải chạy ở nơi khác:

- name: Rút host khỏi load balancer trước khi deploy
  community.general.haproxy:
    state: disabled
    host: "{{ inventory_hostname }}"
  delegate_to: "{{ load_balancer_host }}"     # chạy TRÊN load balancer

run_once: true chạy task đúng một lần (trên host đầu) thay vì mọi host — cho việc chỉ cần làm một lần (chạy migration database, tạo bản ghi DNS):

- name: Chạy migration (chỉ một lần cho cả nhóm)
  ansible.builtin.command: /opt/app/migrate.sh
  run_once: true

Kết hợp delegate_to: localhost + run_once là mẫu phổ biến để chạy thứ gì đó trên control node một lần.

async: task chạy lâu

Task dài (cập nhật hệ thống, build) có thể vượt timeout SSH. async chạy nó nền trên host và bạn poll trạng thái:

- name: Việc lâu
  ansible.builtin.command: /opt/long-job.sh
  async: 600        # cho phép chạy tối đa 600s
  poll: 10          # kiểm tra mỗi 10s

poll: 0 thì "fire and forget" (chạy rồi đi tiếp, kiểm tra sau bằng module async_status) — hữu ích khi khởi động nhiều việc dài song song.

Tăng tốc: fact caching và pipelining

Hai tối ưu giảm thời gian đáng kể:

  • Pipelining (nhớ Bài 1): gửi module qua stdin của SSH thay vì sftp PUT riêng, giảm số vòng SSH mỗi task. Bật trong ansible.cfg:
[ssh_connection]
pipelining = True
  • Fact caching: Gathering Facts (Bài 4) tốn thời gian mỗi lần chạy. Cache facts để tái dùng giữa các lần chạy:
[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 3600

Hoặc đơn giản hơn: gather_facts: false ở play không cần facts. Tìm task chậm bằng callback profile_tasks (Bài 12):

ANSIBLE_CALLBACKS_ENABLED=profile_tasks ansible-playbook site.yml
Gathering Facts -------------------------- 3.49s
Cài gói ----------------------------------- 2.97s

→ biết chính xác task nào ngốn thời gian để tối ưu.

tags: chạy chọn lọc

Gắn tags cho task để chạy một phần playbook (nhắc ở Bài 7):

    - name: Cài gói
      ansible.builtin.dnf: { name: nginx, state: present }
      tags: [install]
    - name: Cấu hình
      ansible.builtin.copy: { ... }
      tags: [config]
ansible-playbook site.yml --tags config       # chỉ chạy task tag "config"
ansible-playbook site.yml --skip-tags install # chạy tất cả TRỪ tag "install"
ansible-playbook site.yml --list-tasks        # xem task + tags trước khi chạy

Chạy --tags config cho thấy chỉ task gắn config chạy, task install bị bỏ. Cực tiện khi playbook dài mà bạn chỉ muốn cập nhật cấu hình (khỏi chạy lại phần cài đặt).

check mode: dry-run trước production

Nhắc lại Bài 5 vì nó thuộc nhóm vận hành an toàn: --check --diff xem playbook sẽ đổi gì mà không áp thật. Luôn --check trước khi chạy thật trên production — và --diff để thấy nội dung file sẽ thay đổi ra sao. Đây là lưới an toàn quan trọng nhất khi vận hành.

Tổng kết

Chạy Ansible quy mô lớn: forks điều khiển song song; strategy (linear đồng bộ / free độc lập); serial chia đợt cho rolling update không gián đoạn (kết hợp load balancer); delegate_to/run_once chạy task ở host khác/một lần; async cho task dài; pipelining + fact caching + gather_facts: false để tăng tốc (profile_tasks tìm task chậm); tags chạy chọn lọc; --check --diff dry-run an toàn trước production. Đây là bộ công cụ biến playbook "chạy được" thành "vận hành được trên fleet thật".

Chạy nhanh và an toàn rồi, nhưng làm sao biết role đúng trước khi đẩy lên hàng trăm host? Bài 14: kiểm thử role bài bản với Molecule.