メインコンテンツまでスキップ

Cron & Scheduling — Tự động hoá tác vụ định kỳ

Là kỹ sư vận hành, bạn sẽ gặp hàng ngày những việc phải chạy định kỳ: backup lúc 2 giờ sáng, dọn dẹp log hàng tuần, gửi báo cáo mỗi sáng, kiểm tra health check mỗi 5 phút. Linux có 3 cơ chế chính cho việc này: cron, systemd timers, và at/batch.


Tổng quan các cơ chế Scheduling


Cron — Cú pháp cơ bản

Anatomy của một cron entry

┌───────────── minute (0–59)
│ ┌─────────── hour (0–23)
│ │ ┌───────── day of month (1–31)
│ │ │ ┌─────── month (1–12 hoặc jan–dec)
│ │ │ │ ┌───── day of week (0–7, 0 và 7 là Sunday)
│ │ │ │ │
│ │ │ │ │
* * * * * command_to_execute

Các ký tự đặc biệt

Ký tựÝ nghĩaVí dụ
*Mọi giá trị* * * * * = mỗi phút
,Liệt kê1,15,30 * * * * = phút 1, 15, 30
-Khoảng1-5 * * * * = phút 1 đến 5
/Bước nhảy*/5 * * * * = mỗi 5 phút
@rebootKhi khởi động@reboot /opt/start.sh
@hourlyMỗi giờtương đương 0 * * * *
@dailyHàng ngàytương đương 0 0 * * *
@weeklyHàng tuầntương đương 0 0 * * 0
@monthlyHàng thángtương đương 0 0 1 * *

Ví dụ thực tế

# Mỗi 5 phút
*/5 * * * * /opt/check_health.sh

# Hàng ngày lúc 2:30 AM (backup)
30 2 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

# Mỗi thứ Hai lúc 9:00 AM
0 9 * * 1 /opt/scripts/weekly_report.sh

# Ngày 1 mỗi tháng lúc nửa đêm (cleanup)
0 0 1 * * /opt/scripts/monthly_cleanup.sh

# Thứ 2–6 (workdays) lúc 8:00 AM
0 8 * * 1-5 /opt/scripts/daily_standup.sh

# Lúc khởi động server
@reboot /opt/scripts/init_app.sh

# Mỗi giờ chẵn (0h, 2h, 4h...) lúc 30 phút
30 */2 * * * /opt/scripts/sync_data.sh

# Ba lần mỗi ngày (8h, 14h, 20h)
0 8,14,20 * * * /opt/scripts/send_report.sh
Công cụ tạo cron expression

Dùng crontab.guru để tạo và kiểm tra cron expression trước khi deploy.


crontab — Quản lý Cron Jobs

Các lệnh cơ bản

crontab -e              # mở editor để sửa cron của user hiện tại
crontab -l # liệt kê tất cả cron jobs hiện tại
crontab -r # XOÁ toàn bộ crontab ⚠️ không hỏi lại
crontab -i -r # xoá với xác nhận (an toàn hơn)

# Quản lý cron của user khác (cần root)
sudo crontab -u nginx -e
sudo crontab -u deployer -l

Vị trí file cron

# Per-user crontabs
/var/spool/cron/crontabs/sontn # Ubuntu/Debian
/var/spool/cron/sontn # RHEL/CentOS

# System-wide cron
/etc/crontab # system crontab (có thêm cột username)
/etc/cron.d/ # drop-in files (cron từ packages)
/etc/cron.hourly/ # scripts chạy mỗi giờ
/etc/cron.daily/ # scripts chạy hàng ngày
/etc/cron.weekly/ # scripts chạy hàng tuần
/etc/cron.monthly/ # scripts chạy hàng tháng

/etc/cron.d/ — System Cron

File trong /etc/cron.d/ có thêm cột username:

# /etc/cron.d/app-backup
# ┌─── minute ┌─── hour ┌─── dom ┌─── month ┌─── dow ← time fields
# │ │ │ │ │
# │ │ │ │ │ ┌── user
30 2 * * * root /opt/backup.sh

# Cài đặt thực tế cho ứng dụng
# /etc/cron.d/myapp
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# Backup database mỗi đêm
0 2 * * * postgres pg_dump myapp > /backup/myapp_$(date +\%Y\%m\%d).sql

# Dọn dẹp session files
15 */6 * * * www-data find /var/lib/app/sessions -mtime +1 -delete

Output và Logging

# Bỏ output hoàn toàn
*/5 * * * * /opt/check.sh > /dev/null 2>&1

# Chỉ lưu stdout
0 2 * * * /opt/backup.sh > /var/log/backup.log

# Lưu cả stdout và stderr, append (không ghi đè)
0 2 * * * /opt/backup.sh >> /var/log/backup.log 2>&1

# Lưu stdout và stderr vào file khác nhau
0 2 * * * /opt/backup.sh >> /var/log/backup.log 2>> /var/log/backup_err.log

# Tắt email (dùng MAILTO)
MAILTO=""
0 2 * * * /opt/backup.sh >> /var/log/backup.log 2>&1

Xem log cron

# Ubuntu/Debian
grep CRON /var/log/syslog
grep CRON /var/log/syslog | tail -20

# RHEL/CentOS
grep CRON /var/log/cron
journalctl -u cron --since "1 hour ago"

# Kiểm tra cron daemon
systemctl status cron # Ubuntu
systemctl status crond # RHEL/CentOS

Debugging Cron Jobs

Cron không chạy đúng là vấn đề hay gặp — nguyên nhân thường là:

# 1. Test script trực tiếp trước
bash -x /opt/scripts/backup.sh

# 2. Luôn dùng PATH đầy đủ trong cron
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 2 * * * /opt/scripts/backup.sh

# HOẶC dùng đường dẫn tuyệt đối trong script
/usr/bin/pg_dump mydb > /backup/db.sql # không dùng pg_dump

# 3. Kiểm tra cron chạy chưa
grep "backup.sh" /var/log/syslog
grep CRON /var/log/syslog | grep ERROR

# 4. Test bằng cách set thời gian gần hiện tại
# Đặt cron chạy sau 1–2 phút để test
Môi trường trong Cron

Cron chạy với minimal environment — biến HOME, LOGNAME, SHELL được set nhưng PATH rất ngắn, không có .bashrc, không có NVM, không có virtual env. Luôn dùng đường dẫn tuyệt đối hoặc khai báo PATH đầu crontab.


Biến môi trường trong Crontab

# Khai báo ở đầu crontab (trước các job)
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin
MAILTO=""
HOME=/home/sontn

# Biến tự định nghĩa
APP_DIR=/opt/myapp
LOG_DIR=/var/log/myapp

0 2 * * * $APP_DIR/backup.sh >> $LOG_DIR/backup.log 2>&1

Systemd Timers — Cron hiện đại

Systemd timers mạnh hơn cron: dependency giữa services, retry khi fail, tích hợp journald, có thể trigger theo event thay vì chỉ theo thời gian.

Tạo Systemd Timer — Ví dụ thực tế

Bước 1: Tạo service unit

# /etc/systemd/system/myapp-backup.service
[Unit]
Description=MyApp Database Backup
After=network.target postgresql.service

[Service]
Type=oneshot
User=postgres
ExecStart=/opt/scripts/backup.sh
StandardOutput=journal
StandardError=journal

Bước 2: Tạo timer unit

# /etc/systemd/system/myapp-backup.timer
[Unit]
Description=Chạy MyApp backup hàng đêm
Requires=myapp-backup.service

[Timer]
OnCalendar=*-*-* 02:30:00 # hàng ngày lúc 2:30 AM
RandomizedDelaySec=10min # thêm random delay để tránh thundering herd
Persistent=true # chạy bù nếu bị miss (vd: server tắt)

[Install]
WantedBy=timers.target

Bước 3: Kích hoạt

sudo systemctl daemon-reload
sudo systemctl enable --now myapp-backup.timer # enable + start ngay
sudo systemctl status myapp-backup.timer
sudo systemctl list-timers --all # xem tất cả timers

OnCalendar — Cú pháp thời gian

# Dạng đầy đủ: DayOfWeek Year-Month-Day Hour:Minute:Second
OnCalendar=Mon *-*-* 09:00:00 # Mỗi thứ Hai 9h
OnCalendar=daily # Hàng ngày (= *-*-* 00:00:00)
OnCalendar=weekly # Hàng tuần (Thứ Hai 0h)
OnCalendar=monthly # Ngày 1 mỗi tháng
OnCalendar=hourly # Mỗi giờ

# Kiểm tra ngay ngày chạy tiếp theo
systemd-analyze calendar "Mon *-*-* 09:00:00"

Quản lý Timers

systemctl list-timers                     # timers đang active
systemctl list-timers --all # cả disabled
systemctl start myapp-backup.timer # start
systemctl stop myapp-backup.timer # stop
systemctl enable myapp-backup.timer # enable khi boot
systemctl disable myapp-backup.timer # disable

# Xem log của timer/service
journalctl -u myapp-backup.service # log của service
journalctl -u myapp-backup.service -f # follow log realtime
journalctl -u myapp-backup.timer # log của timer

So sánh cron vs systemd timers

Tiêu chícronsystemd timer
Cú phápĐơn giảnVerbose hơn
LoggingCần redirect thủ côngTự động vào journald
DependenciesKhông cóCó thể depends on service
Missed runsKhông bắt kịpPersistent=true tự chạy bù
User scopeDễ dàng per-userCần --user flag
MonitoringKhósystemctl list-timers
Retry khi failKhôngCó thể cấu hình Restart=

at và batch — Chạy một lần

at dùng khi bạn cần chạy một lệnh một lần vào thời điểm cụ thể trong tương lai, không lặp lại.

# Cài đặt
sudo apt install at # Ubuntu
sudo dnf install at # RHEL

# Cú pháp
at <time> # mở interactive prompt, Ctrl+D để submit
at <time> -f script.sh # chạy script

