Ansible ช่วย provision และ configure Cloud VPS อัตโนมัติตั้งแต่ต้นจนจบ — ตั้งแต่ initial server setup (user, SSH, firewall) ไปจนถึงติดตั้ง software stack และ deploy application ด้วย playbook เดียว แทนที่จะ login ทำเองทีละขั้นตอน
บทความนี้อธิบาย workflow สำหรับ provision VPS ใหม่ด้วย Ansible, initial server hardening, ติดตั้ง LEMP/LAMP stack อัตโนมัติ, จัดการ multiple servers ด้วย inventory และ pattern สำหรับ scale infrastructure ในระดับ production
Initial Server Setup Playbook
เมื่อได้ VPS ใหม่มา ขั้นตอนแรกคือ initial setup — สร้าง user, ตั้งค่า SSH key, disable root login และ configure firewall ด้วย playbook เดียว
---
# initial_setup.yml — รัน 1 ครั้งบน VPS ใหม่
- name: Initial VPS setup
hosts: new_servers
remote_user: root # login เป็น root ครั้งแรก
become: false
vars:
deploy_user: deploy
ssh_public_key: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
tasks:
# สร้าง deploy user
- name: Create deploy user
ansible.builtin.user:
name: "{{ deploy_user }}"
groups: sudo
shell: /bin/bash
create_home: true
# ติดตั้ง SSH key
- name: Add SSH authorized key
ansible.posix.authorized_key:
user: "{{ deploy_user }}"
key: "{{ ssh_public_key }}"
state: present
# Allow sudo without password สำหรับ deploy user
- name: Configure sudoers
ansible.builtin.lineinfile:
path: /etc/sudoers.d/{{ deploy_user }}
line: "{{ deploy_user }} ALL=(ALL) NOPASSWD: ALL"
create: true
mode: "0440"
validate: "visudo -cf %s"
# Disable root SSH login
- name: Disable root SSH login
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^PermitRootLogin"
line: "PermitRootLogin no"
notify: Restart SSH
# Disable password authentication
- name: Disable password auth
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^PasswordAuthentication"
line: "PasswordAuthentication no"
notify: Restart SSH
handlers:
- name: Restart SSH
ansible.builtin.service:
name: sshd
state: restarted
Firewall Configuration
ตั้งค่า UFW (Ubuntu) หรือ firewalld (CentOS/RHEL) เพื่อเปิดเฉพาะ ports ที่จำเป็น — ลด attack surface ตั้งแต่เริ่ม
---
# firewall.yml — รองรับทั้ง Ubuntu (ufw) และ CentOS (firewalld)
- name: Configure firewall
hosts: all
become: true
tasks:
# Ubuntu/Debian — UFW
- name: Install UFW
ansible.builtin.apt:
name: ufw
state: present
when: ansible_os_family == "Debian"
- name: Allow SSH
community.general.ufw:
rule: allow
port: "22"
proto: tcp
when: ansible_os_family == "Debian"
- name: Allow HTTP and HTTPS
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- "80"
- "443"
when: ansible_os_family == "Debian"
- name: Enable UFW
community.general.ufw:
state: enabled
policy: deny
direction: incoming
when: ansible_os_family == "Debian"
# CentOS/RHEL — firewalld
- name: Enable firewalld
ansible.builtin.systemd:
name: firewalld
state: started
enabled: true
when: ansible_os_family == "RedHat"
- name: Allow HTTP/HTTPS services
ansible.posix.firewalld:
service: "{{ item }}"
permanent: true
state: enabled
immediate: true
loop:
- http
- https
when: ansible_os_family == "RedHat"
Inventory สำหรับ Multiple VPS
จัดการหลาย VPS ด้วย inventory file — แยก groups ตาม role (web, db, cache) หรือ environment (production, staging)
# inventory/hosts.ini
[production]
web1.example.com
web2.example.com
db1.example.com
[staging]
staging.example.com
# แยก groups ตาม role
[webservers]
web1.example.com
web2.example.com
staging.example.com
[dbservers]
db1.example.com
# group_vars/all.yml — variables สำหรับทุก host
ansible_user: deploy
ansible_ssh_private_key_file: ~/.ssh/id_ed25519
ansible_python_interpreter: /usr/bin/python3
# group_vars/webservers.yml
nginx_worker_processes: auto
nginx_worker_connections: 1024
app_port: 8000
# group_vars/dbservers.yml
postgres_version: 16
postgres_max_connections: 200
postgres_shared_buffers: "256MB"
ติดตั้ง LEMP Stack อัตโนมัติ
ติดตั้ง Linux + Nginx + MySQL/MariaDB + PHP ด้วย playbook เดียว — รวม configure services และ deploy web root
---
# lemp_stack.yml
- name: Install and configure LEMP stack
hosts: webservers
become: true
vars:
php_version: "8.3"
site_domain: "example.com"
webroot: "/var/www/{{ site_domain }}"
tasks:
# อัพเดต packages
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
# ติดตั้ง Nginx
- name: Install Nginx
ansible.builtin.apt:
name: nginx
state: present
- name: Start and enable Nginx
ansible.builtin.systemd:
name: nginx
state: started
enabled: true
# ติดตั้ง PHP
- name: Install PHP and extensions
ansible.builtin.apt:
name:
- "php{{ php_version }}-fpm"
- "php{{ php_version }}-mysql"
- "php{{ php_version }}-curl"
- "php{{ php_version }}-xml"
- "php{{ php_version }}-mbstring"
- "php{{ php_version }}-zip"
state: present
# ติดตั้ง MariaDB
- name: Install MariaDB
ansible.builtin.apt:
name:
- mariadb-server
- python3-pymysql
state: present
- name: Start and enable MariaDB
ansible.builtin.systemd:
name: mariadb
state: started
enabled: true
# สร้าง web directory
- name: Create web root
ansible.builtin.file:
path: "{{ webroot }}"
state: directory
owner: www-data
group: www-data
mode: "0755"
# Deploy Nginx site config
- name: Configure Nginx virtual host
ansible.builtin.template:
src: templates/nginx-site.conf.j2
dest: "/etc/nginx/sites-available/{{ site_domain }}"
mode: "0644"
notify: Reload Nginx
- name: Enable site
ansible.builtin.file:
src: "/etc/nginx/sites-available/{{ site_domain }}"
dest: "/etc/nginx/sites-enabled/{{ site_domain }}"
state: link
notify: Reload Nginx
handlers:
- name: Reload Nginx
ansible.builtin.service:
name: nginx
state: reloaded
Database Provisioning
สร้าง database, user และ set privileges บน VPS ที่ติดตั้ง MySQL/MariaDB แล้ว — ใช้ community.mysql collection
---
- name: Provision application database
hosts: dbservers
become: true
tasks:
- name: Create application database
community.mysql.mysql_db:
name: "{{ app_db_name }}"
encoding: utf8mb4
collation: utf8mb4_unicode_ci
state: present
login_unix_socket: /var/run/mysqld/mysqld.sock
- name: Create database user
community.mysql.mysql_user:
name: "{{ app_db_user }}"
password: "{{ vault_db_password }}"
priv: "{{ app_db_name }}.*:ALL"
host: "{{ item }}"
state: present
login_unix_socket: /var/run/mysqld/mysqld.sock
loop:
- localhost
- "{{ webserver_ip }}" # allow web server to connect
no_log: true # ซ่อน password จาก logs
Full Provisioning Workflow
รวม playbooks ทั้งหมดเป็น workflow เดียว — ใช้ import_playbook เรียก sub-playbooks ตามลำดับ
---
# site.yml — master playbook
- name: Initial server setup
import_playbook: playbooks/initial_setup.yml
when: "'new_servers' in group_names"
- name: Configure firewall
import_playbook: playbooks/firewall.yml
- name: Install LEMP stack
import_playbook: playbooks/lemp_stack.yml
when: "'webservers' in group_names"
- name: Configure database server
import_playbook: playbooks/database.yml
when: "'dbservers' in group_names"
- name: Deploy application
import_playbook: playbooks/deploy_app.yml
# รัน full provisioning:
# ansible-playbook site.yml -i inventory/hosts.ini
# รันเฉพาะ step ที่ต้องการ (tags):
# ansible-playbook site.yml --tags "firewall,nginx"
# รันเฉพาะ webservers:
# ansible-playbook site.yml -l webservers
# Dry run ก่อน apply จริง:
# ansible-playbook site.yml --check --diff
สรุป
Ansible เปลี่ยน VPS provisioning จาก manual process ที่ทำซ้ำ × เป็น automated workflow ที่ reproducible — เริ่มจาก initial server setup, firewall configuration ไปจนถึงติดตั้ง application stack ด้วย playbook เดียว
สิ่งสำคัญคือแยก sensitive data ออกด้วย Ansible Vault, ใช้ inventory groups แยก environments, และใช้ tags เพื่อ run เฉพาะ tasks ที่ต้องการแทนที่จะรัน full playbook ทุกครั้ง

