◆ 高機能だけど登録の概念が無いのだけが使いづらいところ

watch

マウントしてると node-dev で変更監視できなかったので デーモン化などいろいろしてくれる高機能な PM2 だともしかしたら と思って一応試してみました

すると

  10569 [2018-08-29T11:10:00.855Z] PM2 error: Error: ENOSPC: no space left on device, watch '/mnt/windows/tmps/fw/laravel/vendor/jakub-onderka/php-console-c
10569 olor/src/JakubOnderka/PhpConsoleColor'
10570 at FSWatcher.start (fs.js:1375:26)
10571 at Object.fs.watch (fs.js:1412:11)
10572 at createFsWatchInstance (/home/nm2exp/.config/yarn/global/node_modules/chokidar/lib/nodefs-handler.js:37:15)
10573 at setFsWatchListener (/home/nm2exp/.config/yarn/global/node_modules/chokidar/lib/nodefs-handler.js:80:15)
10574 at FSWatcher.NodeFsHandler._watchWithNodeFs (/home/nm2exp/.config/yarn/global/node_modules/chokidar/lib/nodefs-handler.js:232:14)
10575 at FSWatcher.NodeFsHandler._handleDir (/home/nm2exp/.config/yarn/global/node_modules/chokidar/lib/nodefs-handler.js:414:19)
10576 at FSWatcher.<anonymous> (/home/nm2exp/.config/yarn/global/node_modules/chokidar/lib/nodefs-handler.js:462:19)
10577 at FSWatcher.<anonymous> (/home/nm2exp/.config/yarn/global/node_modules/chokidar/lib/nodefs-handler.js:467:16)
10578 at FSReqWrap.oncomplete (fs.js:150:5)
10579 [2018-08-29T11:10:00.856Z] PM2 error: Error: ENOSPC: no space left on device, watch '/mnt/windows/tmps/fw/laravel/vendor/jakub-onderka/php-console-c
10579 olor/tests/JakubOnderka/PhpConsoleColor'
10580 at FSWatcher.start (fs.js:1375:26)
10581 at Object.fs.watch (fs.js:1412:11)
10582 at createFsWatchInstance (/home/nm2exp/.config/yarn/global/node_modules/chokidar/lib/nodefs-handler.js:37:15)
10583 at setFsWatchListener (/home/nm2exp/.config/yarn/global/node_modules/chokidar/lib/nodefs-handler.js:80:15)
10584 at FSWatcher.NodeFsHandler._watchWithNodeFs (/home/nm2exp/.config/yarn/global/node_modules/chokidar/lib/nodefs-handler.js:232:14)
10585 at FSWatcher.NodeFsHandler._handleDir (/home/nm2exp/.config/yarn/global/node_modules/chokidar/lib/nodefs-handler.js:414:19)
10586 at FSWatcher.<anonymous> (/home/nm2exp/.config/yarn/global/node_modules/chokidar/lib/nodefs-handler.js:462:19)
10587 at FSWatcher.<anonymous> (/home/nm2exp/.config/yarn/global/node_modules/chokidar/lib/nodefs-handler.js:467:16)
10588 at FSReqWrap.oncomplete (fs.js:150:5)

こういう感じのすごい量のエラーが一気にでました
何も起きないのかと思ったらログにディスクスペースがないエラーがいっぱいです
しかもよくみたら watch 対象が全く関係ないフォルダです
マウントしてるフォルダは Linux で動かしたいもの全部入りなので Node.js 以外にも PHP の laravel とか Python のあれこれとかあるのですがそういう関係ないフォルダでエラーが出てるようです
どうして関係ないフォルダまで watch しようとしてるのか不明ですが 動かないだけじゃなくエラーまで出るので PM2 の watch はマウント環境で使わないほうが良さそうです

基本的な使い方

