Introduction
NATS is an open source high-performance messaging system, often referred to as "the central nervous system of the cloud." It is capable of routing millions of messages per second, which makes it ideal for connecting microservices and Internet of Things (IoT) devices.
NATS is a PubSub messaging system. In such a system, one or more publishers send messages with a specific topic to the message broker, and the message broker delivers these messages to any client or subscriber of a given topic. Publishers don’t understand or even care about subscribers, and vice versa. This architecture makes it easy to extend the system and add new features, because we can add publishers and subscribers without affecting the rest of the system. This type of system is very suitable for monitoring servers and devices; devices can send messages, we can subscribe to these messages, and send notifications via email or other means.
In this tutorial, we will install the official gnatsd
NATS server as a service and access it in a secure way. We will also create a basic server overload warning system that sends emails when the server load is too high and uses gnatsd
as its message agent.
To complete this tutorial, you need:
sudo
command, and the firewall is turned on. Students who don’t have a server can buy it from here, but I personally recommend it You use the free Tencent Cloud [Developer Lab] (https://cloud.tencent.com/developer/labs?from=10680) to experiment, and then [buy the server] (https://cloud.tencent.com/product/cvm?from=10680) after learning to install.)sudo
permissions.Let's first download the gnatsd
server and make sure it runs on our system without any problems.
The latest stable gnatsd
version is version 0.9.4 at the time this tutorial was written. You can check the NATS download page to get the higher version. If you want to use the newer version, you can adjust the following commands as needed.
First, log in to your server using a non-root account:
ssh sammy@your_server_ip
Then, make sure you are in the user's home directory:
cd
Next, use wget
to download gnatsd
to your server:
wget https://github.com/nats-io/gnatsd/releases/download/v0.9.4/gnatsd-v0.9.4-linux-amd64.zip
The archive you just downloaded is a compressed archive, so you need to install unzip
to unzip the file. You can install it with apt
:
sudo apt-get install -y unzip
Then unzip
is used to extract gnatsd
:
unzip -p gnatsd-v0.9.4-linux-amd64.zip gnatsd-v0.9.4-linux-amd64/gnatsd > gnatsd
Then create the gnatsd
executable file to run it:
chmod +x gnatsd
Let's test it, we can run gnatsd
by running it from the current directory. Start gnatsd
with the following command:
. /gnatsd --addr 127.0.0.1--port 4222
The output you see is similar to this example:
[1851]2016 /09/2305:20:02.247420[INF] Starting nats-server version 0.9.4[1851]2016/09/2305:20:02.248182[INF] Listening for client connections on 127.0.0.1:4222[1851]2016/09/2305:20:02.248626[INF] Server is ready
By default, gnatsd
listens on port 0.0.0.0
on the 4222
address corresponding to all interfaces. Using the --port
parameter, you can change the port, and you can use --addr
to change its listening address. We run gnatsd
with --addr 127.0.0.1
, so it can only be accessed on our server and cannot be accessed by external clients. Later in this tutorial, we will ensure that gnatsd
is open to the world.
Press CTRL+C
to close gnatsd
.
Now that you know things work, let's set it up in a more formal way.
On Linux, third-party service related software is often stored in the /srv
directory. We will follow this convention and keep NATS-related files under /srv/nats
. We put the gnatsd
executable file into /srv/nats/bin
.
First, create the /srv/nats/bin
folder:
sudo mkdir -p /srv/nats/bin
Then move gnatsd
to the /srv/nats/bin
folder:
sudo mv ~/gnatsd /srv/nats/bin
The server can load its configuration from a file, which will come in handy when we need to modify the server settings later in this tutorial. Create the file /srv/nats/gnatsd.config
:
sudo nano /srv/nats/gnatsd.config
And add the following to the file:
port:4222
net:'127.0.0.1'
This configuration file tells the gnatsd
server to listen on port 4222
at address 127.0.0.1
, as before, but this time we don't have to specify these options on the command line.
Let's run the server again to make sure we have configured it correctly. Execute the following command to start gnatsd
with the new configuration file:
/srv/nats/bin/gnatsd -c /srv/nats/gnatsd.config
The output is similar to what you saw earlier:
[1869]2016 /06/1805:30:55.988856[INF] Starting nats-server version 0.9.4[1869]2016/06/1805:30:55.989190[INF] Listening for client connections on 127.0.0.1:4222[1869]2016/06/1805:30:55.989562[INF] Server is ready
Press CTRL+C
again to close gnatsd
and return to your prompt. Now let's create a user who will run this service.
Running each service with its own user account to limit the damage when the service is compromised is a good security measure. Let's create a user and group with NATS service and NATS related files.
First, let's create a system user and group named nats
:
sudo adduser --system --group --no-create-home --shell /bin/false nats
OutputAdding system user `nats' (UID 106) ...
Adding new group `nats' (GID 114)...
Adding newuser`nats' (UID 106) with group `nats' ...
Not creating home directory `/home/nats'.
We assign /bin/false
shell to the nats
system user to disable this user's login and prohibit the creation of home directories. We also created a nats
group.
Let's change the owner of the /srv
directory to the nats
user and group:
sudo chown -R nats:nats /srv
Now that we have created the nats
user and group, let's continue to create the NATS service.
We want gnatsd
to start when the system starts, and restart if it crashes. We will use systemd to deal with this problem.
systemd is a service manager for Linux systems. It is responsible for starting services at startup, restarting them as needed, and stopping them in a controlled manner when the system is shut down.
We need to create a service configuration to define how and when the NATS service should be started. The service file created by the user exists in /etc/systemd/system
, so create the file /etc/systemd/system/nats.service
:
sudo nano /etc/systemd/system/nats.service
In the file, place this script to define how gnatsd
should be started:
[ Unit]
Description=NATS messaging server
[ Service]
ExecStart=/srv/nats/bin/gnatsd -c /srv/nats/gnatsd.config
User=nats
Restart=on-failure
[ Install]
WantedBy=multi-user.target
[Unit]
section contains general information about the service, for example, Description
describes information about the service.[Service]
section contains configuration related to the service. ExecStart
is a command to run the server. We use the absolute path of the executable file here in gnatsd
. Restart=on-failure
means that if the service crashes or terminates due to a failure, the service must be restarted. If it is stopped by systemd, it will not restart.[Install]
section contains installation information about the service. WantedBy=multi-user.target
tells systemd to start the service multi-user.target
at startup. This is a general method for starting services when the system boots.Once the service description is in place, we can start it with the following command:
sudo systemctl start nats
Let's confirm that gnatsd
is running by sending a PING
message:
printf "PING\r\n"| nc 127.0.0.14222
We just used nc
to communicate with gnatsd
. nc
is a command line utility to communicate with TCP or UDP server. The command we used prints output similar to the following:
INFO {"server_id":"Os7xI5uGlYFJfLlfo1vHox","version":"0.9.4","go":"go1.6.3","host":"127.0.0.1","port":4222,"auth_required":false,"ssl_required":false,"tls_required":false,"tls_verify":false,"max_payload":1048576}
PONG
The response to PONG
lets us know that the server is listening and working as expected. We need to run the last command to make our NATS server start at boot time:
sudo systemctl enable nats
You will see the following output, confirming that the service is installed:
OutputCreated symlink from/etc/systemd/system/multi-user.target.wants/nats.service to /etc/systemd/system/nats.service.
We have successfully configured gnatsd
to run as a service. Now let us protect it and make it accessible to external clients.
If all the publishers and subscribers gnatsd
we want to use are running on the same server, we can call it complete and move on, but this rarely happens now. We need to allow the external client gnatsd
to connect and publish messages in a secure way.
gnatsd
supports TLS transmission, so we will use it to protect the communication between gnatsd
NATS clients.
First, we need a certificate. You can purchase commercial certificates, retrieve commercial certificates from [Let's Encrypt] (http://letsencrypt.org/) or generate self-signed certificates. We will use the latter method because obtaining a certificate is beyond the scope of this article.
Create a directory to temporarily store certificates:
mkdir ~/priv
Then use the following command to create a self-signed certificate:
openssl req -x509 -nodes -days 3650-newkey rsa:2048 \
- keyout priv/gnatsd.key -out priv/gnatsd.crt \
- subj "/C=US/ST=Texas/L=Austin/O=AwesomeThings/CN=www.example.com"
This command creates a 2048-bit RSA certificate with a validity period of 10 years. Please note that we used an arbitrary domain name because we will not enable TLS verification for the gnatsd
server in this article.
You should now have these files gnatsd.key
and gnatsd.crt
in the ~/priv
directory. Let's move these files to our /srv/nats/
directory structure so that everything is in one place. Execute the following commands:
sudo mv ~/priv /srv/nats
Now, only the nats
user and group are allowed to access /srv/nats/priv
:
sudo chmod 440/srv/nats/priv/*
sudo chmod 550 /srv/nats/priv
sudo chown -R nats:nats /srv/nats/priv
Now we update /srv/nats/gnatsd.config
to include the certificate and key we just created. Open the configuration file again:
sudo nano /srv/nats/gnatsd.config
And add the following section to tell gnatsd
to use your certificate and key:
...
tls {
cert_file:"/srv/nats/priv/gnatsd.crt"
key_file:"/srv/nats/priv/gnatsd.key"
timeout:1}
Save the file and exit the editor. Then restart the service so that it can pick up the changes.
sudo systemctl restart nats
Let's test whether our certificate is valid. Run this command:
printf "PING\r\n"| nc localhost 4222
This time, the command outputs the following message:
INFO {"server_id":"npkIPrCE5Kp8O3v1EfV8dz","version":"0.9.4","go":"go1.6.3","host":"127.0.0.1","port":4222,"auth_required":false,"ssl_required":true,"tls_required":true,"tls_verify":false,"max_payload":1048576}-ERR 'Secure Connection - TLS Required'
The server returns the message -ERR'Secure Connection-TLS Required'
, confirming that it has received the new configuration and requires a secure connection, but nc
does not know how to operate.
In order to be able to communicate with our NATS service without installing a complete NATS client, we will use a tool called catnats. Let's download it first:
wget https://github.com/yuce/catnats/raw/0.1.2/catnats.py
And make it executable:
chmod +x catnats.py
Finally, move catnats.py
to the /srv/nats/bin
folder and rename it to catnats
:
sudo mv catnats.py /srv/nats/bin/catnats
Let's catnats
check whether we can communicate using our NATS service by sending the same message sent before PING
:
printf "PING\r\n"|/srv/nats/bin/catnats --addr 127.0.0.1:4222
You will see this output indicating that our connection is secure:
INFO {"server_id":"npkIPrCE5Kp8O3v1EfV8dz","version":"0.9.4","go":"go1.6.3","host":"127.0.0.1","port":4222,"auth_required":false,"ssl_required":true,"tls_required":true,"tls_verify":false,"max_payload":1048576}
PONG
Now that we have secured the communication, let's enable authentication so that connecting to NATS requires a username and password.
Our NATS service does not require authentication by default. This is fine when the service can only be accessed on a private network, but we want our NATS service to be accessible on the Internet, so we should enable authentication. gnatsd
supports username and password authentication and is easy to enable.
Open the /srv/nats/gnatsd.config
file:
sudo nano /srv/nats/gnatsd.config
Add a new authorization
section specifying credentials. We will use the username user1
and password pass1
for this tutorial. You should use longer, more complex passwords in a production environment:
...
authorization {
user: user1
password: pass1
}
Save the file, and then change the owner of /srv/nats/gnatsd.config
to nats
and make it readable by this user to protect the usernames and passwords of other users on the system:
sudo chown nats /srv/nats/gnatsd.config
sudo chmod 400/srv/nats/gnatsd.config
Then restart the service for the changes to take effect:
sudo systemctl restart nats
Let us send a PING
message to gnatsd
to check if everything is normal. Once again, use catnats
to send messages:
printf "PING\r\n"|/srv/nats/bin/catnats --addr 127.0.0.1:4222
You will see the following output:
NFO {"server_id":"sY0SSJBNbEw53HxzS9mH1t","version":"0.9.4","go":"go1.6.3","host":"127.0.0.1","port":4222,"auth_required":true,"ssl_required":true,"tls_required":true,"tls_verify":false,"max_payload":1048576}-ERR 'Authorization Violation'
This tells us that the changes have been successfully applied, and now we need to send the correct username and password to connect to the service. Let's try again, this time providing username user1
and password pass1
:
printf "PING\r\n"|/srv/nats/bin/catnats --addr 127.0.0.1:4222--user user1 --pass pass1
As you can see from the output below, it worked this time:
INFO {"server_id":"sY0SSJBNbEw53HxzS9mH1t","version":"0.9.4","go":"go1.6.3","host":"127.0.0.1","port":4222,"auth_required":true,"ssl_required":true,"tls_required":true,"tls_verify":false,"max_payload":1048576}+OK
PONG
Now that we have restricted this service to clients that know the username and password, we can reconfigure the service so that external clients can connect.
We have configured our NATS server to listen on 127.0.0.1
, which is the local interface. If we let it listen to 0.0.0.0
, then the world will be able to use it. Our last update of /srv/nats/gnatsd.config
:
sudo nano /srv/nats/gnatsd.config
Then change the IP address associated with the net
setting:
...
net:'0.0.0.0'...
Save the file and restart the service:
sudo systemctl restart nats
Now our NATS service is ready for external client connections. To learn how to use it, let's create a simple monitoring service that uses our NATS server as a message broker.
In this section, you will create a simple overload monitoring system using NATS services. The system will receive the average load of the server and send an email to the administrator when any server is overloaded.
The sample project will contain the following components:
stats.loadaverage
every 60 seconds. You need to run this component on any server where you want to monitor load.stats.loadaverage
, and receive the server's hostname, load average and processor count. If the average load of the host is higher than a certain threshold, the notification program sends an email to a predefined address through the SMTP server.For simplicity, we will run all these components on the same server, but you can try to run each component on a different server after completing this tutorial.
You can read the average load of the Linux system /proc/loadavg
from it. For this project, we are only interested in the last-minute load average, which is the first field of the output. Use this command to get the value:
cat /proc/loadavg | cut -f1 -d" "
You will see the following output:
0.11
The average load obtained by reading /proc/loadavg
depends on the number of processors, so you must normalize it by dividing the average load by the number of processors. You can use the following command to get the number of processors in the server:
getconf _NPROCESSORS_ONLN
You will see the result displayed in the terminal:
1
Since the default shell of our server cannot handle floating point arithmetic, we will send the load average value and the number of processors and host name as the payload of the message, and divide it in the notification program later. This is the command we use to construct the payload:
echo $(hostname)`cat /proc/loadavg | cut -f1 -d" "``getconf _NPROCESSORS_ONLN`
This command displays the host name, average load and number of processors respectively:
your_hostname 0.281
Let's create a shell script that uses the hostname stats.loadaverage
to publish the load average and the number of processors to our NATS server. We will configure our system to run this script regularly. Create a new file named ~/publish_load_average.sh
:
nano ~/publish_load_average.sh
In the file, add the following script:
NATS_ADDR=127.0.0.1:4222
LOADAVG=$(cat /proc/loadavg | cut -f1 -d" ")
NPROC=$(getconf _NPROCESSORS_ONLN)
SUBJECT="stats.loadaverage"
PAYLOAD=$(echo $(hostname) $LOADAVG $NPROC)
MESSAGE="PUB $SUBJECT ${#PAYLOAD}\r\n${PAYLOAD}\r\n"
printf "$MESSAGE"|/srv/nats/bin/catnats -q --raw --addr $NATS_ADDR --user user1 --pass pass1
This script creates a message and then delivers the message to catnats
, which publishes the message to the NATS service. Our catnats
runs with the -q
switch to suppress any output, we use the --raw
switch, so catnats
will not try to interpret the input. $NATS_ADDR
If the NATS service is located on a different server, you can change the value of the variable.
Let's test the script to send the load average to NATS.
The following command runs ~/publish_load_average.sh
every 5 seconds. Note that we use the characters at the end of the &
line to run commands in the background:
whiletrue;do sh ~/publish_load_average.sh; sleep 5; done &
You will see the output showing that the command is running in the background with a process ID:
[1]14123
Note: Write down the process ID somewhere because you need to use that ID to stop the command later.
Now connect to NATS and subscribe to the topic stats.loadaverage
to retrieve the load average:
printf "SUB stats.loadaverage 0\r\n"|/srv/nats/bin/catnats --raw --no-exit --pong --user user1 --pass pass1
We use the --no-exit
flag to disable automatic exit, and --pong
to keep our connection with NATS active. If everything is correct, you should get output similar to the following, updated every 5 seconds:
INFO {"server_id":"A8qJc7mdTy8AWBRhPWACzW","version":"0.8.1","go":"go1.6.2","host":"0.0.0.0","port":4222,"auth_required":true,"ssl_required":true,"tls_required":true,"tls_verify":false,"max_payload":1048576}+OK
+ OK
MSG stats.loadaverage 027
your_hostname 0.081
Press CTRL+C
to exit catnats
. Let's stop the calling publish_load_average.sh
loop, because we will have a better way to run publish_load_average.sh
:
kill 14123
The method we just adopted is very suitable for testing, but it is not the method we want to use permanently. We want the system to be able to run publish_load_average.sh
every minute. To achieve this, we can add a crontab entry. The Linux system uses cron
, a system that can run commands or "jobs" according to the schedule we specify. The crontab
command allows us to manage these jobs.
To create a new entry, execute the following command:
crontab -e
If you have never run the above command, you may see the following prompt asking you to choose a text editor to manage entries:
no crontab for demo - using an empty one
Select an editor. To change later, run 'select-editor'.1./bin/ed
2. /bin/nano <---- easiest
3. /usr/bin/vim.basic
4. /usr/bin/vim.tiny
Choose 1-4[2]:
Enter the number corresponding to the editor you are most familiar with, and press ENTER
. The file will be displayed in the editor of your choice.
At the end of the opened file, add the following line, but if you used the following, please replace your username sammy
:
* /1**** bash /home/sammy/publish_load_average.sh
The above entry tells cron
that we run the publish_load_average.sh
script every minute. Save the file and close the editor.
Now let us test whether the periodic release of load averages is effective:
printf "SUB stats.loadaverage 0\r\n"|/srv/nats/bin/catnats --raw --no-exit --pong --user user1 --pass pass1
Wait a few minutes and the output you see will be similar to the following:
INFO {"server_id":"A8qJc7mdTy8AWBRhPWACzW","version":"0.8.1","go":"go1.6.2","host":"0.0.0.0","port":4222,"auth_required":true,"ssl_required":true,"tls_required":true,"tls_verify":false,"max_payload":1048576}+OK
+ OK
MSG stats.loadaverage 027
your_hostname 0.011
MSG stats.loadaverage 027
your_hostname 0.001
Press CTRL+C
to exit catnats
.
We have successfully set up the monitor and it is sending messages to our NATS server. Next, we will set up a notification program that uses this data.
Let's create a notification program that connects to our NATS service and listens for stats.loadaverage
messages. Whenever our program receives a message, it calculates the average load of each processor. If it is higher than 0.6 or 60% CPU utilization per processor, it will set a warning flag for the host publishing the message and send the email to the predefined address. If the average load of each processor is less than 0.4, the warning flag of the host is cleared. To prevent flooding the inbox, we will send an email when the warning flag is set.
We will use Node.JS to create the notification program, because Node.js has a great NATS client. So, first install Node.js:
sudo apt-get install -y npm
Next, create a directory for the notification program and switch to that directory:
mkdir ~/overload_notifier && cd ~/overload_notifier
The Node.js project uses a file called package.json
, which contains information about the project and its dependencies. Execute the following command to create the file:
npm init -y
Then install the NATS client for Node.js, and the nodemailer
module we will use in this project to send warning emails:
npm install [email protected] [email protected]
Now we can create the notification program. Create the file notifier.js
:
nano notifier.js
Then add the following code to the file:
var NATS_URL ='nats://127.0.0.1:4222';var NATS_USER ='user1';var NATS_PASS ='pass1';var EMAIL_TO ='[email protected]';
Be sure to change these options to match the username and password of the NATS service, and your email address.
Next, add this code to import the Node.js NATS client and connect to the gnatsd
service:
var tlsOptions ={
rejectUnauthorized:false,};var nats =require('nats').connect({url: NATS_URL,
tls: tlsOptions,
user: NATS_USER,
pass: NATS_PASS});
Then add this code to set up the mail program and connect to the SMTP server that will send emails. We will set up this server soon:
var nodemailer =require('nodemailer');var transport = nodemailer.createTransport('smtp://localhost:2525');
Then add the rest of the code to calculate the load average and determine if a notification email needs to be sent:
// keep the state of warnings for each hostvar warnings ={};
functionsendEmail(subject, text){
transport.sendMail({
to: EMAIL_TO,
subject: subject,
text: text
});}
functionprocessMessage(message){// message fields: host load processor_countvar fields = message.split(" ");var host = fields[0];var loadAverage =parseFloat(fields[1])/parseInt(fields[2]);if(loadAverage >0.6){if(!warnings[host]){// send warning email if one wasn't already sentvar res =sendEmail('Warning! Server is Overloaded: '+ host,'Load average: '+ loadAverage);// set warning for the host
warnings[host]=true;}}elseif(loadAverage <0.4){if(warnings[host]){// clear the warning
warnings[host]=false;}}}
nats.subscribe('stats.loadaverage', processMessage);
We subscribe to the message, and every time we receive a message, we will execute the processMessage
function, which parses the payload we sent and determines the average load. If it is too high, we send a message, and we track whether we have sent a message before by setting a flag based on the host name. This way we can track notifications by host. If the load average is below our threshold, we clear the flag.
With the monitor and notification program, we can test our sample project.
Let's try it. We will generate some artificial load and check whether the notifier will send a warning email when the load is too high.
Let's install the stress tool to generate CPU load on our server:
sudo apt-get install -y stress
Next, we need to set up an SMTP server to post messages from the notification program. Installing and configuring a complete SMTP server is overkill for this test, so we will use a simple SMTP server that only displays emails delivered to it, rather than actually sending them. The Python programming language has a DebuggingServer
module that we can load. It discards the emails it receives, but displays them on the screen so that we can make sure it works. Python is already installed on our Ubuntu server, so this is a perfect solution.
We start and debug the SMTP server in the background. We will listen on the localhost
port 2525
, which matches the SMTP address we configured in the notifier.js
code. Execute this command to start the SMTP server:
python -m smtpd -n -c DebuggingServer localhost:2525&
Then use the following command to start the notification program in the background:
nodejs ~/overload_notifier/notifier.js &
Finally, let's generate some load on all the processors of the server. Execute the stress
command with the following options:
stress --cpu $(getconf _NPROCESSORS_ONLN)
After a few minutes, you will see output similar to the following because the SMTP server starts to display messages sent by the notifier:
- - - - - - - - - - MESSAGE FOLLOWS ----------
Content-Type: text/plain
To: [email protected]
Subject: Warning! Server is Overloaded: your_hostname
Message-Id:<1466354822129-04c5d944-0d19670b-780eee12@localhost>
X-Mailer:nodemailer(2.4.2;+http://nodemailer.com/;
SMTP/2.5.0[client:2.5.0])
Content-Transfer-Encoding: 7bit
Date: Sun,19 Jun 201616:47:02+0000
MIME-Version:1.0
X-Peer:127.0.0.1
Load average:0.88------------ END MESSAGE ------------
This will let you know that you have successfully sent emails when the server load is too high.
Press CTRL+C
to stop generating load. You have completed the sample project, and now you should know how to work for you in your own environment.
In this article, you learned about the NATS PubSub messaging system, installed it as a service in a secure way, and tested it in a sample project. The sample project uses the Node.JS client, but NATS has clients with more languages and frameworks, you can find them on NATS download page. You can learn more about NATS in its Official Document.
For more Ubuntu tutorials, please go to [Tencent Cloud + Community] (https://cloud.tencent.com/developer?from=10680) to learn more.
Reference: "How To Install and Configure NATS on Ubuntu 16.04"
Recommended Posts