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çekleme | CDN'in işi, pratikte sınırsız | Süreç/replika sayısı, bellek ve CPU planı |
| Çökme etkisi | Yok (dosya sunulur) | Sayfa hiç açılmaz — süreç denetçisi şart |
| Sızıntı riski | Yok | Modül düzeyi durum tüm kullanıcıların ortağı |
| CPU profili | İstemcide | Sunucuda; render senkron ve olay döngüsünü tutar |
| Bakım | Dosya yayımlamak | Sü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ç | Nerede | Verdiği | Not |
|---|---|---|---|
| PM2 | Tek sunucu / VM | Yeniden başlatma, cluster modu, log dosyası, pm2 startup | Node dünyasının klasiği; konteynerde gereksiz katman |
| systemd | Linux VM | Restart=always, sinyal yönetimi, journald | İşletim sisteminin kendi aracı; ek bağımlılık yok (17. hat) |
| Docker + orkestratör | Konteyner | Yeniden başlatma politikası, replika, sağlık kontrolü, dağıtım | Bugü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 olursa | Süreç öldürülür ve yeniden başlatılır | Trafikten çıkarılır, süreç yaşamaya devam eder |
| Neye bakar | Yalnız sürecin kendisi (ve olay döngüsü gecikmesi) | Bağımlılıklar: DB, önbellek, kritik dış servis |
| Tipik hata | Bağımlılık kontrolü koymak | Kapanış 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
- Log:
console.log'ları pino'ya çevir; korelasyon kimliği middleware'iniAsyncLocalStorageile 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.redactlistesine bir alan ekleyip maskelendiğini doğrula. - 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. - Sağlık:
/healthzve/readyzuçlarını ayrı ayrı yaz. Veritabanını durdur:/readyz503 dönerken/healthz'in 200 dönmeye devam ettiğini doğrula. Bu farkın neden hayat kurtardığını iki cümleyle yaz. - PID 1 tuzağı: yukarıdaki Dockerfile'ı iki sürümde kur —
CMD ["npm","start"]veCMD ["node","dist/server.js"]. İkisini de çalıştırıpdocker stopver vetimeile ölç: birincisi ~10 saniye sürer (grace period dolar), ikincisi anında temiz kapanır. - 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."
