Mở Rộng Ansible: Filter, Lookup và Callback Plugins

K
Kai··5 min read

Bài 11 viết module — code chạy trên host để làm việc. Bài này là chiều mở rộng còn lại: plugin — code chạy trên control node để mở rộng chính Ansible. Hiểu rõ hai khái niệm này là bạn nắm trọn khả năng tùy biến Ansible.

Module vs plugin: khác biệt cốt lõi

Đây là phân biệt quan trọng và hay nhầm:

   MODULE                              PLUGIN
   ─────────────────────────────────────────────────────────────
   chạy TRÊN HOST đích (qua SSH)        chạy TRÊN CONTROL NODE
   làm việc: cài gói, sửa file...       mở rộng bản thân Ansible
   ship lên host (AnsiballZ — Bài 1)    chạy local trong tiến trình ansible
   gọi như task                         dùng trong Jinja2 / inventory / output...

Nói cách khác: module = "làm gì đó trên máy đích"; plugin = "thay đổi cách Ansible vận hành". Bạn đã dùng plugin mà chưa để ý: filter | default (Bài 6) là filter plugin, inventory động aws_ec2 (Bài 3) là inventory plugin, kết nối SSH là connection plugin.

Các loại plugin hay gặp

   filter     biến đổi dữ liệu trong Jinja2 ( {{ x | myfilter }} )
   lookup     lấy dữ liệu từ NGUỒN NGOÀI ( {{ lookup('file', '/path') }} )
   callback   tùy biến OUTPUT/logging (đổi cách hiển thị kết quả)
   connection cách kết nối host (ssh, docker, local, winrm...)
   inventory  nguồn inventory (aws_ec2, k8s... — Bài 3)
   test       phép kiểm tra trong Jinja2 ( {{ x is myverб }} )

Ta đi vào ba loại bạn hay tự viết: filter, lookup, callback.

Filter plugin: biến đổi dữ liệu trong Jinja2

Filter (| something) biến đổi giá trị trong template/biểu thức (Bài 6). Ngoài filter sẵn (default, upper, length...), bạn tự viết. Filter plugin là một file Python trong thư mục filter_plugins/:

# filter_plugins/custom_filters.py
def shout(value):
    return str(value).upper() + "!"

def env_prefix(value, env="dev"):
    return f"{env}-{value}"

class FilterModule(object):
    def filters(self):
        return {
            'shout': shout,
            'env_prefix': env_prefix,
        }

Mỗi filter chỉ là một hàm Python (nhận giá trị bên trái |, trả giá trị mới). Lớp FilterModule.filters() đăng ký tên filter → hàm. Dùng ngay trong playbook:

- hosts: localhost
  vars:
    name: "kkloud"
  tasks:
    - ansible.builtin.debug:
        msg: "{{ name | shout }} / {{ name | env_prefix('prod') }}"
"msg": "KKLOUD! / prod-kkloud"

name | shoutKKLOUD!; name | env_prefix('prod')prod-kkloud (tham số 'prod' truyền vào hàm). Filter chạy trên control node lúc render — đúng định nghĩa plugin. Filter tùy biến rất hữu ích để xử lý dữ liệu phức tạp gọn gàng trong template thay vì nhồi logic vào playbook.

Lookup plugin: lấy dữ liệu ngoài

Lookup lấy dữ liệu từ nguồn bên ngoài vào playbook, dùng qua lookup('tên', ...). Đây là cách chuẩn để đọc file, biến môi trường, secret từ vault ngoài... Vài lookup sẵn:

- ansible.builtin.debug:
    msg: "{{ lookup('ansible.builtin.file', '/etc/hostname') }}"      # nội dung file
- ansible.builtin.debug:
    msg: "{{ lookup('ansible.builtin.env', 'HOME') }}"               # biến môi trường
- ansible.builtin.debug:
    msg: "{{ lookup('ansible.builtin.pipe', 'date +%F') }}"          # output lệnh

Lookup đặc biệt mạnh để lấy secret từ secret manager (nhớ Bài 10 — thay cho Vault tĩnh ở quy mô lớn):

    db_pass: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/db:password') }}"
    api_key: "{{ lookup('amazon.aws.aws_secret', 'prod/api_key') }}"

Bạn tự viết lookup plugin (trong lookup_plugins/) tương tự filter — kế thừa LookupBase, hiện thực run() trả về danh sách. Hữu ích khi cần tích hợp một nguồn dữ liệu nội bộ.

Lưu ý: lookup chạy trên control node, mỗi lần được tham chiếu (không cache mặc định). Đừng đặt lookup nặng (gọi API) trong vòng lặp lớn — sẽ chạy lại nhiều lần.

Callback plugin: tùy biến output

Callback plugin thay đổi cách Ansible hiển thị/ghi lại kết quả — phản ứng với các sự kiện (task bắt đầu, task xong, play kết thúc). Đây là cách đổi giao diện output hoặc tích hợp logging/monitoring. Bật một callback có sẵn trong ansible.cfg:

[defaults]
stdout_callback = yaml          # output dạng YAML dễ đọc hơn mặc định
callbacks_enabled = profile_tasks, timer    # đo thời gian từng task + tổng
  • stdout_callback = yaml — hiển thị kết quả dạng YAML (dễ đọc hơn JSON dồn một dòng).
  • profile_tasks — in thời gian mỗi task (tìm task chậm — liên quan Bài 13).
  • timer — in tổng thời gian playbook.

Callback tự viết hữu ích để: gửi kết quả lên Slack, ghi log JSON cho hệ thống giám sát, định dạng output cho CI. Bạn kế thừa CallbackBase và hiện thực các hook như v2_runner_on_ok, v2_runner_on_failed...

Đặt plugin ở đâu

Giống module (Bài 11), plugin được tìm theo quy ước thư mục cạnh playbook, mỗi loại một thư mục:

   filter_plugins/      filter plugin
   lookup_plugins/      lookup plugin
   callback_plugins/    callback plugin
   library/             module (Bài 11)

Hoặc khai báo đường dẫn trong ansible.cfg (filter_plugins = ...), hoặc — chuẩn nhất ở quy mô lớn — đóng gói vào một collection (Bài 9): collection có thư mục plugins/filter/, plugins/lookup/, plugins/modules/... gom tất cả phần mở rộng của bạn lại để chia sẻ.

Tổng kết

Plugin mở rộng bản thân Ansible (chạy trên control node), khác module (làm việc trên host). Ba loại hay tự viết: filter biến đổi dữ liệu trong Jinja2 (| myfilter, lớp FilterModule); lookup lấy dữ liệu ngoài (lookup('file'/'env'/secret-manager...)); callback tùy biến output/logging (stdout_callback, profile_tasks). Đặt plugin trong filter_plugins/, lookup_plugins/, callback_plugins/ cạnh playbook, hoặc đóng gói vào collection. Cùng module ở Bài 11, plugin cho bạn tùy biến Ansible ở mọi tầng.

Đã nắm cách viết và mở rộng, ta chuyển sang vận hành thực chiến ở quy mô lớn. Bài 13: tối ưu và chiến lược thực thi — chạy Ansible nhanh và an toàn trên hàng trăm host.