Ansible สำหรับจัดการ VPS อัตโนมัติ: Playbook เบื้องต้น
เมื่อคุณมี 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
สร้าง 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"
--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