Nginx ile Docker ve Kubernetes Ingress Yapılandırması

Modern uygulama altyapılarında containerization kaçınılmaz bir standart haline geldi. Docker ile uygulamalarını paketleyen, Kubernetes ile orkestre eden ekipler için en kritik nokta dış dünyaya nasıl servis sunulacağı sorusu. İşte tam bu noktada Nginx devreye giriyor. Hem Docker Compose ortamlarında reverse proxy olarak hem de Kubernetes cluster’larında Ingress Controller olarak Nginx, trafik yönetiminin merkezine oturuyor. Bu yazıda gerçek dünya senaryolarıyla her iki kullanım şeklini de derinlemesine ele alacağız.

Docker Ortamında Nginx ile Reverse Proxy Yapılandırması

Temel Docker Nginx Kurulumu

Docker ile Nginx çalıştırmanın en basit hali tek bir komutla mümkün, ama production’da işler biraz daha karmaşık. Önce temel yapıyı kuralım.

# Nginx imajını çek ve temel container oluştur
docker run -d 
  --name nginx-proxy 
  -p 80:80 
  -p 443:443 
  -v /etc/nginx/nginx.conf:/etc/nginx/nginx.conf:ro 
  -v /etc/nginx/conf.d:/etc/nginx/conf.d:ro 
  -v /etc/ssl/certs:/etc/ssl/certs:ro 
  --restart unless-stopped 
  nginx:1.25-alpine

Ama gerçek dünyada Docker Compose kullanmak çok daha yönetilebilir bir yapı sağlar. Diyelim ki bir e-ticaret platformu kuruyorsunuz: frontend, backend API ve bir admin paneli. Bunların hepsini tek bir Nginx üzerinden yönetmek istiyorsunuz.

Docker Compose ile Multi-Service Nginx Yapılandırması

# docker-compose.yml
version: '3.8'

services:
  nginx:
    image: nginx:1.25-alpine
    container_name: nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - nginx-logs:/var/log/nginx
    depends_on:
      - frontend
      - backend
      - admin
    networks:
      - proxy-network
    restart: unless-stopped

  frontend:
    image: myapp/frontend:latest
    container_name: frontend-app
    expose:
      - "3000"
    networks:
      - proxy-network
    environment:
      - NODE_ENV=production

  backend:
    image: myapp/backend:latest
    container_name: backend-api
    expose:
      - "8080"
    networks:
      - proxy-network
    environment:
      - DB_HOST=postgres
      - REDIS_HOST=redis

  admin:
    image: myapp/admin:latest
    container_name: admin-panel
    expose:
      - "4000"
    networks:
      - proxy-network

networks:
  proxy-network:
    driver: bridge

volumes:
  nginx-logs:

Dikkat ettiyseniz frontend, backend ve admin servislerinde ports yerine expose kullandım. Bu önemli bir güvenlik pratiği. expose sadece container’lar arası iletişimi açar, host’a port açmaz. Dış dünyaya sadece Nginx üzerinden ulaşılır.

Nginx Konfigürasyon Dosyaları

Ana nginx.conf dosyası:

# nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    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" "$http_x_forwarded_for" '
                    'upstream=$upstream_addr rt=$request_time';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    server_tokens off;

    # 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/rss+xml
               application/atom+xml image/svg+xml;

    # Rate limiting zone'ları
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;

    include /etc/nginx/conf.d/*.conf;
}

Şimdi servis bazlı virtual host konfigürasyonları. Bu dosyayı nginx/conf.d/app.conf olarak kaydet:

# nginx/conf.d/app.conf

# Frontend - ana site
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;

    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location / {
        proxy_pass http://frontend:3000;
        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 60s;
    }

    # API endpoint'leri
    location /api/ {
        limit_req zone=api_limit burst=20 nodelay;
        proxy_pass http://backend:8080/;
        proxy_http_version 1.1;
        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_connect_timeout 30s;
        proxy_read_timeout 60s;
    }

    # Login endpoint'i için daha sıkı rate limiting
    location /api/auth/login {
        limit_req zone=login_limit burst=3 nodelay;
        proxy_pass http://backend:8080/auth/login;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

# Admin panel - ayrı subdomain
server {
    listen 443 ssl http2;
    server_name admin.example.com;

    ssl_certificate /etc/nginx/ssl/example.com.crt;
    ssl_certificate_key /etc/nginx/ssl/example.com.key;

    # Admin paneline sadece belirli IP'lerden erişim
    allow 10.0.0.0/8;
    allow 192.168.1.0/24;
    deny all;

    location / {
        proxy_pass http://admin:4000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

Kubernetes Ingress Controller Kurulumu

Kubernetes dünyasına geçtiğimizde işler daha da ilginç bir hal alıyor. Ingress, cluster dışından gelen HTTP/HTTPS trafiğini servislere yönlendiren bir Kubernetes kaynağı. Nginx Ingress Controller ise bu Ingress kaynaklarını okuyarak gerçek Nginx konfigürasyonunu otomatik oluşturan bir sistemdir.

Nginx Ingress Controller Kurulumu

Helm ile kurmak hem daha kolay hem de production için daha uygun:

# Helm repo ekle
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

# ingress-nginx namespace oluştur
kubectl create namespace ingress-nginx

# Production uyumlu değerlerle kur
helm install ingress-nginx ingress-nginx/ingress-nginx 
  --namespace ingress-nginx 
  --set controller.replicaCount=2 
  --set controller.nodeSelector."kubernetes.io/os"=linux 
  --set controller.admissionWebhooks.patch.nodeSelector."kubernetes.io/os"=linux 
  --set controller.service.type=LoadBalancer 
  --set controller.metrics.enabled=true 
  --set controller.podAnnotations."prometheus.io/scrape"=true 
  --set controller.podAnnotations."prometheus.io/port"=10254

# Kurulumu doğrula
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx

LoadBalancer tipi servis, bulut sağlayıcıdan (AWS, GCP, Azure) otomatik bir external IP alır. On-premise ortamda MetalLB veya benzeri bir çözüm gerekir.

Temel Ingress Kaynağı Oluşturma

Diyelim ki Kubernetes’te bir mikroservis mimarisi kuruyorsunuz. İki farklı uygulama var: bir API servisi ve bir web uygulaması.

# ingress-basic.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress
  namespace: production
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
    nginx.ingress.kubernetes.io/proxy-connect-timeout: "30"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/use-regex: "true"
spec:
  tls:
  - hosts:
    - api.example.com
    - app.example.com
    secretName: example-tls-secret
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 8080
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 3000
      - path: /api/v2
        pathType: Prefix
        backend:
          service:
            name: api-v2-service
            port:
              number: 8081

TLS secret’ı da oluşturalım:

# TLS secret oluştur
kubectl create secret tls example-tls-secret 
  --cert=path/to/tls.crt 
  --key=path/to/tls.key 
  -n production

# Cert-manager kullanıyorsanız (önerilen)
kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: example-cert
  namespace: production
spec:
  secretName: example-tls-secret
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
  - api.example.com
  - app.example.com
EOF

Gelişmiş Ingress Konfigürasyonları

Gerçek dünyada ihtiyaç duyulan bazı gelişmiş senaryolara bakalım.

Rate Limiting Ingress Annotation ile:

# ingress-ratelimit.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api-ingress-ratelimited
  namespace: production
  annotations:
    kubernetes.io/ingress.class: "nginx"
    # Saniyede maksimum 10 istek
    nginx.ingress.kubernetes.io/limit-rps: "10"
    # Burst kapasitesi
    nginx.ingress.kubernetes.io/limit-burst-multiplier: "3"
    # Rate limit aşıldığında 429 dön
    nginx.ingress.kubernetes.io/limit-connections: "20"
    # CORS ayarları
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-origin: "https://app.example.com"
    nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
    nginx.ingress.kubernetes.io/cors-allow-headers: "Authorization, Content-Type, X-Request-ID"
    # Backend'e özel header ekle
    nginx.ingress.kubernetes.io/configuration-snippet: |
      more_set_headers "X-Frame-Options: SAMEORIGIN";
      more_set_headers "X-Content-Type-Options: nosniff";
spec:
  tls:
  - hosts:
    - api.example.com
    secretName: example-tls-secret
  rules:
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 8080

Canary Deployment ile A/B Testing:

Bu senaryo özellikle ilginç. Yeni bir versiyon deploy ediyorsunuz ve trafiğin %20’sini yeni versiyona yönlendirmek istiyorsunuz. Geri kalanı eski versiyonda kalsın.

# ingress-canary.yaml

# Ana ingress (v1 - mevcut production)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress-stable
  namespace: production
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app-v1-service
            port:
              number: 3000
---
# Canary ingress (v2 - yeni versiyon, %20 trafik)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: app-ingress-canary
  namespace: production
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "20"
    # Header bazlı yönlendirme de yapılabilir
    # nginx.ingress.kubernetes.io/canary-by-header: "X-Canary"
    # nginx.ingress.kubernetes.io/canary-by-header-value: "true"
spec:
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: app-v2-service
            port:
              number: 3000

ConfigMap ile Global Nginx Ayarları

Nginx Ingress Controller’ın global davranışını ConfigMap ile değiştirebilirsiniz. Bu production ortamlarında çok işe yarıyor:

# Mevcut ConfigMap'i düzenle
kubectl edit configmap ingress-nginx-controller -n ingress-nginx

Ya da doğrudan uygula:

# nginx-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  # Worker process ayarları
  worker-processes: "auto"
  worker-connections: "65536"
  
  # Keep-alive ayarları
  keep-alive: "75"
  keep-alive-requests: "100"
  
  # Buffer ayarları (büyük header'lar için önemli)
  proxy-buffer-size: "16k"
  proxy-buffers-number: "4"
  large-client-header-buffers: "4 16k"
  
  # Timeout değerleri
  proxy-connect-timeout: "30"
  proxy-read-timeout: "60"
  proxy-send-timeout: "60"
  
  # Log formatı - JSON yapısında (log aggregation için ideal)
  log-format-upstream: >
    {"time": "$time_iso8601", "remote_addr": "$proxy_protocol_addr",
    "x_forwarded_for": "$proxy_add_x_forwarded_for",
    "request_id": "$req_id", "remote_user": "$remote_user",
    "bytes_sent": $bytes_sent, "request_time": $request_time,
    "status": $status, "vhost": "$host", "request_proto": "$server_protocol",
    "path": "$uri", "request_query": "$args", "request_length": $request_length,
    "duration": $request_time, "method": "$request_method",
    "upstream_addr": "$upstream_addr",
    "upstream_status": "$upstream_status",
    "upstream_response_time": "$upstream_response_time"}
  
  # SSL ayarları
  ssl-protocols: "TLSv1.2 TLSv1.3"
  ssl-ciphers: "ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384"
  ssl-session-cache-size: "10m"
  
  # Gzip
  use-gzip: "true"
  gzip-level: "6"
  gzip-types: "application/json application/javascript text/css text/plain image/svg+xml"

Troubleshooting ve Debug Senaryoları

Tüm bunları kurdunuz ama bir şeyler yolunda gitmiyor. İşte en sık karşılaşılan durumlar:

Nginx Ingress Controller Log İnceleme

# Ingress controller pod loglarını takip et
kubectl logs -f -n ingress-nginx 
  $(kubectl get pods -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx 
  -o jsonpath='{.items[0].metadata.name}')

# Belirli bir Ingress kaynağının durumunu kontrol et
kubectl describe ingress app-ingress -n production

# Nginx'in ürettiği gerçek konfigürasyonu gör (çok önemli!)
kubectl exec -it -n ingress-nginx 
  $(kubectl get pods -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx 
  -o jsonpath='{.items[0].metadata.name}') 
  -- cat /etc/nginx/nginx.conf | grep -A 20 "example.com"

# Ingress controller'ın event'lerini izle
kubectl get events -n ingress-nginx --sort-by='.lastTimestamp'

# Docker compose ortamında Nginx konfigürasyonunu test et
docker exec nginx-proxy nginx -t
docker exec nginx-proxy nginx -s reload

Yaygın Sorunlar ve Çözümleri

502 Bad Gateway sorunu genellikle backend servisin ayakta olmamasından ya da yanlış port konfigürasyonundan kaynaklanır. Docker ortamında container isimlerinin DNS olarak çalıştığını unutmayın, network’ün aynı olduğundan emin olun.

SSL sertifika sorunları için cert-manager kullanıyorsanız Certificate kaynağının durumunu kontrol edin:

kubectl describe certificate example-cert -n production
kubectl describe certificaterequest -n production
kubectl logs -n cert-manager deploy/cert-manager

Ingress kaynağı uygulanmıyor sorunu genellikle yanlış ingress.class annotation’ından gelir. Kubernetes 1.18 sonrasında ingressClassName spec alanı kullanılmalı:

spec:
  ingressClassName: nginx
  rules:
  ...

Production’da Dikkat Edilmesi Gerekenler

Canlı ortamlarda işler test ortamından farklı gelişiyor. Bazı kritik noktalar:

  • Pod Disruption Budget tanımla: Ingress Controller pod’larının güncelleme sırasında aynı anda kapanmaması için minAvailable: 1 değerini mutlaka ayarla.
  • Resource Limits: Controller pod’larına mutlaka CPU ve memory limiti ver. Sınırsız bırakmak bir trafik artışında tüm node’u çökertebilir.
  • Horizontal Pod Autoscaler: Ingress Controller için HPA kur, özellikle trafik dalgalanmaları yaşayan ortamlarda hayat kurtarır.
  • Metrics ve Alerting: Controller’ın Prometheus metriklerini topla. nginx_ingress_controller_requests_total ve nginx_ingress_controller_request_duration_seconds metriklerini izle.
  • Configuration Reload Takibi: Her Ingress değişikliğinde Nginx reload edilir. nginx_ingress_controller_config_last_reload_successful metriği ile başarısız reload’ları yakala.

Sonuç

Nginx’i Docker ve Kubernetes ortamlarında doğru konfigüre etmek, hem güvenli hem de performanslı bir servis kapısı oluşturmanın temel taşı. Docker Compose ortamlarında servis izolasyonu ve reverse proxy yapılandırması için güçlü bir araç olan Nginx, Kubernetes’te Ingress Controller rolüyle çok daha dinamik bir yapıya kavuşuyor. Annotation tabanlı konfigürasyon sistemi, her servis için ayrı Nginx kuralları yazmak yerine Kubernetes manifest’leri içinde trafik kurallarını tanımlamanızı sağlıyor.

Canary deployment, rate limiting, CORS yönetimi ve SSL sonlandırma gibi production gereksinimlerinin tamamı Nginx Ingress Controller üzerinden karşılanabiliyor. Önemli olan kurulumu doğru yapmak ve ardından monitoring altyapısıyla bu sistemi sürekli gözlemlemek. Bir şeyler ters gittiğinde kubectl exec ile direkt container içine girip üretilen Nginx konfigürasyonunu incelemek, sorunları hızla çözmenin en pratik yolu olmaya devam ediyor.

Yorum yapın