# Ví dụ thời gian
at now + 5 minutes # 5 phút nữa
at now + 2 hours # 2 tiếng nữa
at 14:30 # lúc 14:30 hôm nay
at 14:30 tomorrow # 14:30 ngày mai
at noon friday # buổi trưa thứ Sáu
at 2pm + 3 days # 14h sau 3 ngày nữa
at midnight # nửa đêm hôm nay
# Ví dụ thực tế — schedule deployment
at now + 30 minutes << 'EOF'
cd /opt/myapp
git pull origin main
systemctl restart myapp
echo "Deployment done at $(date)" >> /var/log/deploy.log
EOF

# Quản lý at jobs
atq # liệt kê jobs đang pending
at -l # tương đương atq
atrm 3 # xoá job số 3
at -c 3 # xem nội dung job số 3

batch — Chạy khi CPU nhàn rỗi

batch tương tự at now nhưng chỉ chạy khi load average < 1.5 (server không bận):

batch << 'EOF'
/opt/scripts/heavy_report.sh > /var/log/report.log 2>&1
EOF

Use Cases thực tế trong DevOps

1. Backup Database hàng đêm

# /etc/cron.d/postgres-backup
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""

# Backup PostgreSQL lúc 1:00 AM, giữ 7 ngày
0 1 * * * postgres /opt/scripts/pg_backup.sh >> /var/log/pg_backup.log 2>&1

# /opt/scripts/pg_backup.sh
#!/bin/bash
set -e
BACKUP_DIR="/backup/postgres"
DATE=$(date +%Y%m%d_%H%M%S)
DB_NAME="myapp"

mkdir -p "$BACKUP_DIR"
pg_dump "$DB_NAME" | gzip > "$BACKUP_DIR/${DB_NAME}_${DATE}.sql.gz"

# Xoá backup cũ hơn 7 ngày
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +7 -delete

echo "Backup completed: ${DB_NAME}_${DATE}.sql.gz"

2. Log Rotation và Cleanup tự động

# /etc/cron.d/log-cleanup
MAILTO=""

# Xoá access log cũ hơn 30 ngày lúc 3 AM
0 3 * * * root find /var/log/nginx -name "*.log" -mtime +30 -delete

# Compress log hôm qua
5 3 * * * root find /var/log/app -name "*.log.1" -exec gzip {} \;

# Báo cáo dung lượng disk mỗi sáng
0 8 * * * root df -h | mail -s "Disk Report $(hostname)" admin@company.com

3. Health Check và Auto-Restart

# Check service mỗi 5 phút và restart nếu cần
*/5 * * * * root /opt/scripts/health_check.sh

# /opt/scripts/health_check.sh
#!/bin/bash
SERVICE="nginx"
URL="http://localhost/health"

if ! systemctl is-active --quiet "$SERVICE"; then
echo "$(date): $SERVICE down, restarting..." >> /var/log/health_check.log
systemctl restart "$SERVICE"
fi

# Check HTTP endpoint
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "$URL" --max-time 5)
if [ "$HTTP_CODE" != "200" ]; then
echo "$(date): Health check failed (HTTP $HTTP_CODE)" >> /var/log/health_check.log
systemctl restart "$SERVICE"
fi

4. Sync dữ liệu với S3

# Sync static assets lên S3 mỗi giờ
0 * * * * deployer /usr/local/bin/aws s3 sync /var/www/static s3://mybucket/static \
--delete >> /var/log/s3_sync.log 2>&1

5. Renew SSL Certificate tự động

# Let's Encrypt auto-renew (thêm vào khi cài certbot)
# /etc/cron.d/certbot
0 0,12 * * * root python -c 'import random; import time; time.sleep(random.random() * 3600)' \
&& certbot renew -q >> /var/log/letsencrypt/renew.log 2>&1

Bảo mật Cron Jobs

Best practices bảo mật
  1. Không chạy cron với root nếu không cần thiết — dùng user có quyền tối thiểu
  2. Không đặt credentials trong crontab — dùng file riêng với chmod 600
  3. Validate input nếu cron job đọc từ file/API bên ngoài
  4. Kiểm tra /etc/cron.deny và /etc/cron.allow để giới hạn ai được dùng cron
# /etc/cron.allow — chỉ user trong file này mới dùng cron
# /etc/cron.deny — user trong file này bị chặn dùng cron

# Giới hạn cron chỉ cho root và deployer
echo "root" > /etc/cron.allow
echo "deployer" >> /etc/cron.allow

# Kiểm tra file permission
ls -la /etc/cron.d/
# Đúng: -rw-r--r-- root root (644)
# Script được gọi phải có x bit
chmod 750 /opt/scripts/backup.sh
chown root:root /opt/scripts/backup.sh

Tổng kết — Chọn công cụ nào?

Tình huốngCông cụ
Backup DB hàng đêm, đơn giảncron
Service restart khi fail, cần dependencysystemd timer
Deploy sau 30 phút một lầnat
Report khi server nhàn rỗibatch
Container (Docker, K8s cronjob)Cron syntax trong K8s
Production server phức tạpsystemd timer