Nginx loadbalancer, remote address v NodeJS

Nginx jako loadbalancer

Spouštím na 3 ruzných serverech tu stejnou NodeJS aplikaci, takže mi Nginx rozkláda zátěž rovnoměrně na všechny 3 servery. To je výchozí nastavení: 1. request obslouží 1. server, 2. request 2. server, 3. reques 3. server, 4. request 1. server a furt dokola… Nádhera. Dá se samozřejmě nastavit jiné chování load balanceru, ale tohle je pro rovnoměrné rozložení zátěže ideál. Já si Round Robin můžu dovolit, protože o mé sessions se stará redis a všechny instance aplikace ví, jak si o proměnné session říct. Pokud budete mít session třeba v cookies, pak bacha, protože jednotlivé requesty neví nic co bylo včera a nepředávájí si žádné informace, pokud se o to teda sami nějak nepostaráte…

# /etc/nginx/site-enabled/nazev-virtualu

upstream appka {
    server server1:8080;
    server server2:8080;
    server server3:8080;
}

server {
    gzip on;
    server_name neco.nekde.cz;
    location / {
        proxy_set_header        Host            $host;
        proxy_set_header        X-Real-IP       $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass  http://appka;
    }
}

nodejs, load balancer, nginx

NodeJS apka jako multithread aplikace

Aby to byla ještě větsí legrace, samotná aplikace se spouští pomocí knihovny cluster. To proto, že normální NodeJS je singlethread aplikace… Takže pokud máte vícejádrový procesor na serveru, tak pak pokud nepoužijete cluster, beží vaše aplikace jen v jednom vlákně. Super, ale né dokonalé… Nedokonalé třeba i proto, že když vám aplikace spadne, což se může stát, tak pokud nemáte pořešené jinak, např přes PM2, nebo nodemon, tak webová aplikace zemře a neběží.
Tenhle kus kódu mi zajistí, že aplikace poběží ve všech jádrech procesoru, v případě pádu některého vlákna znovu nastartuje. Každé vlákno má svou konektivitu do MongoDB…

// forkuje apku do vsech vlaken, navic jeste zajistuje jejich restart v pripade selhani...

var cluster = require('cluster');
var numCpus = cfg.numCpus || os.cpus().length;

if (cluster.isMaster) {
    console.log(`Cluster nastartuje ${numCpus} instanci aplikace.`);
    for (var i = 0; i < numCpus; i++) {
        cluster.fork();
    }
    cluster.on('online', function(worker) {
        console.log(`Instance aplikace  PID: ${worker.process.pid} bezi.`);
    });
    cluster.on('exit', function(worker, code, signal) {
       console.log(`Instance aplikace s PID  ${worker.process.pid} zemrela s navratovym kodem: ${code}, a signalem: ${signal}`);
       console.log('Startuji novou instanci aplikace.');
       cluster.fork();
   });
} else {
    // vytvoreni ExpressJS app s cfg jako DI
    var app = require('./server/server')(cfg);
    app.set('cfg', cfg);
    app.set('cfgFileName', cfgFileName);
    app.set('projectRootDir', __dirname);
    app.set('pugOptions', cfg.pugOptions ? cfg.pugOptions : {});

    // Start aplikace
    mongoose.connect(cfg.mongo.uri, cfg.mongo.options);
    mongoose.connection
        .on('error', function() {
            console.error(`[${process.pid}] CHYBA: Nepodarilo se pripojit k MongoDB`);
            process.exit(1);
        })
        .on('connected', function() {
            console.log(`[${process.pid}] MogoDB connected...`);
            app.listen(cfg.port, function() {
                console.log(`[${process.pid}] START: ${Date()} ${cfg.serverUrl}`);
            });

        })
        .on('disconnected', function() {
            console.log('disconnected');
        });
}

Pěkné je, že všechny vlákna aplikace jsou schovaná za jediným portem a master v clusteru se stará o rozdělování trafiku. To mi umožňuje maimální jednoduchost v Nginx konfiguraci, kde jen specifikuju jednotlivé fyzické servery a port onoho masteru aplikace… Super.

NodeJS a remote address

No a pokud si nenastavíte v Nginx konfiguráku proxy_set_header (jak je výše uvedeno), budete mít problém zjistit IP adresu vašeho živého návštěvníka webových stránek, protože Nginx, jako reverzní proxy a loadbalancer uvdete jako IP adresu IP adresu stroje, na kterém běží…

Proto jsou důležitá ty proxy hlavičky, protože až s nimi pak funguje:

function routeIndex(req, res){
  var clientIP = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress;
}