Nginx ile gRPC Proxy Kurulumu ve Yapılandırması

gRPC servisleri son yıllarda microservice mimarilerinin vazgeçilmezi haline geldi. Ancak production ortamında gRPC trafiğini yönetmek, load balancing yapmak ve güvenli hale getirmek ciddi bir iş. Nginx, bu noktada hem bir reverse proxy hem de basit bir API gateway olarak devreye giriyor. Bu yazıda sıfırdan bir Nginx gRPC proxy kurulumu yapacağız, yaygın hataları inceleyeceğiz ve gerçek dünya senaryolarında nasıl configure edeceğimizi ele alacağız.

gRPC ve HTTP/2 İlişkisi

Önce temel bir noktayı netleştirelim: gRPC, HTTP/2 üzerinde çalışır. Bu, klasik HTTP/1.1 proxy konfigürasyonlarından farklı şeyler yapman gerektiği anlamına gelir. Nginx 1.13.10 sürümünden itibaren grpc_pass direktifi ile native gRPC proxy desteği geldi. Daha eski sürümlerde proxy_pass ile HTTP/2 upstream kullanmak gerekiyordu ama bu yöntem pek sağlıklı değildi.

Nginx’in gRPC trafiğini yönlendirmek için iki temel modu var:

  • grpc:// – Şifrelenmemiş gRPC trafiği (h2c – HTTP/2 cleartext)
  • grpcs:// – TLS ile şifrelenmiş gRPC trafiği

Çoğu production senaryosunda client’tan Nginx’e TLS, Nginx’ten backend’e ise iç ağda h2c kullanılır. Bu mimariye TLS termination diyoruz.

Gereksinimler ve Hazırlık

Başlamadan önce ortamı hazırlayalım. Nginx sürümünü kontrol et:

nginx -v
# nginx version: nginx/1.24.0

# HTTP/2 ve gRPC desteğini kontrol et
nginx -V 2>&1 | grep -o with-http_v2_module
nginx -V 2>&1 | grep -o with-http_ssl_module

Eğer Nginx paket yöneticisinden kuruluysa ve sürüm 1.13.10’dan yeniyse gRPC desteği büyük ihtimalle dahildir. Ubuntu/Debian’da güncel Nginx almak için:

# Nginx resmi repo'yu ekle
curl -fsSL https://nginx.org/keys/nginx_signing.key | gpg --dearmor 
  -o /usr/share/keyrings/nginx-archive-keyring.gpg

echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] 
  http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" 
  | tee /etc/apt/sources.list.d/nginx.list

apt update && apt install -y nginx

# Test için grpcurl kur
wget https://github.com/fullstorydev/grpcurl/releases/download/v1.8.9/grpcurl_1.8.9_linux_x86_64.tar.gz
tar -xzf grpcurl_1.8.9_linux_x86_64.tar.gz -C /usr/local/bin/

SSL sertifikası için self-signed bir sertifika oluşturalım (production’da Let’s Encrypt veya kurumsal CA kullanacaksın):

mkdir -p /etc/nginx/ssl

openssl req -x509 -newkey rsa:4096 -keyout /etc/nginx/ssl/server.key 
  -out /etc/nginx/ssl/server.crt -days 365 -nodes 
  -subj "/C=TR/ST=Istanbul/L=Istanbul/O=MyCompany/CN=grpc.example.com"

chmod 600 /etc/nginx/ssl/server.key

Basit gRPC Proxy Konfigürasyonu

En temel senaryodan başlayalım: tek bir backend gRPC servisi var, Nginx bunu dışarıya açıyor.

# /etc/nginx/conf.d/grpc-proxy.conf

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

    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;

    # Modern TLS ayarları
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    # gRPC proxy direktifi
    location / {
        grpc_pass grpc://127.0.0.1:50051;

        # Timeout ayarları - gRPC streaming için kritik
        grpc_read_timeout 300s;
        grpc_send_timeout 300s;
        grpc_connect_timeout 10s;

        # gRPC hata yönetimi
        error_page 502 = /error502grpc;
    }

    location = /error502grpc {
        internal;
        default_type application/grpc;
        add_header grpc-status 14;
        add_header content-length 0;
        return 204;
    }
}

# HTTP trafiğini HTTPS'e yönlendir
server {
    listen 80;
    server_name grpc.example.com;
    return 301 https://$host$request_uri;
}