ドキュメントを見てるとけっこういろいろコマンドがあって複雑なのですが 単に起動や停止する分には複雑なことはなかったです
ドキュメントは古いの新しいのがあって 古いのを開くと新しいのに案内されますが古いほうが情報が多いようです
機能が消えてるわけでもないのに古い方にはあるのに新しい方だと消えてるものがあったりします
以下の○○ って書いてるのに以下が存在しなかったりとかです

なにするもの

そういえばちゃんと書いてなかったのでここで書きます
PM2 という名前が Process Manager の略らしいです
名前の通りプロセス管理してくれるものです

node dir/app.js

と実行するのを

pm2 start dir/app.js

と書けます

これにする意味の一つはバックグラウンドで動かしてくれることです

ウェブサーバを起動してるときに別の操作をしたいときにバックグラウンドで実行してくれるのは便利です
byobu や tmux とかはこういうときに便利ですが コマンドによっては表示が壊れるのであまり使いたくないこともあります
ちなみに pm2 の list や monit は byobu でやるとまともに見れませんでした

コマンドの最後に & をつけてバックグラウンド実行でもいいですが これをすると他の作業中に標準出力が混ざってきます
ファイルへのリダイレクトにできますがファイルの管理も少し面倒です

それにリモートでつないでるときに切断してしまうと jobs のリストにでないです
jobs の番号で kill したり fg → Ctrl-C とはいかないです
「pkill node」 とやってたのですが node コマンドが複数動いてるときもあるのでそういうときに困ります

そういう いろいろがあるので管理してくれるツールに任せてしまうとちょっと楽になれます

他には エラーで終了したときに自動で再起動もしてくれます
個人用じゃなくてちゃんとしたサービスとかに使うなら必要な機能です
開発中だとシンタックスエラーとかで無限ループしそうですがたしかデフォルトで 15 回エラーになったら止まったはずです

起動してるののみ管理される

リストに起動中とか停止中とかそういう表示なので systemd 的なものを想像していたのですが

登録するという概念がないようです
起動=登録です
登録のみというのができません

簡単に使えるように先に登録だけしておいて まだ起動はしないということはできないようです

登録してあると情報が取得できたり 名前で操作できます
登録されていないと起動するファイルを指定することになって 登録済みと未登録で操作方法が変わります

また PM2 自体を終了させて 何かのコマンドで起動すると 前の状態は残らずクリアな状態で起動されます
リストは空です
save と resurrect コマンドを使って 保存と復元をするのですが 復元すると 実行状態は維持されず復元時にすべて実行されます
PM2 を終了する前に停止してるのはリストから除外して save コマンドを実行しないと実行中のものだけを維持できません
リストから除外してしまうと起動するのにまたスクリプト名の指定が必要ですし OS 自体を再起動したときには対処できません
実行状態を管理というよりデーモン化してエラーが起きても止まらずバックグラウンドで動かしてくれるツール程度に考えたほうがいいかもしれないです

systemd に登録できる

OS 起動時の自動起動には対応しています
起動時には resurrect が実行されます
なので上で書いたように適切に save しないと実行状態は維持されません

自動起動は systemd が使われます
fedora の場合なので 別ディストリビューションだとそれ用のツールになります

user1@fedora-server ~/t/03> cat /etc/systemd/system/pm2-user1.service
[Unit]
Description=PM2 process manager
Documentation=https://pm2.keymetrics.io/
After=network.target
[Service]
Type=forking
User=user1
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
Environment=PATH=/home/user1/n/bin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
Environment=PM2_HOME=/home/user1/.pm2
PIDFile=/home/user1/.pm2/pm2.pid
ExecStart=/home/user1/.config/yarn/global/node_modules/pm2/bin/pm2 resurrect
ExecReload=/home/user1/.config/yarn/global/node_modules/pm2/bin/pm2 reload all
ExecStop=/home/user1/.config/yarn/global/node_modules/pm2/bin/pm2 kill
[Install]
WantedBy=multi-user.target

こういう設定ファイルが作られます

設定するには startup コマンドを管理者権限で実行します

God Daemon

