Ansible Facts: รวบรวม System Information อัตโนมัติ

Facts คือข้อมูลระบบที่ Playbook รวบรวมจาก remote host โดยอัตโนมัติก่อน task แรกเสมอ ครอบคลุมตั้งแต่ OS version, IP address, จำนวน CPU ไปจนถึง disk layout ทำให้ Playbook ตัดสินใจตามสภาพแวดล้อมจริงได้โดยไม่ต้อง hardcode

บทความนี้อธิบาย facts ในเชิงลึก ตั้งแต่โครงสร้าง ansible_facts, การใช้ gather_subset เพื่อเลือกเฉพาะข้อมูลที่ต้องการ, การสร้าง custom facts ด้วยไฟล์ .fact, ไปจนถึง magic variables ที่ใช้บ่อยในทางปฏิบัติ

โครงสร้าง ansible_facts

เมื่อ Playbook รัน module setup จะทำงานก่อน task แรกเสมอ (เว้นแต่ปิดด้วย gather_facts: false) ข้อมูลทั้งหมดเก็บอยู่ใน dictionary ansible_facts และเข้าถึงได้โดยตรงด้วยตัวแปรที่ขึ้นต้น ansible_

---
- name: Display basic system facts
  hosts: all
  tasks:
    - name: Show OS and hardware info
      debug:
        msg: |
          Hostname: {{ ansible_hostname }}
          Distro:   {{ ansible_distribution }}
          RAM (MB): {{ ansible_memtotal_mb }}
          IP:       {{ ansible_default_ipv4.address }}

ค่าที่เข้าถึงได้สองแบบ เช่น ansible_facts['os_family'] และ ansible_os_family ให้ผลเหมือนกัน แบบหลังใช้บ่อยกว่าเพราะกระชับกว่า

gather_subset: เลือกเฉพาะ Facts ที่ต้องการ

การรวบรวม facts ทั้งหมดใช้เวลาประมาณ 1-2 วินาทีต่อ host เมื่อรันกับเซิร์ฟเวอร์หลายร้อยเครื่องพร้อมกัน เวลาสะสมอาจเพิ่มขึ้นมากโดยไม่จำเป็น ถ้าต้องการเฉพาะบางหมวด ใช้ gather_subset เพื่อจำกัดขอบเขต

---
- name: Gather only network and hardware facts
  hosts: all
  gather_facts: true
  gather_subset:
    - network
    - hardware
    - "!virtual"      # ยกเว้น virtualization facts

  tasks:
    - name: Show IP address
      debug:
        msg: "Network facts gathered successfully"

Subset ที่ใช้บ่อยมีดังนี้: all รวบรวมทุกอย่าง (default), min เฉพาะข้อมูลพื้นฐานเช่น hostname และวันที่, network สำหรับ IP และ network interface, hardware สำหรับ CPU RAM และ disk, และ virtual สำหรับ virtualization type การใช้ ! นำหน้าชื่อ subset หมายถึงยกเว้น subset นั้นออกจากการรวบรวม

Custom Facts: กำหนดข้อมูลเองบน Remote Host

นอกจาก built-in facts ยังสร้าง custom facts ได้โดยวางไฟล์ในไดเรกทอรี /etc/ansible/facts.d/ บน remote host รองรับสองรูปแบบ: INI และ JSON executable script

Custom Facts แบบ INI

# /etc/ansible/facts.d/app.fact บน remote host
[application]
name=myapp
version=2.5.1
environment=production

[database]
host=db01.internal
port=5432

เข้าถึงผ่าน ansible_local ในรูปแบบ ansible_local.<ชื่อไฟล์>.<section>.<key>:

- name: Use custom facts
  debug:
    msg: |
      App: {{ ansible_local.app.application.name }} v{{ ansible_local.app.application.version }}
      DB:  {{ ansible_local.app.database.host }}:{{ ansible_local.app.database.port }}

Custom Facts แบบ Executable Script

ถ้าไฟล์ .fact เป็น executable script ระบบจะรันและอ่าน JSON output แทน ทำให้ facts คำนวณค่าแบบ dynamic ได้

#!/bin/bash
# /etc/ansible/facts.d/system_status.fact  (chmod +x ก่อนใช้งาน)