Buradaki error_page 502 = /error502grpc kısmına dikkat et. Normal bir 502 hatası gRPC client’ları tarafından düzgün parse edilemez. grpc-status 14 kodu “UNAVAILABLE” anlamına gelir ve client bu hatayı anlayabilir.

Konfigürasyonu test edip yükle:

nginx -t && systemctl reload nginx

Load Balancing ile Çoklu Backend

Gerçek production senaryolarında genellikle birden fazla gRPC instance çalışıyor. Nginx’in upstream bloğunu kullanarak bunları load balance edelim:

# /etc/nginx/conf.d/grpc-loadbalancer.conf

upstream grpc_backend {
    # least_conn streaming için daha iyi performans verir
    least_conn;

    server 10.0.1.10:50051;
    server 10.0.1.11:50051;
    server 10.0.1.12:50051;

    # Keepalive bağlantıları - gRPC için önemli
    keepalive 32;
    keepalive_requests 1000;
    keepalive_timeout 60s;
}

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

    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_protocols TLSv1.2 TLSv1.3;

    # Access log - gRPC metodlarını logla
    access_log /var/log/nginx/grpc_access.log;

    location / {
        grpc_pass grpc://grpc_backend;

        grpc_read_timeout 300s;
        grpc_send_timeout 300s;
        grpc_connect_timeout 5s;

        # Header geçişi
        grpc_set_header Host $host;
        grpc_set_header X-Real-IP $remote_addr;
        grpc_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        error_page 502 = /error502grpc;
    }

    location = /error502grpc {
        internal;
        default_type application/grpc;
        add_header grpc-status 14;
        add_header content-length 0;
        return 204;
    }
}

keepalive 32 direktifi burada kritik. HTTP/2 multiplexing sayesinde tek bir TCP bağlantısı üzerinden çok sayıda gRPC çağrısı yapılabilir. Keepalive olmadan her istek için yeni bağlantı kurulur ve bu ciddi bir performans kaybıdır.

least_conn algoritması, server streaming veya bidirectional streaming kullanan servisler için round-robin’den daha iyi sonuç verir. Çünkü bazı stream’ler uzun süre açık kalabilir ve dengesiz yük dağılımına yol açabilir.

Servis Bazlı Routing

Microservice mimarilerinde genellikle birden fazla gRPC servisi var ve bunları URL path’ine göre farklı backend’lere yönlendirmen gerekiyor. gRPC metodları /paket.ServisAdı/MetodAdı formatında gelir:

# /etc/nginx/conf.d/grpc-routing.conf

upstream user_service {
    least_conn;
    server 10.0.1.10:50051;
    server 10.0.1.11:50051;
    keepalive 16;
}

upstream order_service {
    least_conn;
    server 10.0.2.10:50052;
    server 10.0.2.11:50052;
    keepalive 16;
}

upstream payment_service {
    server 10.0.3.10:50053;
    keepalive 8;
}

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

    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_protocols TLSv1.2 TLSv1.3;

    # User Service - /myapp.UserService/*
    location /myapp.UserService {
        grpc_pass grpc://user_service;
        grpc_read_timeout 60s;
        grpc_send_timeout 60s;
        grpc_set_header X-Real-IP $remote_addr;
        error_page 502 = /error502grpc;
    }

    # Order Service - /myapp.OrderService/*
    location /myapp.OrderService {
        grpc_pass grpc://order_service;
        grpc_read_timeout 120s;
        grpc_send_timeout 120s;
        grpc_set_header X-Real-IP $remote_addr;
        error_page 502 = /error502grpc;
    }

    # Payment Service - sadece belirli IP'lerden erişim
    location /myapp.PaymentService {
        # IP whitelist
        allow 10.0.0.0/8;
        allow 192.168.1.0/24;
        deny all;

        grpc_pass grpc://payment_service;
        grpc_read_timeout 30s;
        grpc_send_timeout 30s;
        grpc_set_header X-Real-IP $remote_addr;
        error_page 502 = /error502grpc;
    }

    location = /error502grpc {
        internal;
        default_type application/grpc;
        add_header grpc-status 14;
        add_header content-length 0;
        return 204;
    }
}

Bu konfigürasyonda Payment servisini yalnızca iç ağ IP’lerine açtık. gRPC metodlarına granüler erişim kontrolü uygulamak istiyorsan daha spesifik location blokları yazabilirsin:

# Sadece belirli bir metodu engellemek
location = /myapp.UserService/DeleteUser {
    return 403;
}

location /myapp.UserService {
    grpc_pass grpc://user_service;
}

Rate Limiting ve Güvenlik

