Ansible Playbooks เจาะลึก: Handlers, Variables, Conditionals และ Loops

บทความนี้อธิบาย Handlers, Variables ในระดับ play, Conditionals, และ Loops พร้อมตัวอย่างที่นำไปใช้ได้ทันที

Playbook เบื้องต้นที่รัน task ตามลำดับใช้งานได้ดี แต่ระบบจริงต้องการความยืดหยุ่นมากกว่านั้น บางครั้งต้องรีสตาร์ต service เฉพาะเมื่อ config เปลี่ยน บางครั้งต้องรัน task เฉพาะบาง server หรือต้องสร้างไฟล์หลายชุดจากรายการข้อมูล features เหล่านี้คือหัวใจของ playbook ที่ทำงานจริงในสภาพแวดล้อม production

Handlers — รัน Task เฉพาะเมื่อมีการเปลี่ยนแปลง

Handler คือ task พิเศษที่จะรันก็ต่อเมื่อถูก notify เท่านั้น ใช้สำหรับ action ที่ต้องการเฉพาะเมื่อ config หรือไฟล์เปลี่ยนแปลง เช่น restart service หรือ reload config โดย handler จะรันเพียงครั้งเดียวต่อ play ไม่ว่าจะถูก notify กี่ครั้ง

- name: Configure nginx
  hosts: webservers
  tasks:
    - name: Copy nginx config
      copy:
        src: nginx.conf
        dest: /etc/nginx/nginx.conf
      notify: restart nginx

    - name: Enable nginx site
      file:
        src: /etc/nginx/sites-available/mysite
        dest: /etc/nginx/sites-enabled/mysite
        state: link
      notify: reload nginx

  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

    - name: reload nginx
      service:
        name: nginx
        state: reloaded

ถ้า config file ไม่เปลี่ยนแปลง (task status = ok แทน changed) handler จะไม่รัน ทำให้ไม่มีการ restart service โดยไม่จำเป็น

Variables — กำหนดค่าระดับ Play

นอกจาก host_vars และ group_vars แล้ว ยังกำหนด variable ในระดับ play ได้โดยตรงด้วย vars: หรือโหลดจากไฟล์ด้วย vars_files:

- name: Deploy application
  hosts: webservers
  vars:
    app_name: myapp
    app_port: 8080
    deploy_dir: /opt/apps/myapp

  vars_files:
    - vars/common.yml
    - vars/{{ env }}.yml  # โหลดตาม environment

  tasks:
    - name: Create app directory
      file:
        path: "{{ deploy_dir }}"
        state: directory

    - name: Configure app
      template:
        src: app.conf.j2
        dest: "{{ deploy_dir }}/config.conf"

Conditionals — รัน Task เฉพาะเมื่อตรงเงื่อนไข

when: ใช้กรอง task ให้รันเฉพาะ host หรือสถานการณ์ที่ตรงเงื่อนไข ช่วยให้ playbook เดียวรองรับได้หลาย OS หรือหลาย environment

- name: Install packages
  hosts: all
  tasks:
    # รันเฉพาะบน Ubuntu/Debian
    - name: Install nginx (apt)
      apt:
        name: nginx
        state: present
      when: ansible_os_family == "Debian"

    # รันเฉพาะบน RHEL/CentOS
    - name: Install nginx (yum)
      yum:
        name: nginx
        state: present
      when: ansible_os_family == "RedHat"

    # ตรวจสอบ variable ก่อนรัน
    - name: Enable SSL
      include_tasks: ssl.yml
      when: ssl_enabled | bool

    # ตรวจสอบหลายเงื่อนไข
    - name: Run in production only
      command: /opt/scripts/backup.sh
      when:
        - env == "production"
        - backup_enabled | default(true)

Loops — ทำซ้ำ Task กับหลาย Items

loop: ใช้เมื่อต้องการรัน task เดิมหลายครั้งกับข้อมูลต่างกัน แทนการเขียน task ซ้ำหลายบล็อก

- name: Setup users and packages
  hosts: all
  tasks:
    # สร้าง user หลายคนจาก list
    - name: Create system users
      user:
        name: "{{ item }}"
        state: present
        shell: /bin/bash
      loop:
        - deploy
        - monitor
        - backup

    # ติดตั้ง packages หลายตัว
    - name: Install required packages
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - git
        - curl
        - vim
        - htop

    # loop กับ dict items
    - name: Create directories with permissions
      file:
        path: "{{ item.path }}"
        mode: "{{ item.mode }}"
        state: directory
      loop:
        - { path: /opt/app, mode: "0755" }
        - { path: /var/log/app, mode: "0750" }
        - { path: /etc/app, mode: "0640" }

ใช้ register เก็บผลลัพธ์จาก Task

register: เก็บ output จาก task ไว้ใน variable เพื่อใช้ใน task ถัดไป มีประโยชน์มากเมื่อต้องตัดสินใจตามผลลัพธ์ของ command

- name: Check and conditionally run
  hosts: all
  tasks:
    - name: Check if app is running
      command: systemctl is-active myapp
      register: app_status
      ignore_errors: true

    - name: Start app if not running
      service:
        name: myapp
        state: started
      when: app_status.rc != 0

    - name: Show status
      debug:
        msg: "App status: {{ app_status.stdout }}"

รวม Features ใน Playbook จริง

ตัวอย่าง playbook ที่ใช้ทุก feature ร่วมกัน เพื่อ deploy และ configure web server

- name: Deploy web application
  hosts: webservers
  vars:
    app_version: "1.2.0"
    app_port: 8080

  handlers:
    - name: restart app
      service:
        name: myapp
        state: restarted

  tasks:
    - name: Install dependencies
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - python3
        - python3-pip

    - name: Deploy application config
      template:
        src: app.conf.j2
        dest: /etc/myapp/config.conf
      notify: restart app
      when: env == "production"

    - name: Check deployment
      uri:
        url: "http://localhost:{{ app_port }}/health"
        status_code: 200
      register: health_check
      retries: 3
      delay: 10

สรุป

Handlers ช่วยลดการ restart service ที่ไม่จำเป็น Conditionals ทำให้ playbook รองรับได้หลาย OS และ environment Loops ลดการเขียน task ซ้ำ และ register เปิดให้ logic ใน playbook ตัดสินใจตามผลลัพธ์จริง ความเข้าใจ feature เหล่านี้เป็นรากฐานของ playbook ที่ใช้ได้จริงในงาน DevOps