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 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; }