Create your own DownNotifier with OpenFaaS
I went to a three weeks holiday during august, and I was happy to see that my blog (hosted on an old Raspberry Pi 1) kept working the whole time!
Call it good or bad luck, but on the day I returned my blog just stopped working.
I discovered it by chance, while I was playing with ansible and inadvertently triggered an uptime
command on the Raspberry Pi hosting the blog. It just hang endlessly.
All I had setup back then, was a free alert on https://www.downnotifier.com/
I knew that this isn't enough and I had to do something.
DownNotifier sends a free alert when your website is down
The kind of alerts you get are mail and SMS. You need to buy credits for SMS to work. Free means that all you get is an email when DownNotifier see that your site is down.
I need a better way to monitor my blog - JM
What we will build
Using Docker, OpenFaaS and a bit of typing, this is what I ended up with in a matter of an hour:
In the top section you can find logs of an OpenFaaS function, the brain of all this, in the bottom section I invoke this function on an inexistant site, and on the right you can see an incoming message to Slack with the site down information!
Problem and solution
The problem I wanted to solve, can be simply formulated as:
I need to be notified in under 15 seconds if my blog goes down.
The notification medium is Slack. I want to see the observed error from the client side.
I wanted needed to write something in a matter of an hour, so I feel a bit safer, then make it better later on.
The idea I had in mind is very simple:
- Schedule a regular ping to the blog
- If everything is ok, say nothing on Slack
- If it fails, send a message to a Slack channel dedicated to this (#blog)
But of course I didn't want to write a single script to do that! I wanted the solution to remain composable, or to quote Docker’s CTO - Solomon Hykes:
"batteries included, but replaceable"
So here comes my solution using serverless functions to allow a fast, scalable and modular design. We’re going to use the OpenFaaS serverless framework for Docker and Kubernetes.
Why is OpenFaaS right for Serverless?
With OpenFaaS you can go serverless, without any vendor lock-in.
Some of the nice features of OpenFaaS include:
- It runs on existing hardware or public/private cloud - Kubernetes or Docker Swarm
- It’s easy to use and has a built-in UI and one-click deployment
- The CLI takes you step-by-step, from scaffolding new functions to deploying them on your cluster
- It comes with bells and whistles such as auto-scaling with respect to demand increase and alerting/monitoring...
but the reason #1 remains the same:
Reason number 1 why I love contributing to @open_faas: the team behind it has great skills and is very helpful
— Johnny Mkhael (@jmkhael) August 28, 2017
But, How?
In order to implement the above steps, I though of creating 3 functions: one that pings, one that send to slack and the last one, the brain, coordinating all of this.
url_ping_node
: this function is a rewrite of url_ping but in node. It basically pings a site and returns a json with error details in case it fails. It also uses RxJS, reactive model for composing asynchronous and event-based programs by using observable sequences. I will need this to retry/timeout later on.slack_it
: this function sends a message to a slack channel
It uses Botkit. It needs a token to access the Slack team. By default it will post to a #blog channel.down_notifier
: this is the top level function or the brain. It calls other functions: firsturl_ping_node
and then decides to pipe a message toslack_it
if the ping fails or not.
and basically, all what remains is to schedule a recurring call to the down_notifier
. (This part is still hackish)
Note
You can find all the code in the accompanying GitHub repository here: https://github.com/jmkhael/blog-pinger, but be sure to read along, as I'll highlight the most relevant sections.
Implementation
Before starting to write our functions, let's install faas-cli
which is a very nice CLI to list
, create new
, build
, deploy
, invoke
, push
and remove
functions to list a few.
It is the Official CLI for the OpenFaaS Serverless framework - GitHub repo
Pre-reqs
Get the faas-cli
, if you haven't yet. You really have to if you want to be productive!
The easiest way to install the faas-cli
is through curl
or brew
:
$ curl -sSL https://cli.openfaas.com | sudo sh
or:
$ brew install faas-cli
Start faas-cli
and be amazed by it's ascii art and help message:
Scaffold
Time to create our functions. Let's use the faas-cli to do so.
faas-cli new --name url_ping_node --lang node
faas-cli new --name down_notifier --lang node
faas-cli new --name slack_it --lang node
rm master.zip
ls
Have a look at what faas-cli just scaffolded for us:
$ ls
down_notifier down_notifier.yml
slack_it slack_it.yml template
url_ping_node url_ping_node.yml
Each yml file is basically a specification we can use to configure/build/deploy individual functions.
You can merge them out into a single 'stack'.
cat *.yml > samples.yml
Check out my samples.yml file for an example.
Writing the functions logic
faas-cli prepared pretty much everything!
faas-cli
, is like Rick, building dedicated robots in no time!
At the time of writing this post, faas-cli
don't know, yet, what's in our mind. So we have to write the functions logic ourselves...
Below I am highlighting the most important aspects of these functions, but you can always refer to the github repo for details.
down_notifier
is kind of more evolved as it chain several functions together. Make sure to check it's code.
url_ping_node
I won't detail much of the code here as it doesn't do a lot besides what will be covered below.
This function will connect to a URL over HTTP and return the status code, or return the error encountered.
$ echo '{"url":"http://jmkhael.io/"}' \
| faas-cli invoke --name url_ping_node
{"url":"http://jmkhael.io/","status":"ok","status_code":200}
$ echo '{"url":"http://jmkhael.no/"}' \
| faas-cli invoke --name url_ping_node
{
"url":"http://jmkhael.no/",
"status":"nok",
"error":{
"code":"ENOTFOUND",
"errno":"ENOTFOUND",
"syscall":"getaddrinfo",
"hostname":"jmkhael.no",
"host":"jmkhael.no",
"port":80
}
}
Note that you can do the same with curl
if you like. But using faas-cli is more elegant as we saw above:
$ curl -d '{"url":"http://jmkhael.io/"}' \
http://localhost:8080/function/url_ping_node
$ curl -d '{"url":"http://jmkhael.no/"}' \
http://localhost:8080/function/url_ping_node
slack_it
This function mission in life is to send a message to a slack channel.
This uses Botkit. By default it will post to a #blog channel.
You need to specify in the environment section a token enabling the function to join your Slack team.
slack_it:
lang: node
handler: ./slack_it
image: jmkhael/faas-slack
environment:
token: "xoxb-insert-your-slack-token-here-but-do-not-add-it-to-github"
The function will require an input in the form of:
$ curl -d '{"message" : "Anything really"}' \
http://localhost:8080/function/slack_it
The message can be any json object. It will be forwarded to the Slack channel.
$ echo '{"message": {"happy": "yes, always!"} }' \
| faas-cli invoke --name slack_it
down_notifier
This function is "the most complicated part" as it orchestrates the whole.
It calls url_ping_node
and decides to send a message to slack_it
if the ping fails. You can find below the full code (~50 lines)!
The interesting bits, are the calls to post('http://gateway:8080/function/url_ping_node')
where we call the first function, and then pipe the result to slack_it
if the status is "nok".
'use strict'
const RxHttpRequest = require('rx-http-request').RxHttpRequest;
var Rx = require('rxjs/Rx');
function down_notifier(url) {
return RxHttpRequest.post('http://gateway:8080/function/url_ping_node', {
body : {
url: url
},
json: true
})
.map(r => r.body)
.map(r => {
//console.log("parsed json from url_ping: " + r.status);
if(r.status !== "ok") {
return RxHttpRequest.post('http://gateway:8080/function/slack_it', {
body: r,
json: true
})
.map(r => r.body);
// "ping failed - told slack"
} else {
return Rx.Observable.of("all ok - slack not informed");
}
});
}
module.exports = (content, callback) => {
//console.log("Slackin: " + content);
let req = JSON.parse(content);
down_notifier(req.url).subscribe(
(x) => {
x.subscribe(r => {
let res = {
"DownNotifier" : "v1",
"request" : req,
"response": r
};
callback(null, JSON.stringify(res));
});
},
(err) => {
console.log('Error: ' + err);
},
() => {
//console.log('Completed');
});
};
Build and deploy
Building our functions is as easy as doing:
$ faas-cli -action build -f ./samples.yml
faas-cli is getting better everyday thanks to the great contributions of the community!
e.g. do faas-cli build --parallel 4
for instance to make things quicker and faas-cli build --filter=name_of_single_function
to build just that one.
In order to deploy, you just issue the command:
$ faas-cli -action deploy -f ./samples.yml
To stay within Dockerland, I am currently using docker-flow-cron to ping the down_notifer
function at regular intervals. I'd like to replace this part but something more native to OpenFaaS.
My deploy.sh
script basically deploys the faas functions we just wrote, and docker-flow-cron stuff. It can be simplified later on.
What can be made better?
Let's start by saying that I did this in a matter of an hour or two tops. It took more time to write this blog than to code this!
I wanted something functioning. I didn't have time to nitpick.
Given time, I would like to:
- Replace the cron logic with a native OpenFaaS one: wait for or contribute back a faas-cron or a faas-scheduler.
- Make the deployment better: now I am dealing with
docker stack deploy
,docker service create
andfaas-cli deploy
. I need an easier way when my stack is mixed. - Check a list of pages, not just the home page
- Setup prometheus as well explained by Alex, here and there, and send alerts to slack or wherever
- Use prometheus alertmanager, which is already part of OpenFaaS stack
- Refactor and move some of these function to faas-and-furious organization
Where to go next?
Things you can do next:
- Play with this/propose pull requests
- Star my repository if you want to show support
- Checkout OpenFaaS and deploy your first function
- Check OpenFaaS samples on faas-and-furious
You can even contribute to OpenFaaS as an active community that welcomes contributions, if you’d like to get involved head over to:
- OpenFaaS or/and faas-cli
- Samples on faas-and-furious. a function like faas-cron would be nice :-)
Show your support by giving us a Star on the project's GitHub.
If you'd like to join the amazing OpenFaaS community Slack channel to chat with contributors or get some help - then send a Tweet to @openfaas, @alexellisuk or email alex@openfaas.com.