gRPC API’leri için rate limiting uygulamak HTTP API’lerinden biraz farklı. limit_req_zone direktifini kullanabiliriz ama gRPC streaming durumlarında dikkatli olmak gerekiyor:

# /etc/nginx/nginx.conf içine veya conf.d/limits.conf'a ekle

# Rate limit zone tanımları
limit_req_zone $binary_remote_addr zone=grpc_limit:10m rate=100r/m;
limit_req_zone $binary_remote_addr zone=grpc_strict:10m rate=20r/m;

# Bağlantı limiti zone
limit_conn_zone $binary_remote_addr zone=grpc_conn:10m;
# /etc/nginx/conf.d/grpc-secure.conf

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

    ssl_certificate     /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_protocols TLSv1.2 TLSv1.3;

    # Genel rate limiting
    location / {
        limit_req zone=grpc_limit burst=20 nodelay;
        limit_conn grpc_conn 10;

        grpc_pass grpc://grpc_backend;
        grpc_read_timeout 300s;
        grpc_send_timeout 300s;

        error_page 429 = /error429grpc;
        error_page 502 = /error502grpc;
    }

    # Kritik metodlar için daha sıkı limit
    location /myapp.AuthService {
        limit_req zone=grpc_strict burst=5 nodelay;
        limit_conn grpc_conn 3;

        grpc_pass grpc://auth_backend;
        grpc_read_timeout 30s;
        grpc_send_timeout 30s;

        error_page 429 = /error429grpc;
        error_page 502 = /error502grpc;
    }

    location = /error502grpc {
        internal;
        default_type application/grpc;
        add_header grpc-status 14;
        add_header content-length 0;
        return 204;
    }

    location = /error429grpc {
        internal;
        default_type application/grpc;
        add_header grpc-status 8;
        add_header content-length 0;
        return 204;
    }
}

grpc-status 8 kodu RESOURCE_EXHAUSTED anlamına gelir. Bu, rate limit aşıldığında gRPC client’larının anlayacağı semantik olarak doğru hata kodudur.

Health Check ve Monitoring

Production’da backend’lerin sağlığını izlemek için Nginx Plus’ın active health check özelliği veya açık kaynak alternatifleri kullanılabilir. Nginx open source ile passive health check yapabiliriz:

upstream grpc_backend {
    least_conn;

    server 10.0.1.10:50051 max_fails=3 fail_timeout=30s;
    server 10.0.1.11:50051 max_fails=3 fail_timeout=30s;
    server 10.0.1.12:50051 max_fails=3 fail_timeout=30s;

    # Yedek sunucu - diğerleri düşünce devreye girer
    server 10.0.1.20:50051 backup;

    keepalive 32;
}

Logging’i gRPC’ye uygun hale getirmek için özel log format tanımlayalım:

# /etc/nginx/nginx.conf http bloğuna ekle
log_format grpc_json escape=json
    '{'
        '"time":"$time_iso8601",'
        '"client":"$remote_addr",'
        '"uri":"$uri",'
        '"grpc_status":"$upstream_http_grpc_status",'
        '"upstream":"$upstream_addr",'
        '"upstream_response_time":"$upstream_response_time",'
        '"request_time":"$request_time",'
        '"bytes_sent":"$bytes_sent",'
        '"ssl_protocol":"$ssl_protocol",'
        '"http_user_agent":"$http_user_agent"'
    '}';

# Server bloğu içinde kullan
access_log /var/log/nginx/grpc_access.log grpc_json;

Bu JSON formatındaki loglar Elasticsearch, Loki veya benzeri log aggregation sistemlerine kolayca gönderilebilir.

Yaygın Hatalar ve Çözümleri

Nginx gRPC proxy kurarken sık karşılaşılan sorunları ve çözümlerini listeleyelim:

“upstream sent invalid header” hatası: Bu hata genellikle backend’in HTTP/2 yerine HTTP/1.1 üzerinden konuştuğunu gösterir. grpc_pass yerine yanlışlıkla proxy_pass kullanılmış olabilir. Ya da backend gerçekten HTTP/2’yi desteklemiyor olabilir.

# Backend'in HTTP/2 destekleyip desteklemediğini test et
curl -v --http2 http://10.0.1.10:50051

# grpcurl ile test et
grpcurl -plaintext 10.0.1.10:50051 list

“recv() failed (104: Connection reset by peer)” hatası: Bu, timeout değerlerinin çok düşük ayarlandığını veya backend’in bağlantıyı beklenmedik şekilde kapattığını gösterir. grpc_read_timeout değerini artır.

