สร้าง systemd Service สำหรับ Node.js และ Python บน VPS

หนึ่งในปัญหาที่พบบ่อยบน VPS คือ Application หยุดทำงานเมื่อ Server รีสตาร์ท หรือ Crash แล้วไม่ฟื้นขึ้นมาเอง systemd คือระบบ init และ service manager ของ Linux ที่ช่วยให้ App รันเป็น Background Service ได้ โดยรันอัตโนมัติตอน Boot และ Restart ทันทีเมื่อมีปัญหา

systemd Service Unit คืออะไร

Service Unit คือไฟล์ Text ที่มีนามสกุล .service เก็บอยู่ใน /etc/systemd/system/ ระบุว่า Process ไหนควรรัน อย่างไร และภายใต้เงื่อนไขอะไร systemd จะอ่านไฟล์นี้และจัดการ Process ตามที่กำหนดไว้

ทำไมไม่ใช้ PM2? PM2 เป็นตัวเลือกยอดนิยมสำหรับ Node.js แต่ systemd เป็น Native solution ที่อยู่ใน Linux ทุกตัว ไม่ต้องติดตั้งเพิ่ม รองรับทุกภาษา และจัดการ Log ผ่าน journald ได้ทันที

โครงสร้างไฟล์ Service Unit

ไฟล์ .service ประกอบด้วย 3 ส่วนหลัก:

[Unit]
Description=ชื่อ/คำอธิบาย Service
After=network.target         # รอให้ Network พร้อมก่อน

[Service]
Type=simple                  # ประเภท process
User=ubuntu                  # user ที่รัน process
WorkingDirectory=/home/ubuntu/myapp
ExecStart=/usr/bin/node server.js
Restart=always               # restart ทุกครั้งที่หยุด
RestartSec=5                 # รอ 5 วิก่อน restart

[Install]
WantedBy=multi-user.target   # Enable ตอน boot

ตัวอย่างที่ 1: Node.js App

สร้างไฟล์ Service สำหรับ Node.js

sudo nano /etc/systemd/system/myapp.service

ใส่เนื้อหาต่อไปนี้ (แก้ path และ user ให้ตรงกับ project ของคุณ):

[Unit]
Description=My Node.js Application
Documentation=https://example.com
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/myapp
ExecStart=/usr/bin/node /home/ubuntu/myapp/server.js
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
Environment=NODE_ENV=production
Environment=PORT=3000

[Install]
WantedBy=multi-user.target

เปิดใช้งาน Service

# โหลด config ใหม่
sudo systemctl daemon-reload

# Enable ให้รันตอน Boot
sudo systemctl enable myapp

# Start Service ตอนนี้
sudo systemctl start myapp

# ตรวจสถานะ
sudo systemctl status myapp

ทำไมต้องใช้ systemd บน VPS สำหรับ Production App

เมื่อ Deploy Application บน VPS Linux หนึ่งในปัญหาที่พบบ่อยที่สุดคือ Application หยุดทำงานโดยไม่มีใครรู้ ไม่ว่าจะเป็นจาก Memory Leak, Unhandled Exception หรือการที่ Server ต้องรีสตาร์ทจากการอัพเดต Kernel systemd แก้ปัญหาเหล่านี้ทั้งหมดด้วยการทำให้ Application ของคุณกลายเป็น Managed Service ของระบบปฏิบัติการ

ข้อดีของการใช้ systemd ที่ชัดเจนในการใช้งานจริงบน VPS ได้แก่: Application รันอัตโนมัติทันทีที่ Server Boot โดยไม่ต้องมีใครเข้าไป Start เอง เมื่อ Application Crash ไม่ว่าด้วยเหตุใดก็ตาม systemd จะ Restart ให้ภายในไม่กี่วินาที ตาม RestartSec ที่กำหนด Log ทุกอย่างถูกบันทึกโดย journald โดยอัตโนมัติ สามารถ Query ย้อนหลังได้ตลอดเวลา และยังสามารถจำกัด Resource เช่น MemoryMax และ CPUQuota เพื่อป้องกัน Application หนึ่งกินทรัพยากรทั้งหมดของ VPS

เปรียบเทียบวิธี Deploy Application บน VPS

วิธี Auto-start Boot Auto-restart Crash ภาษาที่รองรับ
รัน Manual ไม่ ไม่ ทุกภาษา
nohup / Screen ไม่ ไม่ ทุกภาษา
PM2 ได้ (pm2 startup) ได้ Node.js เป็นหลัก
systemd ได้ (Native) ได้ ทุกภาษา

ตรวจสอบ systemd version ที่ติดตั้ง

# ตรวจสอบ systemd version (Ubuntu 22.04 มักมี v249+)
systemctl --version

# ดู Service ทั้งหมดที่กำลังรันอยู่
systemctl list-units --type=service --state=running

# ดู Log ของ Boot ล่าสุด
sudo journalctl -b -0 --no-pager | head -50

ตัวแปร Environment และการจัดการ Secret ใน systemd

การเก็บ API Key, Database Password หรือ Secret ที่ใช้ใน Application ตรงใน Unit File เป็นแนวปฏิบัติที่ไม่ปลอดภัย เพราะ Unit File อาจถูกอ่านได้ด้วย systemctl cat myapp systemd มีวิธีที่ดีกว่าในการจัดการ Secret สำหรับ Production

วิธีที่ 1: EnvironmentFile (แนะนำ)

# สร้างไฟล์ Environment แยกต่างหาก
sudo nano /etc/myapp.env

# เนื้อหาใน /etc/myapp.env (ไม่มี export — แค่ KEY=VALUE)
DB_PASSWORD=SuperSecretPassword123
API_KEY=sk-xxxxxxxxxxxxxxxxxxxx
PORT=3000

# ตั้ง Permission ให้อ่านได้เฉพาะ root
sudo chmod 600 /etc/myapp.env
sudo chown root:root /etc/myapp.env

จากนั้นใน Unit File อ้างอิงด้วย EnvironmentFile:

[Service]
User=ubuntu
EnvironmentFile=/etc/myapp.env
ExecStart=/usr/bin/node /home/ubuntu/myapp/server.js
# Environment variable DB_PASSWORD, API_KEY, PORT จะถูก inject เข้า process อัตโนมัติ

วิธีที่ 2: systemd Credentials (Ubuntu 22.04+)

# สร้าง Credential ด้วย systemd-creds (เข้ารหัสด้วย TPM2 ถ้ามี)
echo -n "SuperSecretPassword" | sudo systemd-creds encrypt --name=db-password -

# อ้างอิงใน Unit File
[Service]
LoadCredential=db-password:/etc/credentials/db-password
# App อ่าน Secret ได้ที่ $CREDENTIALS_DIRECTORY/db-password

กฎด้าน Security: ห้ามเขียน Password ตรงๆ ใน Environment=DB_PASS=secret ใน Unit File เพราะ systemctl show myapp จะแสดง Environment Variables ทั้งหมดรวมถึง Secret ให้ใช้ EnvironmentFile แทนเสมอ

ตัวอย่างที่ 2: Python App (FastAPI / Flask)

สร้าง Virtual Environment ก่อน

cd /home/ubuntu/myapi
python3 -m venv venv
source venv/bin/activate
pip install fastapi uvicorn

สร้างไฟล์ Service สำหรับ Python/FastAPI

sudo nano /etc/systemd/system/myapi.service
[Unit]
Description=FastAPI Application
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/myapi
ExecStart=/home/ubuntu/myapi/venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapi
Environment=ENV=production

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable myapi
sudo systemctl start myapi
sudo systemctl status myapi

คำสั่งจัดการ Service ที่ใช้บ่อย

# ดูสถานะ
sudo systemctl status myapp

# หยุด Service
sudo systemctl stop myapp

# Start Service
sudo systemctl start myapp

