Use Ansible network roles
Roles là một bộ các thành phần của Ansible bao gồm các file mặc định (defaults), file (files), nhiệm vụ (tasks), mẫu (templates), biến (variables) và các thành phần Ansible khác được sắp xếp và làm việc cùng nhau. Như bạn đã thấy trong phần “Run Your First Command and Playbook”, chuyển từ một lệnh đến một playbook giúp bạn thực hiện nhiều tác vụ và lặp lại các tác vụ cùng theo thứ tự. Chuyển từ một playbook đến một role sẽ giúp bạn tái sử dụng và chia sẻ các tác vụ đã được sắp xếp theo đúng thứ tự. Bạn có thể truy cập Ansible Galaxy, nơi cho phép bạn chia sẻ các roles của mình và sử dụng các roles khác của người dùng khác, trực tiếp hoặc như một nguồn cảm hứng.
Understanding roles
Vậy thì một role trong Ansible là gì và tại sao nó lại quan trọng? Ansible role là một tập hợp các file mặc định của Ansible, bao gồm các file, tác vụ, mẫu, biến và các thành phần Ansible khác, được sắp xếp theo một cấu trúc file nhất định. Chuyển từ playbook sang role sẽ giúp việc chia sẻ, đọc và cập nhật quy trình làm việc Ansible của bạn dễ dàng hơn. Người dùng có thể tự viết các role của riêng mình. Ví dụ, thay vì viết playbook riêng cho DNS, bạn chỉ cần chỉ định một máy chủ DNS và một role để cấu hình nó cho bạn.
Để đơn giản hóa quy trình làm việc của bạn hơn nữa, nhóm Ansible Network đã viết một loạt các role cho các trường hợp sử dụng mạng phổ biến. Việc sử dụng các role này có nghĩa là bạn không cần phải phát minh lại bánh xe. Thay vì viết và duy trì các playbook hoặc role riêng để tạo VLAN, bạn có thể tập trung vào thiết kế, mã hóa và duy trì các mẫu phân tích mô tả topologies và inventory mạng của bạn, và để các role mạng của Ansible thực hiện công việc. Các role liên quan đến mạng có trên Ansible Galaxy.
A sample DNS playbook
Để giải thích khái niệm của role, ta có thể sử dụng ví dụ trong file playbook.yml dưới đây. File playbook.yml này chứa một playbook gồm hai task. Playbook này sẽ cấu hình hostname trên một thiết bị Cisco IOS XE, sau đó cấu hình các máy chủ DNS (hệ thống tên miền).
Tuy nhiên, việc sử dụng role sẽ giúp ta phân chia các task này thành các phần tử riêng biệt và sắp xếp chúng theo một cấu trúc file cụ thể. Việc chuyển sang sử dụng role giúp việc chia sẻ, đọc và cập nhật workflow Ansible dễ dàng hơn. Người dùng có thể viết riêng các role của mình. Chẳng hạn, thay vì phải viết playbook DNS của riêng bạn, bạn có thể chỉ định một máy chủ DNS và một role để cấu hình nó cho bạn.
Để đơn giản hóa workflow của bạn hơn nữa, nhóm Ansible Network đã viết một loạt các role cho các trường hợp sử dụng mạng thông thường. Việc sử dụng các role này có nghĩa là bạn không cần phải phát minh lại bánh xe. Thay vì viết và duy trì các playbook hoặc role riêng để tạo VLAN, bạn có thể tập trung vào thiết kế, mã hóa và duy trì các mẫu phân tích mô tả các topologies và inventory của mạng của bạn, và để cho các role của Ansible Network thực hiện công việc. Các role liên quan đến mạng có thể tìm thấy trên Ansible Galaxy.
---
- name: configure cisco routers
hosts: routers
connection: ansible.netcommon.network_cli
gather_facts: no
vars:
dns: "8.8.8.8 8.8.4.4"
tasks:
- name: configure hostname
cisco.ios.ios_config:
lines: hostname {{ inventory_hostname }}
- name: configure DNS
cisco.ios.ios_config:
lines: ip name-server {{dns}}
Nếu bạn chạy playbook này bằng lệnh ansible-playbook, bạn sẽ thấy đầu ra như bên dưới. Trong ví dụ này, chúng ta sử dụng tùy chọn -l để giới hạn playbook chỉ thực thi trên nút rtr1. Kết quả là Ansible sẽ thực thi hai tác vụ trong playbook này trên nút rtr1, trước khi kết thúc và đưa ra kết quả.
[user@ansible ~]$ ansible-playbook playbook.yml -l rtr1
PLAY [configure cisco routers] *************************************************
TASK [configure hostname] ******************************************************
changed: [rtr1]
TASK [configure DNS] ***********************************************************
changed: [rtr1]
PLAY RECAP *********************************************************************
rtr1 : ok=2 changed=2 unreachable=0 failed=0
Playbook này đã cấu hình tên máy chủ và máy chủ DNS. Bạn có thể xác minh cấu hình đó trên bộ định tuyến Cisco IOS XE rtr1 bằng cách sử dụng các lệnh CLI để hiển thị tên máy chủ và danh sách máy chủ DNS mới được cấu hình. Ví dụ:
- Hiển thị tên máy chủ mới:
show running-config | include hostname
- Hiển thị danh sách máy chủ DNS mới:
show running-config | include ip domain-name
rtr1#sh run | i name
hostname rtr1
ip name-server 8.8.8.8 8.8.4.4
Convert the playbook into a role
Bước tiếp theo là chuyển playbook này thành một role có thể tái sử dụng. Bạn có thể tạo thủ công cấu trúc thư mục, hoặc bạn có thể sử dụng lệnh ansible-galaxy init
để tạo khung chuẩn cho một role.
Khi sử dụng lệnh ansible-galaxy init
, nó sẽ tạo ra cấu trúc thư mục chuẩn của một role, bao gồm các thư mục: defaults
, files
, handlers
, meta
, tasks
, templates
, và vars
. Mỗi thư mục chứa các thành phần khác nhau của role, như các biến, file tác vụ, mã mẫu, và các file cấu hình.
Sau khi thư mục role được tạo, bạn có thể di chuyển các thành phần của playbook vào thư mục tương ứng của role. Ví dụ: bạn có thể di chuyển các tác vụ vào thư mục tasks
, các biến vào thư mục vars
, và các mẫu vào thư mục templates
.
Khi role đã được tạo và các thành phần của playbook đã được di chuyển vào các thư mục tương ứng của role, bạn có thể tái sử dụng role này cho các thiết bị mạng khác trong hệ thống của mình.
[user@ansible ~]$ ansible-galaxy init system-demo
[user@ansible ~]$ cd system-demo/
[user@ansible system-demo]$ tree
.
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
Thư mục role chuẩn sẽ bao gồm thư mục tasks, vars, handlers, defaults, meta, templates, files, và tests. Tuy nhiên, ở đây, chúng ta sử dụng hai thư mục tasks và vars để tạo một role đơn giản.
Thư mục tasks chứa các file YAML chứa các task cần thực hiện bởi role, trong khi thư mục vars chứa các biến và giá trị của chúng. Do đó, cấu trúc thư mục của role như sau:
[user@ansible system-demo]$ tree
.
├── tasks
│ └── main.yml
└── vars
└── main.yml
Trong bước này, chúng ta di chuyển các nội dung của các phần vars và tasks từ playbook ban đầu vào trong role mới tạo. Đầu tiên, chúng ta di chuyển hai task vào trong file tasks/main.yml. Cụ thể, nội dung của playbook như sau:
[user@ansible system-demo]$ cat tasks/main.yml
---
- name: configure hostname
cisco.ios.ios_config:
lines: hostname {{ inventory_hostname }}
- name: configure DNS
cisco.ios.ios_config:
lines: ip name-server {{dns}}
Tiếp theo, di chuyển các biến vào vars/main.yml:
[user@ansible system-demo]$ cat vars/main.yml
---
dns: "8.8.8.8 8.8.4.4"
Trong bước cuối cùng, bạn cần chỉnh sửa playbook ban đầu bằng cách loại bỏ phần tasks và vars, và thêm từ khóa roles với tên của role, trong trường hợp này là system-demo. Bạn sẽ có playbook như sau:
---
- name: configure cisco routers
hosts: routers
connection: ansible.netcommon.network_cli
gather_facts: no
roles:
- system-demo
Tóm lại, đây là một ví dụ về việc tạo một role trong Ansible với tổng cộng ba thư mục và ba file YAML. Thư mục system-demo
đại diện cho role này và chứa hai thư mục con là tasks
và vars
. Mỗi thư mục con chứa một file main.yml
. Tệp vars/main.yml
chứa các biến từ playbook.yml
, trong khi file tasks/main.yml
chứa các nhiệm vụ từ playbook.yml
. Tệp playbook.yml
đã được sửa đổi để gọi role thay vì chỉ định các biến và nhiệm vụ trực tiếp. Dưới đây là cây thư mục của thư mục làm việc hiện tại:
[user@ansible ~]$ tree
.
├── playbook.yml
└── system-demo
├── tasks
│ └── main.yml
└── vars
└── main.yml
Cấu trúc thư mục này có thể được tạo thủ công hoặc sử dụng lệnh ansible-galaxy init
để tạo khung chuẩn cho một role.
Chạy playbook cho ra cùng một kết quả nhưng với đầu ra thì hơi khác:
[user@ansible ~]$ ansible-playbook playbook.yml -l rtr1
PLAY [configure cisco routers] *************************************************
TASK [system-demo : configure hostname] ****************************************
ok: [rtr1]
TASK [system-demo : configure DNS] *********************************************
ok: [rtr1]
PLAY RECAP *********************************************************************
rtr1 : ok=2 changed=0 unreachable=0 failed=0
Để tạo khung chuẩn cho một role bằng lệnh ansible-galaxy init
, bạn có thể thực hiện các bước sau:
- Đi đến thư mục nơi bạn muốn tạo role.
- Chạy lệnh
ansible-galaxy init role_name
để tạo role với tên làrole_name
. - Bạn sẽ thấy một thư mục mới được tạo có cấu trúc theo chuẩn của Ansible role:
role_name/
├── README.md
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml
Trong đó:
- README.md: Tệp tin này chứa thông tin mô tả về role.
- defaults/main.yml: Tệp tin này chứa các biến mặc định cho role.
- files/: Thư mục này chứa các file đính kèm của role.
- handlers/main.yml: Tệp tin này chứa các handlers được sử dụng trong role.
- meta/main.yml: Tệp tin này chứa các thông tin meta về role như tên, phiên bản, tác giả, và các phụ thuộc.
- tasks/main.yml: Tệp tin này chứa danh sách các tasks cần thực hiện trong role.
- templates/: Thư mục này chứa các mẫu template cho role.
- tests/: Thư mục này chứa các file inventory và playbook để kiểm tra role.
- vars/main.yml: Tệp tin này chứa các biến được sử dụng trong role.
Sau khi tạo được khung chuẩn, bạn có thể bổ sung các nội dung cần thiết vào các file và thư mục tương ứng.
Như trước đó, playbook đã tạo cấu hình sau trên router Cisco IOS-XE:
- Đổi tên router thành RTR1
- Cấu hình DNS server là 8.8.8.8 và 8.8.4.4
Bạn có thể xác minh cấu hình bằng cách sử dụng CLI của router hoặc các công cụ khác như Ansible ad-hoc command.
rtr1#sh run | i name
hostname rtr1
ip name-server 8.8.8.8 8.8.4.4
Đó là lý do tại sao các role của Ansible có thể được xem như các playbook được phân tách thành các thành phần đơn giản. Chúng đơn giản, hiệu quả và có thể tái sử dụng. Bây giờ người dùng khác có thể đơn giản chỉ cần bao gồm role system-demo thay vì phải tạo một playbook tùy chỉnh “hard-coded”.
Variable precedence
Trong trường hợp bạn muốn thay đổi các máy chủ DNS, bạn không nên thay đổi vars/main.yml trong cấu trúc role. Ansible có nhiều nơi bạn có thể chỉ định các biến cho một play cụ thể. Xem Using Variables để biết thêm chi tiết về biến và sự ưu tiên. Có thực tế là 21 nơi để đặt các biến. Trong khi danh sách này có vẻ áp đảo ở cái nhìn đầu tiên, thì phần lớn các trường hợp sử dụng chỉ liên quan đến biết vị trí của các biến có độ ưu tiên thấp nhất và cách truyền biến với độ ưu tiên cao nhất. Xem Variable precedence: Where should I put a variable? để biết thêm hướng dẫn về nơi bạn nên đặt các biến.
Lowest precedenc:
Trong 21 địa điểm có thể đặt biến, địa điểm ở độ ưu tiên thấp nhất là thư mục defaults bên trong role. Điều này có nghĩa là tất cả 20 địa điểm khác có thể đặt biến sẽ có độ ưu tiên cao hơn so với defaults, bất kể là gì. Để ngay lập tức đặt các biến trong thư mục vars của role system-demo có độ ưu tiên thấp nhất, bạn có thể đổi tên thư mục vars thành defaults.
[user@ansible system-demo]$ mv vars defaults
[user@ansible system-demo]$ tree
.
├── defaults
│ └── main.yml
├── tasks
│ └── main.yml
Trong bước này, chúng ta sẽ tạo một phần vars mới trong playbook để ghi đè lên hành vi mặc định của role system-demo (trong đó biến dns được đặt là 8.8.8.8 và 8.8.4.4). Trong trường hợp này, chúng ta sẽ đặt dns là 1.1.1.1. Do đó, playbook.yml sẽ trở thành:
---
- name: configure cisco routers
hosts: routers
connection: ansible.netcommon.network_cli
gather_facts: no
vars:
dns:
- 1.1.1.1
- 1.0.0.1
roles:
- system-demo
Ở đây, chúng ta đã thêm phần vars mới chứa biến dns được đặt lại thành 1.1.1.1 và 1.0.0.1. Chúng ta vẫn sử dụng role system-demo như trước, nhưng sử dụng phần vars này để ghi đè lên giá trị mặc định của biến dns trong role.
Chạy playbook cập nhật này trên rtr2:
[user@ansible ~]$ ansible-playbook playbook.yml -l rtr2
Vậy cấu hình trên Cisco rtr2 sẽ như sau:
rtr2#sh run | i name-server
ip name-server 1.1.1.1
Để làm rõ hơn về cơ chế ưu tiên biến số (variable precedence) trong Ansible, Ansible hỗ trợ nhiều vị trí để định nghĩa biến số trong một play hoặc role. Mỗi vị trí này có mức ưu tiên khác nhau, được xác định theo thứ tự sau:
- Role defaults (như đã giải thích ở trên).
- Inventory vars (định nghĩa biến số trong file inventory).
- Inventory group_vars (định nghĩa biến số cho tất cả các host trong một nhóm trong inventory).
- Inventory host_vars (định nghĩa biến số cho một host cụ thể trong inventory).
- Play vars (định nghĩa biến số trong section vars của một play).
- Play vars_prompt (định nghĩa biến số từ người dùng khi chạy play).
- Play vars_files (định nghĩa biến số trong một file YAML nằm trong thư mục vars của play).
- Role vars (định nghĩa biến số trong thư mục vars của role).
- Block vars (định nghĩa biến số trong một block).
- Task vars (định nghĩa biến số trong section vars của một task).
- Include vars (định nghĩa biến số trong một file YAML được bao gồm).
- Set_facts tasks (định nghĩa biến số trong một task dùng module set_fact).
- Jinja2 vars (định nghĩa biến số trong một template Jinja2).
- Role and include params (định nghĩa biến số truyền qua command line khi chạy role hoặc include).
- Block vars (định nghĩa biến số trong một block nằm trong một một include).
- Task vars (định nghĩa biến số trong section vars của một task nằm trong một include).
- Include vars (định nghĩa biến số trong một file YAML nằm trong thư mục vars của include).
- Set_facts task (định nghĩa biến số trong một task dùng module set_fact nằm trong một include).
- Role and include params (định nghĩa biến số truyền qua command line khi chạy include).
- Extra vars (định nghĩa biến số truyền qua command line khi chạy playbook, bằng option -e hoặc –extra-vars).
- Role defaults (như đã giải thích ở trên).
Như vậy, khi có cùng một biến số được định nghĩa ở nhiều vị trí khác nhau, Ansible sẽ ưu tiên biến số ở vị trí có mức ưu tiên cao hơn.
Ví dụ, nếu bạn định nghĩa một biến số ở cả playbook và trong file role của một task, Ansible sẽ sử dụng giá trị của biến số trong playbook bởi vì playbook có mức ưu tiên cao hơn. Nếu bạn định nghĩa một biến số ở cả playbook và trong một file inventory hoặc command line, giá trị biến số được định nghĩa trong inventory hoặc command line sẽ được sử dụng bởi vì chúng có mức ưu tiên cao hơn. Nếu có nhiều giá trị biến số trong cùng một vị trí và cùng mức ưu tiên, Ansible sẽ sử dụng giá trị cuối cùng được định nghĩa.
Ngoài ra, bạn có thể sử dụng các option như –extra-vars để định nghĩa giá trị biến số từ command line với mức ưu tiên cao nhất. Nếu cần định nghĩa các biến số được sử dụng bởi nhiều playbooks và roles, bạn nên xem xét sử dụng các file variables riêng biệt được định nghĩa ở các vị trí có mức ưu tiên cao hơn, ví dụ như group_vars và host_vars trong inventory hoặc các file variables được đặt tên riêng trong thư mục roles.
Highest precedence
Trong Ansible, khi định nghĩa các biến cho một role, ta có thể định nghĩa trong thư mục defaults, và các giá trị mặc định này sẽ được sử dụng khi không có giá trị nào khác được định nghĩa cho biến đó. Tuy nhiên, giá trị đó sẽ luôn có ưu tiên thấp nhất trong các giá trị được định nghĩa cho biến đó.
Ngoài ra, ta cũng có thể định nghĩa các biến thông qua option -e hoặc –extra-vars khi chạy playbook. Với cách này, các giá trị được định nghĩa trong option này sẽ luôn có ưu tiên cao nhất, vượt qua cả các giá trị được định nghĩa trong thư mục defaults hoặc trong playbook.
Khi chạy lại playbook với option -e, giá trị của biến sẽ được ghi đè hoàn toàn bởi giá trị mới được định nghĩa trong option này, bao gồm cả giá trị trong thư mục defaults và giá trị mới được định nghĩa trong playbook.
Ví dụ, giả sử ta đang chạy một playbook để cấu hình DNS server, trong đó ta định nghĩa một biến dns_server với giá trị mặc định là 8.8.4.4 và 8.8.8.8 trong thư mục defaults của role. Sau đó, trong playbook ta định nghĩa lại biến này với giá trị là 1.1.1.1. Nếu ta chạy playbook mà không định nghĩa thêm bất kỳ giá trị nào, thì giá trị của biến dns_server sẽ là 8.8.4.4 và 8.8.8.8.
Tuy nhiên, nếu ta chạy playbook với option -e dns_server=2.2.2.2, thì giá trị của biến dns_server sẽ được ghi đè bởi giá trị mới này, và sẽ là 2.2.2.2. Nếu ta chạy lại playbook với option -e dns_server=3.3.3.3, thì giá trị của biến dns_server sẽ được ghi đè bởi giá trị mới này, và sẽ là 3.3.3.3.
Tóm lại, khi sử dụng Ansible, ta cần hiểu rõ thứ tự ưu tiên của các giá trị được định nghĩa cho biến để đảm bảo rằng giá trị cuối cùng sẽ được sử dụng đúng theo mong muốn.
[user@ansible ~]$ ansible-playbook playbook.yml -e "dns=192.168.1.1" -l rtr3
Kết quả trên Cisco IOS XE sẽ chỉ cài đặt DNS ưu tiên cao nhất là 192.168.1.1:
rtr3#sh run | i name-server
ip name-server 192.168.1.1
Việc sử dụng extra vars trong Ansible là rất hữu ích để ghi đè giá trị mặc định của các biến được định nghĩa trong role hoặc playbook. Việc này cho phép người dùng định nghĩa các giá trị động, linh hoạt hơn và dễ dàng hơn trong quá trình triển khai.
Một ví dụ mạnh mẽ về việc sử dụng extra vars trong Ansible là tính năng Job Template Survey trên AWX hoặc Red Hat Ansible Automation Platform. Tính năng này cho phép người dùng hiển thị một biểu mẫu trên giao diện web để điền các thông số cấu hình cho một playbook. Điều này cho phép người dùng non-technical thực hiện playbook một cách dễ dàng và thuận tiện hơn, không cần phải truy cập vào các file cấu hình.
Với extra vars, người dùng có thể chỉ định các giá trị động như địa chỉ IP, mật khẩu, tên người dùng, v.v. khi chạy playbook. Điều này giúp cho việc triển khai trở nên dễ dàng và linh hoạt hơn, đặc biệt là khi triển khai các hệ thống mạng lớn với nhiều thiết bị và cấu hình khác nhau.
Update an installed role
Trang Ansible Galaxy của một role liệt kê tất cả các phiên bản có sẵn của role đó. Để cập nhật một role đã được cài đặt trên máy tính của bạn với một phiên bản mới hoặc khác, bạn có thể sử dụng lệnh ansible-galaxy install với tùy chọn phiên bản và –force. Tuy nhiên, bạn có thể cần phải cập nhật thủ công các role phụ thuộc để hỗ trợ phiên bản mới này. Bạn có thể xem yêu cầu phiên bản tối thiểu của role phụ thuộc trong tab Read Me trên Galaxy.
Việc sử dụng ansible-galaxy install với tùy chọn phiên bản và –force sẽ cho phép bạn cài đặt phiên bản mới nhất của role đó. Tuy nhiên, nếu có các role phụ thuộc, bạn sẽ cần phải cập nhật chúng thủ công để đảm bảo rằng chúng hỗ trợ phiên bản mới nhất của role chính. Nếu không, bạn có thể gặp phải các lỗi không mong muốn trong quá trình triển khai.
Để giúp bạn đảm bảo rằng các role phụ thuộc đã được cập nhật đúng cách, bạn có thể xem các yêu cầu phiên bản tối thiểu của chúng trong tab Read Me trên Ansible Galaxy. Trong trường hợp có yêu cầu phiên bản tối thiểu, bạn cần phải cập nhật các role phụ thuộc trước khi cập nhật role chính.
[user@ansible]$ ansible-galaxy install mynamespace.my_role,v2.7.1 --force