Ansible จัดการ VPS อัตโนมัติ

เมื่อมี VPS หลายเครื่อง การ SSH เข้าไปตั้งค่าทีละเครื่องไม่ใช่วิธีที่ยั่งยืน Ansible คือเครื่องมือ Configuration Management ที่ให้คุณเขียน "สูตร" การตั้งค่าครั้งเดียว แล้วรันพร้อมกันบนทุกเครื่องผ่าน SSH — ไม่ต้องติดตั้ง Agent บน VPS เป้าหมายแม้แต่ตัวเดียว

Ansible คืออะไร ทำไมต้องใช้

Ansible เป็น Open-source IT automation tool ของ Red Hat ใช้ YAML ในการเขียน Playbook ซึ่งเป็นชุดคำสั่งที่บอกว่า "เครื่องนี้ต้องมีสถานะแบบไหน" เช่น ต้องมี Nginx ติดตั้ง, ต้องมีไฟล์ config ที่กำหนด, ต้องมี user นั้น Ansible จะทำให้ระบบตรงตามที่ระบุ (idempotent — รันกี่ครั้งผลเหมือนกัน)

สิ่งที่ต้องเตรียม

ติดตั้ง Ansible บน Control Node

Ubuntu / Debian

sudo apt update
sudo apt install -y ansible

macOS (Homebrew)

brew install ansible

Python pip (ทุก OS)

pip3 install ansible

ตรวจสอบเวอร์ชัน:

ansible --version
# ansible [core 2.17.x]

สร้าง Inventory File

Inventory คือไฟล์ที่ระบุว่า VPS ไหนอยู่กลุ่มไหน Ansible จะรู้ว่าต้องไปทำงานที่เครื่องใด

ไฟล์ 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

ทดสอบ Connectivity

ansible -i inventory.ini all -m ping

ถ้าได้ pong กลับมาทุกเครื่อง แปลว่า Ansible เชื่อมต่อได้สำเร็จ

เขียน Playbook แรก — ติดตั้ง Nginx

สร้างไฟล์ setup-nginx.yml:

---
- name: ติดตั้งและเปิด Nginx
  hosts: webservers
  become: yes

  tasks:
    - name: อัปเดต apt cache
      apt:
        update_cache: yes
        cache_valid_time: 3600

    - name: ติดตั้ง Nginx
      apt:
        name: nginx
        state: present

    - name: เปิด Nginx ทันทีและตั้ง autostart
      service:
        name: nginx
        state: started
        enabled: yes

    - name: เปิด port 80 ใน UFW
      ufw:
        rule: allow
        port: '80'
        proto: tcp

รัน Playbook:

ansible-playbook -i inventory.ini setup-nginx.yml

idempotent ในทางปฏิบัติ: รัน Playbook นี้ 10 ครั้ง Nginx จะไม่ถูกติดตั้งซ้ำ — Ansible ตรวจสอบสถานะจริงก่อน ถ้า Package มีอยู่แล้ว task นั้นจะ skip อัตโนมัติ (สถานะ "ok" ไม่ใช่ "changed")

Variables — ทำให้ Playbook ยืดหยุ่น

---
- name: ติดตั้ง Web Stack
  hosts: webservers
  become: yes
  vars:
    php_version: "8.3"
    app_user: "www-data"

  tasks:
    - name: ติดตั้ง PHP {{ php_version }}
      apt:
        name: "php{{ php_version }}-fpm"
        state: present

แยก Variables ออกเป็นไฟล์

# vars/main.yml
php_version: "8.3"
mysql_root_password: "SecurePass123"
app_domain: "example.com"
# ใช้ใน playbook
  vars_files:
    - vars/main.yml

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

Handler คือ Task พิเศษที่รันก็ต่อเมื่อ task อื่น "notify" มา — เหมาะสำหรับ restart service หลังแก้ config

  tasks:
    - name: คัดลอก Nginx config
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: Restart Nginx

  handlers:
    - name: Restart Nginx
      service:
        name: nginx
        state: restarted

ถ้า config ไม่เปลี่ยน Nginx จะไม่ถูก restart ช่วยลด downtime