# Restart Service
sudo systemctl restart myapp

# Disable ไม่ให้รันตอน Boot
sudo systemctl disable myapp

# ดู Log ของ Service
sudo journalctl -u myapp -n 100

# ดู Log แบบ Real-time (follow)
sudo journalctl -u myapp -f

Tips เพิ่มเติมสำหรับ Production

ตั้งค่า Restart Policy ให้เหมาะสม

จำกัด Resource ด้วย systemd

[Service]
# จำกัด Memory สูงสุด 512MB
MemoryMax=512M

# จำกัด CPU ใช้ได้ไม่เกิน 50%
CPUQuota=50%

Security Tip: ระบุ User= และ Group= ใน [Service] เสมอ เพื่อให้ Process รันด้วย User ที่มีสิทธิ์จำกัด ไม่ใช่ Root โดยสร้าง User เฉพาะสำหรับ App เช่น sudo useradd -r -s /bin/false nodeapp

การ Debug และแก้ไขปัญหา systemd Service ที่พบบ่อย

เมื่อ Service ไม่ทำงานตามที่คาดหวัง การใช้เครื่องมือที่ถูกต้องช่วยประหยัดเวลาได้มาก systemd และ journald มีเครื่องมือ Debug ที่ครบครันอยู่แล้วโดยไม่ต้องติดตั้งอะไรเพิ่ม

Service ไม่ Start ขึ้นมาหลัง Boot

# ตรวจสอบว่า Service enable อยู่จริง
sudo systemctl is-enabled myapp
# ถ้าได้ "disabled" ต้องรัน sudo systemctl enable myapp อีกครั้ง

# ดู Log ของ Boot ล่าสุดเฉพาะ Service นี้
sudo journalctl -u myapp -b -0

# ดูว่า Service ถูก Start หรือเปล่า ถ้าไม่ถูก Start ดู Error ล่าสุด
sudo systemctl status myapp
# บรรทัด "Active:" บอกสถานะ (active/inactive/failed)
# บรรทัด "Loaded:" บอกว่า Unit File อยู่ที่ไหนและ enabled/disabled

Service รันแต่ Application ไม่ตอบสนอง

# ตรวจสอบว่า Process กำลังรันอยู่จริง
sudo systemctl show myapp -p MainPID
ps aux | grep node   # หรือ python, uvicorn

# ดู Log ล่าสุดของ Application
sudo journalctl -u myapp -n 50 --no-pager

# ตรวจสอบว่า Port ที่ App ใช้ถูกใช้งานอยู่หรือไม่
sudo ss -tlnp | grep :3000   # เปลี่ยน 3000 เป็น Port ของ App คุณ

Service Restart วนไม่หยุด (Restart Loop)

# ดูว่า Service Restart กี่ครั้งแล้ว
sudo systemctl show myapp -p NRestarts

# หยุด Restart Loop ชั่วคราวเพื่อ Debug
sudo systemctl stop myapp

# รัน Application ด้วยตัวเองเพื่อดู Error จริง
cd /home/ubuntu/myapp
node server.js   # หรือคำสั่งที่ระบุใน ExecStart

# ถ้า Error ชัดเจน แก้ไข Code แล้ว Start Service ใหม่
sudo systemctl start myapp

จัดการ Log ที่เพิ่มขึ้นเรื่อยๆ

# ดูว่า journald ใช้ Disk ไปเท่าไหร่
sudo journalctl --disk-usage

# ลบ Log เก่าเกิน 7 วัน
sudo journalctl --vacuum-time=7d

# หรือกำหนดขนาด Log สูงสุด
sudo journalctl --vacuum-size=500M

# ตั้งค่าถาวรใน /etc/systemd/journald.conf
# SystemMaxUse=500M
# MaxRetentionSec=7day

ตาราง Restart Policy เปรียบเทียบแบบครบถ้วน

