EQVPS

Cách lên lịch tác vụ trên VPS với cron và timer của systemd

Chạy sao lưu, dọn dẹp và script theo lịch. Cron trong năm phút cùng ba cái bẫy âm thầm làm hỏng tác vụ, sau đó là timer của systemd cho log, chạy bù sau khi máy chủ tắt và thời gian chính xác. Mọi hành vi dưới đây đã được thử trên máy chủ Ubuntu 24.04 mới.

Một máy chủ đáng tiền vì nó làm việc trong lúc bạn ngủ: sao lưu, xoay vòng log, gia hạn chứng chỉ, báo cáo lúc chín giờ. Linux có hai bộ lập lịch cho việc đó. Cron đã bốn mươi tuổi và chỉ cần một dòng. Timer của systemd cần hai file nhỏ, đổi lại có log, chạy bù sau khi máy tắt và thời gian chính xác. Hướng dẫn này nói về cả hai trên Ubuntu 24.04, mọi hành vi đều được kiểm tra trên một máy chủ mới từ image của chúng tôi, kể cả ba lỗi khiến tác vụ cron hỏng mà không báo một lời.

Bạn cần một người dùng có sudo; hướng dẫn thiết lập Ubuntu sẽ tạo một người dùng như vậy.

1. Cron trong năm phút

Cron đã được cài và đang chạy trên image Ubuntu và Debian của chúng tôi. Mỗi người dùng có một bảng tác vụ; chỉnh bảng của bạn bằng:

crontab -e

Mỗi dòng gồm năm trường thời gian và một lệnh:

# min hour day month weekday  command
  30  3    *   *     *        /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
  */5 *    *   *     *        /usr/local/bin/check.sh
  0   9    *   *     1-5      /usr/local/bin/report.sh
  @reboot                     /usr/local/bin/start-tunnel.sh

Tức là: 03:30 mỗi đêm, mỗi 5 phút, 09:00 các ngày trong tuần và một lần sau mỗi lần khởi động. */5 nghĩa là cứ năm giá trị một lần, 1-5 là một khoảng, 1,15 là một danh sách. Múi giờ của máy chủ được áp dụng, là UTC trên image của chúng tôi; đặt múi giờ của bạn một lần theo hướng dẫn hostname và múi giờ và cron sẽ theo.

Xem những gì đã chạy bằng journalctl -u cron. Mỗi lần chạy ghi lại người dùng và lệnh, và đó là nơi ba cái bẫy lộ ra.

Bẫy 1: đầu ra biến mất. Cron gửi đầu ra của tác vụ qua email cho người dùng. Image của chúng tôi không có máy chủ thư, nên journal ghi (CRON) info (No MTA installed, discarding output) và đầu ra mất luôn. Hãy tự chuyển hướng nó, như ví dụ đầu tiên với >> /var/log/backup.log 2>&1.

Bẫy 2: dấu phần trăm. Trong crontab, % kết thúc lệnh và biến phần còn lại thành đầu vào chuẩn. Chúng tôi đã lên lịch date +%F > /tmp/today. Journal cho thấy cron thực sự chạy gì: CMD (date +). Không file nào được ghi và không lỗi nào đến tay chúng tôi. Hãy viết \%, hoặc đặt lệnh vào một script rồi lên lịch cho script.

Bẫy 3: môi trường khác. Cron chạy tác vụ bằng /bin/sh chứ không phải bash, trong thư mục home của bạn và với môi trường tối giản. Trên Ubuntu 24.04, PATH tốt hơn tiếng xấu của nó: đường dẫn hệ thống đầy đủ gồm cả /usr/local/bin và /snap/bin, cho cả root lẫn người dùng thường. Thứ vẫn thiếu là mọi thứ trong thư mục home: ~/.local/bin, node của nvm, pipx, cargo. Dùng đường dẫn đầy đủ trong tác vụ hoặc bắt đầu dòng bằng . $HOME/.profile;.

Tác vụ thuộc về hệ thống chứ không phải người dùng thì đặt trong /etc/cron.d/ dưới dạng file với thêm một trường là người dùng:

30 3 * * * root /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

Ubuntu cũng chạy mọi thứ trong /etc/cron.hourly, cron.daily, cron.weekly và cron.monthly, vào phút 17 mỗi giờ, lúc 06:25, 06:47 vào Chủ nhật và 06:52 ngày mùng 1. Đặt một script có quyền thực thi vào đó và nó sẽ chạy cùng các script khác.

2. Timer của systemd

Một timer gồm hai unit: một service làm việc và một timer nói khi nào. Tạo script trước, rồi tạo các unit.

sudo nano /usr/local/bin/backup.sh
sudo chmod +x /usr/local/bin/backup.sh
sudo nano /etc/systemd/system/backup.service
[Unit]
Description=Nightly backup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
sudo nano /etc/systemd/system/backup.timer
[Unit]
Description=Run the backup every night at 03:30

[Timer]
OnCalendar=*-*-* 03:30
Persistent=true
AccuracySec=1m

[Install]
WantedBy=timers.target

Bật timer, không phải service:

sudo systemctl daemon-reload
sudo systemctl enable --now backup.timer
systemctl list-timers backup.timer

list-timers hiển thị lần chạy kế tiếp, còn bao lâu và lần chạy gần nhất. Log không cần thiết lập: journalctl -u backup.service có mọi lần bắt đầu, kết thúc và từng dòng đầu ra kèm thời gian. Để chạy tác vụ ngay, dùng sudo systemctl start backup.service; đó cũng là cách thử script trước khi tin vào lịch.

Cú pháp lịch. OnCalendar đọc là thứ năm-tháng-ngày giờ:phút:giây, với * cho mọi giá trị. Thử một biểu thức trước khi dùng:

systemd-analyze calendar "Mon..Fri *-*-* 03:00" --iterations 2

Lệnh in ra dạng chuẩn hóa và các ngày kế tiếp. Một số biểu thức chúng tôi đã kiểm tra:

OnCalendarÝ nghĩa
*-*-* 03:30Mỗi ngày lúc 03:30
Mon..Fri *-*-* 09:00Các ngày trong tuần lúc 09:00
*:0/15Mỗi 15 phút
weeklyThứ Hai lúc 00:00
*-*-01 04:00Ngày 1 mỗi tháng lúc 04:00
*-*-* 03:00 Europe/Kyiv03:00 giờ Kyiv; chạy lúc 01:00 UTC trong thử nghiệm của chúng tôi

Ba thiết lập nên biết. Persistent=true lưu lần chạy gần nhất lên đĩa và chạy bù lần bị lỡ khi máy chủ tắt hoặc timer bị dừng. Chúng tôi dừng timer hai phút một lần, chờ qua một lần chạy theo lịch rồi bật lại: lần bị lỡ chạy ngay trong cùng giây đó. Không có Persistent, nó chỉ chờ đến lượt kế tiếp.

AccuracySec là điều bất ngờ. Mặc định systemd gom timer trong cửa sổ một phút để giảm số lần đánh thức, nên lần chạy đầu của chúng tôi đến lúc 07:42:18 chứ không phải 07:42:00. Với AccuracySec=1s, các lần sau rơi đúng giây vào 07:46:00 và 07:48:00. Hãy đặt khi phút là quan trọng; để mặc định cho bản sao lưu ban đêm.

RandomizedDelaySec=10m cố ý làm ngược lại: rải thời điểm bắt đầu trong một cửa sổ để mười máy chủ không cùng đập vào một kho sao lưu trong cùng một giây.

Với tác vụ chạy một lần, không cần file nào:

sudo systemd-run --on-active=30m --unit=cleanup-once /usr/local/bin/cleanup.sh

Lệnh này tạo một timer tạm thời chạy một lần rồi biến mất; trước lúc đó systemctl list-timers sẽ hiển thị nó.

3. Nên dùng cái nào

