RE:NODE

აპლიკაციები13 წუთის საკითხავი

Express API-ის გაშვება production-ში: პორტები, proxy, შეცდომები

Express-ის პარამეტრები, რომლებიც მხოლოდ სერვერზე აქვს მნიშვნელობა: binding, trust proxy, middleware-ის რიგი, async შეცდომები, keep-alive 502-ები და სუფთა გამორთვა.

0 მკითხველი

Express აპლიკაცია, რომელიც შენს კომპიუტერზე მუშაობს, დაახლოებით თხუთმეტი ხაზითაა დაშორებული იმ აპლიკაციისგან, რომელიც სერვერზე იმუშავებს, და ამ ხაზებიდან თითოეული ისაა, რასაც სახელმძღვანელოები გამოტოვებენ, რადგან ლოკალურად მნიშვნელობა არ აქვს. აპლიკაცია უნდა მიება იმ პორტს, რომელიც ჰოსტმა მისცა, ყველა ინტერფეისზე. მან უნდა იცოდეს, რომ proxy-ს უკან დგას, თორემ req.ip proxy-ის მისამართია და შენი rate limiter ყველას ერთ ადამიანად ითვლის. მას სჭირდება შეცდომის დამმუშავებელი, რომელიც უარყოფილ promise-ებს იჭერს, გამორთვა, რომელიც მიმდინარე მოთხოვნებს ასრულებს, და keep-alive დროები, რომლებიც მის წინ მდგომ კომპონენტთან არ ეჯიბრება.

ეს არის ის სია, იმ რიგით, როგორითაც ის გვტკენს. ის ვრცელდება ნებისმიერ Node HTTP სერვერზე - Fastify, Koa, უბრალო http.createServer - მაგრამ კონკრეტიკა აქ Express-ისაა, რადგან ყველაზე მეტი ადამიანი სწორედ მას ავრცელებს.

მოუსმინე სწორ პორტს და სწორ ინტერფეისს#

ორი ხაზი, რომელთაგან ორივე არასწორია ნაგულისხმევ სახელმძღვანელოში:

javascript
const port = Number(process.env.PORT) || 3000;app.listen(port, () => {  console.log(`listening on ${port}`);});

პორტი გარემოდან მოდის. ჰოსტინგის პლატფორმა ანაწილებს ერთს და ცვლადით გატყობინებს; 3000-ის მყარად ჩაწერა ნიშნავს, რომ აპლიკაცია ან ვერ მიება, ან მიება პორტს, რომელზეც არაფერი მარშრუტდება. RE:NODE-ზე აპლიკაციის გეგმებში ერთი პორტის allocation შედის, რომელიც ჩანს Network ჩანართზე და პროცესისთვის ხელმისაწვდომია გარემოს ცვლადად, რომელიც Startup ჩანართზე მოიცემა.

ინტერფეისი უფრო დახვეწილი ნახევარია. app.listen(port) ჰოსტის გარეშე ყველა ინტერფეისზე მიება, რაც გინდა. app.listen(port, "127.0.0.1") მიება loopback-ს კონტეინერის შიგნით, და მაშინ ვერაფერი გარედან - მის წინ მდგომი reverse proxy-ის ჩათვლით - ვერ დაუკავშირდება. სიმპტომი: აპლიკაცია იდეალურად იწყება, წერს "listening" და არაფერს პასუხობს: proxy-დან connection refused, ბრაუზერში 502 და შენს ლოგში ერთი ხაზიც არა. თუ აშკარად უნდა დაწერო, დაწერე "0.0.0.0".

იგივე ეხება ყველაფერს, რაც კი უსმენს. metrics endpoint, debug სერვერი, WebSocket upgrade გზა: ყველა ერთსა და იმავე ერთ allocation-ს უკან ცხოვრობს, ამიტომ მიამაგრე ისინი იმავე სერვერზე და არ გახსნა მეორე პორტი, რომელიც არ გაქვს.

Proxy-ს უკან: trust proxy და კლიენტის რეალური IP#