Roles — จัดระเบียบ Playbook ขนาดใหญ่

เมื่อ Playbook ซับซ้อนขึ้น ใช้ Role แบ่งงานเป็นหมวด:

ansible-galaxy init roles/nginx
# สร้างโครงสร้าง:
# roles/nginx/
#   tasks/main.yml
#   handlers/main.yml
#   templates/
#   vars/main.yml
#   defaults/main.yml

Playbook สำหรับ VPS ใหม่ (Hardening)

ตัวอย่าง Playbook สำหรับ setup VPS ใหม่ทุกเครื่องให้พร้อมใช้งานและปลอดภัย:

---
- name: Setup VPS ใหม่
  hosts: new_servers
  become: yes

  tasks:
    - name: อัปเดตระบบทั้งหมด
      apt:
        upgrade: dist
        update_cache: yes

    - name: ติดตั้ง packages พื้นฐาน
      apt:
        name:
          - ufw
          - fail2ban
          - unattended-upgrades
          - curl
          - git
        state: present

    - name: สร้าง deploy user
      user:
        name: deploy
        shell: /bin/bash
        groups: sudo
        append: yes

    - name: วาง SSH public key
      authorized_key:
        user: deploy
        key: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"

    - name: ปิด Root SSH Login
      lineinfile:
        path: /etc/ssh/sshd_config
        regexp: '^PermitRootLogin'
        line: 'PermitRootLogin no'
      notify: Restart SSH

    - name: เปิด UFW
      ufw:
        state: enabled
        policy: deny

    - name: อนุญาต SSH port
      ufw:
        rule: allow
        port: '22'
        proto: tcp

  handlers:
    - name: Restart SSH
      service:
        name: ssh
        state: restarted

Ad-hoc Commands — รัน Task เดี่ยวโดยไม่ต้องเขียน Playbook

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

# Restart Nginx บน webservers
ansible -i inventory.ini webservers -m service -a "name=nginx state=restarted" --become

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

Ansible Galaxy: มี Role สำเร็จรูปจาก community ที่ galaxy.ansible.com เช่น geerlingguy.mysql, geerlingguy.nginx — ติดตั้งด้วย ansible-galaxy install geerlingguy.mysql แล้วเรียกใช้ใน Playbook ได้ทันที

Tips การใช้งานจริง

Dynamic Inventory — จัดการ VPS จาก Cloud Provider อัตโนมัติ

เมื่อโครงสร้างพื้นฐานมี VPS จำนวนมากหรือมีการเพิ่ม-ลดเครื่องบ่อย การเขียน inventory.ini แบบ static ไม่ใช่วิธีที่ดีนัก Ansible รองรับ Dynamic Inventory ที่ดึงรายชื่อเครื่องจาก API ของ Cloud Provider หรือฐานข้อมูลของคุณเองแบบ real-time

ตัวอย่าง Dynamic Inventory Script (Python)

#!/usr/bin/env python3
# dynamic_inventory.py — ดึงรายชื่อ VPS จาก API
import json, urllib.request

def get_servers():
    # ดึงข้อมูล VPS จาก API (ปรับ URL ให้ตรงกับ provider ของคุณ)
    req = urllib.request.urlopen('https://api.yourprovider.com/v1/servers')
    servers = json.loads(req.read())

    inventory = {
        "webservers": {"hosts": []},
        "_meta": {"hostvars": {}}
    }

    for s in servers:
        if s.get("role") == "web":
            inventory["webservers"]["hosts"].append(s["ip"])
            inventory["_meta"]["hostvars"][s["ip"]] = {
                "ansible_user": "root",
                "app_domain": s["domain"]
            }

    return inventory

print(json.dumps(get_servers(), indent=2))
# ใช้ dynamic inventory แทน static file
ansible-playbook -i dynamic_inventory.py setup-nginx.yml

ข้อดีของ Dynamic Inventory คือเมื่อเพิ่ม VPS ใหม่ใน Cloud Provider ระบบจะรู้จักเครื่องนั้นโดยอัตโนมัติในครั้งถัดไปที่รัน Playbook ไม่ต้องแก้ไฟล์ inventory ด้วยมือ ซึ่งสำคัญมากเมื่อมีการ scale หรือ auto-scaling

