Systemd service là gì? Tạo service cho Node.js, Python và worker

Hướng dẫn thực tế cách tạo và vận hành systemd service cho Node.js, Python và worker trên VPS Linux, từ user, unit file đến restart, log và hardening.

NHT
· 8 phút đọc
Systemd service trên VPS Linux với server tower terminal checklist và các node vận hành

Một ứng dụng Node.js, Python hoặc queue worker chạy ổn định trong terminal chưa có nghĩa là nó đã sẵn sàng cho production. Nếu phiên SSH đóng, tiến trình có thể dừng; nếu máy reboot, ứng dụng không tự chạy lại; nếu process lỗi, người vận hành phải đăng nhập và khởi động bằng tay. systemd service giải quyết lớp vận hành này bằng cách để hệ thống quản lý vòng đời, quyền chạy, thứ tự khởi động và log của tiến trình.

Bài viết này giải thích systemd service theo góc nhìn thực hành trên VPS Linux. Bạn sẽ tạo một unit cho Node.js, một unit cho Python và một worker nền; sau đó kiểm tra trạng thái, restart, log, quyền user và các lỗi thường gặp. Mục tiêu không phải biến mọi ứng dụng thành daemon phức tạp mà là xây một service có thể bàn giao, quan sát và khôi phục được.

1. Systemd service là gì?

systemd là system and service manager thường chạy ở PID 1 trên nhiều bản phân phối Linux hiện đại. Một file có hậu tố .service mô tả một service unit, tức một tiến trình được systemd khởi động và giám sát. Unit có thể khai báo điều kiện phụ thuộc, lệnh chạy, user, môi trường, chính sách restart và mục tiêu lúc boot.

Một service file thường có ba phần: [Unit] mô tả tên và quan hệ phụ thuộc; [Service] mô tả cách chạy tiến trình; [Install] mô tả target mà service được enable. Tài liệu systemd.service chính thức mô tả các kiểu Type= và hành vi lifecycle của từng kiểu.

Với service dài hạn, Type=exec thường giúp phát hiện lỗi khi binary hoặc user không tồn tại sớm hơn Type=simple; nếu ứng dụng hỗ trợ sd_notify thì notify có thể mô tả chính xác thời điểm khởi động hoàn tất.Tóm lược từ tài liệu systemd.service của freedesktop.org.

2. Chuẩn bị user, thư mục và runtime

Không nên chạy ứng dụng web hoặc worker bằng root chỉ vì cách đó nhanh hơn lúc thử nghiệm. Hãy chọn user triển khai có quyền đọc code, ghi đúng thư mục cần thiết và không có quyền hệ thống dư thừa. Với ứng dụng Node.js hoặc Python, nên xác định rõ runtime bằng đường dẫn tuyệt đối thay vì phụ thuộc vào PATH của shell tương tác.

sudo useradd --system --home /srv/myapp --shell /usr/sbin/nologin myapp
sudo install -d -o myapp -g myapp -m 0750 /srv/myapp
sudo install -d -o myapp -g myapp -m 0750 /var/lib/myapp

# Kiểm tra runtime thực tế
command -v node
node --version
command -v python3
python3 --version

Nếu bạn đang triển khai trên VPS mới, hãy hoàn tất checklist user, SSH, firewall và cập nhật hệ thống trước khi tạo service. Bài cài Ubuntu Server tối giản trên VPS có thể dùng làm bước nền, còn service file nên được quản lý cùng source hoặc runbook thay vì chỉ tồn tại trong trí nhớ của một người.

3. Tạo systemd service cho Node.js

Ví dụ dưới đây giả sử ứng dụng nằm ở /srv/myapp, entrypoint là server.js và lắng nghe trên localhost port 3000. Nếu dự án dùng Node version manager, hãy dùng đường dẫn node thực tế trong môi trường production; systemd không đọc đầy đủ các file profile như một phiên shell đăng nhập.

[Unit]
Description=My Node.js application
After=network.target
Wants=network-online.target