production-ში შენი აპლიკაცია ბრაუზერს არ ელაპარაკება. ის reverse proxy-ს ელაპარაკება, რომელმაც TLS დაასრულა, და თავდაპირველი მოთხოვნის დეტალები მხოლოდ header-ებად გადარჩება.

443allocated portafter middlewareClientHTTPSProxy slotTLS, X-Forwarded-ForExpressmiddleware stackRoute handlerშენი კოდიDatabase slotმოთხოვნები
რა დგას კლიენტსა და შენს route handler-ს შორის

კონფიგურაციის გარეშე Express req.ip-ად proxy-ის მისამართს აბრუნებს, req.protocol-ად http-ს და req.secure-ად false-ს. ყველაფერი, რაც მათზეა დამოკიდებული, მაშინ ჩუმად არასწორია: IP-ით გასაღებული rate limit-ები ერთ გლობალურ ვედროდ იქცევა, audit ლოგებში ყველა მომხმარებლისთვის ერთი მისამართი ჩაიწერება, და გადამისამართება კანონიკურ ჰოსტზე ადამიანებს http://-ზე აგზავნის.

javascript
app.set("trust proxy", 1);

1 ნიშნავს "ენდე ერთ hop-ს". ის Express-ს ეუბნება, აიღოს X-Forwarded-For-ის ბოლო ჩანაწერი კლიენტად, რადგან ზუსტად ერთმა proxy-მ დაამატა. დათვალე შენი hop-ები: ერთი proxy არის 1, CDN proxy-ს წინ არის 2. ასევე შეგიძლია გადასცე subnet, სია ან დასახელებული წინასწარი პარამეტრებიდან ერთ-ერთი, მაგალითად loopback.

სწორი პარამეტრით req.ip კლიენტის რეალური მისამართია, req.protocol არის https და req.secure ჭეშმარიტია. RE:NODE-ზე proxy slot კლიენტის მისამართს X-Forwarded-For-ში გადმოსცემს და სერტიფიკატს თავად ამუშავებს - მიუთითე A ჩანაწერი ნაჩვენებ მისამართზე და ის გაიცემა და ავტომატურად განახლდება 21-დღიან ფანჯარაში. მექანიკა აღწერილია პოსტებში რას აკეთებს reverse proxy სინამდვილეში და დომენის მიმართვა შენს სერვერზე.

თუ შენი API WebSocket-ებს ემსახურება, proxy უნდა თანახმა იყოს upgrade-ის გატარებაზე, ხოლო idle timeout-ებს უფრო დიდი მნიშვნელობა აქვს, ვიდრე ჩვეულებრივ მოთხოვნებზე. WebSocket-ები reverse proxy-ს უკან ამ კონკრეტულ შემთხვევას აღწერს.

Middleware იმ რიგით, რომლითაც უნდა იყოს#

Express middleware-ს რეგისტრაციის რიგით უშვებს და რამდენიმე production პრობლემა კონფიგურაციის კი არა, რიგის პრობლემაა.

javascript
import express from "express";import helmet from "helmet";import compression from "compression";import cors from "cors";import pinoHttp from "pino-http";const app = express();app.set("trust proxy", 1);app.use(pinoHttp());                       // log everything, including failuresapp.use(helmet());                         // headers before any response bodyapp.use(compression());                    // before the routes that produce bodiesapp.use(cors({ origin: ["https://app.example.com"], credentials: true }));app.use(express.json({ limit: "100kb" }));

რას აკეთებს თითოეული სინამდვილეში:

  • helmet ადგენს response header-ების ჯგუფს და შლის X-Powered-By-ს. JSON API-სთვის მნიშვნელოვანია X-Content-Type-Options: nosniff, X-Frame-Options, referrer policy და HSTS. მისი ნაგულისხმევი Content Security Policy HTML-ზეა გათვლილი, ამიტომ თუ იმავე აპლიკაციიდან დოკუმენტაციას ან ადმინ გვერდს ემსახურები, პოლიტიკა გაასწორე და helmet-ს ნუ წაშლი. შეამოწმე HSTS max-age იმ ვერსიაში, რომელიც დააყენე, სანამ ვინმეს რიცხვს დაუსახელებ.
  • compression gzip-ით ტკეპნის პასუხებს ზღვარზე ზემოთ. ის ღირს ნებისმიერი ზომის JSON-ისთვის და უაზროა სურათებისთვის, ვიდეოსთვის ან ყველაფრისთვის, რაც უკვე შეკუმშულია. მას Brotli-ს მხარდაჭერა არ აქვს. ის ასევე buffer-ს იყენებს, რაც ტეხავს server-sent events-ს, თუ ყოველი ჩაწერის შემდეგ res.flush()-ს არ გამოიძახებ, და ზედმეტია, თუ წინ მდგომი proxy უკვე კუმშავს - ორჯერ კეთება CPU-ს ტყუილად ხარჯავს.
  • cors უნდა გაეშვას შენს route-ებამდე, რადგან preflight OPTIONS მათ არასოდეს აღწევს. credentials: true wildcard origin-თან ვერ შეიწყობა; ბრაუზერები ამ წყვილს უარყოფენ, ამიტომ origin-ები აშკარად ჩამოთვალე.
  • `express.json`-ს ნაგულისხმევი body ლიმიტი 100 KB აქვს. გაზარდე ეს შეგნებულად, თუ უფრო დიდ payload-ებს იღებ, და გლობალურად ნუ გაზრდი იმიტომ, რომ ერთი endpoint ატვირთვებს იღებს - body ლიმიტი ყველაზე იაფი დაცვაა denial-of-service-ისგან. Rate limit-ები და ბოროტად გამოყენება ამ ზედაპირის დანარჩენ ნაწილს მოიცავს.

ლოგირება პირველია, რომ მოთხოვნა, რომელიც სხვა middleware-ში ვარდება, მაინც ჩაიწეროს. ლოგე JSON-ში stdout-ზე: პანელის კონსოლი მას ცოცხლად აჩვენებს, ხოლო სტრუქტურირებული ხაზები არის განსხვავება grep-სა და გამოცნობას შორის. რის შენახვა და რამდენ ხანს არის აღწერილი პოსტში ლოგები, რომლებიც ღირს შენახვად.

შეცდომები და დამმუშავებელი, რომელიც ალბათ არ გაქვს#

Express შეცდომის დამმუშავებელს არგუმენტების რაოდენობით ცნობს: ოთხი არგუმენტი, რეგისტრირებული ყველაფრის შემდეგ.

javascript
app.use((req, res) => {  res.status(404).json({ error: "not_found" });});app.use((err, req, res, next) => {  req.log.error({ err }, "request failed");  if (res.headersSent) return next(err);  res.status(err.status || 500).json({ error: "internal_error" });});

სამი დეტალი წყვეტს, იმუშავებს ეს თუ არა.

Async handler-ები. Express 4-ში async (req, res) => {}-ის შიგნით უარყოფილ promise-ს Express საერთოდ არ იჭერს. ის ხდება unhandled rejection, რაც მიმდინარე Node-ში პროცესს წყვეტს - ანუ მონაცემთა ბაზის ერთი წარუმატებელი გამოძახება ყველა მიმდინარე მოთხოვნას წაიღებს. გამოსწორებაა handler-ების შეფუთვა, ამის გამასწორებელი პაკეტის გამოყენება, ან Express 5-ზე გადასვლა, რომელიც async handler-ებიდან უარყოფებს შენს შეცდომის middleware-ს გადასცემს. თუ განაახლებ, გაითვალისწინე, რომ Express 5-მა route pattern-ის parser-იც შეცვალა, ამიტომ რამდენიმე ძველი path pattern გადასაწერია; თუ route-ები აღარ ემთხვევა, ნახე migration-ის შენიშვნები.

Response body. Express-ის ჩაშენებული შეცდომის დამმუშავებელი პასუხში stack trace-ს ათავსებს, თუ NODE_ENV არ არის production. stack trace თავდამსხმელს გეუბნება შენი დირექტორიების სტრუქტურას, დამოკიდებულებების ვერსიებს და ხშირად შენი მონაცემთა ბაზის დრაივერს. დააყენე NODE_ENV=production Startup ჩანართზე და დააბრუნე შეცდომის კოდი და არა გამონაკლისის შეტყობინება.

