0

Lab 8 — Ansible Roles

Mục tiêu: Sau Lab này, người học hiểu được Ansible Role, biết cách tổ chức một project Ansible theo chuẩn production, tách Tasks, Handlers, Templates, Variables, Defaults thành các thành phần độc lập và có thể tái sử dụng.


1. Bối cảnh

Ở các Lab trước, chúng ta đã xây dựng Playbook như:

playbook.yml
├── tasks
├── handlers
├── variables
└── templates

Ví dụ:

---
- name: Configure nginx
  hosts: webservers
  become: true

  tasks:
    - name: Install nginx
      ansible.builtin.apt:
        name: nginx
        state: present

    - name: Deploy nginx configuration
      ansible.builtin.template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: Reload nginx

  handlers:
    - name: Reload nginx
      ansible.builtin.service:
        name: nginx
        state: reloaded

Cách này hoàn toàn ổn khi project nhỏ.

Nhưng hãy tưởng tượng project có:

10 Playbook
20 server
5 environment
3 application

Nếu tất cả logic nằm trong playbook.yml, project sẽ rất nhanh trở nên khó quản lý.

Ví dụ:

playbook.yml
  ├── install nginx
  ├── configure nginx
  ├── install docker
  ├── configure docker
  ├── create users
  ├── deploy application
  ├── configure firewall
  ├── setup monitoring
  ├── configure logging
  └── ...

Đây là lúc chúng ta cần Roles.


2. Role là gì?

Có thể hiểu đơn giản:

Role là một package chứa toàn bộ logic để quản lý một thành phần của hệ thống.

Ví dụ:

nginx role

có thể chứa:

Tasks
Handlers
Templates
Files
Defaults
Variables

Mô hình:

                Ansible Role
                     │
        ┌────────────┼────────────┐
        │            │            │
      Tasks       Templates    Handlers
        │            │            │
        └────────────┼────────────┘
                     │
                     ▼
                  Nginx

Thay vì viết:

tasks:
  - install nginx
  - configure nginx
  - deploy config
  - reload nginx

chúng ta có thể:

roles:
  - nginx

Playbook trở nên rất ngắn và dễ đọc.


3. Tại sao cần Roles?

Roles giải quyết 4 vấn đề lớn.

1. Organization

Tách logic thành từng thành phần.

2. Reusability

Một Role có thể sử dụng lại nhiều lần.

3. Maintainability

Dễ sửa đổi khi project lớn.

4. Scalability

Có thể mở rộng từ:

1 server

thành:

100 servers

mà không biến Playbook thành một file khổng lồ.


4. Tư duy quan trọng

Trước Role:

Playbook
   │
   ├── Install
   ├── Configure
   ├── Template
   └── Restart

Sau Role:

Playbook
   │
   ├── nginx role
   ├── docker role
   ├── nodejs role
   └── monitoring role

Playbook trở thành orchestrator.

Role chịu trách nhiệm implementation.

Đây là một cách phân tách rất quan trọng.


5. Cấu trúc Role

Một Role thường có cấu trúc:

roles/
└── nginx/
    ├── defaults/
    │   └── main.yml
    ├── handlers/
    │   └── main.yml
    ├── tasks/
    │   └── main.yml
    ├── templates/
    │   └── nginx.conf.j2
    ├── files/
    ├── vars/
    │   └── main.yml
    ├── meta/
    │   └── main.yml
    └── README.md

Không nhất thiết Role nào cũng phải sử dụng tất cả các thư mục.


6. Ý nghĩa từng thư mục

Directory Mục đích
tasks/ Tasks chính
handlers/ Handlers
templates/ Jinja2 templates
files/ Static files
defaults/ Default variables
vars/ Role variables
meta/ Dependencies và metadata
README.md Documentation

Đây là cấu trúc mà người học cần nhớ.


7. Tạo Role đầu tiên

Từ project hiện tại:

mkdir -p roles

Sau đó:

ansible-galaxy role init roles/nginx

Ansible sẽ tạo:

roles/
└── nginx/
    ├── README.md
    ├── defaults/
    │   └── main.yml
    ├── handlers/
    │   └── main.yml
    ├── meta/
    │   └── main.yml
    ├── tasks/
    │   └── main.yml
    ├── templates/
    ├── tests/
    ├── vars/
    │   └── main.yml
    └── files/

ansible-galaxy role init là cách nhanh để tạo skeleton Role chuẩn.


8. Project sau khi chuyển sang Role

Cấu trúc:

ansible-lab-08/
├── ansible.cfg
├── inventory.ini
├── site.yml
└── roles/
    └── nginx/
        ├── defaults/
        │   └── main.yml
        ├── handlers/
        │   └── main.yml
        ├── tasks/
        │   └── main.yml
        ├── templates/
        │   └── nginx.conf.j2
        └── vars/
            └── main.yml

9. Role tasks/main.yml

File:

roles/nginx/tasks/main.yml

Nội dung:

---
- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present
    update_cache: true

- name: Deploy nginx configuration
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /etc/nginx/sites-available/todo.conf
    owner: root
    group: root
    mode: "0644"
    validate: "nginx -t -c %s"
  notify: Reload nginx

- name: Ensure nginx is running
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true

Lưu ý:

Trong Role, template:

src: nginx.conf.j2

sẽ được tìm trong:

roles/nginx/templates/

Không cần viết:

roles/nginx/templates/nginx.conf.j2

10. Role Template

Tạo:

roles/nginx/templates/nginx.conf.j2

Ví dụ:

server {
    listen {{ nginx_port }};

    server_name {{ server_name }};

    location / {
        proxy_pass http://{{ app_host }}:{{ app_port }};

        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

11. Role Handler

File:

roles/nginx/handlers/main.yml
---
- name: Reload nginx
  ansible.builtin.service:
    name: nginx
    state: reloaded

Chúng ta đã đưa kiến thức từ Lab 7 vào Role:

Template
   ↓
changed
   ↓
notify
   ↓
Handler
   ↓
Reload

12. Role Defaults

File:

roles/nginx/defaults/main.yml
---
nginx_port: 80

server_name: localhost

app_host: 127.0.0.1

app_port: 8080

Đây là một trong những phần quan trọng nhất của Role.

Role không nên hard-code:

80
localhost
127.0.0.1
8080

mà nên expose configuration thông qua Variables.


13. Tại sao dùng defaults/main.yml?

Ví dụ Role nginx có:

nginx_port: 80

Nhưng production muốn:

nginx_port: 8080

Thay vì sửa Role:

# roles/nginx/defaults/main.yml

nginx_port: 8080

chúng ta có thể override từ bên ngoài.

Đây là một nguyên tắc quan trọng:

Role nên reusable và configurable.


14. defaults vs vars

Hai thư mục này rất dễ gây nhầm.

defaults/main.yml

Dùng cho:

Default configuration có thể dễ dàng override.

Ví dụ:

nginx_port: 80

vars/main.yml

Dùng cho:

Variables nội bộ của Role, thường không muốn người dùng override dễ dàng.

Ví dụ:

nginx_config_path: /etc/nginx/sites-available/todo.conf

15. Quy tắc thực tế

Trong Role reusable:

defaults/

thường được sử dụng nhiều hơn:

vars/

Ví dụ:

# defaults/main.yml

nginx_port: 80
server_name: localhost
app_port: 8080

Trong khi:

# vars/main.yml

nginx_config_path: /etc/nginx/sites-available/todo.conf

Người mới nên nhớ:

Configuration mà user có thể muốn thay đổi → defaults.


16. Playbook sử dụng Role

Tạo:

site.yml
---
- name: Configure webservers
  hosts: webservers
  become: true

  roles:
    - nginx

Chỉ vậy thôi.

Playbook từ:

30–50 dòng

có thể trở thành:

roles:
  - nginx

Đây chính là sức mạnh của Role.


17. Chạy Playbook

ansible-playbook site.yml

Ansible sẽ tự động thực hiện:

site.yml
   │
   ▼
nginx role
   │
   ├── tasks/main.yml
   │
   ├── templates/nginx.conf.j2
   │
   ├── handlers/main.yml
   │
   └── defaults/main.yml

18. Kiểm tra Idempotency

Chạy lần 1:

ansible-playbook site.yml

Có thể:

changed > 0

Chạy lần 2:

ansible-playbook site.yml

Mục tiêu:

changed=0

Role không làm mất tính Idempotency.

Đây là điểm rất quan trọng:

Role không thay thế Idempotency. Role giúp tổ chức các automation có tính Idempotent.


19. Override Variables

Ví dụ:

---
- name: Configure webservers
  hosts: webservers
  become: true

  roles:
    - role: nginx
      vars:
        nginx_port: 8080
        server_name: todo.example.com
        app_port: 9090

Bây giờ Role dùng:

nginx_port = 8080
server_name = todo.example.com
app_port = 9090

thay vì default.


20. Cách này có tốt không?

Có thể dùng:

roles:
  - role: nginx
    vars:
      nginx_port: 8080

Nhưng khi project lớn, chúng ta thường muốn tách configuration ra khỏi Playbook.

Ví dụ:

group_vars/
├── webservers.yml
└── production.yml

Sau đó:

nginx_port: 80
server_name: todo.example.com
app_port: 8080

Role chỉ consume variables.


21. Role nên biết gì?

Một Role tốt:

Role
 │
 ├── biết cách install
 ├── biết cách configure
 ├── biết cách restart/reload
 └── biết cách validate

Nhưng không nên biết:

Production server IP
Developer username
Environment name
Company-specific secrets

Ví dụ không nên hard-code:

server_name: production.mycompany.com

trong Role.

Nên:

server_name: "{{ app_domain }}"

app_domain được truyền từ environment.


22. Một Role nên làm một việc

Không nên tạo:

roles/
└── everything/

bên trong:

install nginx
install docker
install node
create users
configure firewall
deploy application
setup monitoring

Thay vào đó:

roles/
├── nginx/
├── docker/
├── nodejs/
├── users/
├── firewall/
├── application/
└── monitoring/

Đây là Separation of Concerns.


23. Nhiều Roles trong một Playbook

Ví dụ:

---
- name: Provision application servers
  hosts: app_servers
  become: true

  roles:
    - common
    - docker
    - nginx
    - application
    - monitoring

Flow:

Server
  │
  ├── common
  │
  ├── docker
  │
  ├── nginx
  │
  ├── application
  │
  └── monitoring

Playbook trở thành một bản mô tả kiến trúc rất rõ ràng.


24. Role common

Một Role phổ biến trong production là:

common

Nó có thể quản lý:

Timezone
Packages
Users
SSH configuration
NTP
Directories
Basic security

Ví dụ:

roles:
  - common
  - docker
  - nginx

25. Role Dependencies

Một Role có thể phụ thuộc vào Role khác.

Ví dụ:

application
    │
    └── depends on
            │
            ▼
          docker

Trong:

roles/application/meta/main.yml

có thể khai báo dependency.

Ví dụ:

---
dependencies:
  - role: docker

Khi chạy:

application
    ↓
docker
    ↓
application

Ansible sẽ xử lý dependency.


26. Tại sao Dependency hữu ích?

Giả sử:

application role

cần Docker.

Thay vì bắt người dùng nhớ:

roles:
  - docker
  - application

Role application có thể tự mô tả:

I depend on docker.

Điều này làm Role reusable hơn.


27. Nhưng Dependency không nên lạm dụng

Ví dụ:

application
 ↓
docker
 ↓
common
 ↓
security
 ↓
firewall
 ↓
monitoring
 ↓
...

Sau một thời gian rất khó hiểu:

Tại sao chạy Role A lại thay đổi 6 thứ khác?

Do đó dependency nên được thiết kế rõ ràng.


28. Exercise 1 — Create Nginx Role

Tạo:

roles/nginx/

Yêu cầu:

tasks/main.yml
handlers/main.yml
defaults/main.yml
templates/nginx.conf.j2

Role phải:

  • [ ] Install Nginx.
  • [ ] Enable Nginx.
  • [ ] Start Nginx.
  • [ ] Deploy configuration.
  • [ ] Validate configuration.
  • [ ] Reload khi config thay đổi.

29. Exercise 2 — Configurable Nginx

Tạo variables:

nginx_port: 80
server_name: localhost
app_host: 127.0.0.1
app_port: 8080

Sau đó override:

nginx_port: 8080
server_name: todo.example.com
app_port: 3000

Kiểm tra configuration thực tế trên server.


30. Exercise 3 — Multiple Roles

Tạo:

roles/
├── common/
├── nginx/
└── docker/

common

Cài:

curl
vim
git
htop

nginx

Cài và configure Nginx.

docker

Cài Docker.

Playbook:

---
- name: Provision server
  hosts: webservers
  become: true

  roles:
    - common
    - docker
    - nginx

31. Exercise 4 — Role Reusability

Có:

web-01
web-02
web-03

Tất cả cùng sử dụng:

nginx role

Nhưng:

web-01 → app_port 8080
web-02 → app_port 8081
web-03 → app_port 8082

Sử dụng variables để cấu hình khác nhau.

Mục tiêu:

Không sửa Role.


32. Exercise 5 — Role + Handler

Thay đổi:

app_port: 8080

thành:

app_port: 8081

Chạy:

ansible-playbook site.yml

Kỳ vọng:

Template → changed
     ↓
notify
     ↓
Reload nginx

Chạy lại:

ansible-playbook site.yml

Kỳ vọng:

Template → ok
Handler → không chạy

33. Exercise 6 — Tạo Role Docker

Tạo:

ansible-galaxy role init roles/docker

Cấu trúc:

roles/docker/
├── defaults/
├── handlers/
├── tasks/
├── templates/
└── vars/

Task cần thực hiện:

Install Docker
    ↓
Enable Docker
    ↓
Start Docker

Không cần copy logic từ Role Nginx.


34. Exercise 7 — Application Role

Tạo:

roles/application/

Role này chịu trách nhiệm:

Create application directory
        ↓
Deploy configuration
        ↓
Deploy Docker Compose
        ↓
Start application

Variables:

app_name: todo
app_dir: /opt/todo
app_port: 8080

35. Bài tập tổng hợp

Xây dựng:

roles/
├── common/
├── docker/
├── nginx/
└── application/

Architecture:

                    site.yml
                       │
          ┌────────────┼────────────┐
          │            │            │
        common       docker       nginx
                                      │
                                      │
                                application

Trong đó:

common
  ↓
Base server

docker
  ↓
Container runtime

application
  ↓
Todo application

nginx
  ↓
Reverse proxy

36. Project Structure hoàn chỉnh

Sau bài tập:

ansible-project/
│
├── ansible.cfg
├── inventory.ini
├── site.yml
│
├── group_vars/
│   └── webservers.yml
│
└── roles/
    │
    ├── common/
    │   ├── defaults/
    │   ├── handlers/
    │   ├── tasks/
    │   ├── templates/
    │   └── vars/
    │
    ├── docker/
    │   ├── defaults/
    │   ├── handlers/
    │   ├── tasks/
    │   └── vars/
    │
    ├── nginx/
    │   ├── defaults/
    │   ├── handlers/
    │   ├── tasks/
    │   ├── templates/
    │   └── vars/
    │
    └── application/
        ├── defaults/
        ├── handlers/
        ├── tasks/
        ├── templates/
        └── vars/

Đây đã bắt đầu giống một Ansible project thực tế.


37. Tư duy Senior #1 — Role là Boundary

Một Role nên có boundary rõ ràng.

Ví dụ:

nginx role

chịu trách nhiệm:

Nginx installation
Nginx configuration
Nginx service

Không nên tự ý:

install PostgreSQL
create application database
deploy backend
configure Kubernetes

Nếu Role làm quá nhiều thứ, nó sẽ trở thành một "God Role".


38. Tư duy Senior #2 — Role phải configurable

Bad:

server_name: todo.example.com

Good:

server_name: "{{ nginx_server_name }}"

Role:

Generic
   ↓
Variables
   ↓
Environment-specific configuration

Nhờ vậy cùng một Role có thể dùng cho:

Development
Staging
Production

39. Tư duy Senior #3 — Role không nên chứa Secret

Không nên:

db_password: "123456"

trong:

roles/database/defaults/main.yml

hoặc:

roles/database/vars/main.yml

Secrets sẽ được học kỹ hơn ở:

Lab 11 — Ansible Secrets & Vault


40. Tư duy Senior #4 — Role nên có README

Một Role tốt nên có:

README.md

Ví dụ:

# Nginx Role

## Description

Install and configure Nginx.

## Requirements

Ubuntu 22.04+

## Variables

nginx_port: 80

server_name: localhost

## Example

roles:
  - role: nginx

Người khác có thể sử dụng Role mà không cần đọc toàn bộ code.


41. Tư duy Senior #5 — Role phải Test được

Một Role production không nên chỉ:

Viết xong
↓
Chạy thử
↓
OK

Mà cần nghĩ:

Role
 ↓
Test
 ↓
Expected State
 ↓
Verify

Ở level cao hơn, chúng ta có thể dùng:

Molecule

để test Role.

Phần này chưa cần đi sâu trong Lab 8, nhưng người học nên biết đây là hướng phát triển tiếp theo.


42. Role và Reusability

Giả sử bạn có:

nginx role

Hôm nay:

Project A

sử dụng.

Ngày mai:

Project B

cũng cần Nginx.

Không cần copy:

nginx-task.yml
nginx-handler.yml
nginx-template.yml

Chỉ cần:

roles:
  - nginx

Đây chính là DRY — Don't Repeat Yourself.


43. Role không phải chỉ là "chia file"

Đây là điểm người học cần hiểu sâu.

Sai cách hiểu:

Role chỉ là cách chia Playbook thành nhiều folder.

Đúng hơn:

Role là một abstraction boundary cho một capability cụ thể của infrastructure.

Ví dụ:

Role
 ↓
"Server có khả năng chạy Nginx"

hoặc:

Role
 ↓
"Server có khả năng chạy Docker"

hoặc:

Role
 ↓
"Server có application Todo"

Đây là cách tư duy quan trọng để tiến lên Senior.


44. Role vs Playbook

Playbook Role
Mục đích Orchestration Implementation
Scope Workflow Capability
Reusable Rất cao
Template Có thể
Handler Có thể
Variables
Project lớn Khó quản lý nếu quá nhiều logic Phù hợp

Có thể nhớ:

Playbook = What should happen?

Role = How is a capability implemented?

45. Role vs Task

Ví dụ:

- name: Install nginx
  ansible.builtin.apt:
    name: nginx

đây là Task.

Còn:

roles/nginx/

là toàn bộ capability:

Install
Configure
Validate
Start
Reload

Do đó:

Task
   ⊂ Role

46. Workflow chuẩn sau Lab 8

Từ bây giờ, khi xây một automation mới, hãy suy nghĩ:

1. Thành phần nào cần quản lý?
        ↓
2. Có nên tạo Role không?
        ↓
3. Role cần Variables nào?
        ↓
4. Có Template không?
        ↓
5. Có Handler không?
        ↓
6. Có Dependency không?
        ↓
7. Có cần Validate không?
        ↓
8. Có Idempotent không?
        ↓
9. Có thể Reuse không?

Đây là checklist rất tốt cho người học.


47. Checklist hoàn thành Lab 8

Người học cần có khả năng:

  • [ ] Hiểu Role là gì.
  • [ ] Biết tại sao cần Role.
  • [ ] Tạo Role bằng ansible-galaxy role init.
  • [ ] Hiểu cấu trúc Role.
  • [ ] Sử dụng tasks/main.yml.
  • [ ] Sử dụng handlers/main.yml.
  • [ ] Sử dụng templates/.
  • [ ] Sử dụng files/.
  • [ ] Sử dụng defaults/main.yml.
  • [ ] Hiểu vars/main.yml.
  • [ ] Hiểu meta/main.yml.
  • [ ] Gọi Role từ Playbook.
  • [ ] Override Role Variables.
  • [ ] Sử dụng nhiều Role trong một Playbook.
  • [ ] Hiểu Role Dependencies.
  • [ ] Kết hợp Role + Jinja2.
  • [ ] Kết hợp Role + Handler.
  • [ ] Kết hợp Role + Idempotency.
  • [ ] Biết thiết kế Role có boundary rõ ràng.
  • [ ] Biết tránh hard-code configuration.
  • [ ] Biết tại sao không nên để Secret trong Role.

48. Bài tập cuối Lab — Production Server

Hãy xây dựng một server theo kiến trúc:

                       site.yml
                          │
          ┌───────────────┼───────────────┐
          │               │               │
          ▼               ▼               ▼
       common           docker          nginx
          │               │               │
          │               │               │
          └───────────────┼───────────────┘
                          │
                          ▼
                     application

Server sau khi chạy Playbook phải đạt:

Ubuntu Server
│
├── Base packages
│
├── Docker
│   └── running
│
├── Nginx
│   ├── running
│   └── configured
│
└── Todo Application
    ├── configured
    └── running

Và quan trọng nhất:

Run 1
   ↓
Infrastructure changes
   ↓
Run 2
   ↓
changed = 0
   ↓
Run 3
   ↓
changed = 0

Nếu thay đổi:

app_port
server_name
nginx configuration
application configuration

thì chỉ những thành phần liên quan mới được thay đổi/reload/restart.


49. Kết quả sau Lab 8

Sau Lab 1 → 8, người học đã đi qua một progression khá quan trọng:

Lab 1
Ansible Fundamentals
        ↓
Lab 2
Inventory & SSH
        ↓
Lab 3
Ad-Hoc Commands
        ↓
Lab 4
Playbook
        ↓
Lab 5
Variables & Facts
        ↓
Lab 6
Jinja2
        ↓
Lab 7
Handlers & Idempotency
        ↓
Lab 8
Roles

Tư duy cũng đã thay đổi:

BEGINNER
"Ansible chạy command trên server"
              ↓
"Ansible chạy Playbook"
              ↓
"Ansible quản lý Desired State"
              ↓
"Ansible automation phải Idempotent"
              ↓
"Ansible automation phải Reusable"
              ↓
INTERMEDIATE
"Ansible project được thiết kế bằng Roles"

Đây là mốc rất quan trọng trước khi bước sang Lab 9 — Ansible Server Provisioning, nơi chúng ta sẽ bắt đầu sử dụng các Role để tự động dựng một server gần với môi trường production: package, user, SSH, Docker, Docker Compose, directory structure và các thiết lập hệ thống cần thiết.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí