Biến, Facts và Template (Jinja2)

K
Kai··5 min read

Playbook ở Bài 4 hard-code mọi giá trị. Bài này làm chúng linh hoạt với ba thứ gắn bó: biến (giá trị tùy biến), facts (thông tin host Ansible tự biết), và template (sinh file động từ biến + facts). Đây là chỗ playbook chuyển từ "chạy được" sang "tái dùng được cho nhiều môi trường".

Biến: khai báo ở đâu

Biến cho phép cùng một playbook chạy với giá trị khác nhau (dev vs prod, port khác nhau...). Có nhiều nơi khai báo:

# 1. Ngay trong play
- hosts: web
  vars:
    site_name: "KKloud Lab"
    http_port: 8080

# 2. Từ file riêng
- hosts: web
  vars_files:
    - vars/main.yml

Ngoài ra (nhớ Bài 3): group_vars/<nhóm>.ymlhost_vars/<host>.yml. Và truyền lúc chạy bằng -e (extra-vars):

ansible-playbook site.yml -e "http_port=9090 app_env=staging"

Dùng biến bằng cú pháp Jinja2 {{ ten_bien }}:

- name: Mở port
  ansible.builtin.debug:
    msg: "App chạy ở môi trường {{ app_env }}, port {{ http_port }}"

Thứ tự ưu tiên (precedence) — chỗ hay rối

Khi cùng một biến được khai báo ở nhiều nơi, nơi nào thắng? Ansible có thứ tự ưu tiên rõ ràng. Đơn giản hóa từ thấp → cao:

   THẤP (dễ bị ghi đè) ─────────────────────────────► CAO (thắng)

   role defaults  <  group_vars/all  <  group_vars/<nhóm>  <  host_vars
       <  play vars  <  task vars  <  ...  <  -e extra-vars (LUÔN thắng)

Hai điều cần nhớ:

  • -e (extra-vars) luôn thắng tất cả — tiện để ghi đè lúc chạy ("chạy với port 9090 cho lần này"), nhưng đừng lạm dụng vì nó vượt mọi thứ.
  • role defaults thấp nhất — đặt giá trị mặc định của role ở defaults/ để dễ bị ghi đè bởi group_vars/host_vars (Bài 8).

Quy tắc thực dụng: giá trị chung đặt ở nơi thấp (defaults, group_vars/all), giá trị riêng đặt ở nơi cao hơn (host_vars, hoặc -e khi cần). (Tài liệu Ansible có bảng precedence đầy đủ ~22 mức; bạn không cần thuộc hết, chỉ cần nắm "cụ thể hơn / muộn hơn thì thắng, -e thắng tất".)

Facts: thông tin host Ansible tự biết

Nhớ Bài 4 — task Gathering Facts đầu mỗi play. Nó thu thập facts: hàng trăm thông tin về host (OS, IP, RAM, CPU, disk, hostname...), đặt vào các biến ansible_*. Xem tất cả:

ansible all -m setup

Vài fact hay dùng:

   ansible_hostname               tên host
   ansible_distribution           "Amazon", "Ubuntu", "CentOS"...
   ansible_distribution_version   "2023", "24.04"...
   ansible_default_ipv4.address   IP chính
   ansible_processor_vcpus        số vCPU
   ansible_memtotal_mb            tổng RAM (MB)

Facts cho phép playbook thích nghi với host: cài gói khác nhau theo ansible_distribution, cấu hình theo số CPU... (kết hợp when: ở Bài 7). Nếu không cần facts, đặt gather_facts: false để chạy nhanh hơn.

Template Jinja2: sinh file động

Module copy đặt file tĩnh. Module template mạnh hơn: nó render một file Jinja2 (.j2) — thay biến và facts vào — trước khi đặt lên host. Đây là cách sinh file cấu hình động (nginx.conf, .env, app config...).

Tạo template templates/index.html.j2:

<h1>{{ site_name }}</h1>
<p>Host: {{ ansible_hostname }} ({{ ansible_distribution }} {{ ansible_distribution_version }})</p>
<p>Môi trường: {{ app_env | default('dev') }}</p>

Playbook dùng nó:

- name: Demo biến, facts, template
  hosts: web
  become: true
  vars:
    site_name: "KKloud Lab"
    app_env: "production"
  tasks:
    - name: Render template ra index.html
      ansible.builtin.template:
        src: templates/index.html.j2
        dest: /usr/share/nginx/html/index.html

Chạy rồi curl xem kết quả render thật trên host:

<h1>KKloud Lab</h1>
<p>Host: ip-172-31-31-60 (Amazon 2023)</p>
<p>Môi trường: production</p>

Để ý: {{ site_name }}{{ app_env }} thay từ biến; {{ ansible_hostname }}, {{ ansible_distribution }} thay từ facts — Ansible biết host tên gì, OS gì mà bạn không phải nhập tay. Một template, áp lên trăm host, mỗi host ra file đúng thông tin của nó.

Jinja2: filter, điều kiện, vòng lặp trong template

Jinja2 (cũng dùng trong {{ }} của playbook) có nhiều công cụ:

{{ app_env | default('dev') }}        {# filter: giá trị mặc định nếu chưa đặt #}
{{ name | upper }}                     {# filter: viết hoa #}
{{ items | length }}                   {# filter: đếm phần tử #}

{% if app_env == 'production' %}        {# điều kiện #}
log_level = warning
{% else %}
log_level = debug
{% endif %}

{% for host in groups['web'] %}         {# vòng lặp — liệt kê các host nhóm web #}
server {{ host }};
{% endfor %}

Vòng lặp {% for %} trong template đặc biệt mạnh: ví dụ sinh cấu hình load balancer liệt kê mọi host nhóm web (qua biến groups). Filter (| default, | upper...) biến đổi giá trị — và bạn tự viết filter riêng được (Bài 12).

registered variables: bắt kết quả task

Bạn còn lưu kết quả của một task vào biến bằng register, để task sau dùng:

- name: Kiểm tra phiên bản nginx
  ansible.builtin.command: nginx -v
  register: nginx_out
  changed_when: false        # lệnh chỉ đọc, không coi là changed

- name: In ra
  ansible.builtin.debug:
    var: nginx_out.stdout

register bắt JSON trả về của module (gồm stdout, rc, changed...) — hữu ích để dựa vào kết quả mà quyết định task sau (kết hợp when: — Bài 7).

🧹 Dọn dẹp

Code mẫu (template, vars) ở repo nghiadaulau/ansible-series, thư mục 06-vars-facts-template. EC2 lab vẫn giữ tới Bài 16.

Tổng kết

Biến làm playbook tái dùng được, khai báo ở nhiều nơi với thứ tự ưu tiên (cụ thể hơn/muộn hơn thắng; -e thắng tất). Facts là thông tin host Ansible tự thu thập (ansible_*) cho playbook thích nghi theo từng máy. Template Jinja2 (module template) render file động từ biến + facts (kèm filter, if, for) — sinh cấu hình đúng cho mỗi host. register bắt kết quả task để dùng tiếp.

Có biến và facts rồi, ta cần điều khiển luồng: chạy task khi nào, lặp lại ra sao, phản ứng với thay đổi thế nào. Bài 7: handlers, loops và conditionals.