პროცესის დონის დამმუშავებლები. ჯერ ჩაწერე ლოგში და მერე გადაწყვიტე, და არა გადაყლაპე:

javascript
process.on("unhandledRejection", (err) => {  console.error("unhandled rejection", err);});process.on("uncaughtException", (err) => {  console.error("uncaught exception", err);  process.exit(1);});

დაუჭერელი გამონაკლისი პროცესს უცნობ მდგომარეობაში ტოვებს, ამიტომ გასვლა და პლატფორმისთვის გადატვირთვის საშუალების მიცემა არის სწორი რეაქცია. რაც არასწორია, არის მისი დაჭერა და გაგრძელება, სწორედ ასე ამთავრებს API მოთხოვნების მომსახურებას პროცესიდან ნახევრად დახურული მონაცემთა ბაზის pool-ით.

კონფიგურაცია, რომელიც სწრაფად ვარდება#

წაიკითხე კონფიგურაცია ერთხელ, გაშვებისას, და უარი თქვი დაწყებაზე, თუ რამე აკლია:

javascript
const required = ["DATABASE_URL", "SESSION_SECRET"];const missing = required.filter((name) => !process.env[name]);if (missing.length) {  console.error(`missing configuration: ${missing.join(", ")}`);  process.exit(1);}

პროცესი, რომელიც ორ წამში გადის გასაგები ხაზით, უსასრულოდ უფრო ადვილი დიაგნოსტირებადია, ვიდრე ის, რომელიც იწყება, იღებს ტრაფიკს და აბრუნებს 500-ებს იმიტომ, რომ connection string იყო undefined. ის ასევე კარგად ერწყმის restart პოლიტიკებს: სერვერი, რომელიც ვერ იწყება, ციკლში არ ზის და მუშაობის იმიტაციას არ აკეთებს.

საიდუმლოებები Startup ჩანართზე გარემოს ცვლადებში უნდა იყოს და არა repository-ში და არა commit-ში მოხვედრილ .env-ში. სად შეინახო საიდუმლოებები აპლიკაციის სერვერზე ამის არგუმენტს გვაძლევს და გვეუბნება, რა უნდა გააკეთო იმ დღეს, როცა ერთ-ერთი გაჟონავს. მონაცემთა ბაზის მონაცემები პანელიდან მოდის, როცა database slot-ს ქმნი - აპლიკაციის გეგმებში ორი შედის - და კავშირი pool-ირებულია, სადაც ბევრი API თავის პირველ ტევადობის შეცდომას უშვებს. Connection pool-ები და ლიმიტები ხსნის, რატომ არის 100 კავშირის pool პატარა მონაცემთა ბაზაზე უფრო ნელი, ვიდრე 10-ის.

გაშვება და გაჩერება მოთხოვნების დაკარგვის გარეშე#

ყოველი deploy შენს პროცესს აჩერებს. ეს შეცდომად დაუჯდება თუ არა შენს მომხმარებლებს, დამოკიდებულია იმაზე, რა ხდება ბოლო ორ წამში.

javascript
const server = app.listen(port);server.keepAliveTimeout = 65_000;server.headersTimeout = 70_000;process.on("SIGTERM", () => {  server.close(() => process.exit(0));  server.closeIdleConnections();  setTimeout(() => process.exit(1), 10_000).unref();});

server.close() ახალ კავშირებს აღარ იღებს და ელოდება მიმდინარე მოთხოვნების დასრულებას. თავისთავად ის შეიძლება გაჩერდეს, რადგან უქმად მდგომი keep-alive კავშირებიც კავშირებია; closeIdleConnections() (Node 18.2 და უფრო ახალი) მათ ასუფთავებს, ხოლო ტაიმერი მარაგია მოთხოვნისთვის, რომელიც არასოდეს მთავრდება.

ორი timeout არის გამოსავალი კონკრეტული და გამაგიჟებელი სიმპტომისთვის: ხანდახან 502-ები მცირე ტრაფიკზე, აპლიკაციის ლოგში არაფრით. Node-ის ნაგულისხმევი keepAliveTimeout 5 წამია. თუ წინ მდგომი proxy უქმ კავშირებს ამაზე დიდხანს ინახავს, ის ხანდახან გაუშვებს მოთხოვნას კავშირზე, რომელსაც Node იმავე წამს ხურავს, და proxy bad gateway-ს აცხადებს. Node-ის keep-alive ფანჯრის proxy-ისაზე გრძელად გაკეთება რბოლას აქრობს, ხოლო headersTimeout კიდევ უფრო დიდი უნდა იყოს, თორემ ის კავშირს პირველი დახურავს.

დაამატე health endpoint, რომელიც მონაცემთა ბაზას არ ეხება:

javascript
app.get("/healthz", (req, res) => res.json({ ok: true, uptime: process.uptime() }));

Liveness პასუხობს კითხვას "ცოცხალია ეს პროცესი" და მხოლოდ ამას უნდა აკეთებდეს. health check, რომელიც query-ს უშვებს, ნელ მონაცემთა ბაზას restart ციკლად აქცევს, ხოლო ეს დეგრადირებულ სერვისს - მწყობრიდან გასვლად. თუ readiness-ის გამოჩენა გინდა, გააკეთე ის მეორე, ცალკე endpoint-ად. Graceful shutdown და health check-ები განსხვავებას განიხილავს.

გაშვება პრაქტიკაში#

start ბრძანება lockfile-იდან აყენებს და ერთ პროცესს უშვებს:

bash
npm ci --omit=dev && node dist/server.js

npm ci აყენებს ზუსტად იმას, რასაც package-lock.json ამბობს, და ვარდება, თუ lockfile და manifest არ ემთხვევა, რაც სერვერზე გინდა; npm install რაღაც ახალს წყვეტს და ასე ტყდება შეუხებელი აპლიკაცია გადატვირთვისას. თუ პროექტი TypeScript-ია, ან CI-ში დააკომპილირე და გააგზავნე შედეგი, ან ამოიღე --omit=dev, რადგან კომპილატორი devDependency-ა. npm ci და npm install დეტალებს შეიცავს.

დააკავშირე repository და ნუ ატვირთავ ფაილებს. RE:NODE-ზე ეს არის GitHub, ხანმოკლე ტოკენებიანი GitHub App-ის გავლით, რომ კერძო repository-ებიც იმუშაოს, ორი დამოუკიდებელი გადამრთველით: pull ბრენჩისა ყოველ გაშვებაზე და deploy push-ზე, რომელიც უკვე გაშვებულ სერვერს გადატვირთავს. თითო deploy-ზე ერთი სტრიქონი გეუბნება, რომელი push არის ცოცხალი. სახელმძღვანელო არის პოსტში Node.js აპლიკაციის გაშვება GitHub-იდან.

პროცესის მენეჯერი არ გჭირდება. პანელი უკვე რესტარტავს პროცესს, როცა ის გადის, აჩვენებს ლოგს და გრაფიკზე გამოსახავს მეხსიერებასა და CPU-ს ლიმიტებთან შედარებით, რაც უმეტესობის მიზეზია, რის გამოც PM2-ს აყენებენ - PM2 თუ ჰოსტინგის პანელი პატიოსანი შედარებაა. ალბათ clustering-იც არ გჭირდება: node:cluster პროცესებს ამრავლებს რამდენიმე ბირთვის გამოსაყენებლად, ხოლო გეგმაზე ნახევარი ბირთვით ის მეხსიერებასა და კონტექსტის გადართვას ამატებს უარყოფითი სარგებლით. ჯერ გაზომე და წაიკითხე რატომ კვდება შენი Node აპლიკაცია 2 GB-ზე 4 GB გეგმაზე, სანამ ჩათვლი, რომ მეხსიერება ისე იქცევა, როგორც ელოდები.

restart მყისიერი არ არის, ამიტომ deploy მოკლე შეფერხებას ჯდება. ერთი პროცესით მის ნულამდე დაყვანას გზა არ აქვს, მაგრამ შეგიძლია პატარა გახადო: ინსტალაცია სწრაფი დატოვე, deploy გააკეთე დაბალი ტრაფიკის დროს და წაიკითხე zero-downtime deploy-ები სერვერზე, რომელსაც ყველაფრის მხოლოდ ერთი აქვს ტექნიკებისთვის, რომლებიც ერთ მანქანაზე მართლა მუშაობს.

რა ტყდება პირველი დატვირთვისას#

API, რომელიც წამში ათ მოთხოვნაზე კარგად არის და ორმოც-ორმოცდაათზე გამოუყენებელი, ჩვეულებრივ ოთხი გზით ვარდება, და არცერთს დიდი გეგმა არ ასწორებს.

გამავალი გამოძახებები timeout-ის გარეშე. fetch-ის ნაგულისხმევი ქცევა უსასრულოდ ლოდინია. ერთი ნელი upstream მაშინ ყველა მოთხოვნას აკავებს, რომელიც მასზეა დამოკიდებული, კავშირები გროვდება და პროცესს მეხსიერება ამოეწურება მათ ჭერაში. ყველა გამავალ გამოძახებას მიეცი ბოლო ვადა:

javascript
const response = await fetch(url, { signal: AbortSignal.timeout(3_000) });

სანამ აქ ხარ, კავშირები ხელახლა გამოიყენე. Node-ის გლობალური fetch კავშირებს origin-ზე ცოცხლად ინახავს, მაგრამ ბიბლიოთეკა, რომელიც ყოველ გამოძახებაზე ახალ agent-ს ქმნის, ყოველ ჯერზე TLS handshake-ს იხდის, რაც წუთში რამდენიმე ასეულ მოთხოვნაზე შენი CPU-ს გაზომვად ნაწილს ხარჯავს.

კონკურენტულობაზე ლიმიტის არარსებობა. ათი ერთდროული მოთხოვნა, რომელთაგან თითოეული მონაცემთა ბაზის კავშირს ხსნის და მეგაბაიტს იკავებს, კარგია. ხუთასი - არა. pool ბუნებრივი ადგილია ამის შესაზღუდად, რადგან მოთხოვნა, რომელიც კავშირს ელოდება, იაფია, ხოლო ის, რომელიც მას ინახავს, არა. pool-ის ზომა შეარჩიე იმის მიხედვით, რის მომსახურებაც მონაცემთა ბაზას რეალურად შეუძლია და არა იმის, რაც შენს აპლიკაციას უნდა.

არაფერი, რაც ქეშირებადია, ქეშირებადად არ არის მონიშნული. Cache-Control header პასუხზე, რომელიც საათში ერთხელ იცვლება, ტრაფიკის უმეტეს ნაწილს შენამდე მოსვლამდე აშორებს და ერთი ხაზი ღირს. ETag Express-ში ნაგულისხმევად ჩართულია, რაც განმეორებით მოთხოვნებს 304-ებად აქცევს, მაგრამ ეს მხოლოდ მაშინ ეხმარება, თუ კლიენტები revalidate-ს აკეთებენ. HTTP ქეშირების header-ები ახსნილი სრული სურათია; მოკლე ვერსია ის არის, რომ ყველაზე იაფი მოთხოვნა ის არის, რომელსაც არასოდეს ემსახურები.

სინქრონული სამუშაო event loop-ზე. დიდი JSON payload-ის დამუშავება, პაროლის hash-ირება მაღალი cost factor-ით, სურათის ზომის შეცვლა, PDF-ის აგება: სანამ ამათგან რომელიმე მუშაობს, პროცესი არავის ემსახურება. პაროლის hash-ირებამ უნდა გამოიყენოს იმ ბიბლიოთეკის ასინქრონული API, რომელიც აირჩიე. ყველაფერი უფრო მძიმე worker-ს ან queue-ს ეკუთვნის.