Client’ın “transport: Error while dialing dial tcp: connection refused” hatası: Nginx konfigürasyonunda http2 parametresinin listen direktifine eklenip eklenmediğini kontrol et:

# YANLIS
listen 443 ssl;

# DOGRU
listen 443 ssl http2;

gRPC reflection çalışmıyor: Bazı araçlar ve gateway’ler server reflection kullanır. Eğer location blokların çok spesifikse reflection path’i eşleşmeyebilir:

# gRPC Server Reflection için ekle
location /grpc.reflection.v1alpha.ServerReflection {
    grpc_pass grpc://grpc_backend;
}

location /grpc.reflection.v1.ServerReflection {
    grpc_pass grpc://grpc_backend;
}

Kubernetes Ortamında Dikkat Edilmesi Gerekenler

Nginx’i Kubernetes’te Ingress controller olarak kullanıyorsan (nginx-ingress veya ingress-nginx), gRPC için annotation’ları doğru set etmek gerekiyor:

# Kubernetes Ingress kaynağı için gerekli annotation'lar
kubectl annotate ingress my-grpc-ingress 
  nginx.ingress.kubernetes.io/ssl-redirect="true" 
  nginx.ingress.kubernetes.io/backend-protocol="GRPC" 
  nginx.ingress.kubernetes.io/grpc-backend="true"

Ayrıca backend Service’in doğru port ismi kullanması önemli:

# Service port ismi "grpc" veya "h2c" olmalı
kubectl patch service my-grpc-service --type='json' 
  -p='[{"op": "replace", "path": "/spec/ports/0/name", "value": "grpc"}]'

Performans Tuning

Yoğun gRPC trafiğinde Nginx worker ayarlarını optimize etmek gerekiyor:

# /etc/nginx/nginx.conf

# CPU core sayısına göre ayarla
worker_processes auto;
worker_rlimit_nofile 65535;

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

http {
    # HTTP/2 için buffer ayarları
    http2_max_concurrent_streams 256;
    http2_recv_buffer_size 256k;

    # Upstream buffer'ları
    grpc_buffer_size 32k;

    # Keepalive ayarları
    keepalive_timeout 65;
    keepalive_requests 10000;

    # TCP optimizasyonları
    tcp_nopush on;
    tcp_nodelay on;
    sendfile on;
}
# Sistem seviyesinde de ayarlamalar yapalım
cat >> /etc/sysctl.conf << EOF
net.core.somaxconn = 65535
net.ipv4.tcp_max_tw_buckets = 1440000
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_keepalive_time = 300
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5
EOF

sysctl -p

# Nginx process limitleri
cat >> /etc/security/limits.conf << EOF
nginx soft nofile 65535
nginx hard nofile 65535
EOF

Değişikliklerden sonra Nginx’i reload et ve yük altında test et:

# Konfigürasyon testi
nginx -t

# Graceful reload
nginx -s reload

# grpcurl ile yük testi (ghz aracı daha iyidir ama bu da çalışır)
for i in {1..100}; do
  grpcurl -insecure grpc.example.com:443 myapp.UserService/GetUser 
    -d '{"id": "123"}' &
done
wait

# Nginx durumunu kontrol et
curl http://127.0.0.1/nginx_status

Sonuç

Nginx ile gRPC proxy kurulumu ilk bakışta karmaşık görünebilir ama temel prensipleri kavradıktan sonra oldukça yönetilebilir bir hal alıyor. Özetlemek gerekirse:

  • grpc_pass direktifini kullan, proxy_pass gRPC için uygun değil
  • listen direktifine mutlaka http2 parametresini ekle
  • Upstream keepalive’ı aktif et, aksi halde performans ciddi şekilde düşer
  • least_conn algoritması streaming workload’lar için round-robin’den daha iyi
  • gRPC hata kodlarını doğru set et: 14 (UNAVAILABLE) ve 8 (RESOURCE_EXHAUSTED)
  • Streaming servisleri için timeout değerlerini mutlaka ayarla
  • Log formatını gRPC’ye özgü $upstream_http_grpc_status değişkeniyle zenginleştir

Production’da bu konfigürasyonu uygulamadan önce test ortamında kapsamlı yük testi yapmayı unutma. gRPC’nin bidirectional streaming özelliği nedeniyle bağlantılar uzun süre açık kalabilir ve bu durum resource yönetimini HTTP/1.1 proxy’lerine kıyasla daha kritik hale getirir.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir