DODAVANJE NODE.JS-a U 2. DEO PROIZVODNJE: UPOTREBA NGINX DA BI SE IZBEGLO NODE.JS UČITAVANJE

Ovo je drugi deo kvazi serije o dodavanju node.js-a, u produkciju sistema (npr. Silly Face Society(zajednica smešnih faca)). Prethodni članak je pokrivao proces supervizora, koji kreira višestruke node.js procese, osluškujući različite portove, radi balansiranja učitavanja. Ovaj članak će se fokusirati na HTTP: kako baciti svetlost na buduća učitavanja node.js procesa. Update(usaglašavanje): Takođe sam postavio 3.deo na nulto početno vreme za slanje u ovoj postavci.

Naše zalihe su sastavljene od nginx i opslužuju spoljni saobraćaj preko proksija, do uzvodnih node.js procesa, pokrećući express.js. Kao što ću objasniti, nginx se koristi za skoro sve: gzip kodiranje, opsluživanje statičkih fajlova, HTTP caching, SSL rukovanje, balansiranje učitavanja i hranjenje klijenata na kašičicu. Ideja je da se upotrebi nginx za prevenciju od pogađanja naših node.js procesa nepotrebnim saobraćajem. Čak i više od toga, uklonili smo, koliko god smo mogli, opterećenja, na saobraćaj koji treba da pogodi node.js.

Previše priče. Evo naše nginx konfiguracije:

 http {
    proxy_cache_path  /var/cache/nginx levels=1:2 keys_zone=one:8m max_size=3000m inactive=600m;
    proxy_temp_path /var/tmp;
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    gzip on;
    gzip_comp_level 6;
    gzip_vary on;
    gzip_min_length  1000;
    gzip_proxied any;
    gzip_types text/plain text/html text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_buffers 16 8k;
 
    upstream silly_face_society_upstream {
      server 127.0.0.1:61337;
      server 127.0.0.1:61338;
      keepalive 64;
    }

    server {
        listen 80;
        listen 443 ssl;

        ssl_certificate /some/location/sillyfacesociety.com.bundle.crt;
        ssl_certificate_key /some/location/sillyfacesociety.com.key;
        ssl_protocols        SSLv3 TLSv1;
        ssl_ciphers HIGH:!aNULL:!MD5;

        server_name sillyfacesociety.com www.sillyfacesociety.com;

        if ($host = 'sillyfacesociety.com' ) {
                rewrite  ^/(.*)$  http://www.sillyfacesociety.com/$1  permanent;
        }

        error_page 502  /errors/502.html;

        location ~ ^/(images/|img/|javascript/|js/|css/|stylesheets/|flash/|media/|static/|robots.txt|humans.txt|favicon.ico) {
          root /usr/local/silly_face_society/node/public;
          access_log off;
          expires max;
        }

        location /errors {
          internal;
          alias /usr/local/silly_face_society/node/public/errors;
        }

        location / {
          proxy_redirect off;
          proxy_set_header   X-Real-IP            $remote_addr;
          proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
          proxy_set_header   X-Forwarded-Proto $scheme;
          proxy_set_header   Host                   $http_host;
          proxy_set_header   X-NginX-Proxy    true;
          proxy_set_header   Connection "";
          proxy_http_version 1.1;
          proxy_cache one;
          proxy_cache_key sfs$request_uri$scheme;
          proxy_pass         http://silly_face_society_upstream;
        }
    }
}

Takođe dostupne kao gist.

Možda ovaj prikaz koda i nije naročito prosvetljujuć: Probaću da prođem kroz konfiguraciju i ukažem na to, na koji način ovo balansira express.js kod.

The nginx <-> node.js link
Na prvom mestu: kako ćemo navesti nginx da proxy / balansira učitavanje saobraćaja za naše node.js instance? Pretpostavićemo da pokrećemo dve instance express.js-a na portovima 61337 i 61338. Bacite pogled na upstream(uzvodno) sekciju:

http {
    ...
    upstream silly_face_society_upstream {
      server 127.0.0.1:61337;
      server 127.0.0.1:61338;
      keepalive 64;
    }
    ...
}

upstream direktive upućuju na to, da te dve instance rade u tandemu, kao upstream server za nginx. keepalive 64; usmerava nginx da sačuva minimum od 64 HTTP/1.1 konekcija sa proxy serverom, u bilo kom trenutkt. Ovo je minimum minimuma: ukoliko bude više saobraćaja, onda će nginx otvoriti više konekcija ka proxy-u.

upstream sam po sebi nije dovoljan – nginx mora da zna kako i kada da usmeri saobraćaj ka node-u. Magija se dešava uokviru ourserver(naš server) sekcije. Skrolovanjem na dno ekrana, nalazimo lokaciju / sekciju, poput:

http {
    ...
    server {
        ...
        location / {
          proxy_redirect off;
          proxy_set_header   X-Real-IP            $remote_addr;
          proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
          proxy_set_header   Host                   $http_host;
          proxy_set_header   X-NginX-Proxy    true;
          ...
          proxy_set_header   Connection "";
          proxy_http_version 1.1;
          proxy_pass         http://silly_face_society_upstream;
        }
        ...
    }
}

Ova sekcija je prolaz za saobraćaj koji se ne uklapa ni u koja pravila: mi imamo node.js da upravlja saobraćajem i nginx proxy odgovor. Najvažniji deo sekcije je proxy_pass – ovo govori nginx-u da upotrebi uzvodni server, koji smo definisali na višem nivou u konfiguraciji. Sledeće na redu je proxy_http_version koja govori nginx-u, da bi trebalo da upotrebi HTTP/1.1 za konekcije sa proxy serverom. Upotreba HTTP/1.1 štedi preopterećenje uspostavljanja konekcije između nginx-a i node.js-a, uz svaki proxy zahtev, i ima velik uticaj na latentnost odgovora. Konačno, imamo nekoliko proxy_set_header direktiva koje govore našim express.js procesima da je ovo proxy zahtev, a ne direktan. Potpuno objašnjenje može se naći u HttpProxyModule dokumentima.

Ovaj deo konfiguracije je minimum potreban da nginx opslužuje port 80 i pravi proxy-je od naših node.js procesa uokviru toga. Ostatak ovog članka će pokrivati upotrebu nginx dodataka, za bacanje svetlosti na učitavanje saobraćaja na node.js-u.

Presretanje statičkog fajla
Iako express.js ima ugrađeno upravljanje staatičkim fajlovima, preko nekih povezanih medija, ne bi trebalo da ga koristite uopšte. Nginx može mnogo više od upravljanja statičkim fajlovima i može da spreči zahteve za ne-dinamičkim sadržajem, radi sprečavanja zagušivanja naših node procesa. Location(lokacija) naredba, u pitanju, je:

http {
    ...
    server {
        ...
        location ~ ^/(images/|img/|javascript/|js/|css/|stylesheets/|flash/|media/|static/|robots.txt|humans.txt|favicon.ico) {
          root /usr/local/silly_face_society/node/public;
          access_log off;
          expires max;
        }
        ...
    }
}
 

Svaki zahtev za tim da URI startuje sa slikama, img, css, js, ... biće uklapan po ovoj lokaciji. U mojoj express.js strukturi direktorijuma, javni/ direktorijum se koristi da skladišti statička podešavanja – stvari poput CSS-a, javascript-a i tome slično. Upotrebom root(usmeri) dajem instrukcije nginx-u da opsluži ove fajlove, bez da se ikada obrati uspostavljenim serverima. expires max(krajnji rok isticanja); sekcija je caching predlog, da se ova podešavanja ne mogu mutirati. Za ostale sajtove bi moglo biti prihvatljivije da koriste brže isticanje roka cache-a, preko nečega nalik na expires 1h;. Potpuna informacija može biti u nginx-ovom HttpHeadersModule.

Caching(cache memorisanje)
Po mom mišljenju, bilo koji cache je bolji nego nijedan. Sajtovi sa ekstremno teškim saobraćajem će koristiti sve moguće vrste cach rešenja, uključujući i varnish za HTTP ubrzanje i memcached za fragmentarno cach memorisanje i cach memorisanje upita. Naš sajt nema tako visok saobraćaj, ali će nam cach, ipak, bogatstvo u troškovima servera. Radi jednostavnosti konfiguracije, odlučio sam da koristim nginx-ov ugrađeni cach.

Nginx-ov ugrađeni cache je krut: kada neki uzvodni server daje HTTP predlog za nslov, poput Cache-Control, on nam omogućuje cach sa vremenom trajanja koje se podudara sa predlogom za naslov. Uokviru vremenskog ograničenja, sledeći zahtev će preuzeti cache fajl sa diska, umesto da pogađa node.js proces. Za podešavanje cach-a, postavio sam dve naredbe u http sekciji nginx konfiguracije:

http {
    ...
    proxy_cache_path  /var/cache/nginx levels=1:2 keys_zone=one:8m max_size=3000m inactive=600m;
    proxy_temp_path /var/tmp;
    ...
}

Ova dva reda daju instrukciju nginx-u, da ćemo ga koristiti u cache modu. proxy_cache_path određuje ciljani direktorijum za naš cache, dubinu direktorijuma (nivoe), max_size(maksimalnu veličinu) cache-a i neaktivirano vreme trajanja. Što je još važnije, to određuje veličinu unutar-memorijskih ključeva za fajlove, kroz keys_zone(zonu ključeva). Kada nginx dobije zahtev, on obračunava MD5 hash i koristi set ovih ključeva da pronađe odgovarajući fajl na disku. Ukoliko on nije dostupan, zahtev će otići do naših uspostavljenih node.js procesa. Konačno, da bi naši proxy zahtevi koristili ovaj cache, moramo da promenimo lokaciju / sekciju, tako da uključuji neke informacije memorisane u cache-u:

http {
  server {
     ...
     location / {
          ...
          proxy_cache one;
          proxy_cache_key sfs$request_uri$scheme;
          ...
     }
     ...
  }
}

Ovo obaveštava nginx, da on može da koristi jedan set ključeva, da u cache smesti buduće zahteve. MD5 hashe-ovi će biti obračunati upotrebom proxy_cache_key

Imamo jedan nedostatak: express.js neće opsluživati odgovarajući HTTP cache predlog naslova. Ja sam na brzinu napisao komad medija koji će omogućiti ovu funkcionalnost.

cacheMiddleware = cacheMiddleware = (seconds) -> (req, res, next) ->
    res.setHeader "Cache-Control", "public, max-age=#{seconds}"
    next()

Nije prihvatljivo ovaj medij koristiti globalno – određene zahteve (npr. post zahtevi koji utiču na stanje servera) ne bi trebalo nikada smeštati na cache-u. Kao takvog, ja ga koristim za usmeravanje u mojoj express.js aplikaciji:

...
app.get "/recent", cacheMiddleware(5 * 60), (req, res, next) ->
  #When someone hits /recent, nginx will cache it for 5 minutes!
...

GZIP
GZIP je operativac za HTTP. Kompresovanjem nadolazećih zahteva, klijenti će manje vremena provoditi u petljanju sa vašim serverom i na kraju svi štede novac. Vi možete koristiti neke express.js posrednike za gzipp-ovanje odlazećih zahteva, ali nginx će to bolje uraditi i ostaviti express.js sa više izvora. Da biste omogućili GZIP-ovane zahteve u nginx-u, dodajte sledeće redove u http odeljak:

http {
    ...
    gzip on;
    gzip_comp_level 6;
    gzip_vary on;
    gzip_min_length  1000;
    gzip_proxied any;
    gzip_types text/plain text/html text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
    gzip_buffers 16 8k;
    ...
}

Neću ulaziti u detalje po pitanju toga šta ove naredbe rade. Kao i cache, bilo kakav gzip bolji nego nikakav gzip. Za veću kontrolu, postoji na hiljade mikro-optimizacija koje možete izvesti, a sve su odlično dokumentovane u nginx-ovom HttpGzipModule.

SSL
Nastavljajući našu ideju da node.js-u prepustimo upravljanje samo bazičnim HTTP-om, dolazimo do SSL-a. Sve dok su uzvodni serveri uokviru poverene mreže, nema smisla kriptovati saobraćaj dalje od nginx-a – node.js može opsluživati HTTP saobraćaja, dok nginx kriptuje. Ovu postavku je lako konfigurisati; u serverskoj naredbi možete reći nginx-u kako da konfiguriše SSL:

http {
   ...
   server {
        ...
        listen 443 ssl;
        ssl_certificate /some/location/sillyfacesociety.com.bundle.crt;
        ssl_certificate_key /some/location/sillyfacesociety.com.key;
        ssl_protocols        SSLv3 TLSv1;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ...
   }
}

Listen(slušaj) govori nginx-u da omogući SSL saobraćaj. ssl_certificate i ssl_certificate_key govore nginx-u gde da pronađe sertifikate za vaše servere. ssl_protocols i ssl_ciphers daju instrukcije
nginx-u kako da opsluži saobraćaj. Evo detalja: potpune konfiguracijske opcije su dostupne na nginx-ovom HttpSslModule.

Skoro smo stigli. Prethodna konfiguracija nateraće nginx na dekriptovanje saobraćaja i proxy-ovanje nekriptovanih zahteva ka našem uzvodnom serveru. Međutim, uzvodni server bi mogao zahtevati da zna, da li je u pitanju siguran kontekst, ili ne. Ovo bi moglo služiti SSL-osposobljenim podešavanjima iz CDN-ova, poput Cloudfront-a, ili za odbacivanje zahteva koji dolaze nekriptovani. Dodajte sledeće redove u datu lokaciju / sekciju:

http {
   ...
   server {
       ...
       location / {
          ...
          proxy_set_header   X-Forwarded-Proto $scheme;
          ...
        }
      }
   }
}

Ovo će poslati HTTP predlog naslova, dole u vaše node.js procese. I još jednom, obrisao sam neke od posredujućih uređaja, kako bih SSL-detekciju učinio nešto lakšom:

app.use (req, res, next) ->
  req.forwardedSecure = (req.headers["x-forwarded-proto"] == "https")
  next()

Uokviru vaših ruta, req.forwardedSecure će biti istinito, ukoliko nginx upravlja HTTPS saobraćajem. Kao preporuku, silly face society koristi SSL za Facebook autentifikaciju odabrane razmene, kada se korisnici uloguju na korišćenje SSO (single sign on-singl prijavljivanje) na njihovim telefonima. Kao produžetak implementacije ovoga, takođe sam izbacio sigurnu verziju tog sajta ovde.

Obavijanje
Ovime smo pokrili, kako da (pod1) podesimo node.js kao uzvodni server za nginx i (pod2) kako da osvetlimo učitavanje na uzvodnom serveru,prepuštajući nginx-u da void računa o balansiranju učitavanja, statičkim fajlovima, SSL, GZIP i cache memorisanju. Caveat emptor: silly face society je još uvek nije lansirala. Konfiguracija koja se nalazi iznad, bazira se na personalnom testiranju i istraživanju: još uvek nismo dostigli prozvodni nivo saobraćaja. Ako neko i dalje čita, pozdraviću sugestije za poboljšanjima, u komentarima.

Srećna okolnost je, da je naša ideja o upotrebi express.js-a za HTTP saobraćaj, slučajnost. Počeli smo da koristimo node.js radi pružanja socket-servera za “party mod”, u realnom vremenu, silly face society-a. Kako smo se približavali lansiranju, odlučili smo da dodamo Draw Something-esque pasivni mod, kako bi Silly Face Society moglo imati početni zalet za uspešno lansiranje. Umesto da ponovo ispisujemo naše tehnološke zalihe, upotrebili smo, što smo više mogli, starog koda i izložili HTTP interfejs. Kada bih sve morao ispočetka, preispitao bih naše izbore: node.js je nezgodna zverka za CRUD-aplikacije.

Da li se na vašu zainteresovanost za nginx nadovezuje zainteresovanost za smešne face? Isprobajte Silly Face Society na iPhone-u.