DISK_FREE=$(df -BG / | tail -1 | awk '{print $4}' | tr -d 'G')
LOAD=$(uptime | awk -F'load average:' '{print $2}' | cut -d, -f1 | tr -d ' ')

echo "{\"disk_free_gb\": ${DISK_FREE}, \"load_1min\": \"${LOAD}\"}
- name: Alert if disk low
  debug:
    msg: "WARNING: Low disk space!"
  when: ansible_local.system_status.disk_free_gb | int < 10

Deploy Custom Facts ด้วย Playbook

แทนที่จะวางไฟล์ manual สามารถ deploy custom fact files ด้วย Playbook ได้เลย

---
- name: Deploy custom facts
  hosts: all
  become: true
  tasks:
    - name: Create facts directory
      file:
        path: /etc/ansible/facts.d
        state: directory
        mode: '0755'

    - name: Deploy app fact file
      copy:
        dest: /etc/ansible/facts.d/app.fact
        content: |
          [application]
          name={{ app_name }}
          version={{ app_version }}
          environment={{ env }}
        mode: '0644'

    - name: Re-gather facts to load new custom facts
      setup:
        filter: ansible_local

    - name: Verify custom fact loaded
      debug:
        msg: "Custom facts loaded successfully"

การเรียก setup หลัง deploy ทำให้โหลด custom facts ที่เพิ่งสร้างได้ทันทีโดยไม่ต้องรอ Playbook รอบถัดไป

Magic Variables

Magic variables คือตัวแปรพิเศษที่ระบบสร้างขึ้นโดยอัตโนมัติ ไม่ได้มาจาก fact gathering แต่มาจากโครงสร้าง inventory และ playbook เอง

# Magic variables ที่ใช้บ่อยที่สุด

hostvars          # dict ของ facts และ variables จากทุก host ใน inventory

groups            # dict ของ group → list of hosts
                  # เช่น groups['webservers'] = ['web01', 'web02']

group_names       # list ของ group ที่ host ปัจจุบันอยู่

inventory_hostname      # ชื่อ host ใน inventory

ตัวอย่างการใช้ hostvars เพื่ออ่าน IP ของ database server แล้วเขียนลง config ของ web server:

---
- name: Configure app servers with DB IP
  hosts: webservers
  tasks:
    - name: Write DB connection config
      template:
        src: templates/db.conf.j2
        dest: /etc/myapp/db.conf

# templates/db.conf.j2
# DB_HOST = {{ hostvars[groups['dbservers'][0]]['ansible_default_ipv4']['address'] }}
# DB_PORT = 5432

Facts ใน Conditional: ปรับ Playbook ตาม OS

Pattern ที่พบบ่อยที่สุดคือใช้ fact เพื่อเลือก task ตาม OS family ทำให้ Playbook เดียวรันได้ทั้งบน Ubuntu และ CentOS:

---
- name: Install web server across OS families
  hosts: all
  become: true
  tasks:
    - name: Install on Debian/Ubuntu
      apt:
        name: nginx
        state: present
      when: ansible_os_family == "Debian"

    - name: Install on RHEL/CentOS/Rocky
      dnf:
        name: nginx
        state: present
      when: ansible_os_family != "Debian"

    - name: Set worker_processes to match CPU count
      lineinfile:
        path: /etc/nginx/nginx.conf
        regexp: '^worker_processes'
        line: "worker_processes {{ ansible_processor_vcpus }};"

    - name: Skip swap config if RAM > 8GB
      debug:
        msg: "Enough RAM, skipping swap setup"
      when: ansible_memtotal_mb > 8192

สรุป

Facts ทำให้ Playbook ปรับตัวได้ตามสภาพแวดล้อมจริงโดยไม่ต้อง hardcode ค่าใด ๆ Pattern หลักที่ควรจำ: ใช้ gather_subset เพื่อเพิ่มความเร็วเมื่อต้องการเฉพาะบางหมวด, สร้าง custom facts ด้วยไฟล์ .fact เมื่อต้องการข้อมูลที่ built-in ไม่มี, และใช้ hostvars เมื่อต้องการข้อมูลของ host อื่นจาก play เดียวกัน

จุดสำคัญที่ต้องระวัง: custom facts ที่เพิ่งสร้างจะยังไม่โหลดโดยอัตโนมัติในรอบเดิม ต้องเรียก setup ซ้ำหลัง deploy หรือรอรอบถัดไปจึงจะใช้งานได้