systemdを使ってnodejsサーバー起動する

nodejsのプロセスバックグラウンドで実行する(デーモン化)する場合、だいたいforeverなどが選択されると思う。
今回は、Fedora、CentOS 7のデーモンとなるsystemdを使ってデーモン化してみた。

まずforeverでプロセスをバックグランドで実行させる。

1
2
3
4
5
6
7
8
npm i -g forever
forever start server.js
info: Forever processing file: server.js
node]# forever list
info: Forever processes running
data: uid command script forever pid id logfile uptime
data: [0] OhQD /usr/bin/node server.js 97 102 /root/.forever/OhQD.log 0:0:0:19.532

実際はこのようにforeverを実行する起動ファイルを作成し、自動起動できるようにしないといけない。
場合によっては、起動順序やプロセスの依存関係などを考慮しないといけないため、起動スクリプトは複雑になることが考えられる。

これをsystemdに置き換えることで、

  • 起動スクリプトを設定ファイルで記述できる
  • 依存関係や起動順序など複雑さを回避できる
  • 起動処理をできる限り並列化するとのことで起動にかかk流時間が短縮できる

などが実現する様子。

さっそく適当なDocker環境でsystemdを使ってnodejsサーバーのデーモン化を試してみた。

NodeJsサーバー作成

ミニマムなサーバーを作成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const http = require('http');
const hostname = '0.0.0.0';
const port = 3000;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello World\n');
})
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});

systemd の設定ファイル

systemdでは起動処理をUnitという単位で管理している。
Unitには幾つかのタイプ(service, socketやdebviceなど)が存在するが、それらはすべてファイルの拡張子で判別されている。

設定ファイルにはUnit, Install, Service(serviceであれば)セクションで構成される。

それぞれの設定項目に関してはmanページに詳しくあるので、必要そうなものをかいつまんでみる。

Unitセクション

起動サービスの依存・順序関係を解決する。

ディレクティブ 内容
Description ユニットの説明
Documentation ドキュメントのURI
After/Before 実行順番の設定
Requires/Wants 依存関係の定義。(順序は関係なく並列起動されるべきサービス)
RequiresとWantsの違い。Requiresは依存するサービス起動失敗した時は、サービスの起動を中断とする

Serviceセクション

起動サービスの設定

ディレクティブ 内容
ExecStart 実行するサービスの起動コマンド
ExecReload サービスのリロードコマンド
ExecStop サービスの停止コマンド
Restart プロセス停止時の再起動の有無
always 常に再起動
no 再起動しない
on-success 終了ステータスコード 0 (exit code 0) で終了時に再起動
on-failure 終了ステータスコード 0以外 (exit code 0以外) で終了時に再起動
Type プロセスの起動方法
フォアグラウンドで実行する場合はsimple
プロセスをforkして実行する場合はforking
Environment 環境変数
RestartSec 再起動までのスリープ時間
StandardOutput,StandardError ログ、エラー出力の設定

Installセクション

ユニットの自動起動に関する設定。

ディレクティブ 内容
WantedBy systemctl enable でインストールされるとき、 .wants/にシンボリックを作成
RequiredBy systemctl enable でインストールされるとき、 .requires/にシンボリックを作成

wantsとrequiresはの違いは、wantsはUnitの依存関係のユニットが失敗すると起動を中断する。
requiresは依存関係のユニットが失敗すると起動を継続する。


ということで、今回はサービスの起動を行うため.serviceを使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description=Node.js Server
[Service]
WorkingDirectory=/home/node
Type=simple
ExecStart=/usr/bin/node server.js
Restart=always
RestartSec=10
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=node-server
Environment=NODE_ENV=production PORT=3000
[Install]
WantedBy=multi-user.target

上記を使ってDockerfileを作成。
centos7でNodeJs 4.x を入れるのが大変だったので、nodesourceからセットアップすることにした。

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM centos:7
RUN yum -y update && \
yum -y install epel-release.noarch curl make gcc gcc-c++ tree which && \
yum repolist
RUN curl -sL https://rpm.nodesource.com/setup_4.x | bash - && \
yum -y install nodejs
RUN mkdir /home/node/
ADD server.js /home/node/server.js
ADD node_server.service /etc/systemd/system/node_server.service

systemdを試してみる

1
docker build -t server .

Dockerコンテナ内でDocker daemonの起動はできないので、privilegedオプションで起動する。

1
2
3
4
5
6
7
docker run --privileged -d -p 3000:3000 server:latest /sbin/init
72956dbd3cdf37268a0fa9678922f116e48d1730dd5c69771fa7abffe4affd49
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
72956dbd3cdf server:latest "/sbin/init" 10 seconds ago Up 9 seconds 0.0.0.0:3000->3000/tcp silly_pike
docker exec -it silly_pike /bin/bash