[Service]
Type=exec
User=myapp
Group=myapp
WorkingDirectory=/srv/myapp
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/usr/bin/node /srv/myapp/server.js
Restart=on-failure
RestartSec=5
TimeoutStopSec=30

[Install]
WantedBy=multi-user.target

Lưu file ở /etc/systemd/system/myapp.service. WorkingDirectory= giúp process dùng đúng thư mục tương đối; Environment= tách cấu hình vận hành khỏi command line; Restart=on-failure chỉ yêu cầu systemd khởi động lại khi process kết thúc bất thường. Không nên dùng Restart=always một cách mù quáng vì lỗi cấu hình có thể tạo vòng lặp restart liên tục.

4. Tạo service cho Python app

Với Python, hãy tạo virtual environment và trỏ thẳng tới executable trong virtual environment. Cách này tránh việc systemd gọi nhầm Python hệ thống hoặc thiếu package mà bạn đã cài trong môi trường khác.

sudo -u myapp python3 -m venv /srv/myapp/.venv
sudo -u myapp /srv/myapp/.venv/bin/pip install -r /srv/myapp/requirements.txt

# /etc/systemd/system/myapp-python.service
[Unit]
Description=My Python application
After=network-online.target
Wants=network-online.target

[Service]
Type=exec
User=myapp
Group=myapp
WorkingDirectory=/srv/myapp
Environment=PYTHONUNBUFFERED=1
ExecStart=/srv/myapp/.venv/bin/python /srv/myapp/app.py
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Nếu Python app cần socket, file upload hoặc cache, hãy xác định nơi ghi dữ liệu và quyền của thư mục đó. Một service khởi động được nhưng không ghi được log hoặc file tạm vẫn là service chưa hoàn thiện. Hãy kiểm tra cả user thực tế và umask khi đánh giá sự cố permission.

5. Tạo service cho queue worker

Worker nền có đặc điểm khác web server: nó thường chạy vòng lặp, đọc queue và cần xử lý graceful shutdown để không làm mất job. Nếu worker của Laravel, hãy cân nhắc Supervisor hoặc Horizon khi phù hợp; nếu bạn dùng một worker Node.js hoặc Python riêng, systemd có thể quản lý process chính.

[Unit]
Description=Orders queue worker
After=network-online.target redis.service
Wants=network-online.target

[Service]
Type=exec
User=myapp
Group=myapp
WorkingDirectory=/srv/myapp
Environment=NODE_ENV=production
ExecStart=/usr/bin/node /srv/myapp/worker.js
Restart=on-failure
RestartSec=3
KillSignal=SIGTERM
TimeoutStopSec=60

[Install]
WantedBy=multi-user.target

Dependency trong After= chỉ điều chỉnh thứ tự; nó không tự đảm bảo Redis phải chạy thành công. Nếu worker thực sự không thể hoạt động khi Redis chết, hãy thiết kế health check và retry ở tầng ứng dụng, đồng thời cân nhắc Requires=redis.service sau khi hiểu rõ hành vi phụ thuộc. Đừng biến một dependency đơn giản thành chuỗi khởi động mong manh.

6. Nạp unit và bật service

Sau khi tạo hoặc sửa file unit, systemd chưa tự đọc nội dung mới ngay lập tức. Hãy gọi daemon-reload, kiểm tra syntax, khởi động service và chỉ enable sau khi test thủ công thành công.

sudo systemd-analyze verify /etc/systemd/system/myapp.service
sudo systemctl daemon-reload
sudo systemctl start myapp.service
sudo systemctl status myapp.service --no-pager
sudo systemctl enable myapp.service
systemctl is-enabled myapp.service
systemctl is-active myapp.service

enablestart là hai hành động khác nhau: enable tạo liên kết để chạy cùng boot, còn start khởi động ngay trong phiên hiện tại. enable --now gộp hai việc nhưng khi xử lý sự cố, tách từng bước giúp bạn biết lỗi nằm ở việc đọc unit hay ở chính process.

7. Reload, restart và dừng an toàn

