Server Maintenance เป็นงานที่ต้องทำสม่ำเสมอทุกเซิร์ฟเวอร์ ไม่ว่าจะเป็น Backup ข้อมูล, อัปเดต System Package, หรือตรวจสอบ Disk Usage แต่เมื่อมีเซิร์ฟเวอร์หลายสิบหรือหลายร้อยเครื่อง การทำงานเหล่านี้ด้วยมือกลายเป็นเรื่องที่ใช้เวลามากและเสี่ยงต่อการตกหล่น
Workshop นี้สร้าง Ansible Playbook สำหรับงาน Server Maintenance ที่ครอบคลุม 3 ด้านหลัก ได้แก่ การ Backup ไฟล์สำคัญและ Database, การอัปเดต System Package อย่างปลอดภัย, และการติดตั้ง Monitoring Agent บนเซิร์ฟเวอร์ทุกเครื่องพร้อมกัน
โครงสร้าง Project
server-maintenance/
├── inventory/
│ └── hosts.ini
├── group_vars/
│ └── all.yml
├── roles/
│ ├── backup/
│ │ └── tasks/main.yml
│ ├── update/
│ │ └── tasks/main.yml
│ └── monitoring/
│ ├── tasks/main.yml
│ └── templates/
│ └── node_exporter.service.j2
├── maintenance.yml # Playbook หลัก
└── backup-only.yml # Playbook เฉพาะ Backup
Inventory และ Variables
[production]
web01 ansible_host=192.168.1.10 ansible_user=ubuntu server_type=web
web02 ansible_host=192.168.1.11 ansible_user=ubuntu server_type=web
db01 ansible_host=192.168.1.20 ansible_user=ubuntu server_type=db
[staging]
stage01 ansible_host=192.168.1.30 ansible_user=ubuntu server_type=web
[all:vars]
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible_python_interpreter=/usr/bin/python3
กำหนด Variables ใน group_vars/all.yml:
# Backup settings
backup_dir: /var/backups/ansible
backup_retention_days: 30
backup_timestamp: "{{ ansible_date_time.date }}"
# Paths to backup (สามารถ override ต่อ host ได้)
backup_paths:
- /etc/nginx
- /etc/mysql
- /var/www
# Update settings
update_reboot_allowed: false # ตั้งเป็น true ถ้าต้องการ reboot อัตโนมัติ
update_cache_valid_time: 3600
# Monitoring
node_exporter_version: "1.7.0"
node_exporter_port: 9100
Role: Backup
สร้าง roles/backup/tasks/main.yml — Backup ไฟล์ Config และ MySQL Database:
---
- name: Ensure backup directory exists
file:
path: "{{ backup_dir }}/{{ backup_timestamp }}"
state: directory
owner: root
group: root
mode: "0700"
- name: Backup configuration directories
archive:
path: "{{ backup_paths }}"
dest: "{{ backup_dir }}/{{ backup_timestamp }}/configs.tar.gz"
format: gz
exclude_path:
- "*/cache"
- "*/tmp"
register: backup_result
ignore_errors: yes
- name: Show backup size
debug:
msg: "Backup size: {{ backup_result.expanded_size | default('unknown') }} bytes"
- name: Backup MySQL databases (only on db servers)
shell: |
mysqldump --all-databases \
--single-transaction \
--quick \
--lock-tables=false \
| gzip > {{ backup_dir }}/{{ backup_timestamp }}/mysql-all.sql.gz
args:
creates: "{{ backup_dir }}/{{ backup_timestamp }}/mysql-all.sql.gz"
when: server_type == "db"
no_log: true
- name: Remove backups older than retention period
find:
paths: "{{ backup_dir }}"
age: "{{ backup_retention_days }}d"
file_type: directory
register: old_backups
- name: Delete old backup directories
file:
path: "{{ item.path }}"
state: absent
loop: "{{ old_backups.files }}"
when: old_backups.files | length > 0
Role: System Update
สร้าง roles/update/tasks/main.yml — อัปเดต Package อย่างปลอดภัยพร้อม Reboot ถ้าจำเป็น:
---
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: "{{ update_cache_valid_time }}"
- name: List packages to be upgraded
apt:
upgrade: yes
autoremove: yes
autoclean: yes
check_mode: yes
register: apt_upgrade_check
- name: Show packages to be updated
debug:
msg: "Packages to update: {{ apt_upgrade_check.stdout_lines | length }} packages"
when: apt_upgrade_check.stdout_lines is defined
- name: Apply security updates only
apt:
upgrade: safe
autoremove: yes
autoclean: yes
register: update_result
- name: Show update summary
debug:
msg: "Updated: {{ update_result.stdout | regex_findall('upgraded') | length }} packages"
when: update_result.stdout is defined
- name: Check if reboot is required
stat:
path: /var/run/reboot-required
register: reboot_required
- name: Reboot if required and allowed
reboot:
reboot_timeout: 300
pre_reboot_delay: 10
msg: "Rebooting for system updates"
when:
- reboot_required.stat.exists
- update_reboot_allowed | bool
- name: Notify if reboot is needed but not allowed
debug:
msg: "⚠️ Reboot required on {{ inventory_hostname }} but update_reboot_allowed=false — please reboot manually"
when:
- reboot_required.stat.exists
- not (update_reboot_allowed | bool)
Role: Monitoring (Node Exporter)
สร้าง roles/monitoring/tasks/main.yml — ติดตั้ง Prometheus Node Exporter สำหรับ Monitoring:
---
- name: Check if node_exporter is already installed
stat:
path: /usr/local/bin/node_exporter
register: node_exporter_binary
- name: Check installed version
command: /usr/local/bin/node_exporter --version
register: installed_version
changed_when: false
failed_when: false
when: node_exporter_binary.stat.exists
- name: Download Node Exporter
get_url:
url: "https://github.com/prometheus/node_exporter/releases/download/v{{ node_exporter_version }}/node_exporter-{{ node_exporter_version }}.linux-amd64.tar.gz"
dest: /tmp/node_exporter.tar.gz
mode: "0644"
when: >
not node_exporter_binary.stat.exists or
node_exporter_version not in (installed_version.stdout | default(''))
- name: Extract and install Node Exporter
unarchive:
src: /tmp/node_exporter.tar.gz
dest: /tmp
remote_src: yes
when: >
not node_exporter_binary.stat.exists or
node_exporter_version not in (installed_version.stdout | default(''))
- name: Copy node_exporter binary
copy:
src: "/tmp/node_exporter-{{ node_exporter_version }}.linux-amd64/node_exporter"
dest: /usr/local/bin/node_exporter
owner: root
group: root
mode: "0755"
remote_src: yes
notify: Restart node_exporter
when: >
not node_exporter_binary.stat.exists or
node_exporter_version not in (installed_version.stdout | default(''))
- name: Deploy systemd service for node_exporter
template:
src: node_exporter.service.j2
dest: /etc/systemd/system/node_exporter.service
owner: root
group: root
mode: "0644"
notify:
- Reload systemd
- Restart node_exporter
- name: Ensure node_exporter is started and enabled
service:
name: node_exporter
state: started
enabled: yes
- name: Verify node_exporter is responding
uri:
url: "http://localhost:{{ node_exporter_port }}/metrics"
status_code: 200
retries: 3
delay: 5
Main Playbook: maintenance.yml
รวม 3 Roles ด้วย Tags เพื่อให้รันเลือกบางส่วนได้:
---
- name: Server Maintenance — Backup, Update, Monitoring
hosts: all
become: yes
serial: 1 # รันทีละ 1 เครื่อง ป้องกัน downtime พร้อมกัน
pre_tasks:
- name: Check disk space before maintenance
command: df -h /var
register: disk_space
changed_when: false
- name: Alert if disk space is low (< 20%)
fail:
msg: "Low disk space on {{ inventory_hostname }}: {{ disk_space.stdout }}"
when: >
disk_space.stdout | regex_search('(\d+)%') | int > 80
roles:
- role: backup
tags: [backup]
- role: update
tags: [update]
- role: monitoring
tags: [monitoring]
post_tasks:
- name: Record maintenance completion
copy:
content: "Maintenance completed: {{ ansible_date_time.iso8601 }}\n"
dest: /var/log/ansible-maintenance.log
changed_when: false
การใช้งาน: รัน Maintenance Tasks
# รัน Maintenance ทุกอย่างบน production
ansible-playbook -i inventory/hosts.ini maintenance.yml --limit production
# รันเฉพาะ Backup บนทุก Server
ansible-playbook -i inventory/hosts.ini maintenance.yml --tags backup
# รันเฉพาะ Update บน staging ก่อน production
ansible-playbook -i inventory/hosts.ini maintenance.yml \
--limit staging --tags update
ansible-playbook -i inventory/hosts.ini maintenance.yml \
--limit production --tags update
# รันเฉพาะ Monitoring บน servers ที่ยังไม่มี node_exporter
ansible-playbook -i inventory/hosts.ini maintenance.yml --tags monitoring
# Dry run ดูว่าจะทำอะไร
ansible-playbook -i inventory/hosts.ini maintenance.yml --check --diff
ตรวจสอบ Disk Space และ Backup ทั้งหมดด้วย Ad-hoc
# ตรวจสอบ disk usage บนทุก server พร้อมกัน
ansible all -i inventory/hosts.ini -m command -a "df -h /" --become
# ตรวจสอบ backup ล่าสุด
ansible all -i inventory/hosts.ini -m find \
-a "paths=/var/backups/ansible age=1d file_type=directory" --become
# ตรวจสอบ node_exporter metrics
ansible all -i inventory/hosts.ini -m uri \
-a "url=http://localhost:9100/metrics" --become
ตั้ง Cron ให้รัน Maintenance อัตโนมัติ
เพิ่ม Cron Job บน Ansible Control Node เพื่อให้ Maintenance รันอัตโนมัติทุกสัปดาห์:
# เพิ่มใน crontab บน Ansible Control Node
# รัน Backup ทุกคืน 02:00 น.
0 2 * * * cd /opt/server-maintenance && \
ansible-playbook -i inventory/hosts.ini maintenance.yml --tags backup \
>> /var/log/ansible-backup.log 2>&1
# รัน Update ทุกวันจันทร์ 03:00 น.
0 3 * * 1 cd /opt/server-maintenance && \
ansible-playbook -i inventory/hosts.ini maintenance.yml --tags update \
--limit staging >> /var/log/ansible-update.log 2>&1
สรุป
Workshop นี้สร้าง Ansible Maintenance Playbook ที่ครอบคลุม Backup, System Update, และ Monitoring ในไฟล์เดียว โดยใช้ Tags ให้รันเลือกบางส่วนได้ ใช้ serial: 1 ป้องกัน downtime พร้อมกัน และมี pre_task ตรวจสอบ disk space ก่อนเริ่มทุกครั้ง
การกำหนด server_type ใน Inventory ทำให้ Task บางอย่างเช่น MySQL dump จะรันเฉพาะบน Database server โดยใช้ when: server_type == "db" โดยไม่ต้องสร้าง Playbook แยก ทำให้ดูแลรักษา Codebase เดียวได้ง่ายขึ้น