色々と確認する

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# 起動unitの確認
systemctl list-units --type=service
UNIT LOAD ACTIVE SUB DESCRIPTION
dbus.service loaded active running D-Bus System Message Bus
[email protected] loaded active running Getty on tty1
ldconfig.service loaded active exited Rebuild Dynamic Linker Cache
systemd-hwdb-update.service loaded active exited Rebuild Hardware Database
systemd-journal-catalog-update.service loaded active exited Rebuild Journal Catalog
systemd-journal-flush.service loaded active exited Flush Journal to Persistent Storage
systemd-journald.service loaded active running Journal Service
systemd-logind.service loaded active running Login Service
systemd-random-seed.service loaded active exited Load/Save Random Seed
● systemd-remount-fs.service loaded failed failed Remount Root and Kernel File Systems
systemd-sysctl.service loaded active exited Apply Kernel Variables
systemd-tmpfiles-setup-dev.service loaded active exited Create Static Device Nodes in /dev
iles and Directoriesetup.service loaded active exited Create Volatile
FDevicesd-udev-trigger.service loaded active exited udev Coldplug
e Manager-udevd.service loaded active running udev Kernel Devic
ed d-update-done.service loaded active exited Update is Complet
System Boot/Shutdown.service loaded active exited Update UTMP about
ons d-user-sessions.service loaded active exited Permit User Sessi
sole d-vconsole-setup.service loaded active exited Setup Virtual Con
LOAD = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB = The low-level unit activation state, values depend on unit type.
o. d units listed. Pass --all to see loaded but inactive units, to--More--
To show all installed unit files use 'systemctl list-unit-files'.
# 起動の有無に関係なくunitの一覧表示する
systemctl list-unit-files --type=service
UNIT FILE STATE
proc-sys-fs-binfmt_misc.automount static
dev-hugepages.mount static
dev-mqueue.mount static
proc-sys-fs-binfmt_misc.mount static
sys-fs-fuse-connections.mount static
sys-kernel-config.mount static
sys-kernel-debug.mount static
tmp.mount disabled
systemd-ask-password-console.path static
systemd-ask-password-wall.path static
blk-availability.service disabled
console-getty.service disabled
console-shell.service disabled
dbus-org.freedesktop.hostname1.service static
dbus-org.freedesktop.locale1.service static
dbus-org.freedesktop.login1.service static
dbus-org.freedesktop.machine1.service static
dbus-org.freedesktop.network1.service invalid
dbus-org.freedesktop.timedate1.service static
dbus.service static
...
...

staticはInstallを持たないので、単体で起動できない(他のUnitに依存)する。
enabled/disabled はシステム起動時の自動起動の有効/無効を意味する。

1
2
3
4
5
6
7
# 自動起動を有効にする
systemctl enable node_server.service
Created symlink from /etc/systemd/system/multi-user.target.wants/node_server.service to /etc/systemd/system/node_server.service.
# 自動起動を無効にする
systemctl disable node_server.service
Removed symlink /etc/systemd/system/multi-user.target.wants/node_server.service.

systemd/system にシンボリックリンクを作ることがわかる。

自動起動を有効にする

1
2
3
4
5
systemctl enable node_server.service
Created symlink from /etc/systemd/system/multi-user.target.wants/node.service to /etc/systemd/system/node.service.
systemctl list-unit-files --type=service | grep node_server.service
node_server.service enabled

サービスをスタートする

1
systemctl start node_server.service

起動を確認する

1
2
3
4
5
6
7
8
9
10
11
systemctl status node_server.service
● node_server.service - Node.js Server
Loaded: loaded (/etc/systemd/system/node_server.service; enabled; vendor preset: disabled)
Active: active (running) since Sun 2016-06-06 20:11:11 UTC; 4s ago
Main PID: 90 (node)
CGroup: /system.slice/node_server.service
└─90 /usr/bin/node /home/node/server.js
Jun 06 20:11:11 5bb4328b5c84 systemd[1]: Started Node.js Server.
Jun 06 20:11:11 5bb4328b5c84 systemd[1]: Starting Node.js Server...
Jun 06 20:11:11 5bb4328b5c84 node-server[90]: Server running at http://0.0.0.0:3000/

アクセス確認する

1
2
curl http://$(docker-machine ip dev):3000/
Hello World

.serveceファイルの再読み込み

1
systemctl daemon-reload

ついでに再起動

1
systemctl restart node_server.service

まとめ

起動スクリプトの依存性や順序などは場合によっては複雑化するが、systemdを使うことで設定ファイルに落としこめることは、
メリットになるのではないだろうか。
ただ、systemdは全体像が大きいため把握できていないが、まだまだ落とし穴も多い様子。
iniからsystemdに置き換わっているのは事実なので、さらに使用して理解する必要がありそうだ。

参考にしたページ

Comments