PM2 の何かのコマンドを実行すると PM2 デーモンがバックグラウンド起動します
ps でみてみると

PM2 v3.0.4: God Daemon

のような名前のプロセスが存在するはずです
PM2 のコマンドの操作ではこのデーモンにアクセスして情報を取得したりします

止めるときは kill コマンドです
God Daemon を停止する前に実行中のスクリプトはすべて停止されます

pm2 start app.js
pm2 list
pm2 kill
pm2 list

の順に実行すれば上で書いた毎回クリアされるというのが確認できます
最後の list では何も実行されていないです

ログは console.log の延長

ログが思ったよりしっかりしていて ローテートまでできるみたいで期待したのですが あくまで console.log の延長みたいです
標準出力と標準エラー出力を保存してるだけなので 自分で出し分けたりできません

デバッグ用に多くの情報表示するけど保存期間は少なめなもの 情報は少ないけど長く保存しておきたいもの とかファイルを分けられないです
ユーザ入力が間違ってるパターンのエラーとバグが原因の想定外のエラーを分けるのも難しそうです
単純にサーバを起動してアクセスがあったアクセスログを別にしたいというのですらできなそうです

そういう特別な別ファイルにログしたいなら別にロガーを使った方がいいのかもしれません

設定ファイル

登録だけはできず起動してるものしか管理できないと書きましたが 設定ファイルを使うと少し改善できます

init コマンドでサンプルのファイルが作られます
ecosystem.config.js というファイル名で 設定情報が書かれたオブジェクトをエクスポートする JavaScript のファイルです

module.exports = {
apps : [
{
name: "main-app",
script: "main/app.js",
},
{
name: "sub-app",
script: "sub/app.js",
}
],
}

これだけでもおっけいです

ここに書いておくと

pm2 start --only main-app

のように名前を使って実行できます
only がないとファイルに書いた全部が起動するのでひとつだけなら only で指定します

こう書けるのは ecosystem.config.js という名前のファイルがカレントディレクトリにある場合のみで 別のパスの場合は指定が必要です

pm2 start pm2-apps.config.js --only main-app

プログラムから操作

pm2 を require すればプログラムからも操作できます
デーモン化しているので 停止したいときは pm2 を操作する必要があります
process.exit しても再起動されてしまいます

接続してスクリプトを起動して 1 秒後に停止して切断するという処理はこう書けます

const util = require("util")
const pm2 = require("pm2")

!async function(){
const connect_info = await util.promisify(pm2.connect.bind(pm2))()
const start_info = await util.promisify(pm2.start.bind(pm2))({script: "x.js", name: "app"})

await util.promisify(fn => setTimeout(() => fn(), 1000))()

const stop_info = await util.promisify(pm2.stop.bind(pm2))("app")
pm2.disconnect()
}()

catch できない

プログラムから操作してみて少し困ったのですが キャッチできないエラーがあります

> const pm2 = require("pm2")
undefined
> try{ pm2.stop({ name: "", script: "" }, (...args) => console.log(1)) } catch(err){ console.log(2) }
undefined
> TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type object
// 略

stop に間違った引数で start のようなオブジェクトを渡すとエラーになりますが try では受け取れません
エラー情報が入ってコールバック関数が呼び出されるはずなのにコールバック関数も呼び出されていません
Promise 使わず非同期実行するとエラー時のコールバック関数の呼び出し漏れが使う側に影響するので 困るところです

ダウンロード数って結構適当

公式サイトのダウンロード数をみてるとすごくスピードで増えてます
https://pm2.io/runtime/

さすがに早すぎない?と思ったのとこんな頻度で更新するために通信をページ開いてるユーザ全員からってけっこう大変そうなので 実際のダウンロード数じゃなさそうと思ってソース覗いてみました

!function e() {
setTimeout(function() {
t.todayDownloads++,
t.totalDownloads++,
e()
}, Math.floor(1300 * Math.random() + 200))
}();

だそうです