การเลือก Restart Policy ที่เหมาะสมเป็นสิ่งสำคัญสำหรับ Production การ Restart บ่อยเกินไปอาจบ่งชี้ว่ามีปัญหาในโค้ด ขณะที่การไม่ Restart เลยทำให้ Service ล่มโดยไม่มีใครรู้

Restart Policy Restart เมื่อ ไม่ Restart เมื่อ เหมาะกับ
no ไม่ Restart เลย ทุกกรณี One-time tasks
on-failure Exit Code ≠ 0, Signal kill systemctl stop Web App ทั่วไป
always ทุกกรณีที่ Process หยุด ไม่มี Critical Service
on-abnormal Signal, Timeout, Watchdog Exit Code ≠ 0 ปกติ Background worker

ป้องกัน Restart Loop ด้วย StartLimitInterval

[Unit]
Description=My Application
StartLimitIntervalSec=60   # ในช่วง 60 วินาที
StartLimitBurst=3           # ถ้า Restart เกิน 3 ครั้งให้หยุดพยายาม

[Service]
Restart=on-failure
RestartSec=10
# ถ้า Restart เกิน 3 ครั้งใน 60 วินาที systemd จะหยุด Restart
# ต้องรัน systemctl reset-failed myapp ก่อนจะ Start ได้อีกครั้ง

Monitoring และ Alerting สำหรับ systemd Service บน VPS

การรู้ว่า Service หยุดทำงานก่อนที่ลูกค้าจะรายงานปัญหาเป็นสิ่งสำคัญสำหรับ Production systemd มีระบบ Notification ในตัวที่สามารถส่ง Alert ได้เมื่อ Service ล้มเหลว นอกจากนี้ยังมีเครื่องมือภายนอกที่ทำงานร่วมกับ systemd ได้ดี

ส่ง Email Alert เมื่อ Service Fail

วิธีง่ายที่สุดคือสร้าง Notify Service ที่ systemd จะเรียกเมื่อ Service หลักล้มเหลว

# สร้าง Script สำหรับส่ง Alert
sudo nano /usr/local/bin/service-failure-notify.sh
#!/bin/bash
# /usr/local/bin/service-failure-notify.sh
SERVICE=$1
HOST=$(hostname)
DATE=$(date)
echo "Subject: [VPS Alert] Service $SERVICE failed on $HOST
Service $SERVICE failed on $HOST at $DATE.
Check: sudo systemctl status $SERVICE
Logs: sudo journalctl -u $SERVICE -n 50" | sendmail [email protected]
# ให้สิทธิ์ Execute
sudo chmod +x /usr/local/bin/service-failure-notify.sh

จากนั้นเพิ่ม OnFailure ใน Unit File:

[Unit]
Description=My Application
OnFailure=notify-failure@%n.service   # เรียก Notify Service เมื่อ Fail

[Service]
Type=simple
ExecStart=/usr/bin/node /home/ubuntu/myapp/server.js
Restart=on-failure
RestartSec=10

ใช้ systemd Timer แทน Cron (แนะนำสำหรับ VPS)

systemd Timer เป็นทางเลือกแทน Cron ที่ทำงานร่วมกับ journald ทำให้ Log ของ Scheduled Task ถูกบันทึกเหมือน Service ปกติ สามารถ Debug ได้ง่ายกว่า Cron ที่ Log อยู่คนละที่

# สร้าง Service สำหรับ Task
sudo nano /etc/systemd/system/backup-db.service
[Unit]
Description=Database Backup Task

[Service]
Type=oneshot
User=ubuntu
ExecStart=/home/ubuntu/scripts/backup-db.sh
StandardOutput=journal
StandardError=journal
# สร้าง Timer
sudo nano /etc/systemd/system/backup-db.timer
[Unit]
Description=Run Database Backup Daily at 2 AM

[Timer]
OnCalendar=*-*-* 02:00:00   # รันทุกวันตอนตี 2
Persistent=true              # รัน Task ที่ค้างไว้ถ้า Server ดับตอนกำหนดเวลา

