Ansible automation จัดการ VPS หลายเครื่อง

Ansible สำหรับจัดการ VPS อัตโนมัติ: Playbook เบื้องต้น

มิถุนายน 2026 · อ่าน 9 นาที

เมื่อคุณมี VPS มากกว่า 1-2 เครื่อง การ SSH เข้าไปติดตั้งและตั้งค่าทีละเครื่องเริ่มเสียเวลา Ansible คือ configuration management tool ที่ช่วยให้คุณสั่ง "ทำทุกเครื่องแบบนี้" ในครั้งเดียว ผ่าน SSH โดยไม่ต้องติดตั้ง Agent บน VPS เลย เขียนเป็น YAML ที่อ่านเข้าใจง่าย และรัน idempotent ได้ (รันซ้ำกี่ครั้งก็ได้ผลเหมือนเดิม)

ติดตั้ง Ansible บนเครื่อง Control (Laptop/Jumphost)

# Ubuntu/Debian
apt install ansible -y

# macOS
brew install ansible

# pip (ทุก OS)
pip3 install ansible

# ตรวจสอบ version
ansible --version
Ansible ทำงานจากเครื่อง Control เท่านั้น: VPS ที่เป็น target ไม่ต้องติดตั้งอะไรเพิ่ม แค่ SSH และ Python ซึ่งมีใน Ubuntu/Debian ทุก version แล้ว

สร้าง Inventory ไฟล์

Inventory คือไฟล์ที่บอก Ansible ว่ามี VPS อะไรบ้างและอยู่ที่ IP ไหน:

# สร้างโฟลเดอร์โปรเจกต์
mkdir ~/ansible-vps && cd ~/ansible-vps

# สร้าง inventory.ini
nano inventory.ini
[webservers]
web1 ansible_host=203.0.113.10 ansible_user=root
web2 ansible_host=203.0.113.11 ansible_user=root

[databases]
db1 ansible_host=203.0.113.20 ansible_user=root

[all:vars]
ansible_ssh_private_key_file=~/.ssh/id_ed25519
ansible_python_interpreter=/usr/bin/python3
# ทดสอบ connection ทุกเครื่อง
ansible all -i inventory.ini -m ping

Ansible Ad-hoc Commands — สั่งเดียวทุกเครื่อง

# Update package ทุกเครื่องพร้อมกัน
ansible all -i inventory.ini -m apt -a "update_cache=yes upgrade=dist" -b

# รีสตาร์ท nginx ทุก webserver
ansible webservers -i inventory.ini -m service -a "name=nginx state=restarted" -b

# ดู disk usage ทุกเครื่อง
ansible all -i inventory.ini -m command -a "df -h"

# Copy ไฟล์ไปทุกเครื่อง
ansible webservers -i inventory.ini -m copy -a "src=./nginx.conf dest=/etc/nginx/nginx.conf" -b

เขียน Playbook: ตั้งค่า VPS ใหม่ในครั้งเดียว

Playbook คือไฟล์ YAML ที่รวม task หลายอย่างไว้ด้วยกัน:

# สร้างไฟล์ setup-server.yml
nano setup-server.yml
---
- name: Initial VPS Setup
  hosts: all
  become: yes   # รันด้วย sudo

  vars:
    deploy_user: deployer
    ssh_port: 22

  tasks:
    - name: Update apt cache and upgrade packages
      apt:
        update_cache: yes
        upgrade: dist
        cache_valid_time: 3600

    - name: Install essential packages
      apt:
        name:
          - curl
          - wget
          - git
          - htop
          - ufw
          - fail2ban
          - unattended-upgrades
        state: present

    - name: Create deploy user
      user:
        name: "{{ deploy_user }}"
        shell: /bin/bash
        groups: sudo
        create_home: yes

    - name: Set UFW default deny incoming
      ufw:
        default: deny
        direction: incoming

    - name: Allow SSH
      ufw:
        rule: limit
        port: "{{ ssh_port }}"
        proto: tcp

    - name: Allow HTTP and HTTPS
      ufw:
        rule: allow
        port: "{{ item }}"
      loop:
        - "80"
        - "443"

    - name: Enable UFW
      ufw:
        state: enabled

    - name: Start and enable fail2ban
      service:
        name: fail2ban
        state: started
        enabled: yes

    - name: Set timezone to Asia/Bangkok
      timezone:
        name: Asia/Bangkok
