AI Automation

Lập lịch tác vụ nâng cao với cron và systemd timer

Bạn có một script backup database cần chạy mỗi 6 tiếng, nhưng hôm qua nó không chạy vì máy tắt? Hay bạn cần một job chạy đúng 1 phút sau khi máy khởi động xong, nhưng cron không làm được? Đây là lúc bạn cần hiểu sâu hơn về lập lịch tác vụ trên Linux, không chỉ dừng ở crontab 5 cột, mà còn biết đến systemd timer: công cụ mạnh hơn, linh hoạt hơn, và tích hợp chặt với systemd. Bài này dành cho sysadmin đã biết cron cơ bản, muốn nâng cấp workflow lập lịch, đồng thời hiểu khi nào dùng cron, khi nào dùng timer. Tất cả đều chạy trên VPS Ubuntu 24.04 LTS.

Yêu cầu trước khi bắt đầu

  • Một VPS chạy VPS Linux Ubuntu 24.04 LTS, đã có quyền root hoặc sudo.
  • Hiểu cơ bản về crontab (cú pháp 5 trường * * * * *).
  • Biết cách dùng systemctl để quản lý service.
  • Một script đơn giản để kiểm tra: /usr/local/bin/backup.sh (có thể chỉ là #!/bin/bash date >> /var/log/test.log).
  • Firewall (UFW) đã mở port SSH, không cần port nào khác.

Vì sao bạn cần cron nâng cao và systemd timer?

Hầu hết sysadmin đều bắt đầu với crontab -e. Nó đơn giản, dễ dùng. Nhưng cron có 3 nhược điểm chính:

  • Không biết máy tính có chạy hay không: Cron không đảm bảo job chạy nếu máy tắt đúng lúc (ví dụ: backup 2h sáng, máy reboot lúc 1h45, mất lịch).
  • Log và debug khó: Mặc định cron ghi log vào syslog, nhưng khi job chạy xong không có output, bạn không biết nó thành công hay thất bại. Phải chủ động redirect output để theo dõi.
  • Thiếu kiểm soát phụ thuộc: Cron không biết job trước có chạy xong không, không đợi nguồn (network, database) sẵn sàng.

Systemd timer giải quyết các điểm này: nó là một phần của systemd, có thể yêu cầu service khác, chạy sau khi máy boot (với OnStartupSec=), và ghi log vào journald, rất dễ debug bằng journalctl. Nhưng cron vẫn tốt cho các job đơn giản, không cần phức tạp. Cả hai đều có chỗ đứng.

Bước 1 - Viết crontab nâng cao: redirect log, biến môi trường, và xử lý lỗi

Cron cơ bản chỉ là dòng: 0 3 * * * /path/to/script.sh. Nếu script có lỗi, bạn không biết. Cách viết nâng cao là chủ động ghi log và xử lý lỗi ngay trong crontab.

# Định nghĩa biến môi trường (cách dòng)
MAILTO=""
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# Chạy script mỗi ngày lúc 3h sáng, ghi output ra file log riêng
0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

# Chạy script kiểm tra disk mỗi giờ, chỉ ghi khi có lỗi
0 * * * * /usr/local/bin/check-disk.sh 2>> /var/log/disk-error.log

Lưu ý quan trọng: cron không load .bashrc hay .profile. Nếu script của bạn dùng command không có trong PATH mặc định (ví dụ docker, node, pm2), bạn phải khai báo PATH đầy đủ ở đầu crontab hoặc dùng đường dẫn rất cao trong script. Ví dụ: thay vì ghi docker ps, ghi /usr/bin/docker ps.

Verify: Sau khi chỉnh sửa, kiểm tra log để biết cron đã chạy chưa:

grep CRON /var/log/syslog | tail -5
# Output mẫu: May 28 03:00:01 vps CRON[12345]: (root) CMD (/usr/local/bin/backup.sh > ...)

Bước 2 - Tạo systemd service để cron không làm được

Systemd timer chạy một systemd service. Trước hết, bạn cần tạo unit file cho service. Giả sử script backup của bạn là /usr/local/bin/backup.sh.

Tạo file /etc/systemd/system/backup.service:

[Unit]
Description=Backup database service
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
StandardOutput=journal
StandardError=journal
User=root

[Install]
WantedBy=multi-user.target

Giải thích: Type=oneshot nghĩa là service chạy một lần rồi kết thúc, chuẩn cho job định kỳ. After=network-online.target đảm bảo service chỉ chạy khi network đã sẵn sàng, khác với cron, cron hiếm khi đợi network. StandardOutput=journal giúp ghi log vào journald, dễ debug.

Nạp và thử chạy service ngay:

systemctl daemon-reload
systemctl start backup.service
systemctl status backup.service
# Output mẫu: Active: active (exited) since ... (chạy xong)
# Hoặc: Failed => xem journal

Nếu service fail, bạn đã có sẵn log ở bước sau.

Bước 3 - Tạo systemd timer cho service vừa tạo

Timer là file cùng tên với service, đuôi .timer. Tạo /etc/systemd/system/backup.timer:

[Unit]
Description=Run backup every 6 hours
Requires=backup.service

[Timer]
OnCalendar=*-*-* 00/6:00:00
RandomizedDelaySec=300
Persistent=true

[Install]
WantedBy=timers.target

Giải thích các directive:

  • OnCalendar=*-*-* 00/6:00:00: chạy mỗi 6 tiếng, bắt đầu từ nửa đêm. Cú pháp calendar tương tự cron nhưng linh hoạt hơn (có thể ghi Mon..Fri 10:00, 2026-12-31 23:59).
  • RandomizedDelaySec=300: trì hoãn ngẫu nhiên 0-5 phút. Cực kỳ hữu ích khi hàng trăm server làm cùng lúc, tránh dồn tải.
  • Persistent=true: quan trọng. Nếu máy tắt đúng lúc job lẽ ra phải chạy, timer sẽ chạy ngay sau khi boot. Cron KHÔNG làm được điều này.

Kích hoạt timer:

systemctl daemon-reload
systemctl start backup.timer
systemctl enable backup.timer
systemctl list-timers --all | grep backup
# Output mẫu: NEXT                         LEFT     LAST PASSED UNIT
#             Wed 2026-05-28 06:00:00 UTC 2h ago  n/a  n/a    backup.timer

Dòng PASSED n/a hoặc có số là do Persistent=true, nếu job đã missed, timer sẽ chạy ngay.

Bước 4 - So sánh: khi nào dùng cron, khi nào dùng systemd timer?

Tiêu chíCronSystemd timer
Độ đơn giảnCao: chỉ cần 1 dòng trong crontabTrung bình: cần 2 file (service + timer)
LogPhải chủ động redirect với >>2>&1Tự động ghi vào journald, xem bằng journalctl -u backup.service
Xử lý nguồn phụ thuộcKhôngCó: After=, Wants=, Requires=
Chạy sau boot nếu lỡ lịchKhôngCó: Persistent=true
Trì hoãn ngẫu nhiênKhôngCó: RandomizedDelaySec=
Kiểm soát tài nguyênKhôngCó: CPUQuota=, MemoryMax= trong service
Phù hợpJob đơn giản, dev, user lẻJob hệ thống, sản xuất, cần độ tin cậy cao

Mẹo: Nếu bạn đã dùng systemd để quản lý service của mình (ví dụ Nginx, PostgreSQL, hay workflow automation trên VPS n8n), thì việc dùng systemd timer là bước tự nhiên, bạn đã có sẵn unit file, chỉ cần thêm timer.

Bước 5 - Debug lỗi thường gặp với cron và timer

Lỗi 1: Cron không chạy, không thấy log trong syslog
Nguyên nhân: cron daemon chết hoặc không chạy. Kiểm tra:

systemctl status cron
# Nếu inactive => systemctl start cron && systemctl enable cron
# Kiểm tra log: journalctl -u cron -n 20

Lỗi 2: Script chạy bằng cron nhưng không ra kết quả (output trống)
Nguyên nhân thường gặp: thiếu PATH hoặc môi trường (environment). Cách debug nhanh: ghi toàn bộ môi trường vào log.

# Trong crontab, thay dòng script bằng:
* * * * * env > /tmp/cron-env.log 2>&1 && /usr/local/bin/backup.sh >> /tmp/backup-debug.log 2>&1
# Sau đó so sánh env với môi trường khi bạn login SSH.

Lỗi 3: Timer báo "Unit backup.service not loaded"
Nguyên nhân: bạn enable timer nhưng service chưa enable, đơn giản là không cần enable service (systemctl enable backup.service). Vì timer sẽ start service mỗi lần chạy. Nhưng file backup.service phải tồn tại và không lỗi. Kiểm tra:

systemctl list-unit-files | grep backup.service
# Phải thấy: backup.service   static
# Nếu disabled hoặc không tìm thấy => kiểm tra lại đường dẫn và syntax file.

Lỗi 4: Timer chạy nhưng service báo Failed
Xem log của service:

journalctl -u backup.service -n 20 --no-pager
# Dòng red flag: Process: 12345 ExecStart=/usr/local/bin/backup.sh (code=exited, status=1/FAILURE)

Sau đó sửa script hoặc kiểm tra quyền thực thi (chmod +x /usr/local/bin/backup.sh).

Câu hỏi thường gặp

Sự khác nhau chính giữa cron và systemd timer là gì?

Cron đơn giản, chỉ lập lịch dựa trên thời gian, không xử lý được máy tắt, không quản lý phụ thuộc. Systemd timer là extension của systemd, có thể yêu cầu network sẵn sàng, chạy lại job bị lỡ khi boot, kiểm soát tài nguyên, và ghi log vào journald.

Có nên thay thế hoàn toàn cron bằng systemd timer không?

Không. Cron vẫn tốt cho job đơn giản, cá nhân, không cần độ tin cậy cao. Systemd timer hợp cho job hệ thống quan trọng (backup, cleanup, automation production) cần Persistent và log tập trung. Cả hai nên dùng song song.

Làm sao để systemd timer chạy ngay sau khi máy khởi động?

Dùng OnBootSec= trong timer: OnBootSec=10min sẽ chạy 10 phút sau khi boot. Kết hợp với OnCalendar= để có cả hai: một lần sau boot, và định kỳ sau đó.

Systemd timer có hỗ trợ chạy mỗi thứ Hai không?

Có. Dùng OnCalendar=Mon *-*-* 03:00:00 để chạy 3h sáng thứ Hai hàng tuần. Cú pháp chi tiết xem man systemd.time.

Làm sao xóa một systemd timer?

systemctl stop backup.timer && systemctl disable backup.timer && rm /etc/systemd/system/backup.timer && systemctl daemon-reload. Xóa cả service nếu không còn dùng nữa.

Bài viết liên quan

Lưu ý: Bài viết mang tính tham khảo, tổng hợp kiến thức chung. Mỗi hệ thống, hạ tầng và nhu cầu có đặc thù riêng, nên kiểm thử trong môi trường an toàn và tham vấn kỹ sư trước khi triển khai thực tế.