Roles: Tổ Chức Code Tái Sử Dụng

K
Kai··5 min read

Một playbook dài với hàng chục task khó đọc, khó tái dùng, khó chia sẻ. Role là cách Ansible giải quyết: đóng gói tất cả những gì cần để làm một việc (task, handler, template, file, biến) thành một đơn vị có cấu trúc thư mục chuẩn, dùng lại được giữa các playbook và dự án. Đây là bước chuyển từ "viết playbook" sang "thiết kế tự động hóa".

Vì sao cần role

Tưởng tượng bạn cấu hình web server, database, monitoring... trong một file site.yml khổng lồ. Vấn đề:

  • Khó đọc: hàng trăm dòng lẫn lộn.
  • Khó tái dùng: muốn dùng phần "web server" cho dự án khác phải copy-paste.
  • Khó chia sẻ: không có cách đóng gói gọn để người khác dùng.

Role giải quyết cả ba: gom logic "web server" vào một role webserver, rồi mọi playbook chỉ cần gọi role đó. Đóng gói một lần, dùng nhiều nơi.

Tạo role: ansible-galaxy init

Ansible có sẵn lệnh tạo khung role với cấu trúc chuẩn:

ansible-galaxy init roles/webserver
- Role roles/webserver was created successfully

Nó dựng cây thư mục:

roles/webserver/
├── defaults/      # biến MẶC ĐỊNH (ưu tiên thấp nhất — Bài 6) → main.yml
├── vars/          # biến của role (ưu tiên cao hơn defaults)
├── tasks/         # các task chính → main.yml (điểm vào)
├── handlers/      # handler (notify) → main.yml
├── templates/     # file Jinja2 (.j2)
├── files/         # file tĩnh (copy nguyên)
├── meta/          # metadata + phụ thuộc role khác
└── tests/         # khung test

Mỗi thư mục có vai trò rõ ràng, và Ansible tự tìm đúng chỗ: khi role chạy, nó tự đọc tasks/main.yml làm điểm vào, tự nạp defaults/main.ymlvars/main.yml, tự tìm template trong templates/. Bạn không phải khai báo đường dẫn — đây là sức mạnh của quy ước.

Điền nội dung role

roles/webserver/tasks/main.yml (điểm vào):

---
- name: Cài nginx
  ansible.builtin.dnf: { name: nginx, state: present }

- name: Bật nginx
  ansible.builtin.service: { name: nginx, state: started, enabled: true }

- name: Trang index từ template
  ansible.builtin.template:
    src: index.html.j2          # tự tìm trong roles/webserver/templates/
    dest: /usr/share/nginx/html/index.html
  notify: Restart nginx          # handler trong roles/webserver/handlers/

roles/webserver/defaults/main.yml (giá trị mặc định, dễ ghi đè):

site_name: "Default Site"

roles/webserver/templates/index.html.j2:

<h1>{{ site_name }} — role webserver</h1>

roles/webserver/handlers/main.yml:

---
- name: Restart nginx
  ansible.builtin.service: { name: nginx, state: restarted }

Để ý src: index.html.j2 không cần đường dẫn đầy đủ — Ansible tự tìm trong templates/ của role. Tương tự handler Restart nginx được tìm trong handlers/ của role.

Dùng role trong playbook

Playbook giờ rất gọn — chỉ gọi role và truyền biến:

---
- hosts: web
  become: true
  roles:
    - role: webserver
      vars:
        site_name: "KKloud qua Role"

Chạy:

TASK [webserver : Cài nginx] **************
ok: [lab]
TASK [webserver : Trang index từ template] *
changed: [lab]
RUNNING HANDLER [webserver : Restart nginx] *
changed: [lab]
PLAY RECAP ********************************
lab : ok=5  changed=2  failed=0

curl ra <h1>KKloud qua Role — role webserver</h1>. Để ý task hiện dạng webserver : <tên task> — Ansible tự gắn tên role, nên log rõ task nào của role nào. Và site_name ta truyền (KKloud qua Role) ghi đè giá trị mặc định trong defaults/ (đúng thứ tự ưu tiên Bài 6).

defaults vs vars: đặt biến ở đâu trong role

Role có hai chỗ đặt biến, khác nhau ở ưu tiên (Bài 6):

  • defaults/main.yml — ưu tiên thấp nhất. Đặt giá trị mặc định ở đây để người dùng role dễ ghi đè (qua group_vars, hoặc vars khi gọi role). Đây là nơi đặt "tham số" của role.
  • vars/main.yml — ưu tiên cao hơn, khó ghi đè. Đặt giá trị nội bộ role không nên đổi.

Quy tắc: tham số người dùng tùy chỉnh → defaults/; hằng số nội bộ → vars/.

Các cách gọi role

# 1. Khai báo roles: (chạy trước tasks của play)
roles:
  - webserver
  - role: database
    vars: { db_name: app }

# 2. include_role (động, trong tasks — có thể đặt trong when/loop)
- ansible.builtin.include_role:
    name: webserver

# 3. import_role (tĩnh, nạp lúc parse)
- ansible.builtin.import_role:
    name: webserver

roles: là cách phổ biến nhất. include_role/import_role linh hoạt hơn (gọi role có điều kiện, trong vòng lặp). Khác biệt include vs import: import nạp tĩnh lúc phân tích playbook; include nạp động lúc chạy (cho phép when/loop quyết định có gọi không).

Phụ thuộc giữa role

meta/main.yml khai báo role này phụ thuộc role khác (tự chạy trước):

dependencies:
  - role: common      # chạy "common" trước "webserver"

Hữu ích: ví dụ mọi role app phụ thuộc role common (cấu hình cơ bản, user, firewall). Nhưng đừng lạm dụng — dependency ngầm khó theo dõi; nhiều người thích gọi tường minh trong playbook hơn.

Galaxy: chia sẻ và tái dùng role của người khác

ansible-galaxy không chỉ tạo role — nó còn tải role/collection người khác đã viết từ Ansible Galaxy (kho cộng đồng). Ví dụ thay vì tự viết role cài Docker, dùng role có sẵn. Đây là cầu nối sang Bài 9 (Collections) — cách đóng gói và phân phối hiện đại.

🧹 Dọn dẹp

Role mẫu ở repo nghiadaulau/ansible-series, thư mục 08-roles/roles/webserver. EC2 lab giữ tới Bài 16.

Tổng kết

Role đóng gói task, handler, template, file, biến thành đơn vị tái sử dụng theo cấu trúc thư mục chuẩn (tasks/, handlers/, templates/, defaults/, vars/, meta/). Tạo bằng ansible-galaxy init; Ansible tự tìm đúng file theo quy ước. Đặt tham số ở defaults/ (dễ ghi đè), hằng số ở vars/. Dùng role qua roles: (hoặc include_role/import_role), truyền biến khi gọi. Role là nền của tổ chức code Ansible chuyên nghiệp.

Role giúp tổ chức trong một dự án. Bài 9 lên một tầng: collection — đơn vị đóng gói và phân phối hiện đại gom nhiều role, module, plugin lại, với FQCN.