Hướng dẫn quản lý tiến trình với systemd trên Linux: Tips thực chiến

Linux tutorial - IT technology blog
Linux tutorial - IT technology blog

Làm ngay trong 5 phút — Các lệnh systemd bạn dùng mỗi ngày

Mới bắt đầu với Linux server? Đây là 5 lệnh bạn sẽ gõ nhiều đến mức thuộc lòng mà không cần cố ý:

# Kiểm tra trạng thái một service
systemctl status nginx

# Khởi động / dừng / khởi động lại
systemctl start nginx
systemctl stop nginx
systemctl restart nginx

# Bật / tắt tự khởi động khi reboot
systemctl enable nginx
systemctl disable nginx

# Reload cấu hình mà không restart (nếu service hỗ trợ)
systemctl reload nginx

Chạy thử ngay với nginx hoặc bất kỳ service nào đang có trên máy. Output màu xanh active (running) là ổn, màu đỏ failed là có chuyện — rất trực quan, không cần đọc docs.

Hiểu systemd hoạt động như thế nào

Ra đời để thay thế SysVinit, systemd hiện là init system mặc định trên hầu hết distro Linux: Ubuntu, Debian, CentOS/AlmaLinux, Fedora… Điểm khác biệt lớn nhất so với thế hệ cũ: khởi động song song, dependency management giữa các service, và journal log tập trung thay vì rải rác khắp /var/log/.

Mỗi thứ systemd quản lý gọi là unit. Bạn sẽ hay gặp nhất 4 loại:

  • .service — tiến trình chạy nền (nginx, mysql, sshd…)
  • .timer — tương tự cron, chạy task theo lịch
  • .socket — socket activation (chỉ khởi động service khi có kết nối đến)
  • .mount — quản lý mount điểm

File unit nằm ở 2 nơi chính:

  • /lib/systemd/system/ — do package manager cài, không nên sửa trực tiếp
  • /etc/systemd/system/ — override hoặc tạo mới, đây là chỗ bạn làm việc

Tạo systemd service tùy chỉnh cho ứng dụng của bạn

Đây là phần mình dùng nhiều nhất. Mỗi khi deploy script Python hay Node.js lên server, mình đều tạo service file — không bao giờ dùng nohup hay screen nữa.

Ví dụ: deploy một ứng dụng Python chạy ở cổng 8000:

sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=My Python App
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/myapp
ExecStart=/home/ubuntu/myapp/venv/bin/python app.py
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
# Reload để systemd nhận file mới
sudo systemctl daemon-reload

# Bật và khởi động
sudo systemctl enable --now myapp

Chú ý Restart=on-failure — service tự khởi động lại khi crash. Hồi tháng trước service của mình bị crash lúc 2h sáng, tự restart trong vòng 5 giây, và mình chỉ biết chuyện đó khi xem log sáng hôm sau. Không cần thức đêm SSH vào xử lý.

Các giá trị Restart hay dùng

  • no — không tự restart (mặc định)
  • on-failure — restart khi exit code khác 0
  • always — luôn restart, kể cả khi bạn systemctl stop thủ công
  • on-abnormal — restart khi bị kill bằng signal

Dùng on-failure cho 99% trường hợp. always nghe có vẻ an toàn nhưng sẽ tạo vòng lặp khởi động vô tận nếu app lỗi ngay khi start — systemd sẽ thử restart đến khi đạt giới hạn rồi đánh dấu failed luôn.

Debug khi service bị lỗi

Service báo failed? Đừng vội panic. Quy trình debug của mình:

# Xem trạng thái chi tiết + vài dòng log cuối
systemctl status myapp -l

# Xem toàn bộ log của service
journalctl -u myapp

# Xem log realtime (theo dõi trực tiếp)
journalctl -u myapp -f

# Chỉ xem log từ lần boot này
journalctl -u myapp -b

# 50 dòng cuối
journalctl -u myapp -n 50

journalctl tập trung tất cả log vào một chỗ, có timestamp, filter được theo unit và theo thời gian. Từ khi quen với nó, mình bỏ hẳn thói quen tail -f /var/log/....

Lỗi kinh điển nhất: sửa file .service xong mà quên chạy daemon-reload. systemd vẫn đang dùng bản cũ trong cache, mọi thay đổi của bạn không có tác dụng. Cứ mỗi lần sửa file là phải reload — không có ngoại lệ.

Nâng cao — Override service mà không sửa file gốc

Bài toán thực tế: muốn tăng giới hạn file descriptor cho nginx lên 65536 (mặc định chỉ 1024), nhưng không muốn sửa /lib/systemd/system/nginx.service vì lần sau apt upgrade sẽ ghi đè mất.

# Dùng lệnh edit — tự tạo thư mục override
sudo systemctl edit nginx

Lệnh trên mở editor và tạo file tại /etc/systemd/system/nginx.service.d/override.conf. Chỉ cần viết phần muốn thay đổi:

[Service]
LimitNOFILE=65536
Environment="EXTRA_OPTS=-g 'worker_processes 4;'"

Muốn kiểm tra kết quả cuối cùng sau khi gộp tất cả override lại:

systemctl cat nginx

Giới hạn tài nguyên với cgroups

Cgroups là tính năng ít người biết nhưng cực kỳ hữu dụng — systemd tích hợp sẵn, cho phép giới hạn CPU và RAM cho từng service:

[Service]
# Giới hạn tối đa 512MB RAM
MemoryMax=512M
# Giới hạn 50% CPU
CPUQuota=50%
# Ưu tiên CPU thấp hơn (mặc định 100)
CPUWeight=50

Trên VPS 2GB RAM chạy 5-6 service cùng lúc, đây là thứ giúp mình ngủ yên hơn — không lo một service nào đó ngốn hết tài nguyên và kéo cả hệ thống xuống theo.

systemd timer — Thay thế cron hiệu quả hơn

Hầu hết cronjob của mình đã chuyển sang systemd timer. Lý do đơn giản: debug dễ hơn nhiều. Log vào journal, xem được lần chạy cuối, lần chạy tiếp theo — không phải mò trong /var/log/syslog như với cron.

Ví dụ chạy script backup lúc 2h sáng mỗi ngày:

sudo nano /etc/systemd/system/backup.service
[Unit]
Description=Daily backup script

[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
sudo nano /etc/systemd/system/backup.timer
[Unit]
Description=Run backup daily at 2am

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target
sudo systemctl enable --now backup.timer

# Xem tất cả timer và trạng thái
systemctl list-timers

Persistent=true xử lý tình huống server bị tắt đúng lúc 2h và bỏ lỡ lịch chạy — timer sẽ chạy bù ngay khi server boot lại, thay vì đợi đến đêm hôm sau.

Tips thực tế từ kinh nghiệm vận hành

1. Dùng --now để enable và start cùng lúc

systemctl enable --now myapp
# Tương đương:
systemctl enable myapp && systemctl start myapp

2. Xem tất cả service đang failed

systemctl --failed

Lệnh đầu tiên mình chạy sau khi server reboot. Nếu có service nào fail, biết ngay — không cần kiểm tra từng cái một.

3. Phân tích thời gian boot

systemd-analyze blame | head -20

Liệt kê service tốn thời gian boot nhất. Trên máy dev của mình, snapd từng ngốn hơn 30 giây — disable đi, boot từ 45s xuống còn 12s.

4. Chạy service với user riêng (security)

Không bao giờ chạy service bằng root nếu không cần thiết. Tạo user riêng:

sudo useradd -r -s /bin/false myappuser
[Service]
User=myappuser
Group=myappuser
# Thêm sandbox nếu muốn
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true

5. ExecStartPre / ExecStartPost

Chạy lệnh trước khi service start — ví dụ chờ database thực sự sẵn sàng chứ không chỉ đang khởi động:

[Service]
ExecStartPre=/bin/sh -c 'until pg_isready -h localhost; do sleep 1; done'
ExecStart=/home/ubuntu/myapp/venv/bin/python app.py

Cách này tốt hơn After=postgresql.serviceAfter chỉ đảm bảo thứ tự start, không đảm bảo postgres đã nhận được kết nối. Mình từng bị lỗi này khi app khởi động xong trước, postgres vẫn đang warm up, kết nối fail ngay từ đầu.

Share: