ลดความยุ่งยากในการใช้คลาวด์ พูดคุยกับเจ้าหน้าที่

ตั้งเวลางานอัตโนมัติด้วย systemd Timer แทน Cron
Home ตั้งเวลางานอัตโนมัติด้วย systemd Timer แทน Cron

ตั้งเวลางานอัตโนมัติด้วย systemd Timer แทน Cron

Cron ทำงานได้ดีมาหลายสิบปี แต่เมื่อมีงานตามเวลาหลายตัวบนเซิร์ฟเวอร์เดียว ข้อจำกัดจะเริ่มโผล่ ได้แก่ Log ที่ต้อง redirect เอง งานที่รันทับกันเมื่อรอบก่อนยังไม่จบ และงานที่หายไปเงียบ ๆ เมื่อเครื่องดับในช่วงเวลานั้นพอดี

systemd Timer แก้ทั้งสามข้อนี้ในตัว คู่มือนี้สร้าง Timer สำหรับงานสำรองข้อมูลเป็นตัวอย่าง แล้วอธิบายรูปแบบเวลา การดู Log และการย้ายงานเดิมจาก Cron ใช้ได้กับ Ubuntu, Debian, AlmaLinux และ Rocky Linux ทุกรุ่นที่ใช้ systemd

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

  • Linux Server ที่ใช้ systemd และสิทธิ์ sudo
  • สคริปต์หรือคำสั่งที่ต้องการให้ทำงานตามเวลา ทดสอบรันด้วยมือจนผ่านแล้ว

ก่อนเริ่ม ให้ทดสอบสคริปต์ด้วยมือก่อนเสมอ ปัญหาส่วนใหญ่ของงานตามเวลาไม่ได้อยู่ที่ตัวตั้งเวลา แต่อยู่ที่สคริปต์ที่พึ่งพาตัวแปรสภาพแวดล้อมซึ่งมีเฉพาะตอนล็อกอิน

ขั้นตอนที่ 1: เขียนสคริปต์ที่จะให้ทำงาน

sudo nano /usr/local/bin/backup-db.sh
#!/bin/bash
set -euo pipefail

DEST=/var/backups/mysql
STAMP=$(date +%F-%H%M)
mkdir -p "$DEST"

mysqldump --single-transaction --all-databases | gzip > "$DEST/all-$STAMP.sql.gz"
find "$DEST" -name 'all-*.sql.gz' -mtime +14 -delete

echo "backup written: $DEST/all-$STAMP.sql.gz"

ให้สิทธิ์รันและทดสอบ

sudo chmod +x /usr/local/bin/backup-db.sh
sudo /usr/local/bin/backup-db.sh

บรรทัด set -euo pipefail สำคัญมาก มันทำให้สคริปต์หยุดทันทีเมื่อคำสั่งใดล้มเหลว แทนที่จะทำงานต่อจนได้ไฟล์สำรองที่ว่างเปล่าโดยไม่มีใครรู้

ขั้นตอนที่ 2: สร้างไฟล์ .service

Timer ไม่ได้รันคำสั่งเอง แต่ไปสั่ง service ให้ทำงาน จึงต้องมีสองไฟล์เสมอ ชื่อไฟล์ต้องตรงกันยกเว้นนามสกุล

sudo nano /etc/systemd/system/backup-db.service
[Unit]
Description=สำรองฐานข้อมูล MySQL ทั้งหมด
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-db.sh
User=root
# ให้หยุดเองหากงานค้างเกินหนึ่งชั่วโมง
TimeoutStartSec=3600

Type=oneshot บอก systemd ว่างานนี้ทำครั้งเดียวแล้วจบ ไม่ใช่โปรแกรมที่ต้องรันค้างไว้ ทดสอบสั่งด้วยมือได้เลย

sudo systemctl daemon-reload
sudo systemctl start backup-db.service
sudo systemctl status backup-db.service --no-pager

ขั้นตอนที่ 3: สร้างไฟล์ .timer

sudo nano /etc/systemd/system/backup-db.timer
[Unit]
Description=สำรองฐานข้อมูลทุกวันตอนตีสาม

[Timer]
OnCalendar=*-*-* 03:00:00
# กระจายเวลาเริ่มแบบสุ่มไม่เกิน 10 นาที กันงานหลายตัวชนกัน
RandomizedDelaySec=600
# หากเครื่องดับตอนถึงรอบ ให้รันทันทีที่เปิดเครื่องกลับมา
Persistent=true

[Install]
WantedBy=timers.target

บรรทัด Persistent=true คือสิ่งที่ Cron ไม่มี ถ้าเครื่องปิดอยู่ตอนตีสาม Cron จะข้ามรอบนั้นไปเงียบ ๆ แต่ systemd จะรันให้ทันทีที่เครื่องกลับมา

เปิดใช้งาน

sudo systemctl daemon-reload
sudo systemctl enable --now backup-db.timer

ขั้นตอนที่ 4: ตรวจสอบว่ารอบเวลาถูกต้อง

