Node.js スクリプトからサービスへ:systemd や pm2 による常駐運用

Node.js アプリケーションを開発中は「node app.js」で簡単に動作確認できますが、本番環境では話が変わります。サーバーの再起動やプロセスの異常終了があっても、アプリケーションが自動で復旧し続ける必要があります。
今回は、Node.js スクリプトを安定した常駐サービスに変身させる方法をご紹介します。systemd と pm2 という 2 つの強力なツールを使って、あなたのアプリケーションを本格的な本番運用レベルまで引き上げていきましょう。
背景
Node.js スクリプトの基本実行方法
通常、Node.js アプリケーションは以下のような方法で実行されます。
javascript// app.js - シンプルなWebサーバー
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
実行は非常にシンプルです。
bash# 基本的な実行方法
node app.js
# または package.json の scripts を使用
yarn start
しかし、この実行方法では重要な問題があります。ターミナルを閉じたり、SSH 接続が切れると、プロセスも一緒に終了してしまうのです。
開発環境と本番環境の違い
開発環境と本番環境では、求められる要件が大きく異なります。
項目 | 開発環境 | 本番環境 |
---|---|---|
可用性 | 開発中は停止 OK | 24 時間 365 日稼働 |
復旧 | 手動再起動で問題なし | 自動復旧が必須 |
ログ | コンソール出力で十分 | ファイル保存・ローテーション |
モニタリング | 不要 | リソース監視・アラート |
セキュリティ | 緩い制限 | 厳格な権限管理 |
以下の図は、開発から本番への移行で必要となる要素を示しています。
mermaidflowchart TB
dev[開発環境] -->|移行| prod[本番環境]
dev --> manual[手動実行]
dev --> console[コンソール出力]
dev --> simple[シンプル構成]
prod --> auto[自動起動]
prod --> logs[ログ管理]
prod --> monitor[監視]
prod --> security[セキュリティ]
auto --> systemd[systemd]
auto --> pm2[pm2]
logs --> rotation[ローテーション]
monitor --> metrics[メトリクス]
security --> user[専用ユーザー]
本番環境では、アプリケーションが予期せず停止した場合の自動復旧、適切なログ管理、リソース監視など、多くの追加要件が発生します。
プロセス管理の重要性
Node.js アプリケーションの本番運用では、以下の理由からプロセス管理が極めて重要になります。
まず、単一プロセスの脆弱性です。Node.js はシングルスレッドで動作するため、一つのエラーでアプリケーション全体が停止する可能性があります。
javascript// 未処理の例外でプロセス全体が停止
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
// プロセス終了を避けるためのハンドリング
process.exit(1);
});
次に、リソース管理の課題があります。メモリリークや CPU 使用率の急増など、長時間稼働することで発生する問題への対処が必要です。
さらに、可用性の要求として、サーバーメンテナンスやアップデート時でも、サービスの継続性を保つ仕組みが求められます。
課題
スクリプトの手動実行による運用の限界
手動で Node.js スクリプトを実行する従来の方法には、いくつかの深刻な制約があります。
ターミナル依存の問題:
bash# SSH経由で実行した場合
ssh user@server
node app.js
# SSH接続が切れると、プロセスも終了してしまう
この問題を回避するため、nohup
や screen
を使用することもありますが、根本的な解決にはなりません。
bash# nohup を使った実行(一時的な解決策)
nohup node app.js > app.log 2>&1 &
# バックグラウンドプロセスの確認
ps aux | grep node
運用作業の煩雑さも大きな課題です。サーバーの再起動後、手動でアプリケーションを起動し忘れることが頻繁に発生します。また、複数のアプリケーションを管理する場合、起動順序や依存関係の管理が困難になります。
プロセス停止時の自動復旧問題
Node.js アプリケーションは様々な理由で予期せず停止することがあります。
メモリ不足による停止:
javascript// メモリリークの例
const data = [];
setInterval(() => {
// メモリを消費し続ける処理
data.push(new Array(1000000).fill('memory leak'));
}, 100);
未処理の例外:
javascript// 未処理のPromise rejection
async function riskyOperation() {
throw new Error('Something went wrong');
}
// この呼び出しで unhandledRejection が発生
riskyOperation();
こうした停止が発生した場合、手動運用では以下の問題が生じます:
- 停止の検知遅れ:システム管理者が気づくまでサービスが停止
- 復旧の遅延:手動での再起動作業に時間がかかる
- ビジネス影響:サービス停止による機会損失
ログ管理とモニタリングの困難さ
手動実行では、適切なログ管理とモニタリングの実現が困難です。
ログ出力の問題:
javascript// コンソール出力のみでは不十分
console.log('User login:', userId);
console.error('Database connection failed');
// ファイル出力も手動で実装が必要
const fs = require('fs');
const logMessage = `${new Date().toISOString()} - Error occurred\n`;
fs.appendFileSync('app.log', logMessage);
モニタリングの課題:
以下の図は、手動運用時のモニタリングの問題点を示しています。
mermaidstateDiagram-v2
[*] --> Running: アプリ起動
Running --> Error: 例外発生
Error --> Stopped: プロセス終了
Stopped --> [*]: サービス停止
Running --> MemoryLeak: メモリリーク
MemoryLeak --> OutOfMemory: メモリ不足
OutOfMemory --> Stopped
note right of Error : 自動復旧なし
note right of Stopped : 手動確認が必要
手動運用では、アプリケーションの状態をリアルタイムで把握することができず、問題の早期発見と対処が困難になります。
解決策
Node.js アプリケーションの常駐化には、主に 2 つのアプローチがあります。それぞれ異なる特徴と利点を持っているため、用途に応じて選択することが重要です。
systemd による OS レベルでのサービス化
systemd は、現代的な Linux ディストリビューションで標準的に採用されているシステム・サービス管理ツールです。
systemd の主な特徴:
- OS レベルでの統合管理
- 依存関係の自動解決
- 標準的な Linux 環境での動作保証
- systemctl コマンドによる統一的な操作
bash# systemd サービスの基本操作
sudo systemctl start myapp # サービス開始
sudo systemctl stop myapp # サービス停止
sudo systemctl restart myapp # サービス再起動
sudo systemctl status myapp # ステータス確認
sudo systemctl enable myapp # 自動起動有効化
systemd を使用することで、Node.js アプリケーションを OS の他のサービス(nginx、postgres など)と同等に扱えるようになります。
pm2 による Node.js 特化型プロセス管理
pm2 は Node.js アプリケーション専用に設計されたプロセス管理ツールです。
pm2 の主な特徴:
- Node.js に特化した豊富な機能
- クラスター機能による負荷分散
- ホットリロード対応
- 詳細なモニタリング機能
bash# pm2 の基本操作
pm2 start app.js # アプリ開始
pm2 stop app # アプリ停止
pm2 restart app # アプリ再起動
pm2 list # プロセス一覧
pm2 monit # リアルタイム監視
以下の図は、systemd と pm2 のアーキテクチャの違いを示しています。
mermaidflowchart TB
subgraph systemd_arch[systemd アーキテクチャ]
linux_kernel[Linux Kernel]
systemd[systemd]
node_service[Node.js Service]
linux_kernel --> systemd
systemd --> node_service
end
subgraph pm2_arch[pm2 アーキテクチャ]
linux_kernel2[Linux Kernel]
pm2_daemon[PM2 Daemon]
node_cluster[Node.js Cluster]
node1[Node.js Process 1]
node2[Node.js Process 2]
node3[Node.js Process N]
linux_kernel2 --> pm2_daemon
pm2_daemon --> node_cluster
node_cluster --> node1
node_cluster --> node2
node_cluster --> node3
end
pm2 は Node.js プロセスの上位層で動作し、複数のプロセスインスタンスを管理できるのが特徴です。
各ツールの特徴と使い分け
適切なツールの選択は、プロジェクトの要件とインフラ環境によって決まります。
観点 | systemd | pm2 |
---|---|---|
学習コスト | 中程度 | 低い |
設定の複雑さ | 中程度 | 簡単 |
OS 統合 | 優秀 | 限定的 |
Node.js 特化機能 | 基本的 | 豊富 |
クラスター | 手動設定 | 自動 |
モニタリング | 基本的 | 詳細 |
ログ管理 | journald 統合 | 独自ログ |
systemd が適している場面:
- システム全体の一貫した管理が必要
- 他のシステムサービスとの連携が重要
- セキュリティ要件が厳格
- 長期間の安定運用が最優先
pm2 が適している場面:
- Node.js 開発チームでの運用
- 頻繁なデプロイとアップデート
- クラスター機能の活用が必要
- 詳細なアプリケーション監視が重要
具体例
実際の Node.js アプリケーションを使って、systemd と pm2 それぞれの実装方法を詳しく見ていきましょう。
サンプルアプリケーションの準備
まず、共通のサンプルアプリケーションを準備します。
javascript// app.js - Express を使用したWebアプリケーション
const express = require('express');
const os = require('os');
const app = express();
const PORT = process.env.PORT || 3000;
// ヘルスチェック用エンドポイント
app.get('/health', (req, res) => {
res.json({
status: 'OK',
timestamp: new Date().toISOString(),
pid: process.pid,
hostname: os.hostname(),
uptime: process.uptime(),
});
});
// メインエンドポイント
app.get('/', (req, res) => {
res.json({
message: 'Node.js Service is running!',
pid: process.pid,
environment: process.env.NODE_ENV || 'development',
});
});
// グレースフルシャットダウンの実装
process.on('SIGTERM', () => {
console.log('SIGTERM received, shutting down gracefully');
server.close(() => {
console.log('Process terminated');
process.exit(0);
});
});
const server = app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
console.log(`PID: ${process.pid}`);
});
package.json も準備しておきます。
json{
"name": "nodejs-service-demo",
"version": "1.0.0",
"description": "Node.js service demonstration",
"main": "app.js",
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
},
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
systemd での実装
サービスファイルの作成
systemd サービスファイルを作成します。これはアプリケーションの設定を定義する重要なファイルです。
bash# サービスファイルの配置先ディレクトリを確認
sudo ls -la /etc/systemd/system/
サービスファイルを作成します:
ini# /etc/systemd/system/nodejs-app.service
[Unit]
Description=Node.js Web Application
Documentation=https://example.com/docs
After=network.target
Wants=network.target
[Service]
Type=simple
User=nodeuser
Group=nodeuser
WorkingDirectory=/opt/nodejs-app
Environment=NODE_ENV=production
Environment=PORT=3000
ExecStart=/usr/bin/node app.js
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=nodejs-app
# セキュリティ設定
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/opt/nodejs-app
[Install]
WantedBy=multi-user.target
各設定項目の説明:
設定項目 | 説明 |
---|---|
Type=simple | プロセスがフォアグラウンドで実行 |
User/Group | 実行ユーザー・グループ |
WorkingDirectory | 作業ディレクトリ |
Environment | 環境変数の設定 |
Restart=on-failure | 失敗時の自動再起動 |
RestartSec=5 | 再起動までの待機時間 |
専用ユーザーとディレクトリの準備
セキュリティのため、専用ユーザーでアプリケーションを実行します。
bash# 専用ユーザーの作成
sudo useradd --system --shell /bin/false nodeuser
# アプリケーションディレクトリの作成
sudo mkdir -p /opt/nodejs-app
sudo chown nodeuser:nodeuser /opt/nodejs-app
# アプリケーションファイルのコピー
sudo cp app.js package.json /opt/nodejs-app/
sudo chown nodeuser:nodeuser /opt/nodejs-app/*
依存関係のインストール:
bash# アプリケーションディレクトリに移動
cd /opt/nodejs-app
# 本番用依存関係のインストール
sudo -u nodeuser yarn install --production
自動起動設定
systemd でサービスを有効化し、自動起動を設定します。
bash# サービスファイルの読み込み
sudo systemctl daemon-reload
# サービスの有効化(自動起動設定)
sudo systemctl enable nodejs-app
# サービスの開始
sudo systemctl start nodejs-app
# ステータス確認
sudo systemctl status nodejs-app
正常に動作している場合、以下のような出力が表示されます:
bash● nodejs-app.service - Node.js Web Application
Loaded: loaded (/etc/systemd/system/nodejs-app.service; enabled; vendor preset: enabled)
Active: active (running) since Sun 2024-01-15 10:30:45 UTC; 2min ago
Docs: https://example.com/docs
Main PID: 1234 (node)
Tasks: 11 (limit: 1152)
Memory: 25.6M
CGroup: /system.slice/nodejs-app.service
└─1234 /usr/bin/node app.js
ログ管理
systemd は journald と統合されているため、ログ管理が簡単です。
bash# リアルタイムログの確認
sudo journalctl -u nodejs-app -f
# 最新100行のログ表示
sudo journalctl -u nodejs-app -n 100
# 特定期間のログ表示
sudo journalctl -u nodejs-app --since "2024-01-15 10:00:00"
# ログのフィルタリング
sudo journalctl -u nodejs-app --grep "ERROR"
pm2 での実装
pm2 インストールと基本設定
pm2 をグローバルにインストールします。
bash# pm2 のグローバルインストール
yarn global add pm2
# または npm を使用
npm install -g pm2
# インストール確認
pm2 --version
基本的なアプリケーション起動:
bash# シンプルな起動
pm2 start app.js
# 名前を指定して起動
pm2 start app.js --name "web-app"
# プロセス一覧の確認
pm2 list
設定ファイルによる管理
pm2 では設定ファイル(ecosystem.config.js)を使用して、より詳細な設定を行えます。
javascript// ecosystem.config.js
module.exports = {
apps: [
{
name: 'nodejs-web-app',
script: 'app.js',
cwd: '/opt/nodejs-app',
instances: 'max',
exec_mode: 'cluster',
env: {
NODE_ENV: 'development',
PORT: 3000,
},
env_production: {
NODE_ENV: 'production',
PORT: 3000,
},
log_file: '/var/log/pm2/nodejs-app.log',
error_file: '/var/log/pm2/nodejs-app-error.log',
out_file: '/var/log/pm2/nodejs-app-out.log',
log_date_format: 'YYYY-MM-DD HH:mm:ss Z',
max_memory_restart: '500M',
restart_delay: 5000,
max_restarts: 10,
min_uptime: '10s',
},
],
};
設定ファイルを使用したアプリケーション起動:
bash# 設定ファイルでアプリケーション起動
pm2 start ecosystem.config.js --env production
# 設定の再読み込み
pm2 reload ecosystem.config.js --env production
クラスター運用
pm2 の最大の利点の一つは、簡単にクラスター構成を組めることです。
bash# CPU コア数に応じたクラスター起動
pm2 start app.js -i max --name "web-cluster"
# 特定インスタンス数での起動
pm2 start app.js -i 4 --name "web-cluster"
# クラスターの状態確認
pm2 show web-cluster
以下の図は、pm2 クラスター構成を示しています。
mermaidflowchart TD
client[クライアント] --> loadbalancer[PM2 Load Balancer]
loadbalancer --> instance1[Node.js Instance 1]
loadbalancer --> instance2[Node.js Instance 2]
loadbalancer --> instance3[Node.js Instance 3]
loadbalancer --> instance4[Node.js Instance 4]
subgraph pm2_process[PM2 Process Management]
pm2_daemon[PM2 Daemon]
pm2_daemon --> instance1
pm2_daemon --> instance2
pm2_daemon --> instance3
pm2_daemon --> instance4
end
pm2_daemon --> logs[ログ管理]
pm2_daemon --> monitoring[監視・メトリクス]
pm2 は自動的にリクエストを各インスタンスに分散し、一つのインスタンスに問題が発生しても他のインスタンスでサービスを継続できます。
監視とデプロイ
pm2 には強力な監視機能が備わっています。
bash# リアルタイム監視ダッシュボード
pm2 monit
# プロセス情報の詳細表示
pm2 show nodejs-web-app
# ログのリアルタイム表示
pm2 logs
# 特定アプリのログ表示
pm2 logs nodejs-web-app
# メモリ使用量の確認
pm2 jlist | jq '.[0].pm2_env.memory'
システム起動時の自動開始
pm2 をシステム起動時に自動で開始するように設定します。
bash# 起動スクリプトの生成
pm2 startup
# 現在の pm2 プロセス一覧を保存
pm2 save
# 設定の確認
pm2 list
デプロイメントを簡素化するスクリプトも作成できます:
bash#!/bin/bash
# deploy.sh - 簡単なデプロイスクリプト
echo "Deploying nodejs-web-app..."
# アプリケーションの停止
pm2 stop nodejs-web-app
# 最新コードの取得
git pull origin main
# 依存関係の更新
yarn install --production
# アプリケーションの再起動
pm2 restart nodejs-web-app
echo "Deployment completed!"
まとめ
各手法の比較と選択指針
Node.js アプリケーションの常駐化において、systemd と pm2 それぞれに明確な利点があります。
systemd の利点:
- OS レベルでの統合管理により、他のシステムサービスとの一貫性
- セキュリティ機能が充実(PrivateTmp、ProtectSystem など)
- journald との統合による標準的なログ管理
- 依存関係の明確な定義が可能
pm2 の利点:
- Node.js に特化した豊富な機能
- 簡単なクラスター構成とロードバランシング
- 詳細なアプリケーション監視とメトリクス
- ホットリロードやゼロダウンタイムデプロイ
以下の図は、選択の判断基準を示しています。
mermaidflowchart TD
start[プロジェクト要件の確認] --> team_size{チームサイズ}
team_size -->|小規模・個人| simple_needs{シンプルな要件?}
team_size -->|中〜大規模| complex_needs{複雑な要件?}
simple_needs -->|Yes| systemd_choice[systemd を選択]
simple_needs -->|No| consider_features{必要な機能}
complex_needs -->|Yes| pm2_choice[pm2 を選択]
complex_needs -->|No| hybrid_choice[ハイブリッド構成]
consider_features -->|クラスター必要| pm2_choice
consider_features -->|OS統合重視| systemd_choice
systemd_choice --> implement_systemd[systemd で実装]
pm2_choice --> implement_pm2[pm2 で実装]
hybrid_choice --> implement_hybrid[systemd + pm2]
選択の判断基準:
-
チーム規模と専門性
- 小規模・インフラ重視 → systemd
- 中〜大規模・Node.js 重視 → pm2
-
運用要件
- シンプルで安定 → systemd
- 柔軟で高機能 → pm2
-
既存インフラ
- システム管理者主導 → systemd
- 開発者主導 → pm2
運用時の注意点
どちらの手法を選択しても、以下の点に注意することが重要です。
リソース監視:
javascript// メモリ使用量の監視例
const used = process.memoryUsage();
console.log('Memory usage:', {
rss: Math.round(used.rss / 1024 / 1024) + 'MB',
heapTotal:
Math.round(used.heapTotal / 1024 / 1024) + 'MB',
heapUsed: Math.round(used.heapUsed / 1024 / 1024) + 'MB',
});
ログローテーション: 長期運用では、ログファイルのサイズ管理が重要になります。
bash# logrotate の設定例(systemd の場合)
# /etc/logrotate.d/nodejs-app
/var/log/nodejs-app/*.log {
daily
missingok
rotate 30
compress
notifempty
sharedscripts
postrotate
systemctl reload nodejs-app
endscript
}
セキュリティ更新: 定期的な Node.js と依存パッケージの更新を計画的に実施しましょう。
bash# 依存関係の脆弱性チェック
yarn audit
# 自動更新スクリプトの例
#!/bin/bash
# update.sh
yarn upgrade
yarn audit --audit-level high
pm2 restart all
バックアップ戦略: アプリケーションコードだけでなく、設定ファイルも含めた包括的なバックアップ計画を立てることが重要です。
Node.js アプリケーションの常駐化は、本格的な本番運用への重要な第一歩です。systemd と pm2 の特徴を理解し、プロジェクトの要件に最適な選択をすることで、安定したサービス運用を実現できるでしょう。
関連リンク
- article
Node.js スクリプトからサービスへ:systemd や pm2 による常駐運用
- article
GitHub Actions × Node.js:テストとデプロイを自動化する
- article
Node.js × FFmpeg で音声抽出・変換:MP3/AAC/Opus への最短手順
- article
Node.js のロギング設計:winston・pino の活用法
- article
Node.js × FFmpeg でサムネイル自動生成:キーフレーム抽出とスプライト化
- article
Node.js × FFmpeg で H.264/H.265/AV1 最適化:ビットレートと CRF の正解
- article
Zod で非同期バリデーション(async)を実装する方法
- article
Node.js スクリプトからサービスへ:systemd や pm2 による常駐運用
- article
Web Components Shadow DOM を使いこなす - スタイルカプセル化と Slot 活用テクニック
- article
Next.js の Middleware 活用法:リクエスト制御・認証・リダイレクトの実践例
- article
Vue.js のエラーと警告メッセージを完全理解
- article
Tailwind CSS × Three.js でインタラクティブな 3D 表現を実装
- blog
iPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
- blog
Googleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
- blog
【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
- blog
Googleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
- blog
Pixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
- blog
フロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
- review
今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
- review
ついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
- review
愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
- review
週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
- review
新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
- review
科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来