Sổ tay kỹ thuật

Viết systemd service và timer thay cron

how-to#linux#systemd· cập nhật 2026-11-06

Dùng khi cần chạy một app/script như service tự khởi động lại, đọc env từ file, hoặc lập lịch bằng timer thay cron.

Sửa trang này
Mục lục

Tóm tắt: Đặt unit tự viết ở /etc/systemd/system/, chạy dưới user riêng, Restart=on-failure, env đọc từ EnvironmentFile=. Việc định kỳ dùng cặp .service (Type=oneshot) + .timer (OnCalendar=, Persistent=true). Sau mỗi lần sửa file: systemctl daemon-reload.

Luồng hoạt động

flowchart LR A["/etc/systemd/system/netcollector.service"] --> B["systemctl daemon-reload"] B --> C["systemctl enable --now netcollector"] C --> D{Process thoát?} D -->|"exit != 0, Restart=on-failure"| E["chờ RestartSec rồi chạy lại"] E --> D D -->|"quá StartLimitBurst"| F["failed"] G["config-backup.timer"] -->|OnCalendar| H["config-backup.service (oneshot)"]

Cấu hình

Service chạy lâu dài

/etc/systemd/system/netcollector.service
[Unit]
Description=Net collector (gNMI -> InfluxDB)
Documentation=https://example.com/netcollector
# Chờ mạng thật sự có IP, không chỉ "network.target"
Wants=network-online.target
After=network-online.target
# Restart quá 5 lần trong 5 phút thì dừng hẳn (state failed)
StartLimitIntervalSec=300
StartLimitBurst=5
 
[Service]
Type=exec
User=netcollector
Group=netcollector
WorkingDirectory=/opt/netcollector
# Dấu "-" = file không tồn tại thì bỏ qua, không lỗi
EnvironmentFile=-/etc/default/netcollector
Environment=LOG_LEVEL=info
ExecStart=/opt/netcollector/.venv/bin/python -m netcollector --config /etc/netcollector/config.yml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
# Giới hạn tài nguyên (cgroups v2)
MemoryMax=512M
CPUQuota=50%
# Hardening cơ bản
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
StateDirectory=netcollector
LogsDirectory=netcollector
 
[Install]
WantedBy=multi-user.target
/etc/default/netcollector
# KEY=VALUE, không có "export"; quyền 0600, owner root
INFLUX_URL=http://127.0.0.1:8086
INFLUX_TOKEN=***
Directive Ý nghĩa
Wants= / Requires= Kéo unit khác lên cùng; Requires thì unit kia fail sẽ kéo theo unit này. Không quy định thứ tự
After= / Before= Chỉ quy định thứ tự khởi động, thường dùng kèm Wants=
Type=simple / exec Process chính chính là service; exec báo lỗi ngay nếu binary không chạy được
Type=forking Daemon kiểu cũ tự fork, nên kèm PIDFile=
Type=notify App tự báo sẵn sàng qua sd_notify
Type=oneshot Chạy xong thoát, dùng cho job/timer
Restart= no, on-failure, on-abnormal, always... on-failure hợp lý cho đa số app
StateDirectory= Tạo /var/lib/<tên> thuộc User=, ghi được dù ProtectSystem=strict

Ghi đè không sửa file gốc (drop-in)

sudo systemctl edit nginx          # tạo /etc/systemd/system/nginx.service.d/override.conf
systemctl cat nginx                # xem unit gốc + mọi drop-in
/etc/systemd/system/nginx.service.d/override.conf
[Service]
Restart=on-failure
RestartSec=3
# Với list như ExecStart= phải reset bằng dòng rỗng trước khi gán lại
ExecStart=
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'

Timer thay cron

/etc/systemd/system/config-backup.service
[Unit]
Description=Backup config thiết bị mạng
Wants=network-online.target
After=network-online.target
 
[Service]
Type=oneshot
User=netbackup
EnvironmentFile=/etc/default/config-backup
ExecStart=/opt/netbackup/run.sh
/etc/systemd/system/config-backup.timer
[Unit]
Description=Chạy config-backup 02:00 thứ Hai đến thứ Sáu
 
[Timer]
OnCalendar=Mon..Fri *-*-* 02:00:00
# Máy tắt lúc đến giờ thì chạy bù khi bật lại
Persistent=true
RandomizedDelaySec=5min
 
[Install]
WantedBy=timers.target
Cron OnCalendar=
*/15 * * * * *:0/15
0 * * * * hourly
0 0 * * * daily
0 2 * * 1 Mon *-*-* 02:00:00
30 8 1 * * *-*-01 08:30:00
Sau boot 5 phút, rồi mỗi giờ OnBootSec=5min + OnUnitActiveSec=1h (không dùng OnCalendar)

Lệnh

systemd-analyze verify /etc/systemd/system/netcollector.service   # bắt lỗi cú pháp
systemd-analyze calendar "Mon..Fri *-*-* 02:00"                   # xem lần chạy kế tiếp
sudo systemctl daemon-reload
sudo systemctl enable --now netcollector.service
sudo systemctl enable --now config-backup.timer
systemctl status netcollector
systemctl list-timers --all
sudo systemctl start config-backup.service    # chạy job ngay để thử
journalctl -u netcollector -f
systemctl show netcollector -p NRestarts -p MainPID -p ActiveState
sudo systemctl reset-failed netcollector      # xoá trạng thái failed/start-limit
systemd-analyze security netcollector         # chấm điểm hardening

Lỗi thường gặp

Triệu chứng Nguyên nhân Cách xử lý
Sửa unit mà không có tác dụng Chưa reload systemctl daemon-reload rồi restart
start request repeated too quickly Vượt StartLimitBurst trong StartLimitIntervalSec Sửa lỗi app, systemctl reset-failed <unit>; đặt StartLimit* trong [Unit]
status=203/EXEC Sai đường dẫn ExecStart, thiếu quyền thực thi, sai shebang Dùng đường dẫn tuyệt đối, chmod +x, kiểm tra #!
status=217/USER User= không tồn tại useradd --system --no-create-home <user>
Biến $VAR/pipe/> trong ExecStart không chạy như shell ExecStart không qua shell Gọi /bin/bash -c '...' hoặc chuyển vào script
App lỗi kết nối lúc boot Chỉ có After=network.target Wants= + After=network-online.target
Env không được đọc File có export KEY=... hoặc sai đường dẫn Bỏ export; kiểm tra systemctl show <unit> -p Environment (không hiện biến từ file) hoặc cat /proc/<pid>/environ
Timer không chạy Enable .service thay vì .timer systemctl enable --now <tên>.timer, kiểm tra list-timers
Ghi file báo Read-only file system ProtectSystem=strict Thêm ReadWritePaths= hoặc StateDirectory=

Tham khảo