linux · tech

How to install Laravel Echo Server in production

Laravel Echo Server – LES from now on – is a NodeJS and Socket.io-based server to use with Laravel Echo broadcasting.

From what we gathered, LES does not have any way to have a decent clustered / HA setup: the internal state is in memory, only the channel subscriptions are shared via database (Sqlite3 or Redis), so we agreed with our client that a floating Virtual IP handled by a Pacemaker+Corosync cluster would do, even if that meant the state would be lost in case of a cluster switch.

Installing NodeJS and NPM

The first step was to install NodeJS and the Node Package Manager on our host:

# curl -LO 'https://rpm.nodesource.com/setup_8.x'
# bash setup_8.x
# yum -y install nodejs gcc-c++ make

Creating a user and a group for LES

We wanted LES to run as the same nginx user ID and group ID as we had on the webservers, so we created a user and group.

# groupadd -g 994 nginx
# useradd -m -u 996 -g nginx nginx

Generating the SSL certificates

LES would serve clients through SSL, so we had a self-signed certificate set up for testing purposes, to be switched for a trusted cert in production.

# su - nginx

nginx$ mkdir -p /home/nginx/ssl/les/2018-selfsigned
nginx$ cd /home/nginx/ssl/les/2018-selfsigned
nginx$ openssl req -x509 -nodes -newkey rsa:4096 -keyout server.key -out server.pem -days 365
Generating a 4096 bit RSA private key
.......................................................................++
........................................................................................................................................++
writing new private key to 'server.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:IT
State or Province Name (full name) []:Parma
Locality Name (eg, city) [Default City]:Parma
Organization Name (eg, company) [Default Company Ltd]:Stardata.it
Organizational Unit Name (eg, section) []:LES
Common Name (eg, your name or your server\'s hostname) []:les.stardata.it
Email Address []:info@stardata.it

nginx$ cd /home/nginx/ssl/les/
nginx$ ln -s 2018-selfsigned/server.key ./
nginx$ ln -s 2018-selfsigned/server.pem ./

Installing LES and PM2

We finally get to install LES; we’ll also install pm2 to handle the startup and restart of LES.

The official website recommends Supervisor, but in our experience pm2 works a lot better for NodeJS.

# su - nginx

nginx$ npm config set prefix /home/nginx
nginx$ npm install pm2 sqlite3 laravel-echo-server

Configuring LES

LES needs a configuration file that you can generate with the init command.

# su - nginx

nginx$ /home/nginx/node_modules/laravel-echo-server/bin/server.js init
? Do you want to run this server in development mode? No
? Which port would you like to serve from? 6001
? Which database would you like to use to store presence channel members? redis
? Enter the host of your Laravel authentication server. http://localhost
? Will you be serving on http or https? https
? Enter the path to your SSL cert file. /home/nginx/ssl/les/server.pem
? Enter the path to your SSL key file. /home/nginx/ssl/les/server.key
? Do you want to generate a client ID/Key for HTTP API? Yes
? Do you want to setup cross domain access to the API? Yes
? Specify the URI that may access the API: *
? Enter the HTTP methods that are allowed for CORS: GET, POST
? Enter the HTTP headers that are allowed for CORS: Origin, Content-Type, X-Auth-Token, X-Requested-With, Accept, Authorization, X-CSRF-TOKEN, X-Socket-Id

This will create a laravel-echo-server.json file in our nginx home.

Unfortunately, even if the init command asks about the database, it doens’t create a valid configuration for Redis, you’ll have to manually add the host and port parameters, as shown below:

{
    "authHost": "http://localhost",
    "authEndpoint": "/broadcasting/auth",
    "clients": [
        {
            "appId": "XXXX",
            "key": "YYYY"
        }
    ],
    "database": "redis",
    "databaseConfig": {
        "redis": {
      "host": "vip-redis",
      "port": "6379"
    },
        "sqlite": {
            "databasePath": "/database/laravel-echo-server.sqlite"
        }
    },
    "devMode": false,
    "host": null,
    "port": "6001",
    "protocol": "https",
    "socketio": {},
    "sslCertPath": "/home/nginx/ssl/les/server.pem",
    "sslKeyPath": "/home/nginx/ssl/les/server.key",
    "sslCertChainPath": "",
    "sslPassphrase": "",
    "apiOriginAllow": {
        "allowCors": true,
        "allowOrigin": "*",
        "allowMethods": "GET, POST",
        "allowHeaders": "Origin, Content-Type, X-Auth-Token, X-Requested-With, Accept, Authorization, X-CSRF-TOKEN, X-Socket-Id"
    }
}

Configuring PM2

Once LES is set up, we can configure pm2 by creating an echo-server.json:

{
  "name": "echo",
  "script": "/home/nginx/node_modules/laravel-echo-server/bin/server.js",
  "args": "start"
}

And we can start it and verify that it works correctly:

# su - nginx

nginx$ pm2 start echo-server.json

[PM2][WARN] Applications echo not running, starting...
[PM2] App [echo] launched (1 instances)
┌──────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────┬───────────┬───────┬──────────┐
│ App name │ id │ mode │ pid  │ status │ restart │ uptime │ cpu │ mem       │ user  │ watching │
├──────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────┼───────────┼───────┼──────────┤
│ echo     │ 0  │ fork │ 2193 │ online │ 0       │ 0s     │ 4%  │ 11.0 MB   │ nginx │ disabled │
└──────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────┴───────────┴───────┴──────────┘
 Use `pm2 show ` to get more details about an app

nginx$ pm2 log
[...]
PM2        | [2018-03-26 11:27:19] PM2 log: Starting execution sequence in -fork mode- for app name:echo id:0
PM2        | [2018-03-26 11:27:19] PM2 log: App name:echo id:0 online
[...]
0|echo     | L A R A V E L  E C H O  S E R V E R
0|echo     |
0|echo     | version 1.3.6
0|echo     |
0|echo     | Starting server...
0|echo     |
0|echo     | ✔  Running at localhost on port 6001
0|echo     | ✔  Channels are ready.
0|echo     | ✔  Listening for http events...
0|echo     | ✔  Listening for redis events...
0|echo     |
0|echo     | Server ready!

Since everything is ok, we save the setup so pm2 will be able to restart everything once invoked with the resurrect command.

nginx$ pm2 save
[PM2] Saving current process list...
[PM2] Successfully saved in /home/nginx/.pm2/dump.pm2

Creating a systemd service for PM2

The last step in our setup is to create a systemd service to start pm2.

We created a pm2-nginx.service file in /etc/systemd/system:

[Unit]
Description=PM2 process manager
Documentation=https://pm2.keymetrics.io/
After=network.target

[Service]
Type=forking
User=nginx
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
Environment=PATH=/usr/bin:/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
Environment=PM2_HOME=/home/nginx/.pm2
PIDFile=/home/nginx/.pm2/pids/echo-0.pid

ExecStart=/home/nginx/node_modules/pm2/bin/pm2 resurrect
ExecReload=/home/nginx/node_modules/pm2/bin/pm2 reload all
ExecStop=/home/nginx/node_modules/pm2/bin/pm2 kill

[Install]
WantedBy=multi-user.target

Then we made sure to enable the service at boot time:

# systemctl daemon-reload
# systemctl enable pm2-nginx

Then we checked to make sure everything started up after a reboot:

# service pm2-nginx status
Redirecting to /bin/systemctl status pm2-nginx.service
● pm2-nginx.service - PM2 process manager
   Loaded: loaded (/etc/systemd/system/pm2-nginx.service; enabled; vendor preset: disabled)
   Active: active (running) since lun 2018-03-26 12:15:24 CEST; 4min 46s ago
     Docs: https://pm2.keymetrics.io/
  Process: 1128 ExecStart=/home/nginx/node_modules/pm2/bin/pm2 resurrect (code=exited, status=0/SUCCESS)
 Main PID: 1246 (laravel-echo-se)
   CGroup: /system.slice/pm2-nginx.service
           ├─1236 PM2 v2.10.1: God Daemon (/home/nginx/.pm2)
           └─1246 laravel-echo-server

mar 26 12:15:18 nodejs01 systemd[1]: Starting PM2 process manager...
mar 26 12:15:23 nodejs01 pm2[1128]: [PM2] Spawning PM2 daemon with pm2_home=/home/nginx/.pm2
mar 26 12:15:24 nodejs01 pm2[1128]: [PM2] PM2 Successfully daemonized
mar 26 12:15:24 nodejs01 pm2[1128]: [PM2] Resurrecting
mar 26 12:15:24 nodejs01 pm2[1128]: [PM2] Restoring processes located in /home/nginx/.pm2/dump.pm2
mar 26 12:15:24 nodejs01 pm2[1128]: [PM2] Process /home/nginx/node_modules/laravel-echo-server/bin/server.js restored
mar 26 12:15:24 nodejs01 systemd[1]: pm2-nginx.service: Supervising process 1246 which is not our child. We\'ll most likely not notice when it exits.
mar 26 12:15:24 nodejs01 systemd[1]: Started PM2 process manager.

Advertisements