Skip to content

Bölüm 11 / 13

Üretime alma

Ortam değişkeni, PM2, Docker'da Node, günlükleme, sağlık kontrolü.

16 dakikalık okuma

Bu hattın ilk dersinde bir senet açılmıştı: "SSR sunucusu, işletilen bir Node sürecidir." Bugün tam olarak ödeniyor. Uygulamanı kendi makinende çalıştırmak ile onu bir sunucuda işletmek arasındaki fark, bu dersin tamamıdır: yapılandırma ve sırların dışarıdan verilmesi, sürecin kim tarafından ayakta tutulacağı, kapanma sinyalinin nasıl karşılanacağı, logun nereye akacağı ve sağlık sorusunun kime hangi cevabı vereceği. Buradaki hataların ortak özelliği geliştirmede hiç görünmemeleridir — ilk dağıtımda kaybolan istekler, grace period boyunca kapanmayan konteynerler ve teşhis edilemeyen çökmeler hep bu dersin konusudur.

01Ne öğreneceksin

  • SSR senedinin ödenmesi: SSR'ın üretimde ne anlama geldiği ve kimin sorumluluğuna girdiği
  • Yapılandırma ve sırlar: dışarıdan verme disiplini, NODE_ENV=production'ın gerçek etkisi
  • Süreci kim ayakta tutar: PM2, systemd ve orkestratör; "çök ve yeniden başla" felsefesinin aracı
  • Graceful shutdown: SIGTERM, sıralama, bekleyen isteklerin bitirilmesi, zorla çıkış zamanlayıcısı
  • Yapılandırılmış log (stdout'a JSON), korelasyon kimliği ve console.log'un neden yetmediği
  • Liveness ile readiness ayrımı ve sağlık ucunun doğru derinliği
  • Docker'da Node: çok aşamalı imaj, kök olmayan kullanıcı ve PID 1 sinyal tuzağı

02Senedin ödenmesi: SSR bir sunucu işletmektir

10. ve 11. hatlarda SSR'ı bir işleme yöntemi olarak gördün: HTML istemcide değil sunucuda üretilir, ilk boyama hızlanır, arama motoru içeriği görür. Bu hattın ilk dersinde ise altını çizmiştik: o "sunucu" bir Node sürecidir. Şimdi bunun faturasını kalem kalem çıkaralım — çünkü mülakatta "SSR'ın dezavantajı nedir?" sorusunun zayıf cevabı "biraz yavaş olabilir", güçlü cevabı ise şu tablodur:

SPA (statik dosyalar)SSR (Node süreci)
Dağıtım çıktısıHTML/JS/CSS — CDN'e kopyalanırÇalışan uygulama — işletilir
ÖlçeklemeCDN'in işi, pratikte sınırsızSüreç/replika sayısı, bellek ve CPU planı
Çökme etkisiYok (dosya sunulur)Sayfa hiç açılmaz — süreç denetçisi şart
Sızıntı riskiYokModül düzeyi durum tüm kullanıcıların ortağı
CPU profiliİstemcideSunucuda; render senkron ve olay döngüsünü tutar
BakımDosya yayımlamakSürüm yükseltme, güvenlik yaması, izleme, nöbet

Üç satır özellikle önemlidir. Sızıntı satırı 11. hattaki uyarının kanıtıdır ve 7. derste kendi gözünle gördün: sunucu tek süreç olduğundan modül düzeyinde tutulan kullanıcı verisi bütün ziyaretçilerin ortak belleğidir — SSR'da bu bir güvenlik hatasıdır, çünkü A kullanıcısının verisi B'nin sayfasında işlenmiş olarak dönebilir. CPU satırı 10. dersin diliyle okunur: React'in renderToString çağrısı senkrondur, yani ağır bir sayfa render'ı olay döngüsünü doğrudan bloklar ve o an gelen bütün isteklerin gecikmesini yükseltir; bu yüzden akış tabanlı render (renderToPipeableStream) ve önbellek stratejisi SSR'ın ayrılmaz parçasıdır. Bakım satırı ise organizasyon sorusudur: "frontend ekibi bir sunucu işletmeye hazır mı?" Cevap hayırsa, ya yönetilen bir platform kullanılır ya da SSG/ISR gibi statiğe yakın seçenekler tercih edilir. Karar teknik değil, işletme kapasitesi kararıdır.

Bu dersin geri kalanı, o işletmeyi doğru yapmanın beş parçasıdır: yapılandırma, süreç, kapanış, log, sağlık.

03Yapılandırma ve sırlar

Kural 4. derste konmuştu ve 9. derste şemaya bağlanmıştı: yapılandırma dışarıdan gelir, ortam değişkeni daima dizgedir, açılışta doğrulanır ve geçersizse süreç hemen çöker. Üretim tarafında buna üç madde eklenir.

Birincisi NODE_ENV=production'ın gerçek etkisi. Bu değişken sihirli değildir ama gerçek işler yapar: Express şablon önbelleğini açar ve yığın izini istemciye göndermeyi keser, birçok kütüphane geliştirme uyarılarını ve ek doğrulamalarını kapatır, React üretim yolunu seçer. Unutulduğunda uygulama çalışmaya devam eder — sadece daha yavaş ve daha konuşkan olur; bu yüzden fark edilmesi zordur. Bağımlılık tarafında karşılığı npm ci --omit=dev'dir: 3. dersteki "lock gerçektir, npm ci onu uygular" cümlesinin üretim yüzü.

İkincisi sırların yeri. Sırlar koda ve imaja girmez; .env dosyası depoya commit edilmez. Ortam değişkeni pratik bir taşıyıcıdır ama zayıflıkları bilinmelidir: süreç ortamı /proc/<pid>/environ üzerinden okunabilir, docker inspect çıktısında görünür, hata izleme araçlarına gönderilen bağlamda kazara sızabilir. Daha güvenli olan, sırrı dosya olarak bağlamak (Docker/K8s secret mount) ya da bir sır yöneticisinden çalışma anında çekmektir. Detayı 13. hatta işleyeceğiz; bu derste alışkanlık şudur: sırrı loglama, hata mesajına koyma, istemciye gönderdiğin yapılandırmaya karıştırma. Frontend'e giden yapılandırma ayrı bir nesnedir ve içinde yalnız herkese açık değerler olur.

Üçüncüsü değişmezlik. Aynı imaj bütün ortamlarda çalışır; ortamlar arasındaki fark yalnız yapılandırmadır. "Test için ayrı build almak" hem 3. dersteki lock disiplinini hem de dağıtımın en temel güvencesini bozar: test ettiğin şey ile yayımladığın şey aynı olmalıdır.

04Süreci kim ayakta tutar?

6. derste bir felsefe kurmuştuk: beklenmeyen hatada "yakala, logla, devam et" bir zombi süreç bırakır; doğrusu çök ve yeniden başla'dır. Ama bu cümlenin ikinci yarısı uygulamanın işi değildir — denetçi dışarıdadır. Üç seçenek vardır:

AraçNeredeVerdiğiNot
PM2Tek sunucu / VMYeniden başlatma, cluster modu, log dosyası, pm2 startupNode dünyasının klasiği; konteynerde gereksiz katman
systemdLinux VMRestart=always, sinyal yönetimi, journaldİşletim sisteminin kendi aracı; ek bağımlılık yok (17. hat)
Docker + orkestratörKonteynerYeniden başlatma politikası, replika, sağlık kontrolü, dağıtımBugünün varsayılanı; süreç denetimi platformun işi
pm2 start server.js -i max --name api      # -i max: çekirdek sayısı kadar süreç
pm2 logs api                               # akan log
pm2 startup && pm2 save                    # sunucu yeniden başlayınca kendiliğinden aç

PM2'nin cluster modu 10. dersteki cluster modülünün paketlenmiş hâlidir; aynı uyarı geçerlidir: süreçler bellek paylaşmaz, bellekte durum tutulmaz. Konteyner dünyasında ise konteyner başına tek Node süreci tercih edilir ve çoğaltma orkestratöre bırakılır — böylece her kopyanın kaynak limiti, sağlığı ve dağıtımı tek bir yerden yönetilir.

Yeniden başlatmanın bir tuzağı vardır: çökme döngüsü. Açılışta çöken bir uygulama (ör. geçersiz yapılandırma) saniyede onlarca kez başlatılıp öldürülür, loglar dolar ve gerçek hata görünmez olur. Denetçilerin hepsinde artan bekleme (backoff) ve "kısa sürede N kez çöktüyse durdur" ayarı vardır; üretimde bu ayar açık olmalıdır. Ayrıca uygulama doğru çıkış kodu vermelidir: normal kapanış 0, ölümcül hata sıfırdan farklı — denetçi kararını buna göre verir.

05Graceful shutdown: kapanışın doğru sırası

Her dağıtım bir kapanıştır. Orkestratör eski sürümü durdurmak için önce SIGTERM gönderir, belirli bir süre (grace period) bekler, sonra SIGKILL ile öldürür. Bu arada süreç ne yapmalı? Yanlış cevap "hemen çıkmak"tır: o anda işlenmekte olan istekler yarıda kalır, kullanıcı 502 görür, yazılmakta olan bir transaction yarım kalabilir. Doğru sıra şudur:

let kapaniyor = false;

async function kapat(sinyal) {
  if (kapaniyor) return;                          // ikinci sinyalde tekrar girme
  kapaniyor = true;
  log.info({ sinyal }, "kapanış başlıyor");

  const zorla = setTimeout(() => {                // 1) emniyet supabı
    log.error("zamanında kapanamadı, zorla çıkılıyor");
    process.exit(1);
  }, 15_000).unref();

  hazirMi = false;                                // 2) readiness'i kapat: yeni istek gelmesin
  await bekle(5_000);                             // 3) yük dengeleyicinin fark etmesini bekle

  await new Promise(c => sunucu.close(c));        // 4) yeni bağlantı yok, mevcutları bitir
  sunucu.closeIdleConnections?.();                //    keep-alive boşta bağlantıları kapat

  await havuz.end();                              // 5) DB havuzu, kuyruk, istemciler
  clearTimeout(zorla);
  log.info("temiz kapanış");
  process.exit(0);
}

process.on("SIGTERM", () => kapat("SIGTERM"));    // orkestratör / systemd
process.on("SIGINT",  () => kapat("SIGINT"));     // Ctrl+C

Sıradaki iki adım en çok atlananlardır. Üçüncü adım (readiness'i kapatıp birkaç saniye beklemek) Kubernetes'in klasik yarışını çözer: yük dengeleyicinin pod'u listeden çıkarması anlık değildir; server.close() hemen çağrılırsa o aradaki istekler "connection refused" alır. Önce "beni listeden çıkar" dersin, sonra kapatırsın. Dördüncü adımın ayrıntısı da önemlidir: server.close() yeni bağlantı kabulünü durdurur ve aktif isteklerin bitmesini bekler, ancak HTTP keep-alive ile açık duran boştaki bağlantılar yüzünden geri çağrı hiç tetiklenmeyebilir; bu yüzden closeIdleConnections() çağrılır ve her hâlükârda bir zorla çıkış zamanlayıcısı kurulur.

Son olarak zamanlama: uygulamanın kapanış bütçesi, platformun grace period'undan kısa olmalıdır (ör. 15 sn'ye karşı 30 sn). Aksi hâlde SIGKILL gelir ve bütün bu çaba boşa gider.

06Log: stdout'a yapılandırılmış satırlar

5. hatta .NET tarafında ILogger, seviyeler ve kapsam (scope) kavramlarını öğrendin; Node'da karşılığı pino ya da winston'dır. Üç kural:

(1) console.log üretim aracı değildir. Serbest metin üretir — makine ayrıştıramaz, seviye yoktur, filtrelenemez; üstelik çıktı bir dosyaya ya da bloke olabilen bir kanala yönlendirildiğinde process.stdout.write senkron davranabilir ve olay döngüsünü tutar (10. dersin gecikme metriği bunu yakalar). Yapılandırılmış logcular JSON üretir ve yazmayı tamponlar.

(2) Log stdout'a yazılır, dosyaya değil. Konteyner dünyasının kuralıdır: uygulama logu bir akış olarak görür; toplama, döndürme (rotation), saklama ve arama platformun işidir. Uygulamanın içine dosya döndürme kodu yazmak, birden çok replika ve salt okunur dosya sistemiyle birlikte hemen sorun çıkarır.

(3) Her satır bağlam taşır. En değerlisi korelasyon kimliğidir: isteğin başında üretilen (ya da gelen başlıktan alınan) bir kimlik, o isteğin ürettiği bütün log satırlarına ve aşağı akıştaki çağrılara eklenir. Node'da bunu parametre geçirmeden yapmanın yolu AsyncLocalStorage'dır:

import { AsyncLocalStorage } from "node:async_hooks";
import pino from "pino";

export const baglam = new AsyncLocalStorage();
const temelLog = pino({
  level: config.LOG_SEVIYE,
  redact: ["req.headers.authorization", "*.parola", "*.token"],   // sır asla loglanmaz
});

app.use((req, res, next) => {
  const istekId = req.header("x-request-id") ?? crypto.randomUUID();
  const log = temelLog.child({ istekId, yol: req.path });
  res.setHeader("x-request-id", istekId);
  baglam.run({ log, istekId }, next);             // zincirin tamamı aynı bağlamı görür
});

export const log = new Proxy({}, {                // her yerden: log.info(...)
  get: (_h, k) => (baglam.getStore()?.log ?? temelLog)[k],
});

Seviye disiplini: debug geliştirmede, info iş olaylarında (istek tamamlandı, sipariş oluştu), warn beklenen ama istenmeyen durumlarda (yeniden deneme, düşülen istek), error müdahale gerektiren hatalarda. Her isteğe bir info satırı (metot, yol, durum, süre) iyi bir tabandır; her satırda gövdenin tamamını basmak hem maliyet hem gizlilik ihlalidir. Kişisel veri ve sırlar redact ile maskelenir — 14. hattaki log ve KVKK konularının tohumu budur.

07Sağlık kontrolü: iki farklı soru

Sağlık ucu tek bir /health yazıp geçilecek bir detay değildir; iki ayrı soruya iki ayrı cevap gerekir:

Liveness — "süreç yaşıyor mu?"Readiness — "istek alabilir mi?"
Başarısız olursaSüreç öldürülür ve yeniden başlatılırTrafikten çıkarılır, süreç yaşamaya devam eder
Neye bakarYalnız sürecin kendisi (ve olay döngüsü gecikmesi)Bağımlılıklar: DB, önbellek, kritik dış servis
Tipik hataBağımlılık kontrolü koymakKapanış sırasında kapatmayı unutmak

En sık yapılan hata birinci satırdadır: liveness ucuna veritabanı kontrolü koymak. Veritabanı birkaç dakika yavaşladığında bütün pod'lar "ölü" sayılır ve hepsi aynı anda yeniden başlatılır — geçici bir aksaklık, kendi kendini büyüten bir kesintiye dönüşür. Doğrusu: liveness sığ, readiness derindir.

app.get("/healthz", (_req, res) => {             // liveness: sığ
  const p99 = olcer.percentile(99) / 1e6;         // 10. dersin metriği
  res.status(p99 < 1000 ? 200 : 503).json({ durum: "ok", gecikmeP99: p99 });
});

app.get("/readyz", async (_req, res) => {         // readiness: bağımlılıklarla
  if (!hazirMi) return res.status(503).json({ durum: "kapaniyor" });
  try {
    await Promise.race([                          // zaman aşımı ŞART: 6. dersin kuralı
      havuz.query("select 1"),
      new Promise((_, red) => setTimeout(() => red(new Error("db timeout")), 2000)),
    ]);
    res.json({ durum: "hazir", surum: config.SURUM });
  } catch {
    res.status(503).json({ durum: "hazir-degil" });
  }
});

Üç ayrıntı: sağlık uçları kimlik istemez ama iç bilgi de sızdırmaz — bağlantı dizgesi, sürüm ağacı, ortam değişkeni dökümü koyulmaz. Kontroller zaman aşımlı olmalıdır, yoksa yavaş bir bağımlılık sağlık ucunu da askıya alır. Ve sağlık ucu erişim logunda gürültü yapar; genellikle log filtresine alınır.

08Docker'da Node: imaj ve PID 1 tuzağı

17. hatta Docker'ı ayrıntısıyla işleyeceğiz; burada Node'a özgü olan kısım var. Örnek çok aşamalı bir imaj:

FROM node:22-alpine AS kur
WORKDIR /app
COPY package*.json ./
RUN npm ci                                   # önce bağımlılık: katman önbelleği için
COPY . .
RUN npm run build

FROM node:22-alpine AS uretim
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=kur /app/dist ./dist
USER node                                    # kök olmayan kullanıcı
EXPOSE 3000
CMD ["node", "dist/server.js"]               # npm start DEĞİL — sinyal tuzağı

Son satır bu dersin en pahalı ayrıntısıdır. PID 1 sinyal tuzağı: konteynerde ilk süreç PID 1'dir ve Linux, PID 1'e varsayılan sinyal davranışı uygulamaz — SIGTERM için bir dinleyici yoksa sinyal hiçbir şey yapmaz; konteyner grace period boyunca bekler, sonra SIGKILL ile öldürülür ve 5. bölümdeki graceful shutdown hiç çalışmaz. İki koşul birden sağlanmalıdır: (1) uygulamada process.on("SIGTERM", ...) kayıtlı olmalı; (2) sinyal Node'a ulaşmalıdır. CMD ["npm", "start"] yazıldığında PID 1 npm olur ve npm sinyali çocuğuna güvenilir biçimde iletmez — belirti tanıdıktır: "dağıtımda her seferinde birkaç istek kayboluyor ve konteynerler tam 30 saniyede kapanıyor". Çözüm doğrudan node çalıştırmak, gerekiyorsa docker run --init ya da tini gibi küçük bir init süreci kullanmaktır (bu aynı zamanda zombi süreç toplama işini de üstlenir).

Diğer Node'a özgü noktalar: package*.json'ı önce kopyalayıp npm ci çalıştırmak katman önbelleğini korur — kaynak değiştiğinde bağımlılıklar yeniden kurulmaz; .dockerignore içine node_modules, .env, .git yazılır (yerel node_modules'ün imaja kopyalanması hem şişirir hem yanlış platform ikilileri taşıyabilir); USER node ile kök olmayan kullanıcıya düşülür; bellek limiti verilen bir konteynerde --max-old-space-size limitin altında ayarlanır (10. ders). Sağlık kontrolü orkestratörde tanımlanır; imaja gömülen HEALTHCHECK tek makinelik kurulumlar için yeterlidir.

09Mini alıştırma — dağıtıma hazır hâle getir

Yaklaşık 20 dakika · 9. dersin API'si üzerinde · npm i pino · Docker kuruluysa daha iyi

  1. Log: console.log'ları pino'ya çevir; korelasyon kimliği middleware'ini AsyncLocalStorage ile ekle ve yanıt başlığına da yaz. Bir istek at, o isteğin ürettiği bütün satırlarda aynı istekId'yi gör. redact listesine bir alan ekleyip maskelendiğini doğrula.
  2. Graceful shutdown: 5 saniye süren bir uç yaz. İsteği başlat ve hemen kill -TERM <pid> gönder. Önce kapanış kodu olmadan dene — isteğin yarıda kesildiğini gör; sonra bölüm 05'teki sırayı ekleyip isteğin tamamlandığını, ardından sürecin çıktığını gör.
  3. Sağlık: /healthz ve /readyz uçlarını ayrı ayrı yaz. Veritabanını durdur: /readyz 503 dönerken /healthz'in 200 dönmeye devam ettiğini doğrula. Bu farkın neden hayat kurtardığını iki cümleyle yaz.
  4. PID 1 tuzağı: yukarıdaki Dockerfile'ı iki sürümde kur — CMD ["npm","start"] ve CMD ["node","dist/server.js"]. İkisini de çalıştırıp docker stop ver ve time ile ölç: birincisi ~10 saniye sürer (grace period dolar), ikincisi anında temiz kapanır.
  5. Hızlı çök: DATABASE_URL'i boş bırakarak konteyneri başlat. Uygulamanın açılmadığını ve anlamlı bir hata bastığını doğrula; ardından yeniden başlatma politikasının çökme döngüsüne girdiğini gözle ve backoff ayarını not et.

10Kendini sına

"SSR'ı seçmek bir Node sürecini işletmeyi kabul etmektir" cümlesini aç: SPA'ya göre üretimde hangi yeni sorumluluklar doğar, hangi ikisi güvenlik ve gecikme açısından kritiktir?

SPA'nın dağıtım çıktısı statik dosyalardır: CDN'e kopyalanır, ölçekleme CDN'in işidir, "çökme" diye bir durum yoktur. SSR'da çıktı çalışan bir uygulamadır; buna karşılık gelen sorumluluklar: süreç denetimi ve yeniden başlatma, replika/bellek/CPU planı, sürüm yükseltme ve güvenlik yamaları, log toplama, sağlık kontrolü ve nöbet. İki tanesi kritiktir. Güvenlik: sunucu tek süreç olduğundan modül düzeyinde tutulan durum bütün kullanıcıların ortak belleğidir — istemcide zararsız görünen "global bir değişkende kullanıcıyı tut" kalıbı SSR'da bir kullanıcının verisinin başkasının sayfasında görünmesine yol açar; istek verisi istekle taşınır. Gecikme: sunucu tarafı render senkron çalışır ve olay döngüsünü tutar; ağır bir sayfa yalnız kendini değil o an gelen bütün istekleri yavaşlatır — bu yüzden akış tabanlı render ve önbellek stratejisi SSR'ın ayrılmaz parçasıdır. Kararın kendisi de teknik olmaktan çok işletme kapasitesi sorusudur: ekip bir sunucu işletmeye hazır değilse yönetilen platform ya da statiğe yakın seçenekler (SSG/ISR) tercih edilir.

SIGTERM geldiğinde uygulaman ne yapmalı? Adımları sırasıyla ve gerekçeleriyle anlat; readiness'i kapatıp beklemek neden ilk adımlardan biri, zorla çıkış zamanlayıcısı neden gerekli?

Sıra: (1) Zorla çıkış zamanlayıcısı kur (ör. 15 sn) — kapanışın kendisi asılırsa süreç sonsuza kadar yaşamasın; bu süre platformun grace period'undan kısa olmalıdır, yoksa SIGKILL gelir ve emek boşa gider. (2) Readiness'i kapat — sağlık ucu artık 503 döner. (3) Birkaç saniye bekle — yük dengeleyicinin pod'u listeden çıkarması anlık değildir; hemen kapatırsan o aradaki istekler "connection refused" alır, bu Kubernetes'in klasik yarışıdır. (4) server.close() — yeni bağlantı kabul edilmez, işlenmekte olan istekler tamamlanır; keep-alive ile boşta duran bağlantılar geri çağrıyı hiç tetiklemeyebileceği için closeIdleConnections() de çağrılır. (5) Kaynakları kapat — DB havuzu, kuyruk bağlantıları, dış istemciler; sonra process.exit(0). Ayrıca ikinci sinyalde tekrar girmeyi engelleyen bir bayrak tutulur ve normal kapanışta 0, ölümcül hatada sıfırdan farklı çıkış kodu verilir — denetçi kararını buna göre verir. Bu kodun hiç çalışmadığı bir durum vardır: konteynerde PID 1 npm ise sinyal Node'a ulaşmaz; doğrudan node çalıştırılmalıdır.

Liveness ile readiness'i ayır: hangisi neye bakar, başarısızlıkta ne olur? Liveness ucuna veritabanı kontrolü koymak neden tehlikelidir? Sağlık uçlarının iki kuralı nedir?

Liveness "süreç yaşıyor mu?" sorusudur; başarısız olursa platform süreci öldürür ve yeniden başlatır. Bu yüzden yalnız sürecin kendisine bakmalıdır — en fazla olay döngüsü gecikmesi gibi sürecin kendi sağlığını gösteren bir metrik eklenir (kilitlenmiş bir süreç "ayakta" görünür, oysa hiçbir isteği cevaplayamaz). Readiness "istek alabilir mi?" sorusudur; başarısız olursa süreç yaşamaya devam eder ama trafikten çıkarılır; bağımlılıkları (veritabanı, önbellek, kritik dış servis) burada kontrol edilir ve kapanış başlarken ilk kapatılan budur. Liveness'a veritabanı kontrolü koymak tehlikelidir çünkü veritabanı birkaç dakika yavaşladığında bütün kopyalar "ölü" sayılır ve aynı anda yeniden başlatılır: geçici bir aksaklık, kendi kendini büyüten bir kesintiye dönüşür — üstelik yeniden başlayan süreçler veritabanına yeni bağlantı fırtınası yapar. Kural: liveness sığ, readiness derindir. İki kural daha: sağlık kontrolleri zaman aşımlı olmalıdır (yavaş bağımlılık sağlık ucunu askıya almamalı) ve iç bilgi sızdırmamalıdır (bağlantı dizgesi, ortam dökümü, yığın izi yok).

Üretimde console.log yerine yapılandırılmış log kullanmanın gerekçelerini say; korelasyon kimliği nedir ve Node'da parametre geçirmeden nasıl taşınır? Logda neye asla yer yoktur?

Gerekçeler: (1) Ayrıştırılabilirlik — serbest metin makine tarafından işlenemez; JSON satırlar filtrelenir, aranır, uyarıya bağlanır. (2) Seviye — debug/info/warn/error ayrımı olmadan üretimde gürültüyü kısamazsın; seviye ortam değişkeninden verilir. (3) Performans — process.stdout.write hedefe göre senkron davranabilir ve olay döngüsünü tutar; pino gibi kütüphaneler yazmayı tamponlar. (4) Bağlam — her satıra ortak alanlar (servis, sürüm, istek kimliği) eklenir. Log stdout'a yazılır, dosyaya değil: toplama, döndürme ve saklama platformun işidir; uygulamanın içine dosya döndürme yazmak çok replikalı ve salt okunur dosya sistemli kurulumlarda hemen sorun çıkarır. Korelasyon kimliği, bir isteğin bütün log satırlarını ve aşağı akıştaki çağrılarını birbirine bağlayan tekil kimliktir; istekle gelen başlıktan alınır ya da üretilir, yanıt başlığına da yazılır. Node'da her fonksiyona parametre geçirmeden taşımanın yolu AsyncLocalStorage'dır: istek başında bir bağlam açılır, o isteğin asenkron zincirindeki her kod aynı depoyu görür. Logda asla yer olmayanlar: parolalar, jetonlar ve authorization başlığı, tam kart/kimlik numaraları ve gereksiz kişisel veri, gövdelerin tamamı — bunlar redact ile maskelenir; hem güvenlik hem maliyet hem de mevzuat meselesidir.

11Sınav köşesi

Yazılıda nasıl sorulur

(1) SSR kullanan bir uygulamanın SPA'ya göre üretimdeki ek sorumlulukları nelerdir? (2) NODE_ENV=production neyi değiştirir? (3) Sırlar neden imaja ve depoya konmaz; ortam değişkeninin zayıflığı nedir? (4) Node uygulamasını üretimde ayakta tutmak için hangi araçlar kullanılır? (5) "Çök ve yeniden başla" yaklaşımı neyi savunur? (6) SIGTERM alındığında yapılması gerekenleri sırasıyla yazın. (7) server.close() tek başına neden yetmeyebilir? (8) Liveness ve readiness probe farkı nedir? (9) Liveness'a bağımlılık kontrolü koymanın riski nedir? (10) Uygulama logu neden stdout'a yazılır? (11) Korelasyon kimliği nedir? (12) Dockerfile'da CMD ["npm","start"] yazmanın sakıncası nedir?

Sözlüde nasıl anlatılır — 2 dakikalık taslak

Düzen: (1) Çerçeve — "Üretime almak beş başlıktır: yapılandırma, süreç, kapanış, log, sağlık. SSR'ı seçmek de bunları kabul etmektir; SSR'ın çıktısı dosya değil, işletilen bir Node sürecidir." (2) Yapılandırma — "Ayarlar dışarıdan gelir, açılışta şemayla doğrulanır ve geçersizse hızlı çökerim; yanlış kurulumla hizmet vermektense hiç açılmamak iyidir. Sır ne koda ne imaja girer, loglanmaz." (3) Süreç — "Beklenmeyen hatada yakala-logla-devam etmem, çökerim; ayakta tutmak denetçinin işidir: PM2, systemd ya da orkestratör. Konteynerde süreç başına tek Node, çoğaltmayı platform yapar." (4) Kapanış — "SIGTERM'de önce readiness'i kapatır, yük dengeleyicinin fark etmesini bekler, sonra server.close ile mevcut istekleri bitirir, havuzları kapatır ve zorla çıkış zamanlayıcısıyla kendimi emniyete alırım." (5) Kıdem vuruşu — "İki tuzağı ismiyle bilirim: Dockerfile'da npm start yazarsam PID 1 npm olur, SIGTERM Node'a ulaşmaz ve bütün graceful shutdown kodu ölü koddur — doğrudan node çalıştırırım. Ve liveness ucuna asla veritabanı kontrolü koymam; veritabanı yavaşladığında bütün kopyalar aynı anda yeniden başlar ve küçük bir aksaklık tam kesintiye dönüşür."