Archives de catégorie : Node.js

Real time monitoring for your Storj farming nodes with Grafana, Influxdb and Collectd

A couple of weeks ago, I put online three Storj farming nodes. Two of them are hosted on my OVH dedicated servers and I even built my own node with Meccano, a Raspberry PI and four old used hard drives.

After putting them online, I found myself connecting every day on those three different machines to run storjshare status, htop, iotop and even ifconfig to gather metrics and understand how my nodes were behaving. While this could be OK at first, this doesn’t seem to be a good solution in the long run.

Monitoring your nodes and their host is really important to help you understand how they perform, how to improve their efficiency over time and of course, being alerted if something goes wrong. As you know, the more your node is online, the more data it will collect.

To simplify the monitoring process, I setup a very classic combination of Grafana, influxdb and collectd. Feel free to replace each one of these components by one of their many alternatives, according to your likings. Have a look at Telegraf for example to replace Collectd.

Setup your Storj monitoring stack

First thing to do is to setup collectd which will be responsible to collect metrics from your host and from storj-daemon RPC port. Assuming your using debian, run the following command:

sudo apt install collectd

Then setup the Storj collectd plugin by running:

npm install -g storj-collectd-plugin

Now, edit config file /etc/collectd/collectd.conf to enable the plugins your interested in. At least, configure the network plugin with the IP address or domain name of the webserver on which you will setup InfluxDB (127.0.0.1 if influxdb is on same host) and add a plugin exec entry for the collectd-storj-exec-plugin:

LoadPlugin ...
LoadPlugin exec
LoadPlugin network

<Plugin network>
  Server "IP_SERVER_INFLUXDB" "25826"
</Plugin>

<Plugin exec>
        Interval 120
        Exec "youruser" "collectd-storj-exec-plugin"
</Plugin>

Finally, add the following lines in /usr/share/collectd/types.db:

peers                   value:GAUGE:0:U
shared                  value:GAUGE:0:U
restarts                value:GAUGE:0:U

Don’t forget to restart collectd service:

systemctl restart collectd

Repeat the operation on every node’s host.

It’s now time to setup influxdb. Assuming your still using debian, run the following commands:

curl -sL https://repos.influxdata.com/influxdb.key | sudo apt-key add -
source /etc/os-release
test $VERSION_ID = "7" && echo "deb https://repos.influxdata.com/debian wheezy stable" | sudo tee /etc/apt/sources.list.d/influxdb.list
test $VERSION_ID = "8" && echo "deb https://repos.influxdata.com/debian jessie stable" | sudo tee /etc/apt/sources.list.d/influxdb.list

sudo apt update && sudo apt install influxdb

See documentation for more informations on the setup process.

Then, enable influxdb collectd listener by adding the following lines in /etc/influxdb/influxdb.conf:

[collectd]
  enabled = true
  bind-address = ":25826"
  database = "collectd_db"
  typesdb = "/usr/share/collectd/types.db"

Restart influxdb:

sudo systemctl restart influxdb

Finally, install Grafana wherever you want by executing:

wget https://s3-us-west-2.amazonaws.com/grafana-releases/release/grafana_4.2.0_amd64.deb
sudo apt-get install -y adduser libfontconfig
sudo dpkg -i grafana_4.2.0_amd64.deb

See documentation for more informations on the setup process.

Build a cool handy dashboard in Grafana

Now that everything is setup, collectd should already be sending metrics to your influx datastore and you should be ready to create your very own dashboard on Grafana.

Here is what mine is currently looking:

And below a few queries I used to build it.

Downloaded vs Uploaded data per host:

Storj peers per node:

Storj shared data per node:

Don’t forget to add alerts on Grafana according to your needs:

After a few days of monitoring, I’m sure you should see interesting patterns emerge from your graphs! Be careful though, it’s quite hypnotic at the beginning 🙂

A few reads that might be interesting

Build your own Storj.io farming node with Raspberry and Meccano

EDIT 04-30-2017

After a chat with the community, it seems that running 4 nodes at the same time on a RPI3 is not efficient on uploads peaks. That’s why I updated my setup to use a union filesystem and make storj-daemon see my 4 hard drives as only one. See the setup below…

What is Storj?

Since a couple of months, a project grabs all of my attention. That’s Storj.io.

If you never heard about it yet, Storj presents itself as a decentralized and encrypted cloud storage system based on blockchain technologies. The press, always optimistic, even names it the Airbnb of cloud storage.

What is it, really? Storj is a network built on top of the equally named protocol, which aims to share extra disk space of individuals all around the world in order to provide a cloud storage system, cheaper and more resilient than Amazon S3 or Google Cloud Storage.

Of course, in exchange of its extra disk space shared accross the Storj network, the user (farmer) earns a couple of bucks each month, depending on the amount of megabytes stored on its disks and the amount of data downloaded and uploaded during the same period.

The vision of Storj is fascinating to me. The developers chose and implemented blockchain technologies at the very heart of the solution, which ease the de-centralization, allow faster data distribution, more resilient network, prevent security issues by encrypting everything and therefore, bring a lot of new features and opportunities to the end users.

Make use of extra disk space of individual could seem a bit surprising first but did you know that most of the personal computers hard drives are just partly used, leaving as much as one trillion gigabytes of space when the entirety of Google is estimated to be 10 to 15 billion gigabytes?! Did you know that a gigantic part of the data a French individual owns is actually stored and retrieved from Ireland (Amazon) or even from US? To store that amount of data, big companies like Google, Amazon, Facebook or Microsoft build massive datacenters absorbing gigawatts of power. And of course, when one of those fail, Internet goes down.

I think what Storj is building is quite similar to what is happening in power production and distribution. With the smart grid concept, we will progressively migrate from a centralized model, which cause around 10 percent of power production loss in transport in France, to a more efficient distributed model.

If you want to learn more about Storj vision, please read the Storj Master Plan. It’s a bit old but still seems quite accurate.

Build your own Storj node

Well, it’s now time to build your own Storj node to share your hard drive and earn a bit of money! In fact, it may be just the right time because StorjLabs company is bout to reveal a partership which should cause a big increase in storage demand!

So, what do you need to build your own Storj.io farming node? In fact, not much, just a computer with unused disk space. But if you want to build a cost efficient solution, always up and running, using something like a Raspberry PI may be a better idea than leaving your Macbook always turned on!

I built my own with the following parts:

As you can see on the pictures below, the USB hub powers the four hard drives. I used another power supply connected to the Raspberry PI because all USB ports are currently used. But, I think that, with a larger USB hub, I could have powered the RPI directly from it.

The software part is quite simple to setup. First you need to format your hard drives:

parted -a opt /dev/sda mkpart primary ext4 0% 100%
mkfs.ext4 -L storj1 /dev/sda1

parted -a opt /dev/sdb mkpart primary ext4 0% 100%
mkfs.ext4 -L storj2 /dev/sdb1

I chose to mount the four disks in /mnt/storjX. Here is what my fstab is looking:

#/etc/fstab
LABEL=storj1                                      /mnt/storj1      ext4          defaults          0       1
LABEL=storj2                                      /mnt/storj2      ext4          defaults          0       1
LABEL=storj3                                      /mnt/storj3      ext4          defaults          0       1
LABEL=storj4                                      /mnt/storj4      ext4          defaults          0       1
/mnt/storj1:/mnt/storj2:/mnt/storj3/:/mnt/storj4  /mnt/storjmerge  fuse.mergerfs defaults,allow_other,use_ino,fsname=storjmerge  0       0

As you can see on the last line of my fstab, I make use of mergerfs to merge all of my hard drives in a single volume, mounted in /mnt/storjmerge. It’s mostly because Raspberry PI 3 has not enough RAM to run efficiently more than 1 node at once (1GB of RAM seems barely enough while receiving big uploads from peers).

To setup mergerfs on your Raspberry PI 3, run the following:

apt install fuse
wget https://github.com/trapexit/mergerfs/releases/download/2.20.0/mergerfs_2.20.0.debian-wheezy_armhf.deb
dpkg -i mergerfs_2.20.0.debian-wheezy_armhf.deb
rm mergerfs_2.20.0.debian-wheezy_armhf.deb

When the hard drives are setup, you need to install storjdaemon:

npm install --global storjshare-daemon

Then, create the storj node using the following command:

storjshare create --sjcx=YOURSJCXTOKEN --storage=/mnt/storjmerge/storj.io/
...

Then, make a script to start everything at once:

$ cat start-farming.sh
storjshare daemon
storjshare start --config /path/to/storjconfig/xxxx.json

That’s it!

Going further – Interesting reads about Storj

Designez des API asynchrones VRAIMENT asynchrones

Je suis tombé récemment sur un vieil article d’Isaac Zimmitti Schlueter à lire ABSOLUMENT.

Pour résumer rapidement le propos, IZS explique suite à un avertissement on ne peut plus clair (Do Not Release Zalgo) que lors du design d’une API Asynchrone, il faut éviter à tout prix la situation suivante :

var cachedData;
function getSomeData(callback) {
  if (cachedData === undefined) {
    $.get('some/random/api').then(function(data) {
      callback(cachedData = data);
    });
  } else {
    callback(cachedData);
  }
}

Quel est le problème avec ce bout de code ?

Si vous êtes développeur AngularJs / EmberJs / votre framework javascript préféré, je suis certain que vous avez vous-même été amené à écrire du code similaire ou au moins à en rencontrer au sein de vos applications.

Alors quel est le problème ?

Mon API fait un calcul coûteux (requête HTTP vers une API tiers) au premier appel, stocke la donnée retournée dans une variable servant de cache et appelle le callback. Au deuxième appel, mon API constate que la donnée est déjà en cache et appelle immédiatement le callback.

L’utilisation d’une API de ce type est extrêmement complexe et source de bugs. Pourquoi ? Son comportement est difficile à prédire…

L’auteur l’explique en ces termes sur son article :

If you have an API which takes a callback, and sometimes that callback is called immediately, and other times that callback is called at some point in the future, then you will render any code using this API impossible to reason about, and cause the release of Zalgo.

Prenons l’exemple du code javascript suivant :

// Quelque part dans mon code
getSomeData(function(result) {
  // Faire quelque chose avec mon résultat
});

function clickHandler() {
  var data;
  getSomeData(function(result) {
    data = result;
  });

  if (data.id === 1) {
    // Traitement important
    alert('gouzigouza');
  }
}

Ligne 2, on appelle notre API asynchrone. Un peu plus bas, on définit une fonction appelée lors d’un clic sur un bouton dans une page web. Au sein de ce clickHandler on utilise l’objet data en partant du principe qu’il est disponible immédiatement. L’erreur saute ici évidemment aux yeux. Rien ne garantit que notre objet data ait déjà été retourné arrivé à la condition if.

Malheureusement, le design de notre API et l’utilisation qui en a été faite ici fait que le bug n’est pas identifiable immédiatement. En revanche, si le développeur supprime le premier appel à notre API ligne 2 ou si le clic sur le bouton au sein de la page web intervient avant que le résultat de notre API asynchrone n’ait été mis en cache, alors on se retrouvera avec une belle erreur javascript :

TypeError: Cannot read property 'id' of undefined

Quelle solution ?

Pour une API asynchrone, l’objectif est donc de toujours respecter le caractère asynchrone.
Pour se faire, une approche simpliste est de modifier notre API de la manière suivante :

var cachedData;
function getSomeData(callback) {
  if (cachedData === undefined) {
    $.get('some/random/api').then(function(data) {
      callback(cachedData = data);
    });
  } else {
    setTimeout(function() {
      callback(cachedData = data);
    }, 0);
  }
}

Ici, l’utilisation du setTimeout() est une astuce qui permet de forcer l’appel du callback de manière asynchrone. Si l’API était codée avec nodejs, il serait bien plus judicieux d’utiliser process.nextTick() qui permet de mettre en place un comportement similaire.

Ainsi, à l’usage, il est désormais systématiquement impossible de faire fonctionner le code suivant :

var data;
getSomeData(function(result) {
  data = result;
});

if (data.id === 1) {
  // Bug systématique
  alert('gouzigouza');
}

Le développeur identifie immédiatement son erreur et est obligé alors de modifier son code de la manière suivante :

getSomeData(function(result) {
  var data = result;
  if (data.id === 1) {
    alert('gouzigouza'); // OK
  }
});

Pour aller plus loin

Voici quelques liens pour approfondir le sujet. Je vous conseille évidemment la lecture de l’article de IZS qui ajoute des exemples de design pour des API où les données sont généralement présentes immédiatement et où les performances sont très importantes.
Vous pouvez également aller jeter un oeil à la partie Keeping callbacks truly asynchronous de cet article sur la fonction process.nextTick de nodejs.

Symfony2 MopaBoostrapBundle – Les problèmes fréquents

Ce bundle est parfait pour se lancer dans la réalisation d’une application basée sur le Twitter Boostrap. En revanche, c’est à chaque fois une vrai galère pour l’installer sur un projet sf2 existant.
Si vous partez sur une application toute neuve, je vous suggère de jeter un oeil à l’édition Symfony Boostrap hébergée sur ce repo github : https://github.com/phiamo/symfony-bootstrap. Elle intègre nativement les bundles suivants pré-configurés :

  • Symfony2
  • bootstrap
  • MopaBootstrapBundle
  • MopaBootstrapSandboxBundle

Si vous souhaitez intégrer ce même bundle avec vos petites mains, vous risquez de rencontrer quelques problèmes facilement solvables. Je ne vais pas refaire la documentation du projet qui est tout de même assez complète. Vous la trouverez à cette adresse : https://github.com/phiamo/MopaBootstrapBundle/blob/master/Resources/doc/index.md

Le fail le plus courant, c’est la compilation des fichiers less. Première chose à faire, vérifier qu’une version récente de nodejs est installée.
Sinon, sur Ubuntu :

sudo add-apt-repository ppa:chris-lea/node.js
sudo apt-get update
sudo apt-get install nodejs npm
sudo npm install -g less

Plus d’infos à ce sujet ici : http://blog.overnetcity.com/19-05-2011/premiers-pas-avec-node-js-installation-de-node-et-express/

Une fois node, npm et less installés, il faut indiquer au MopaBoostrapBundle où se situent les binaires de node et du compilateur less.

Rajoutez dans votre fichier de config les lignes suivantes :

node: /usr/bin/node
node_paths: [/opt/lessc/lib, /usr/lib/node_modules]

Si le chargement de votre fichier css échoue et que vous vous choppez une stacktrace de ce type, c’est vraisemblablement que les chemins indiqués vers node ou lessc ne sont pas les bons :

Error Output:

module.js:340
throw err;
^
Error: Cannot find module less
at Function.Module._resolveFilename (module.js:338:15)
at Function.Module._load (module.js:280:25)
at Module.require (module.js:362:17)
at require (module.js:378:17)
at Object.<anonymous> (/tmp/assetic_lessnn6vMt:1:74)
at Module._compile (module.js:449:26)
at Object.Module._extensions..js (module.js:467:10)
at Module.load (module.js:356:32)
at Function.Module._load (module.js:312:12)
at Module.runMain (module.js:492:10)

Faites un petit whereis lessc et whereis node pour trouver les chemins qui vont bien !

Autre problème fréquent au moment de la compilation :

Error Output:
[ERROR] /path/to/project/src/YourProject/MainBundle/Resources/public/less/../img/glyphicons-halflings.png (No such file or directory)

Pour le coup, une petite recherche Google remonte pas mal de résultats à ce sujet. C’est un problème avec cssembed qui fonctionne mal avec les chemins relatifs. Il suffit en fait de recréer l’arborescence attendue par le boostrap twitter, soit en copiant le sprite vers le dossier img/, juste au dessus de votre fichier less principal, soit en utilisant un lien symbolique.
Plus d’infos ici : https://github.com/phiamo/MopaBootstrapBundle/blob/master/Resources/doc/assetic-configuration.md

Voilà c’est tout pour le moment. Je tâcherai de mettre à jour cet article en fonction des nouveaux points bloquants que je rencontrerai éventuellement.