Using N|Solid with PM2
The pm2
npm package is a nice tool to
launch and manage multiple apps, including "clustered" versions of apps.
"Clustered" versions of app instances - aka exec_mode: "cluster"
- will use
the cluster
module to launch your app
instances.
In order to have N|Solid configure your applications correctly, you should
provide the NSOLID_COMMAND
, NSOLID_APPNAME
, and any other required
environment variables in the pm2
config json file under the env
attribute.
N|Solid should be able to use this information to have your managed
applications appear in the N|Solid Console and nsolid-cli
.
For the examples below, we have created 4 applications requiring app.js
and starting a process:app-1.js
, app-2.js
,app-3.js
, app-4.js
; they all do the same thing but have different "names".
There are two pm2
JSON files to launch the apps in cluster mode - 3 instances
of each app will be created. Those files are pm2-12.json
and pm2-34.json
.
The first launches app-1
and app-2
, the second launches app-3
andapp-4
.
app-1.js
:
require("./app").start("app-1")
app-2.js
:
require("./app").start("app-2")
app-3.js
:
app-4.js
:
require("./app").start("app-1")
require("./app").start("app-3")
app-4.js
:
require("./app").start("app-4")
app.js
:
"use strict"
module.exports.start = start
const http = require("http")
const cluster = require("cluster")
const ID = cluster.isMaster ? "master" : `worker-${cluster.worker.id}`
const PORT = process.env.PORT || 0
function start(name) {
log(`NSOLID_COMMAND: ${process.env.NSOLID_COMMAND}`)
log(`NSOLID_APPNAME: ${process.env.NSOLID_APPNAME}`)
const server = http.createServer(requestHandler)
server.listen(PORT, listenHandler)
function requestHandler (req, res) {
log(`${req.method} ${req.url}`)
res.end("not much here")
setImmediate(burnCPU)
}
function listenHandler () {
const port = server.address().port
log(`listening at http://localhost:${port}`)
}
function log (message) {
const date = new Date().toISOString()
console.log(`${name}-${ID}: ${date}: ${message}`)
}
}
function burnCPU () {
const count = 100000
const numbers = []
for (let i=0; i<=count; i++) {
numbers.push(Math.random())
}
numbers.sort()
}
pm2-12.json
:
{ "apps": [
{
"name": "app-1",
"script": "app-1.js",
"instances": 3,
"merge_logs": true,
"env": {
"NSOLID_COMMAND": "localhost:9001",
"NSOLID_APPNAME": "app-1",
"NODE_ENV": "production",
"PORT": "8081"
}
},
{
"name": "app-2",
"script": "app-2.js",
"instances": 3,
"merge_logs": true,
"env": {
"NSOLID_COMMAND": "localhost:9001",
"NSOLID_APPNAME": "app-2",
"NODE_ENV": "production",
"PORT": "8082"
}
}
]}
pm2-34.json
:
{ "apps": [
{
"name": "app-3",
"script": "app-3.js",
"instances": 3,
"merge_logs": true,
"env": {
"NSOLID_COMMAND": "localhost:9001",
"NSOLID_APPNAME": "app-3",
"NODE_ENV": "production",
"PORT": "8083"
}
},
{
"name": "app-4",
"script": "app-4.js",
"instances": 3,
"merge_logs": true,
"env": {
"NSOLID_COMMAND": "localhost:9001",
"NSOLID_APPNAME": "app-4",
"NODE_ENV": "production",
"PORT": "8084"
}
}
]}
The examples assume you have pm2
installed globally, via
npm install -g pm2
To start your applications, issue the following command:
$ pm2 start pm2-12.json
[PM2] Spawning PM2 daemon
[PM2] PM2 Successfully daemonized
[PM2] Process launched
[PM2] Process launched
┌──────────┬────┬─────────┬──────┬────────┬─────────┬────────┬─────────────┬──────────┐
│ App name │ id │ mode │ pid │ status │ restart │ uptime │ memory │ watching │
├──────────┼────┼─────────┼──────┼────────┼─────────┼────────┼─────────────┼──────────┤
│ app-1 │ 0 │ cluster │ 4375 │ online │ 0 │ 0s │ 28.543 MB │ disabled │
│ app-1 │ 1 │ cluster │ 4376 │ online │ 0 │ 0s │ 29.176 MB │ disabled │
│ app-1 │ 2 │ cluster │ 4379 │ online │ 0 │ 0s │ 29.086 MB │ disabled │
│ app-2 │ 3 │ cluster │ 4384 │ online │ 0 │ 0s │ 29.004 MB │ disabled │
│ app-2 │ 4 │ cluster │ 4385 │ online │ 0 │ 0s │ 28.922 MB │ disabled │
│ app-2 │ 5 │ cluster │ 4388 │ online │ 0 │ 0s │ 20.727 MB │ disabled │
└──────────┴────┴─────────┴──────┴────────┴─────────┴────────┴─────────────┴──────────┘
Use `pm2 show <id|name>` to get more details about an app
At this point, you can see both app-1
and app-2
instances in N|Solid asapp-1
and app-2
as expected.
Please note that the previously advised solution of passing the N|Solid
configuration environment variables to pm2
is no longer recommended. If you
are still doing this, these variables will take precedence over the ones in
your pm2
config file, and you will continue to see the pm2
supervisor
process in your applications. By following the preceding instructions, this
should no longer be visible.
Programmatic configuration
If your N|Solid connectivity or metadata is not known at the time of pm2
configuration, you have the option of configuring the N|Solid agent using the
Node API provided with N|Solid 2.0. This can be performed by invoking thestart
method on the nsolid
module.
require('nsolid').start({
'command': 'localhost:9001',
'app': 'app-5',
'tags': ['some', 'nsolid', 'tags']
})
If any environment variables are present in process.env
, they will be used if
the corresponding attributes are not set in the config object passed tostart
. The complete mapping of environment to attribute is as follows:
NSOLID_COMMAND => "command"
NSOLID_DATA => "data"
NSOLID_BULK => "bulk"
NSOLID_PUBKEY => "pubkey"
NSOLID_APPNAME => "app"
NSOLID_TAGS => "tags"
The value for tags
may be either an array of tags, or a comma-delimited
string as would be set in NSOLID_TAGS
Be aware that if the N|Solid agent has been initialized by the environment,
calling the start
method will not cause these values to take effect. You
should only expect one method to be effective at a time.
Running multiple apps
Due to the change in configuration, it is also no longer necessary to run
multiple pm2
processes to get the correct NSOLID_APPNAME
or NSOLID_TAGS
,
although it is still safe to do so.
Testing that everything works fine
From the N|Solid Console, you'll now see all 4 apps. These are all web servers
running on ports 8081-8084, so you can send them some http traffic with an
Apache Bench invocation like:
ab -t 20 -c 10 http://localhost:8081/
This will send requests to the app-1
server instances (3, on port 8081) for
20 seconds, with 10 concurrent requests running. When viewing the "app-1" app
in N|Solid, you should see 3 of the instances start using CPU (move to the
right).