[Install]
WantedBy=timers.target
# เปิดใช้ Timer
sudo systemctl daemon-reload
sudo systemctl enable --now backup-db.timer

# ดู Timer ทั้งหมดที่ enable อยู่
sudo systemctl list-timers --all

การตั้งค่า systemd Service สำหรับ Use Case เฉพาะทาง

นอกจาก Node.js และ Python แล้ว systemd ยังใช้ได้กับ Application หลายประเภทบน VPS ต่อไปนี้คือตัวอย่างการตั้งค่าสำหรับ Use Case ที่พบบ่อย

Service สำหรับ Go Application

sudo nano /etc/systemd/system/mygoapp.service
[Unit]
Description=My Go Application
After=network.target

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/mygoapp
ExecStart=/home/ubuntu/mygoapp/mygoapp
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=mygoapp
EnvironmentFile=/etc/mygoapp.env

[Install]
WantedBy=multi-user.target

Service สำหรับ Background Worker (Queue Consumer)

สำหรับ Background Worker ที่ต้องรันตลอดเวลาเพื่อดึงงานจาก Queue เช่น Redis Queue หรือ RabbitMQ

[Unit]
Description=Queue Worker Service
After=network.target redis.service
Requires=redis.service   # หยุดถ้า Redis หยุด

[Service]
Type=simple
User=ubuntu
WorkingDirectory=/home/ubuntu/worker
ExecStart=/usr/bin/python3 /home/ubuntu/worker/consumer.py
Restart=always
RestartSec=3
StartLimitIntervalSec=60
StartLimitBurst=5   # Restart ได้สูงสุด 5 ครั้งใน 60 วินาที

[Install]
WantedBy=multi-user.target

Service ที่ต้องรอ Database ก่อน Start

[Unit]
Description=API Server (requires MySQL)
After=network.target mysql.service
Wants=mysql.service   # ต้องการ MySQL แต่ไม่บังคับให้ MySQL running ตลอด

[Service]
Type=simple
User=ubuntu
ExecStartPre=/usr/bin/mysqladmin ping -h localhost -u root -pYourPass --connect-timeout=30
ExecStart=/usr/bin/node /home/ubuntu/api/server.js
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Service สำหรับ Static File Server หรือ Reverse Proxy เพิ่มเติม

ถ้าต้องการรัน Custom HTTP Server เล็กๆ สำหรับ serve ไฟล์ Static หรือทำหน้าที่เป็น Reverse Proxy ชั่วคราว สามารถสร้าง systemd Service ได้เช่นกัน ตัวอย่างเช่น Python HTTP Server สำหรับ Internal Dashboard หรือ File Sharing บน LAN ทั้งหมดนี้จัดการผ่าน systemd ได้อย่างเป็นระบบ ไม่ต้องใช้ Screen หรือ nohup ที่ยากต่อการจัดการใน Production

After= vs Requires= vs Wants=: After= กำหนดลำดับการ Start เท่านั้น ไม่ได้บังคับให้ Service นั้น Running Requires= บังคับให้ Dependency Service Running ตลอด ถ้า Dependency หยุด Service นี้หยุดด้วย Wants= แนะนำให้มี Dependency แต่ไม่บังคับ — ใช้ Wants= สำหรับ Optional Dependency

ตรวจสอบว่า Service พร้อม Production

# ดู Service ทั้งหมดที่ enable อยู่
sudo systemctl list-unit-files --type=service --state=enabled

# ทดสอบ auto-restart โดยสั่ง kill process
sudo kill -9 $(sudo systemctl show myapp -p MainPID | cut -d= -f2)

# ดูว่า systemd restart ให้อัตโนมัติหรือไม่
sudo systemctl status myapp

พร้อมรัน App บน VPS ของคุณแล้วหรือยัง?

VPS Linux Ubuntu เริ่มต้น 500 บาท/เดือน พร้อม Full Root Access รัน Node.js, Python, FastAPI และทุก Stack ที่ต้องการได้อย่างอิสระ

ดูแพ็กเกจ VPS