การอัปเดตระบบปฏิบัติการและจัดการ Package เป็นงานที่ Sysadmin ต้องทำเป็นประจำ โดยเฉพาะเมื่อมีเซิร์ฟเวอร์หลายสิบหรือหลายร้อยเครื่อง การทำด้วยมือทีละเครื่องนั้นเสียเวลาและเสี่ยงต่อความผิดพลาด เช่น ลืม server บางเครื่อง หรือใช้คำสั่งไม่ถูกต้อง
บทความนี้จะอธิบายวิธีสร้าง Playbook สำหรับงาน System Updates และ Package Management ทั้งบน Ubuntu/Debian (apt) และ CentOS/RHEL (yum/dnf) พร้อมเทคนิคการอัปเดตแบบ Rolling Update ที่ไม่กระทบ production service
ทำไมต้องจัดการ System Updates ด้วย Automation
การอัปเดตระบบด้วยตนเองบนหลายเครื่องพร้อมกันมีความเสี่ยงสูง เพราะยากต่อการควบคุมและตรวจสอบ ปัญหาที่พบบ่อยได้แก่ server บางเครื่องได้รับ patch ล่าช้า, version ของ package ไม่สม่ำเสมอข้ามเซิร์ฟเวอร์, และไม่มี log บันทึกการเปลี่ยนแปลง การใช้ Automation ช่วยแก้ปัญหาเหล่านี้ได้โดยตรง
- ความสม่ำเสมอ: ทุกเซิร์ฟเวอร์ได้รับ package version เดียวกันในเวลาเดียวกัน
- ตรวจสอบได้: มี log บันทึกว่า host ใดอัปเดตสำเร็จหรือล้มเหลว
- ปลอดภัยกว่า: ทดสอบด้วย dry-run ก่อนรันจริงบน production ได้
- ประหยัดเวลา: รัน update 100 เครื่องพร้อมกันในเวลาเดียวกับการอัปเดต 1 เครื่องด้วยตนเอง
Playbook สำหรับ Ubuntu/Debian (apt)
ระบบที่ใช้ Debian-based distribution เช่น Ubuntu ใช้ apt เป็น package manager Ansible มี module apt ที่ทำงานเหมือนคำสั่ง apt-get แต่มีความสามารถเพิ่มเติมหลายอย่าง เช่น ตรวจสอบ cache validity และ autoremove ได้ในคำสั่งเดียว
อัปเดต Cache และ Upgrade ทั้งระบบ
---
- name: System Updates on Ubuntu/Debian
hosts: ubuntu_servers
become: true
tasks:
- name: Update apt package cache
ansible.builtin.apt:
update_cache: yes
cache_valid_time: 3600
- name: Upgrade all packages to latest version
ansible.builtin.apt:
upgrade: dist
autoremove: yes
- name: Check if reboot is required
ansible.builtin.stat:
path: /var/run/reboot-required
register: reboot_required
- name: Reboot if required
ansible.builtin.reboot:
reboot_timeout: 300
when: reboot_required.stat.exists
Playbook นี้ทำตามลำดับดังนี้: อัปเดต cache (โดยข้ามถ้า cache ยังไม่เก่าเกิน 1 ชั่วโมง), upgrade ทุก package พร้อมลบ dependency ที่ไม่จำเป็น, ตรวจสอบว่าต้อง reboot หรือเปล่า แล้ว reboot อัตโนมัติถ้าจำเป็น
ติดตั้งและลบ Package เฉพาะ
- name: Install essential packages
ansible.builtin.apt:
name:
- curl
- wget
- vim
- htop
- unzip
state: present
update_cache: yes
- name: Remove insecure packages
ansible.builtin.apt:
name:
- telnet
- ftp
state: absent
purge: yes
ใช้ state: present สำหรับติดตั้ง และ state: absent สำหรับลบ การใส่ purge: yes จะลบทั้ง package และ configuration files ออกจากระบบ
Playbook สำหรับ CentOS/RHEL (yum/dnf)
ระบบที่ใช้ Red Hat-based distribution เช่น CentOS, Rocky Linux, AlmaLinux ใช้ yum หรือ dnf เป็น package manager โครงสร้าง Playbook คล้ายกัน แต่เปลี่ยน module เป็น yum แทน
---
- name: System Updates on CentOS/RHEL
hosts: rhel_servers
become: true
tasks:
- name: Update all packages
ansible.builtin.yum:
name: "*"
state: latest
update_only: yes
- name: Install essential packages
ansible.builtin.yum:
name:
- epel-release
- curl
- wget
- vim
- htop
state: present
- name: Clean yum cache
ansible.builtin.command:
cmd: yum clean all
changed_when: false
บน CentOS 8 หรือ Rocky Linux 9 ขึ้นไป สามารถเปลี่ยนไปใช้ module dnf แทน yum ได้เลย เนื่องจาก dnf เป็น package manager หลักของระบบรุ่นใหม่และมีประสิทธิภาพสูงกว่า
ควบคุม Version ของ Package ให้สม่ำเสมอ
ใน production environment บางครั้งจำเป็นต้อง pin version ของ package เฉพาะเจาะจง เพื่อป้องกันการ upgrade โดยไม่ตั้งใจที่อาจทำให้แอปพลิเคชันทำงานผิดปกติ
# ติดตั้ง nginx version เฉพาะ
- name: Install specific nginx version
ansible.builtin.apt:
name: "nginx=1.24.*"
state: present
update_cache: yes
# Lock nginx ไม่ให้ถูก upgrade
- name: Hold nginx at current version
ansible.builtin.dpkg_selections:
name: nginx
selection: hold
# ตรวจสอบ version ที่ติดตั้ง
- name: Check installed nginx version
ansible.builtin.command:
cmd: nginx -v
changed_when: false
register: nginx_version
- name: Show nginx version
ansible.builtin.debug:
msg: "{{ nginx_version.stderr }}"
การใช้ dpkg_selections: hold จะป้องกันไม่ให้ package ถูก upgrade โดยอัตโนมัติ ซึ่งมีประโยชน์มากใน environment ที่ต้องการความเสถียรสูง
อัปเดต Security Patches เฉพาะ (ไม่ Upgrade ทั้งหมด)
หลายองค์กรต้องการ apply เฉพาะ security patches โดยไม่อัปเดต feature packages ทั้งหมด เพื่อลดความเสี่ยงจากการ breaking change
---
- name: Apply security patches only
hosts: all
become: true
tasks:
- name: Install unattended-upgrades (Ubuntu)
ansible.builtin.apt:
name: unattended-upgrades
state: present
when: ansible_os_family == "Debian"
- name: Run security updates only
ansible.builtin.command:
cmd: unattended-upgrade --dry-run -v
changed_when: false
register: upgrade_output
when: ansible_os_family == "Debian"
- name: Show pending security updates
ansible.builtin.debug:
var: upgrade_output.stdout_lines
when: ansible_os_family == "Debian"
- name: Apply security updates on RHEL
ansible.builtin.yum:
name: "*"
state: latest
security: yes
when: ansible_os_family == "RedHat"
การใช้ ansible_os_family fact ทำให้สามารถเขียน Playbook เดียวที่รองรับได้ทั้ง Ubuntu และ CentOS โดยอัตโนมัติ
Rolling Update: อัปเดตแบบไม่กระทบ Service
การอัปเดตเซิร์ฟเวอร์ทั้งหมดพร้อมกันอาจทำให้ service หยุดชะงักได้ กลยุทธ์ Rolling Update คือการอัปเดตทีละกลุ่มเล็ก ๆ โดย load balancer ยังคงรับ traffic ได้ระหว่างนั้น
---
- name: Rolling Update - Web Servers
hosts: webservers
become: true
serial: 2 # อัปเดตทีละ 2 เครื่อง
max_fail_percentage: 25 # หยุดทั้งหมดถ้าล้มเหลวเกิน 25%
pre_tasks:
- name: Remove server from load balancer
ansible.builtin.uri:
url: "http://lb01/api/disable/{{ inventory_hostname }}"
method: POST
delegate_to: lb01
- name: Wait for connections to drain
ansible.builtin.pause:
seconds: 10
tasks:
- name: Update all packages
ansible.builtin.apt:
upgrade: dist
update_cache: yes
- name: Restart web service
ansible.builtin.service:
name: nginx
state: restarted
- name: Verify service is running
ansible.builtin.uri:
url: "http://{{ inventory_hostname }}:80/health"
status_code: 200
post_tasks:
- name: Re-add server to load balancer
ansible.builtin.uri:
url: "http://lb01/api/enable/{{ inventory_hostname }}"
method: POST
delegate_to: lb01
serial: 2 บอกให้รันทีละ 2 host ก่อนเริ่ม batch ถัดไป ส่วน pre_tasks และ post_tasks ใช้นำ server ออกจาก load balancer ก่อนอัปเดต และนำกลับเข้าหลังจากตรวจสอบแล้วว่า service ทำงานปกติ
Dry-run และ Limit: ทดสอบก่อนรันจริง
ก่อนรัน update บน production ควรทดสอบด้วย --check flag เสมอ เพื่อดูว่าจะมีการเปลี่ยนแปลงอะไรบ้างโดยไม่แตะระบบจริง
# Dry-run ดูว่าจะเปลี่ยนแปลงอะไร
ansible-playbook update-systems.yml --check --diff
# รันเฉพาะ server กลุ่ม staging ก่อน
ansible-playbook update-systems.yml --limit staging_servers
# รันเฉพาะ task ที่มี tag "update"
ansible-playbook update-systems.yml --tags update
# ตรวจสอบ package ที่จะถูก upgrade บน Ubuntu
ansible ubuntu_servers -m apt -a "upgrade=dist update_cache=yes" --check -b
แนะนำให้ทดสอบบน staging environment ก่อนเสมอ โดยใช้ --limit กำหนดเฉพาะกลุ่มที่ต้องการ จากนั้นค่อย apply บน production เมื่อมั่นใจแล้ว
สรุป
การจัดการ System Updates ด้วย Automation ช่วยให้ infrastructure ของคุณมีความสม่ำเสมอ ปลอดภัย และตรวจสอบได้ ไม่ว่าจะเป็น Ubuntu (apt) หรือ CentOS/RHEL (yum/dnf) สามารถจัดการด้วย Playbook ชุดเดียว และขยายไปรองรับ server ใหม่ได้โดยแค่เพิ่ม host ใน inventory
หัวใจสำคัญคือ: ใช้ --check ก่อนรันจริง, ใช้ rolling update สำหรับ production เพื่อไม่ให้ service หยุดชะงัก, และใส่ reboot check เพื่อให้ security patch มีผลครบถ้วน การวางแผนและทดสอบอย่างรอบคอบก่อน deploy จริงจะช่วยลดความเสี่ยงได้มาก