CronTimer systemd
Thiết lập1 dòng2 file
LogChỉ khi bạn chuyển hướng đầu rajournalctl -u name.service
Lần chạy bị lỡ khi máy chủ tắtMấtChạy khi khởi động với Persistent=true
Tác vụ vẫn đang chạy ở nhịp kế tiếpMột bản sao thứ hai khởi độngBỏ qua đến khi bản đầu xong
Thời gian chính xácĐến phútĐến giây với AccuracySec=1s
Trạng tháiKhông cólist-timers, systemctl status

Dùng cron cho một dòng nhanh mà một năm sau bạn vẫn nhận ra. Dùng timer cho mọi thứ mà bạn cần thấy khi nó hỏng, không được chồng lấn, hoặc vẫn phải chạy sau một lần khởi động lại bị lỡ: sao lưu, gia hạn chứng chỉ, dọn dẹp cơ sở dữ liệu. Nếu tác vụ cron có thể chồng lấn, hãy bọc nó: flock -n /tmp/backup.lock /usr/local/bin/backup.sh, và bản sao thứ hai sẽ thoát ngay.

Cả hai bộ lập lịch đều chạy một tác vụ rồi dừng. Với chương trình phải chạy suốt ngày đêm và tự lên lại sau sự cố, bạn cần một service chứ không phải lịch; xem cách tạo service systemd và với Windows là cách chạy script 24/7 trên Windows Server. Một timer đầu tiên tốt là timer trong hướng dẫn sao lưu bằng restic của chúng tôi; cập nhật bảo mật tự động là timer Ubuntu đã có sẵn, apt-daily-upgrade.timer, một trong 17 timer chạy trên máy chủ mới.

FAQ

Vì sao tác vụ chạy được trong terminal nhưng không chạy trong cron?

Ba lý do thường gặp. Cron chạy tác vụ bằng sh chứ không phải bash, nên cú pháp chỉ có ở bash sẽ lỗi. Công cụ cài trong thư mục home như nvm, pipx hay cargo không có trong PATH của cron, nên hãy dùng đường dẫn đầy đủ. Và mọi dấu % trong dòng lệnh đều kết thúc lệnh: date +%F thành date + trên máy chủ của chúng tôi. Hãy viết \% hoặc chuyển lệnh vào một script.

Đầu ra của cron đi đâu?

Mặc định là gửi email. Image của chúng tôi không có máy chủ thư, nên cron ghi 'No MTA installed, discarding output' và đầu ra mất luôn. Thêm >> /var/log/myjob.log 2>&1 vào tác vụ, hoặc chuyển sang timer systemd, đầu ra của nó tự vào journal.

Timer systemd hơn cron ở điểm nào?

Log trong journalctl, không có bản sao thứ hai của tác vụ còn đang chạy, chạy bù với Persistent=true những lần bị lỡ khi máy chủ tắt, và một dòng trạng thái cho biết lần chạy kế tiếp và lần gần nhất. Cron thắng về sự ngắn gọn: một dòng thay vì hai file.

Timer của tôi chạy trễ 18 giây. Vì sao?

Mặc định systemd gom các timer trong một cửa sổ một phút để giảm số lần đánh thức hệ thống, đó là thiết lập AccuracySec. Timer hai phút một lần của chúng tôi chạy lúc 07:42:18 thay vì 07:42:00. Với AccuracySec=1s nó chạy đúng 07:46:00. Hãy đặt khi phút chính xác là quan trọng.

Cron và timer dùng múi giờ nào?

Múi giờ của máy chủ, trên image của chúng tôi là UTC. Đặt một lần bằng timedatectl, xem hướng dẫn về hostname và múi giờ của chúng tôi, rồi cả cron lẫn timer đều theo. Timer cũng có thể mang múi giờ riêng: OnCalendar=*-*-* 03:00 Europe/Kyiv đã chạy lúc 01:00 UTC trong thử nghiệm của chúng tôi.

Bình luận

Chưa có bình luận nào. Hãy là người đầu tiên.

Để lại bình luận

Bình luận được kiểm duyệt trước khi hiển thị.