Ansible ยังรองรับ Plugin สำเร็จรูปสำหรับ Cloud Provider หลายเจ้า เช่น amazon.aws.aws_ec2, google.cloud.gcp_compute, vultr.cloud.vultr ติดตั้งผ่าน Ansible Galaxy แล้วกำหนดค่าใน YAML file สั้นๆ แทน static inventory ได้ทันที ช่วยให้ทีม DevOps สามารถจัดการ infrastructure ขนาดใหญ่ได้อย่างมีระเบียบและลดความผิดพลาดจากการพิมพ์ IP address ผิด

Jinja2 Templates — สร้าง Config File แบบ Dynamic

หนึ่งในฟีเจอร์ที่ทรงพลังของ Ansible คือการใช้ Jinja2 Template สร้างไฟล์ config ที่แตกต่างกันในแต่ละเครื่อง โดยใช้ตัวแปรที่กำหนดไว้ใน inventory หรือ vars

ตัวอย่าง Nginx config template

# templates/nginx.conf.j2
server {
    listen 80;
    server_name {{ app_domain }};
    root /var/www/{{ app_name }}/public;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php{{ php_version }}-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

ใช้ template module ใน Playbook

  tasks:
    - name: สร้าง Nginx config จาก template
      template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/{{ app_name }}
        owner: root
        group: root
        mode: '0644'
      notify: Reload Nginx

แต่ละ VPS ในกลุ่ม webservers จะได้ config ที่แตกต่างกันตามค่าตัวแปรของตัวเอง เช่น domain, app name, php version ทำให้ manage หลายเว็บไซต์บน VPS ต่างเครื่องได้สะดวกโดยใช้ Playbook เดียว Jinja2 ยังรองรับ Condition, Loop และ Filter ที่ทรงพลัง ทำให้สร้าง config file ที่ซับซ้อนได้โดยไม่ต้องเขียนไฟล์แยกสำหรับแต่ละ environment เหมาะอย่างยิ่งสำหรับ VPS ที่ต้องการ config ที่คล้ายกันแต่มีความแตกต่างกันเล็กน้อยในแต่ละโดเมนหรือแต่ละลูกค้า

Ansible Vault — เข้ารหัส Secret อย่างปลอดภัย

เมื่อ Playbook มี password หรือ API key ที่ต้องเก็บเป็นความลับ ใช้ Ansible Vault เข้ารหัสไฟล์ก่อน commit ขึ้น Git เพื่อความปลอดภัย

สร้างและเข้ารหัสไฟล์ Variables

# สร้างไฟล์ secrets ที่เข้ารหัส
ansible-vault create vars/secrets.yml

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

# เข้ารหัสไฟล์ที่มีอยู่แล้ว
ansible-vault encrypt vars/secrets.yml

# ถอดรหัสชั่วคราว (เพื่อดู)
ansible-vault decrypt vars/secrets.yml

ใช้ Vault ใน Playbook

  vars_files:
    - vars/main.yml
    - vars/secrets.yml   # ไฟล์ที่เข้ารหัส
# รัน Playbook พร้อม vault password
ansible-playbook -i inventory.ini deploy.yml --ask-vault-pass

# หรือใช้ไฟล์ password (สำหรับ CI/CD)
ansible-playbook -i inventory.ini deploy.yml --vault-password-file ~/.vault_pass

แนะนำ: ใส่ vars/secrets.yml ใน .gitignore ไม่ได้ผล เพราะอาจลืม encrypt ก่อน commit ให้ใช้ ansible-vault encrypt ทุกครั้งแทน แล้ว commit ไฟล์ที่เข้ารหัสแล้วได้อย่างปลอดภัย

Ansible ร่วมกับ CI/CD Pipeline

นำ Ansible ไปใช้ร่วมกับ CI/CD เช่น GitHub Actions ให้ deploy อัตโนมัติเมื่อมีการ push โค้ดขึ้น repository

ตัวอย่าง GitHub Actions Workflow

# .github/workflows/deploy.yml
name: Deploy with Ansible
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install Ansible
        run: pip3 install ansible

      - name: Write SSH Key
        run: |
          mkdir -p ~/.ssh
          echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519

      - name: Run Ansible Playbook
        run: |
          ansible-playbook -i inventory.ini deploy.yml \
            --vault-password-file <(echo "${{ secrets.VAULT_PASS }}")

ด้วย workflow นี้ทุกครั้งที่ merge โค้ดเข้า main GitHub Actions จะรัน Ansible Playbook โดยอัตโนมัติ ทำให้ deploy ไปยัง VPS ทุกเครื่องพร้อมกันโดยไม่ต้องทำเอง

เครื่องมือ CI/CD ใช้งานร่วมกับ Ansible ความซับซ้อน
GitHub Actionspip install ansible ใน runnerต่ำ
GitLab CIDocker image ที่มี Ansible ติดตั้งแล้วต่ำ
JenkinsAnsible plugin หรือ shell stepกลาง
AWX / Ansible TowerBuilt-in — ออกแบบมาคู่กันสูง (enterprise)

Troubleshooting ปัญหาที่พบบ่อย

ปัญหาที่นักพัฒนาเจอบ่อยเมื่อเริ่มใช้ Ansible และวิธีแก้ไข

SSH Connection Refused

# ตรวจสอบว่า SSH ใช้ key ถูกต้อง
ansible -i inventory.ini all -m ping -vvv

# ตรวจสอบ fingerprint ก่อน (ครั้งแรก)
ssh-keyscan -H 203.0.113.10 >> ~/.ssh/known_hosts

Permission Denied เมื่อรัน Task

# ถ้า task ต้องการ sudo ต้องใส่ become: yes
- name: ติดตั้ง Package
  apt:
    name: nginx
    state: present
  become: yes   # ← บังคับต้องมี

# หรือใส่ระดับ play
- hosts: webservers
  become: yes   # ← ใช้กับทุก task ใน play นี้

Python ไม่ถูก Detect บน Managed Node

# ระบุ Python interpreter โดยตรงใน inventory
web1 ansible_host=203.0.113.10 ansible_python_interpreter=/usr/bin/python3

# หรือกำหนดระดับ group
[all:vars]
ansible_python_interpreter=/usr/bin/python3

เคล็ดลับ Debug: ใช้ -v, -vv, หรือ -vvv เพื่อเพิ่ม verbosity ของ output จะเห็น SSH command จริงที่ Ansible รัน ช่วยหาปัญหาได้รวดเร็ว

เปรียบเทียบ Ansible กับ Configuration Management Tools อื่น

มี tool หลายตัวที่ทำงานคล้าย Ansible แต่แต่ละตัวมีจุดแข็งและจุดอ่อนต่างกัน ช่วยให้เลือกได้เหมาะกับโปรเจกต์

Tool Agent ภาษา Config เหมาะกับ
Ansibleไม่มี (Agentless)YAMLทั่วไป ทุกขนาด
PuppetมีDSLEnterprise ขนาดใหญ่
ChefมีRuby DSLDev ที่รู้ Ruby
Terraformไม่มีHCLInfrastructure provisioning

Ansible เหมาะที่สุดสำหรับการจัดการ VPS เมื่อต้องการความง่าย ไม่มี agent และทำงานผ่าน SSH ที่มีอยู่แล้ว โดยเฉพาะเมื่อทีมส่วนใหญ่คุ้นเคยกับ YAML มากกว่า Ruby หรือ DSL เฉพาะของ Puppet สำหรับโปรเจกต์ที่เพิ่งเริ่มต้นและมี VPS ไม่เกิน 50 เครื่อง Ansible แบบ standalone ก็เพียงพอมากโดยไม่ต้องลงทุนกับ Ansible Tower หรือ AWX ซึ่งเหมาะกับ enterprise ขนาดใหญ่ที่ต้องการ GUI และ RBAC สำหรับทีมหลายคน

ต้องการ VPS สำหรับรัน Ansible?

AsiaGB มี VPS Linux Ubuntu/Debian พร้อม Full Root Access เริ่มต้น 500 บาท/เดือน

ดู VPS Plans

ดูแพ็กเกจ VPS ไทย ราคาถูกทั้งหมด →