Không phải ứng dụng nào cũng hỗ trợ reload cấu hình. Chỉ khai báo ExecReload= khi bạn biết process xử lý tín hiệu hoặc lệnh đó chính xác. Với worker, restart thường cần thời gian để nhận SIGTERM và hoàn tất job hiện tại; đặt timeout quá ngắn có thể khiến systemd kill process giữa chừng.

sudo systemctl reload myapp.service
sudo systemctl restart myapp.service
sudo systemctl stop myapp.service
sudo systemctl reset-failed myapp.service
sudo systemctl show myapp.service -p User -p ExecStart -p Restart

8. Đọc trạng thái và log khi service lỗi

Khi service failed, đừng chỉ chạy restart nhiều lần. Hãy đọc trạng thái, exit code và journal ở đúng khoảng thời gian. Các nguyên nhân phổ biến là đường dẫn runtime sai, user không có quyền đọc code, port đã bị chiếm, biến môi trường thiếu hoặc WorkingDirectory không tồn tại.

sudo systemctl status myapp.service --no-pager -l
sudo journalctl -u myapp.service -b --no-pager -n 100
sudo systemctl show myapp.service -p Result -p ExecMainStatus -p ExecMainCode
sudo ss -ltnp | grep :3000 || true

Bài journalctl: đọc log systemd nhanh sẽ đi sâu vào bộ lọc theo service, boot và thời gian. Việc tách bài hướng dẫn service khỏi bài log giúp runbook dễ tra cứu: một bài tập trung vào lifecycle, bài còn lại tập trung vào bằng chứng sự cố.

9. Hardening cơ bản cho service

Sau khi service chạy, có thể bổ sung các lớp giới hạn quyền phù hợp với ứng dụng. Tuy nhiên, hardening phải đi sau baseline hoạt động; nếu thêm quá nhiều directive cùng lúc, bạn sẽ khó biết ứng dụng hỏng vì permission, filesystem hay network.

[Service]
# Các directive cần kiểm tra kỹ trước khi áp dụng
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/var/lib/myapp
UMask=0027

Mỗi directive có thể làm thay đổi khả năng đọc file, ghi cache, truy cập home hoặc gọi binary phụ. Hãy triển khai từng thay đổi, chạy smoke test và lưu kết quả systemctl status cùng journal. Nếu ứng dụng cần upload, database socket hoặc thư mục runtime, hãy khai báo đúng ngoại lệ thay vì vô hiệu hóa toàn bộ hardening.

10. Checklist bàn giao systemd service

  1. Tên unit, mô tả và owner đã được ghi trong runbook; file nằm ở vị trí đúng và có permission phù hợp.
  2. User chạy service không phải root nếu ứng dụng không cần quyền đặc biệt; thư mục code và data có owner rõ ràng.
  3. ExecStart dùng đường dẫn tuyệt đối, runtime đúng version và WorkingDirectory tồn tại.
  4. daemon-reload, systemd-analyze verify, start, status và enable đã được kiểm tra trên máy thực tế.
  5. Restart policy, timeout và graceful shutdown phù hợp với web process hoặc worker.
  6. Có lệnh journalctl, cách kiểm tra port và cách rollback unit khi release mới lỗi.
  7. Service đã được test sau reboot thay vì chỉ test trong phiên SSH hiện tại.

11. Kết luận

systemd service không chỉ là một file để chạy lệnh lúc boot. Nó là hợp đồng vận hành giữa ứng dụng và hệ điều hành: process chạy bằng user nào, bắt đầu sau điều kiện gì, dừng ra sao, lỗi được khôi phục thế nào và bằng chứng nằm ở đâu. Bắt đầu bằng unit đơn giản, dùng đường dẫn rõ ràng, kiểm tra status/journal và chỉ harden thêm khi đã có smoke test.

Khi bạn chuẩn hóa các service Node.js, Python và worker theo cùng một checklist, việc bàn giao VPS và xử lý sự cố sẽ ít phụ thuộc vào người cài đặt ban đầu hơn. Đây cũng là nền tảng để nối systemd với reverse proxy, queue, backup và giám sát trong các bài tiếp theo của roadmap TechNT.