ამ ყველაფერზე მეტად დგას მეხსიერების ლიმიტი. RE:NODE-ზე კონტეინერი, რომელიც თავის ლიმიტს აღწევს, ჩერდება და სუფთად იწყება ხელახლა და არა swap-ში რჩება, ამიტომ გაჟონვა ჩანს, როგორც restart ყოველ რამდენიმე საათში და არა ნელი დაცემა, ხოლო საათში სამი მოულოდნელი restart გაფრთხილებას წარმოქმნის და ავტომატურად ხსნის ticket-ს. ეს ქცევა კარგი ნაგულისხმევია და მიზეზი, რომ ყოველი deploy-ის შემდეგ კონსოლში მეხსიერების გრაფიკს უყურო და არა მხოლოდ მაშინ, როცა რაღაც უკვე არასწორია.

FAQ#

რატომ მუშაობს ჩემი API ლოკალურად, მაგრამ სერვერზე timeout-ს იღებს?

თითქმის ყოველთვის bind მისამართი. localhost კონტეინერის შიგნით გარედან მიუწვდომელია. მოუსმინე 0.0.0.0-ს ან საერთოდ გამოტოვე ჰოსტის არგუმენტი და გამოიყენე გარემოდან მოსული პორტი და არა მყარად ჩაწერილი.

მჭირდება nginx Express-ის წინ?

გჭირდება რაღაც, რაც TLS-ს ასრულებს, და მართულ პლატფორმაზე ის უკვე არსებობს. კონტეინერის შიგნით საკუთარი nginx-ის გაშვება მას ორმაგებს. რაც გჭირდება, არის სწორად დაყენებული trust proxy, რომ შენმა აპლიკაციამ იცოდეს, რა გააკეთა მის წინ მდგომმა proxy-მ.

მართლა მნიშვნელოვანია NODE_ENV=production?

დიახ. ის Express-ს უკრძალავს შეცდომის პასუხებში stack trace-ების დაბრუნებას, რთავს view-ების ქეშირებას და მას ბიბლიოთეკების გრძელი სია კითხულობს, რომ development ქცევა გამორთოს. ეს ერთი გარემოს ცვლადია და ის რეალურ ქცევას ცვლის.

რატომ ვიღებ ხანდახან 502 შეცდომებს ჩემს ლოგებში არაფრით?

ზემოთ აღწერილი keep-alive რბოლა. Node უქმ კავშირს იმავე მომენტში ხურავს, როცა proxy მას ხელახლა იყენებს. დააყენე server.keepAliveTimeout proxy-ის idle timeout-ზე მეტი და server.headersTimeout მასზე მეტი.

გავუშვა რამდენიმე Node პროცესი cluster-ით?

მხოლოდ მაშინ, თუ ერთზე მეტი ბირთვი გაქვს გამოსაყენებელი, და მხოლოდ გაზომვის შემდეგ. ერთი event loop I/O-ზე დამოკიდებული სამუშაოს დიდ რაოდენობას ართმევს თავს; clustering გეხმარება, როცა CPU-ზე ხარ დამოკიდებული, და პროცესზე მეხსიერება ღირს. ვებ აპლიკაციის ზომა გაშვების დღისთვის ტრაფიკის შეფასებებს worker-ების რაოდენობად აქცევს.

სად უნდა წავიდეს ატვირთვები და გენერირებული ფაილები?

არა deploy დირექტორიაში, რადგან შემდეგი pull ხელს შეუშლის. ჩაწერე ტრეკინგ ხის გარეთ არსებულ გზაზე ან object storage-ში და გახსოვდეს, რომ პატარა გეგმაზე დისკი ერთნიშნა გიგაბაიტებში იზომება. database slot სტრუქტურირებულ მონაცემებს ამუშავებს; მონაცემთა ბაზის ჰოსტინგი მოიცავს შემთხვევას, როცა ის აპლიკაციის სერვერს აჭარბებს.


კომენტარები

სრულიად ანონიმურად: ანგარიშის, ელფოსტის და cookie-ის გარეშე. ინახება მხოლოდ სახელი, ტექსტი და დრო - სხვა არაფერი. ბმულების რაოდენობა ლიმიტირებულია.

0/2000