Node.js uygulamalarını production ortamında çalıştırmak, geliştirme ortamından çok farklı bir deneyim. Tek bir process ile çalışan Node.js, modern sunucuların sahip olduğu çok çekirdekli CPU’ların potansiyelini tam anlamıyla kullanamıyor. İşte bu noktada Cluster modu devreye giriyor ve Nginx ile birleştiğinde gerçekten güçlü bir yapı ortaya çıkıyor. Bu yazıda, Node.js Cluster uygulamasını nasıl kuracağını, PM2 ile nasıl yöneteceğini ve Nginx’i önüne nasıl koyacağını adım adım anlatacağım.
Node.js Cluster Nedir ve Neden Önemli?
Node.js tek thread üzerinde çalışır. Bu tasarım, I/O yoğun işlemler için mükemmel, ancak CPU yoğun işlemler veya yüksek trafik senaryolarında bir darboğaz oluşturabiliyor. Örneğin 8 çekirdekli bir sunucun var ve Node.js’in yalnızca bir çekirdeği kullandığını düşün. Geri kalan 7 çekirdek boşta bekliyor. Bu tam anlamıyla kaynakların israfı.
Cluster modülü sayesinde ana process (master) birden fazla worker process başlatıyor ve gelen istekler bu worker’lar arasında dağıtılıyor. Her worker bağımsız bir process olduğu için bir worker çökse bile diğerleri çalışmaya devam ediyor. Bu sayede hem performans artıyor hem de uygulama dayanıklılığı ciddi ölçüde yükseliyor.
Gerçek dünya senaryosu olarak düşün: Bir e-ticaret sitesi çalıştırıyorsun. Kampanya döneminde trafik 10 katına çıkıyor ve tek process ile bu yükü kaldırmak neredeyse imkansız. Cluster yapısıyla tüm CPU çekirdeklerini kullanmaya başlayınca sunucu aynı yükü çok daha rahat kaldırıyor.
Uygulama Ortamının Hazırlanması
Önce temel gereksinimleri kuralım. Ubuntu 22.04 üzerinde çalışacağız.
# Node.js 20.x LTS kurulumu
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# Nginx kurulumu
sudo apt-get install -y nginx
# PM2 global kurulumu
sudo npm install -g pm2
# Kurulum doğrulama
node --version
npm --version
pm2 --version
nginx -v
Şimdi örnek uygulamamızı oluşturalım. Gerçek bir projede bu kısım mevcut uygulamanla değişecek elbette.
# Uygulama dizini oluştur
sudo mkdir -p /var/www/nodeapp
sudo chown $USER:$USER /var/www/nodeapp
cd /var/www/nodeapp
# Package.json oluştur
npm init -y
# Express kur
npm install express
Node.js Cluster Uygulaması Yazımı
İki farklı yaklaşımla cluster yapısı kurabilirsin. İlki native Node.js cluster modülü, ikincisi PM2’nin bunu otomatik yapmasına izin vermek. Her ikisini de göreceğiz.
Önce native cluster modülü ile temel yapıyı anlayalım:
// /var/www/nodeapp/cluster.js
const cluster = require('cluster');
const os = require('os');
const express = require('express');
const numCPUs = os.cpus().length;
if (cluster.isMaster) {
console.log(`Master process ${process.pid} başlatıldı`);
console.log(`${numCPUs} worker başlatılıyor...`);
// CPU sayısı kadar worker başlat
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// Worker çökerse yenisini başlat
cluster.on('exit', (worker, code, signal) => {
console.log(`Worker ${worker.process.pid} çöktü. Yenisi başlatılıyor...`);
cluster.fork();
});
cluster.on('online', (worker) => {
console.log(`Worker ${worker.process.pid} aktif`);
});
} else {
// Her worker için Express uygulaması
const app = express();
app.use(express.json());
app.get('/health', (req, res) => {
res.json({
status: 'OK',
pid: process.pid,
uptime: process.uptime(),
memory: process.memoryUsage()
});
});
app.get('/api/data', (req, res) => {
// Simüle edilmiş veri işleme
const result = heavyComputation();
res.json({
data: result,
worker: process.pid
});
});
function heavyComputation() {
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += Math.random();
}
return sum;
}
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Worker ${process.pid} port ${PORT} üzerinde dinliyor`);
});
}
Bu yapıda master process worker’ları yönetiyor, her worker bağımsız olarak aynı portu dinliyor. Node.js işletim sistemi seviyesinde gelen bağlantıları worker’lar arasında dağıtıyor.
PM2 ile Cluster Yönetimi
Native cluster kullanmak işe yarasa da PM2, cluster yönetimini çok daha pratik hale getiriyor. Production ortamında kesinlikle PM2 kullanmanı öneririm. Log yönetimi, monitoring, otomatik restart ve sıfır kesintili yeniden başlatma gibi özellikleri hayatını kolaylaştırıyor.
Önce PM2 için ekosistem dosyasını oluştur:
// /var/www/nodeapp/ecosystem.config.js
module.exports = {
apps: [
{
name: 'nodeapp',
script: './app.js',
instances: 'max', // CPU sayısı kadar instance
exec_mode: 'cluster', // Cluster modu aktif
watch: false, // Production'da watch kapalı
max_memory_restart: '1G', // 1GB üzerinde restart
env: {
NODE_ENV: 'production',
PORT: 3000
},
error_file: '/var/log/nodeapp/error.log',
out_file: '/var/log/nodeapp/out.log',
log_file: '/var/log/nodeapp/combined.log',
time: true,
restart_delay: 4000, // Restart arası bekleme süresi (ms)
max_restarts: 10, // Maksimum restart sayısı
autorestart: true // Otomatik restart aktif
}
]
};
Şimdi basit ama production’a hazır bir Express uygulaması yazalım:
// /var/www/nodeapp/app.js
const express = require('express');
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Güvenlik başlıkları
app.use((req, res, next) => {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
next();
});
// Sağlık kontrolü endpoint'i
app.get('/health', (req, res) => {
res.status(200).json({
status: 'healthy',
pid: process.pid,
timestamp: new Date().toISOString(),
uptime: `${Math.floor(process.uptime())} saniye`,
memory: {
used: `${Math.round(process.memoryUsage().heapUsed / 1024 / 1024)} MB`,
total: `${Math.round(process.memoryUsage().heapTotal / 1024 / 1024)} MB`
}
});
});
// API endpoint'leri
app.get('/api/users', (req, res) => {
res.json({ users: [], worker: process.pid });
});
app.post('/api/users', (req, res) => {
const { name, email } = req.body;
if (!name || !email) {
return res.status(400).json({ error: 'Ad ve email gerekli' });
}
res.status(201).json({ message: 'Kullanıcı oluşturuldu', worker: process.pid });
});
// 404 handler
app.use((req, res) => {
res.status(404).json({ error: 'Sayfa bulunamadı' });
});
// Error handler
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Sunucu hatası' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, '127.0.0.1', () => {
console.log(`Process ${process.pid} port ${PORT} üzerinde başlatıldı`);
});
Dikkat ettiysen listen fonksiyonuna 127.0.0.1 bağladım. Bu önemli bir güvenlik detayı. Node.js’in dışarıdan direkt erişilebilir olmasını istemiyoruz, her şey Nginx üzerinden geçmeli.
Şimdi log dizinini oluştur ve uygulamayı başlat:
# Log dizini oluştur
sudo mkdir -p /var/log/nodeapp
sudo chown $USER:$USER /var/log/nodeapp
# PM2 ile uygulamayı başlat
cd /var/www/nodeapp
pm2 start ecosystem.config.js
# Durumu kontrol et
pm2 status
pm2 logs nodeapp --lines 50
# Sistem başlangıcında otomatik başlatma
pm2 startup
pm2 save
pm2 status çıktısında tüm worker’ların online durumunda olduğunu görmeni bekliyoruz. 8 çekirdekli bir sunucuda 8 ayrı instance çalışıyor olacak.
Nginx Yapılandırması
Şimdi asıl meseleye geldik. Nginx’i Node.js cluster önüne koyacağız. Nginx bu yapıda reverse proxy olarak çalışıyor ve şunları üstleniyor:
- SSL/TLS sonlandırma
- Statik dosya sunumu
- Rate limiting
- Gzip sıkıştırma
- Load balancing
- Bağlantı yönetimi
Önce ana Nginx yapılandırmasını düzenleyelim:
# Ana nginx.conf optimizasyonu
sudo nano /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
worker_rlimit_nofile 65535;
pid /run/nginx.pid;
events {
worker_connections 4096;
multi_accept on;
use epoll;
}
http {
# Temel ayarlar
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 1000;
types_hash_max_size 2048;
server_tokens off;
# Buffer ayarları
client_body_buffer_size 128k;
client_max_body_size 10m;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
# Gzip sıkıştırma
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json
application/javascript application/xml+rss
application/atom+xml image/svg+xml;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Log formatı
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct=$upstream_connect_time '
'uht=$upstream_header_time urt=$upstream_response_time';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log warn;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Şimdi uygulama için site yapılandırmasını oluşturalım:
# /etc/nginx/sites-available/nodeapp.conf
# Upstream tanımı - Node.js cluster'a bağlanıyoruz
upstream nodeapp_cluster {
# least_conn ile en az bağlantısı olan worker'a yönlendir
least_conn;
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
# Birden fazla port kullanıyorsan ayrı upstream ekle
# server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
# server 127.0.0.1:3002 max_fails=3 fail_timeout=30s;
keepalive 64;
}
# Rate limiting zone tanımı
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=30r/s;
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# HTTP'yi HTTPS'e yönlendir
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
# SSL sertifika ayarları
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
# Güvenlik başlıkları
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
root /var/www/nodeapp/public;
index index.html;
# Statik dosyalar direkt Nginx'ten sun
location /static/ {
alias /var/www/nodeapp/public/;
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# API rate limiting
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_conn conn_limit 10;
proxy_pass http://nodeapp_cluster;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# Timeout ayarları
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Buffer ayarları
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
# Sağlık kontrolü
location /health {
proxy_pass http://nodeapp_cluster;
proxy_http_version 1.1;
proxy_set_header Host $host;
access_log off;
}
# Ana uygulama
location / {
proxy_pass http://nodeapp_cluster;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300s;
}
}
Yapılandırmayı aktif et ve test et:
# Siteyi aktif et
sudo ln -s /etc/nginx/sites-available/nodeapp.conf /etc/nginx/sites-enabled/
# Nginx yapılandırmasını test et
sudo nginx -t
# Nginx'i yeniden yükle
sudo systemctl reload nginx
# Let's Encrypt sertifikası al (gerçek domain için)
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
Sıfır Kesintili Deployment
Cluster yapısının en güzel özelliklerinden biri sıfır kesintili güncelleme. PM2 bunu reload komutuyla yapıyor. Reload sırasında her worker sırayla yeniden başlatılıyor, yeni istekler sağlıklı worker’lara yönlendiriliyor.
# Kodu güncelle
cd /var/www/nodeapp
git pull origin main
npm install --production
# Sıfır kesintili reload
pm2 reload nodeapp
# Reload durumunu takip et
pm2 logs nodeapp --lines 100
# Gerekirse belirli bir worker'ı yeniden başlat
pm2 restart nodeapp --update-env
Deployment sürecini otomatikleştirmek için basit bir script yazalım:
#!/bin/bash
# /usr/local/bin/deploy-nodeapp.sh
set -e
APP_DIR="/var/www/nodeapp"
APP_NAME="nodeapp"
echo "Deployment başlıyor..."
cd $APP_DIR
# Git'ten son kodu çek
git pull origin main
# Bağımlılıkları güncelle
npm install --production
# PM2 ile sıfır kesintili reload
pm2 reload $APP_NAME
# Nginx yapılandırmasını kontrol et ve yenile
sudo nginx -t && sudo systemctl reload nginx
echo "Deployment tamamlandı!"
pm2 status $APP_NAME
# Script'i çalıştırılabilir yap
sudo chmod +x /usr/local/bin/deploy-nodeapp.sh
# Deployment başlat
deploy-nodeapp.sh
Monitoring ve Sorun Giderme
Production’da neler döndüğünü görmek şart. PM2 bu konuda oldukça yetenekli.
# Gerçek zamanlı monitoring
pm2 monit
# Tüm process bilgileri
pm2 info nodeapp
# Log takibi
pm2 logs nodeapp --lines 200 --err
# CPU ve memory istatistikleri
pm2 status
# Worker detayları
pm2 describe nodeapp
# Nginx hata logları
sudo tail -f /var/log/nginx/error.log
# Erişim loglarında yavaş istekleri bul
sudo grep 'rt=[0-9]{2,}' /var/log/nginx/access.log | tail -50
# Aktif bağlantı sayısı
ss -tnp | grep :3000 | wc -l
Bir de basit bir sağlık kontrolü scripti yazalım:
#!/bin/bash
# /usr/local/bin/check-nodeapp.sh
HEALTH_URL="http://127.0.0.1/health"
THRESHOLD_TIME=2
response=$(curl -s -o /dev/null -w "%{http_code}:%{time_total}"
--max-time 5 $HEALTH_URL)
http_code=$(echo $response | cut -d: -f1)
response_time=$(echo $response | cut -d: -f2)
if [ "$http_code" != "200" ]; then
echo "KRITIK: Health check basarisiz! HTTP: $http_code"
# Burada alerting sisteminize bildirim gönderin
pm2 reload nodeapp
exit 1
fi
echo "OK: Uygulama saglikli - HTTP: $http_code, Yanit suresi: ${response_time}s"
# PM2 worker durumlarini kontrol et
pm2 jlist | python3 -c "
import json, sys
apps = json.load(sys.stdin)
for app in apps:
if app['name'] == 'nodeapp':
status = app['pm2_env']['status']
pid = app['pid']
if status != 'online':
print(f'UYARI: Worker {pid} durumu: {status}')
else:
print(f'OK: Worker {pid} online')
"
Performans Tuning İpuçları
Cluster yapısını daha da iyileştirmek için birkaç pratik öneri:
Worker sayısını optimize et: Her zaman CPU sayısı kadar worker başlatmak en iyisi değil. Uygulamanın I/O yoğun mu yoksa CPU yoğun mu olduğuna bak. I/O yoğun uygulamalarda CPU sayısının 2 katı worker bile mantıklı olabilir.
Memory leak takibi: PM2’nin max_memory_restart özelliği burada can kurtarıcı. Worker belirli bir hafıza limitini aştığında otomatik restart yapıyor.
--zero-downtime-reload önemi: PM2’de reload komutu, restart komutundan farklı. restart tüm worker’ları aynı anda kapatıp açarken reload sıralı yapıyor. Production’da her zaman reload kullan.
Nginx upstream keepalive: Yapılandırmada keepalive 64 ayarı, Nginx ile Node.js arasındaki bağlantıların tekrar kullanılmasını sağlıyor. Her istek için yeni TCP bağlantısı açmak gereksiz overhead yaratıyor.
X-Forwarded-For başlığı: Node.js tarafında gerçek IP adresine ihtiyaç duyarsan req.headers['x-forwarded-for'] veya Express’te trust proxy ayarını kullan. Aksi halde tüm istekler Nginx’in IP’sinden geliyormuş gibi görünür.
Güvenlik Konuları
Cluster yapısında güvenliği atlamak istemem. Birkaç kritik nokta:
Node.js’i asla root olarak çalıştırma. PM2’yi normal bir kullanıcı ile çalıştır. Nginx dışarıdan erişimi yönetsin, Node.js yalnızca localhost’u dinlesin.
Firewall ayarını kontrol et:
# UFW ile port yönetimi
sudo ufw allow 'Nginx Full'
sudo ufw allow OpenSSH
sudo ufw enable
# Node.js portuna dışarıdan erişimi engelle
sudo ufw deny 3000
sudo ufw status
Sonuç
Nginx ve Node.js Cluster kombinasyonu, production ortamı için neredeyse ideal bir yapı sunuyor. Nginx’in güçlü proxy özellikleri ile Node.js’in cluster mimarisi birleşince hem yüksek performans hem de yüksek erişilebilirlik elde ediyorsun.
Bu yapının gerçek faydalarını görmek için ab veya wrk gibi araçlarla yük testi yapmanı şiddetle tavsiye ederim. Tek process ile cluster yapısı arasındaki farkı kendi gözlerinle görmek, bu mimarinin neden bu kadar değerli olduğunu net biçimde anlayacaksın.
PM2 izleme, Nginx log analizi ve sağlık kontrolü scriptlerini düzenli olarak çalıştırarak sisteminin nabzını tutmayı ihmal etme. Küçük bir anomaliyi erken fark etmek, gece yarısı yaşanacak bir krizi önlemenin en etkili yolu.