# รัน Playbook
ansible-playbook -i inventory.ini setup-server.yml

# ตรวจสอบด้วย --check (dry run ไม่เปลี่ยนแปลงจริง)
ansible-playbook -i inventory.ini setup-server.yml --check

Playbook ติดตั้ง LEMP Stack

---
- name: Install LEMP Stack
  hosts: webservers
  become: yes

  tasks:
    - name: Install Nginx
      apt:
        name: nginx
        state: present

    - name: Install PHP-FPM 8.2
      apt:
        name:
          - php8.2-fpm
          - php8.2-mysql
          - php8.2-xml
          - php8.2-curl
          - php8.2-mbstring
          - php8.2-zip
        state: present

    - name: Install MariaDB
      apt:
        name: mariadb-server
        state: present

    - name: Start and enable services
      service:
        name: "{{ item }}"
        state: started
        enabled: yes
      loop:
        - nginx
        - php8.2-fpm
        - mariadb

    - name: Deploy Nginx config
      template:
        src: templates/nginx-site.conf.j2
        dest: /etc/nginx/sites-available/default
      notify: reload nginx

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

ใช้ Variables และ Templates

Ansible รองรับ Jinja2 Template ทำให้ Config ไฟล์แต่ละเครื่องแตกต่างกันได้:

# templates/nginx-site.conf.j2
server {
    listen 80;
    server_name {{ server_name }};
    root {{ web_root }};

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}
# group_vars/webservers.yml
server_name: example.com
web_root: /var/www/html

ตัวอย่าง: Deploy โค้ดจาก Git Repository

- name: Deploy application
  hosts: webservers
  become: yes

  tasks:
    - name: Clone/pull latest code
      git:
        repo: https://github.com/myorg/myapp.git
        dest: /var/www/myapp
        version: main
        force: yes

    - name: Install composer dependencies
      command: composer install --no-dev --optimize-autoloader
      args:
        chdir: /var/www/myapp

    - name: Run database migrations
      command: php artisan migrate --force
      args:
        chdir: /var/www/myapp

    - name: Reload PHP-FPM
      service:
        name: php8.2-fpm
        state: reloaded

เก็บ Password ปลอดภัยด้วย Ansible Vault

# เข้ารหัสไฟล์ที่มี secret
ansible-vault encrypt group_vars/all/secrets.yml

# แก้ไขไฟล์ที่เข้ารหัสแล้ว
ansible-vault edit group_vars/all/secrets.yml

# รัน Playbook พร้อม Vault password
ansible-playbook -i inventory.ini deploy.yml --ask-vault-pass
# หรือเก็บ password ในไฟล์
ansible-playbook -i inventory.ini deploy.yml --vault-password-file ~/.vault_pass

Ansible เป็นจุดเริ่มต้นที่ดีของ Infrastructure as Code สำหรับ VPS เพราะไม่ต้องเรียนรู้ architecture ใหม่ ใช้ SSH ที่รู้จักอยู่แล้ว เขียน YAML ที่อ่านเข้าใจง่าย และสเกลได้ตั้งแต่ 1 เครื่องถึง 100 เครื่องด้วย Playbook เดิม

ตรวจสอบว่า Ansible ทำงานถูกต้อง

# ทดสอบ connection ไปยัง VPS ทุกเครื่อง
ansible all -i inventory.ini -m ping

# ผลที่ถูกต้อง: SUCCESS + pong
# web1 | SUCCESS => {
#     "changed": false,
#     "ping": "pong"
# }

# ทดสอบ Playbook แบบ dry-run ก่อนรันจริง
ansible-playbook -i inventory.ini setup-server.yml --check --diff

# ตรวจสอบว่า Python มีบน target host
ansible all -i inventory.ini -m command -a "python3 --version"

# ดู fact ของ host (hardware, OS, IP)
ansible web1 -i inventory.ini -m setup | grep -E "distribution|version|processor"
Tip: รัน --check ก่อนทุกครั้งที่รัน Playbook บน Production — จะแสดงว่า task ไหนจะ changed โดยไม่เปลี่ยนอะไรจริง

แก้ปัญหาที่พบบ่อย

Error: "UNREACHABLE! SSH connection failed"

Ansible เชื่อมต่อ SSH ไม่ได้ มักเกิดจาก SSH key ยังไม่ได้ copy หรือ known_hosts ขวาง

# copy SSH key ไปยัง target host ก่อน
ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]

# ถ้า known_hosts มีปัญหา (host key changed)
ssh-keygen -R 203.0.113.10

# ทดสอบ SSH manual ก่อน
ssh -i ~/.ssh/id_ed25519 [email protected] echo "OK"

Error: "Could not find /usr/bin/python"

Target host มี Python 3 แต่ Ansible หา python2 ที่ path เดิม ต้องบอก interpreter ใน inventory

# เพิ่มใน inventory.ini
[all:vars]
ansible_python_interpreter=/usr/bin/python3

# หรือระบุใน playbook
- hosts: all
  vars:
    ansible_python_interpreter: /usr/bin/python3

Error: "Missing sudo password" หรือ task ไม่ได้ผล become: yes

User ที่ใช้ต้อง sudo โดยไม่ใส่ password หรือต้องส่ง password ไปด้วย

# วิธีที่ 1: ส่ง sudo password ตอนรัน
ansible-playbook -i inventory.ini setup.yml -b --ask-become-pass

# วิธีที่ 2: ตั้ง NOPASSWD ใน /etc/sudoers บน target
# deployer ALL=(ALL) NOPASSWD:ALL

Playbook รันซ้ำแล้ว task "changed" ทุกครั้ง — ไม่ idempotent

Module command และ shell ไม่รู้ว่า command เคยรันไปหรือยัง ต้องใช้ module เฉพาะ

# แทน: shell: apt install nginx -y  (เปลี่ยนทุกรอบ)
# ใช้:
- name: Install nginx
  apt:
    name: nginx
    state: present   # idempotent — ถ้าติดตั้งแล้วจะ skip

# ถ้าจำเป็นต้องใช้ shell/command ใส่ creates: หรือ when: เพื่อกำหนดเงื่อนไข
- name: Init something
  command: /usr/bin/init-once.sh
  args:
    creates: /var/lock/init-once.lock

YAML Syntax Error — "could not find expected ':'"

YAML indent ผิด หรือใช้ tab แทน space ตรวจสอบด้วย yamllint

# ติดตั้ง yamllint
pip3 install yamllint

# ตรวจสอบไฟล์
yamllint setup-server.yml

# ใช้ ansible-lint ตรวจ best practice ด้วย
pip3 install ansible-lint
ansible-lint setup-server.yml

คำถามที่พบบ่อย

Ansible "agentless" หมายความว่าอะไร?

หมายความว่า VPS ที่เป็น target ไม่ต้องติดตั้ง Ansible หรือ agent software ใดๆ Ansible รันจากเครื่อง control (laptop หรือ jumphost) แล้วเชื่อมต่อผ่าน SSH ธรรมดา ใช้ Python ที่มีอยู่แล้วใน Linux ทุก distro เพื่อรัน task ข้อได้เปรียบคือไม่มี agent ให้ดูแลหรืออัพเดท

idempotent คืออะไร และทำไมสำคัญ?

idempotent หมายถึงรัน Playbook กี่ครั้งก็ได้ผลลัพธ์เหมือนกัน Ansible modules เช่น apt, copy, service ออกแบบมาให้ตรวจสถานะปัจจุบันก่อนเสมอ ถ้า package ติดตั้งแล้วก็ข้ามไป ไม่ต้องติดตั้งซ้ำ ทำให้ Playbook ปลอดภัยที่จะรันซ้ำหลังแก้ไข config เล็กน้อย

Ansible ใช้กับ Windows Server ได้ไหม?

ได้ แต่ใช้ WinRM แทน SSH และต้องติดตั้ง module เพิ่มเติม (pywinrm) ฝั่ง Ansible control node ส่วน module ที่ใช้กับ Windows จะมีชื่อต่างออกไป เช่น win_package, win_service, win_copy แทน apt, service, copy