ดู Timer ทั้งหมดในเครื่องพร้อมเวลาที่จะทำงานครั้งถัดไป

systemctl list-timers --all

คอลัมน์ NEXT คือเวลาที่จะรันรอบหน้า และ LAST คือรอบล่าสุด ถ้ายังไม่แน่ใจว่ารูปแบบเวลาที่เขียนหมายถึงอะไร ให้ทดสอบด้วย

systemd-analyze calendar "*-*-* 03:00:00"
systemd-analyze calendar "Mon *-*-* 02:30:00"
systemd-analyze calendar "*-*-01 00:00:00"

คำสั่งนี้จะบอกว่าเวลาที่ตรงเงื่อนไขครั้งถัดไปคือเมื่อไร เป็นวิธีที่เร็วที่สุดในการยืนยันว่าเขียนถูก

ขั้นตอนที่ 5: รูปแบบเวลาที่ใช้บ่อย

  • OnCalendar=hourly ทุกต้นชั่วโมง
  • OnCalendar=daily ทุกวันเที่ยงคืน
  • OnCalendar=*-*-* 03:00:00 ทุกวันตอนตีสาม
  • OnCalendar=Mon..Fri 08:30:00 จันทร์ถึงศุกร์ 8:30 น.
  • OnCalendar=*-*-* *:00/15:00 ทุก 15 นาที
  • OnCalendar=*-*-01 04:00:00 ทุกวันที่ 1 ของเดือน

เวลาทั้งหมดอิงตาม Timezone ของเครื่อง ตรวจได้ด้วย timedatectl หากเซิร์ฟเวอร์ตั้งเป็น UTC แต่คุณคิดเป็นเวลาไทย จะคลาดกัน 7 ชั่วโมง

ขั้นตอนที่ 6: ดู Log

ทุกอย่างที่สคริปต์พิมพ์ออกมาถูกเก็บให้อัตโนมัติ ไม่ต้อง redirect เอง

journalctl -u backup-db.service -n 50 --no-pager
journalctl -u backup-db.service --since today
journalctl -u backup-db.service -f

คำสั่งสุดท้ายคือเฝ้าดูแบบเรียลไทม์ มีประโยชน์ตอนสั่งรันด้วยมือเพื่อทดสอบ

ขั้นตอนที่ 7: ย้ายงานเดิมจาก Cron

ดูงาน Cron ที่มีอยู่ก่อน

sudo crontab -l
ls -l /etc/cron.d/

เทียบรูปแบบเวลาได้ตรงตัว เช่น 30 2 * * 1 ใน Cron เท่ากับ OnCalendar=Mon *-*-* 02:30:00 เมื่อย้ายเสร็จและยืนยันว่า Timer ทำงานแล้ว จึงค่อยลบบรรทัดเดิมออกจาก Cron อย่าปล่อยให้ทั้งสองระบบรันงานเดียวกันพร้อมกัน

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

Timer ขึ้นว่า enabled แต่ไม่เคยรัน

ตรวจว่าชื่อไฟล์ .service กับ .timer ตรงกันทุกตัวอักษร ถ้าชื่อไม่ตรง ต้องระบุเป้าหมายให้ชัดด้วย Unit= ในส่วน [Timer]

รันด้วยมือได้ แต่รันตามเวลาแล้วพัง

เกือบทุกครั้งเป็นเรื่อง PATH และตัวแปรสภาพแวดล้อม systemd ไม่โหลด .bashrc หรือ .profile ให้ แก้โดยใช้พาธเต็มในสคริปต์ เช่น /usr/bin/mysqldump แทน mysqldump หรือกำหนดเพิ่มใน [Service] ด้วย Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

งานรอบใหม่เริ่มทั้งที่รอบเก่ายังไม่เสร็จ

systemd ป้องกันให้อยู่แล้ว service หนึ่งตัวจะไม่ถูกสั่งเริ่มซ้ำขณะที่ยังทำงานอยู่ ต่างจาก Cron ที่ยิงซ้ำได้เรื่อย ๆ หากอยากให้รอบที่ทับกันถูกยกเลิกไปเลย ให้ดูที่ Log ว่ามีข้อความ start request repeated too quickly หรือไม่

อยากรันหลังบูตเครื่องแทนเวลาที่กำหนด

ใช้ OnBootSec=5min แทน OnCalendar หรือใส่ทั้งสองบรรทัดพร้อมกันได้ เช่น รันหลังบูต 5 นาที และรันทุกวันตอนตีสาม

หากต้องการให้ทีมงานช่วยวางรอบงานสำรองข้อมูลให้เหมาะกับระบบของคุณ ติดต่อ THAI DATA CLOUD ได้ที่ https://thaidata.cloud/contact/

ผู้ให้บริการคลาวด์ไทย
เพื่อธุรกิจของคนไทย

"มุ่งมั่น" และ "มั่นคง"
พร้อมรับมือทุกการเติบโต
Trust Cloud
คลาว์ที่ปลอดภัย
คือรากฐานที่มั่นคง
cloud security