Caddy ile gRPC ve HTTP/2 Backend Proxy Yapılandırması

Modern mikroservis mimarilerinde gRPC protokolü giderek daha fazla tercih edilir hale geldi. Özellikle servisler arası yüksek performanslı iletişim gerektiren sistemlerde gRPC’nin HTTP/2 tabanlı yapısı ciddi avantajlar sağlıyor. Ancak bu servisleri dışa açarken ya da load balancer arkasına almak istediğinizde karşınıza bazı zorluklar çıkıyor. İşte tam burada Caddy devreye giriyor. Caddy’nin modern HTTP/2 ve gRPC proxy desteği, bu işi hem kolaylaştırıyor hem de güvenli bir şekilde yapmanızı mümkün kılıyor.

Bu yazıda gerçek dünya senaryolarıyla Caddy’yi bir gRPC ve HTTP/2 backend proxy olarak nasıl kullanacağınızı, hangi direktifleri nasıl yapılandıracağınızı ve olası sorunları nasıl çözeceğinizi ele alacağız.

gRPC ve HTTP/2 Neden Özel Bir Proxy Konfigürasyonu Gerektirir?

Klasik HTTP/1.1 proxy senaryolarından farklı olarak gRPC, kalıcı bağlantılar üzerinden çift yönlü streaming yapabilen bir protokoldür. Bu durum, birkaç önemli teknik gereksinimi beraberinde getirir.

  • HTTP/2 zorunluluğu: gRPC sadece HTTP/2 üzerinden çalışır, HTTP/1.1 ile uyumlu değildir
  • Uzun süreli bağlantılar: Streaming RPC’ler için bağlantının açık kalması gerekir
  • Trafikte header yönetimi: gRPC, özel header’lar kullanır ve bunların doğru iletilmesi kritiktir
  • TLS gereksinimleri: Birçok gRPC client, TLS olmadan H2 (HTTP/2) kullanmayı reddeder
  • Timeout yönetimi: gRPC’nin kendi deadline mekanizması vardır, proxy timeout’ları bunu engellememeli

Nginx bu konuda biraz zahmetli olabiliyor. Özellikle gRPC için ayrı grpc_pass direktifini doğru configure etmek ve TLS sonlandırmayı sağlamak bazı durumlarda sizi Nginx Plus’a yönlendirebiliyor. Caddy ise HTTP/2’yi neredeyse otomatik olarak halleden ve gRPC proxy için hazır direktiflere sahip bir sunucu olarak bu işi gerçekten sadeleştiriyor.

Caddy Kurulumu

Sisteminizde Caddy yoksa önce onu kuralım. Debian/Ubuntu tabanlı sistemler için:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

RHEL/CentOS/Rocky Linux için:

dnf install 'dnf-command(copr)'
dnf copr enable @caddy/caddy
dnf install caddy

Kurulumdan sonra servisi başlatıp durumunu kontrol edelim:

sudo systemctl enable --now caddy
sudo systemctl status caddy

Caddy versiyonunu doğrulayın. gRPC proxy için Caddy 2.x gereklidir:

caddy version

Temel gRPC Proxy Konfigürasyonu

Caddy’de gRPC proxy için reverse_proxy direktifini kullanıyoruz. Ancak burada dikkat edilmesi gereken önemli nokta var: backend’e bağlanırken protokolü açıkça belirtmeniz gerekiyor.

Senaryo: Elimizde localhost:50051 portunda çalışan bir gRPC servisi var. Bu servisi TLS ile dışa açmak istiyoruz.

api.example.com {
    reverse_proxy h2c://localhost:50051
}

Buradaki h2c:// prefiksi çok önemli. Bu, Caddy’ye backend ile bağlantının cleartext HTTP/2 (TLS olmadan HTTP/2) kullanacağını söylüyor. Bu senaryo genellikle şu yapıda kullanılır:

  • İstemci -> Caddy (TLS ile H2) -> Backend (h2c, TLS’siz H2)

Yani TLS sonlandırması Caddy’de yapılıyor, backend’e giden trafik iç ağda TLS’siz HTTP/2 ile iletiliyor. Bu oldukça yaygın bir production pattern’idir.

Eğer backend de TLS kullanıyorsa:

api.example.com {
    reverse_proxy {
        to https://localhost:50051
        transport http {
            versions 2
        }
    }
}

Gelişmiş gRPC Proxy Konfigürasyonu

Gerçek production ortamında genellikle daha fazla kontrol ihtiyaç duyuyorsunuz. Aşağıdaki konfigürasyon hem gRPC hem de standart HTTP/2 trafiğini yönetmek için daha kapsamlı bir örnek:

grpc.example.com {
    # gRPC servislerine yönlendirme
    @grpc {
        protocol grpc
    }

    reverse_proxy @grpc h2c://localhost:50051 {
        header_up X-Forwarded-Proto https
        header_up X-Real-IP {remote_host}
        
        # gRPC için flush interval ayarı - streaming için kritik
        flush_interval -1
        
        # Health check
        health_uri /grpc.health.v1.Health/Check
        health_interval 30s
        health_timeout 5s
        
        lb_policy round_robin
    }

    # Normal HTTP/2 istekleri için fallback
    reverse_proxy localhost:8080 {
        flush_interval -1
    }
}

Bu konfigürasyonda birkaç önemli nokta var:

  • @grpc matcher: Sadece gRPC trafiğini yakalıyor, Content-Type üzerinden
  • flush_interval -1: Bu değer streaming gRPC için kritik. -1, Caddy’nin response’u buffer’lamadan anında client’a iletmesini sağlıyor
  • health_uri: gRPC Health Checking Protocol standart endpoint’i

Birden Fazla gRPC Servisi için Path Bazlı Yönlendirme

Mikroservis mimarisinde sıkça karşılaşılan bir senaryo şudur: Tek bir domain altında birden fazla gRPC servisi çalıştırmanız gerekiyor. Caddy bunu path bazlı yönlendirmeyle çok temiz bir şekilde hallediyor.

api.example.com {
    # UserService
    @userService {
        path /user.UserService/*
    }
    reverse_proxy @userService h2c://localhost:50051

    # OrderService
    @orderService {
        path /order.OrderService/*
    }
    reverse_proxy @orderService h2c://localhost:50052

    # ProductService
    @productService {
        path /product.ProductService/*
    }
    reverse_proxy @productService h2c://localhost:50053

    # gRPC reflection (geliştirme ortamında)
    @reflection {
        path /grpc.reflection.v1alpha.ServerReflection/*
    }
    reverse_proxy @reflection h2c://localhost:50051
    
    # Wildcard - kalan tüm gRPC trafiği
    @grpcDefault {
        protocol grpc
    }
    reverse_proxy @grpcDefault h2c://localhost:50054
}

Bu yapıyı kullanırken servis path’lerinin proto dosyanızdaki package ve service isimlendirmesiyle uyumlu olduğundan emin olun. gRPC path formatı genellikle /package.ServiceName/MethodName şeklindedir.

Load Balancing ile gRPC Proxy

Yüksek trafikli ortamlarda tek bir backend yetmez, birden fazla instance arasında yük dağıtmanız gerekir. Ancak gRPC’de load balancing biraz özel dikkat ister. Normal HTTP/1.1’de her istek ayrı bağlantı üzerinden gidebilirken, gRPC’de uzun süreli bağlantılar bu dengelemeyi zorlaştırır.

grpc-lb.example.com {
    @grpc {
        protocol grpc
    }

    reverse_proxy @grpc {
        to h2c://10.0.1.10:50051
        to h2c://10.0.1.11:50051
        to h2c://10.0.1.12:50051
        
        # least_conn, round_robin veya random kullanabilirsiniz
        # gRPC streaming için least_conn genellikle daha iyi sonuç verir
        lb_policy least_conn
        
        # Streaming bağlantılar için flush interval
        flush_interval -1
        
        # Passive health check
        lb_try_duration 5s
        lb_try_interval 250ms
        
        # Active health check
        health_uri /grpc.health.v1.Health/Check
        health_interval 15s
        health_timeout 3s
        health_status 200
        
        # Bağlantı havuzu ayarları
        transport http {
            keepalive 30s
            keepalive_idle_conns 10
            versions 2
        }
    }
}

lb_try_duration ve lb_try_interval parametreleri önemli. Bir backend yanıt vermediğinde Caddy ne kadar süre deneme yapacağını ve bu denemeler arasında ne kadar bekleyeceğini belirliyor. gRPC için bu değerlerin gerçekçi tutulması gerekiyor, aksi halde client’ın kendi deadline’ı dolmadan önce proxy gereksiz yere retry yapabilir.

gRPC ile TLS Mutual Authentication (mTLS)

Kurumsal ortamlarda servisler arası güvenlik için mTLS sıkça kullanılır. Hem client’ın hem de server’ın birbirini doğruladığı bu yapıyı Caddy ile şöyle kurabilirsiniz:

# Önce sertifikaları oluşturalım
# CA sertifikası
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt 
  -subj "/CN=Internal CA/O=MyOrg"

# Backend servis sertifikası
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr 
  -subj "/CN=grpc-backend.internal"
openssl x509 -req -days 365 -in server.csr 
  -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt

# Caddy client sertifikası
openssl genrsa -out caddy-client.key 2048
openssl req -new -key caddy-client.key -out caddy-client.csr 
  -subj "/CN=caddy-proxy"
openssl x509 -req -days 365 -in caddy-client.csr 
  -CA ca.crt -CAkey ca.key -CAcreateserial -out caddy-client.crt

Caddy konfigürasyonu mTLS ile:

secure-grpc.example.com {
    # Client'tan gelen mTLS (opsiyonel, gerekirse zorunlu yapılabilir)
    tls {
        client_auth {
            mode require_and_verify
            trusted_ca_cert_file /etc/caddy/certs/ca.crt
        }
    }
    
    @grpc {
        protocol grpc
    }
    
    reverse_proxy @grpc {
        to https://grpc-backend.internal:50051
        
        transport http {
            tls_trusted_ca_certs /etc/caddy/certs/ca.crt
            tls_client_auth /etc/caddy/certs/caddy-client.crt /etc/caddy/certs/caddy-client.key
            versions 2
        }
        
        flush_interval -1
        header_up X-Forwarded-For {remote_host}
    }
}

HTTP/2 Only Backend Proxy

gRPC dışında, sadece HTTP/2 kullanan modern backend servislerinizi de Caddy üzerinden proxy yapabilirsiniz. Örneğin bazı Python (hypercorn, uvicorn), Go veya Java servisleri HTTP/2 ile ayağa kalkabilir.

app.example.com {
    reverse_proxy {
        to h2c://localhost:8000
        
        transport http {
            versions 2
            # HTTP/2 için keep-alive kritik
            keepalive 60s
            keepalive_idle_conns 20
            # Response buffer boyutu
            response_body_size 100MB
        }
        
        # Header yönetimi
        header_up Host {upstream_hostport}
        header_up X-Forwarded-For {remote_host}
        header_up X-Forwarded-Proto {scheme}
        
        flush_interval -1
    }
    
    # HTTP/2 Server Push (HTTP/2 özelliği)
    header {
        Link "</static/app.js>; rel=preload; as=script"
        Link "</static/styles.css>; rel=preload; as=style"
    }
    
    encode gzip zstd
}

Mixed Traffic: gRPC ve REST Aynı Port Üzerinde

Gerçek dünyada sıkça karşılaşılan bir senaryo: Aynı servis hem gRPC hem de REST HTTP/2 isteklerini kabul ediyor. Bu genellikle “gRPC-Gateway” pattern’iyle uygulanır.

api.example.com {
    # gRPC trafiğini algıla ve yönlendir
    @isGrpc {
        protocol grpc
    }
    
    # JSON/REST trafiği için matcher
    @isRest {
        not protocol grpc
        path /api/*
    }
    
    # gRPC backend
    reverse_proxy @isGrpc h2c://localhost:9090 {
        flush_interval -1
        header_up Content-Type {>Content-Type}
    }
    
    # REST/HTTP backend (gRPC-Gateway)
    reverse_proxy @isRest localhost:8080 {
        header_up X-Forwarded-For {remote_host}
        header_up X-Request-ID {uuid}
    }
    
    # Swagger/OpenAPI dokümantasyonu
    @docs {
        path /docs*
        path /swagger*
    }
    reverse_proxy @docs localhost:8081
    
    # Logging
    log {
        output file /var/log/caddy/api.example.com.log
        format json
    }
}

Bu yapıda {>Content-Type} kullanımına dikkat edin. gRPC istekleri application/grpc Content-Type ile gelir ve bu header’ın backend’e doğru iletilmesi şart.

Monitoring ve Logging

Production ortamında proxy’nizin sağlıklı çalışıp çalışmadığını takip etmek kritik. Caddy’nin logging sistemi JSON formatını desteklediği için ELK Stack veya Grafana Loki ile entegrasyonu oldukça kolay:

# Caddy log dosyasını tail ile takip edin
tail -f /var/log/caddy/api.example.com.log | jq '.'

Prometheus metrics için Caddy’nin admin endpoint’ini kullanabilirsiniz:

# Admin API ile istatistikleri sorgulayın
curl http://localhost:2019/metrics
curl http://localhost:2019/debug/vars

gRPC bağlantılarını test etmek için grpcurl aracını kullanın:

# grpcurl kurulumu
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest

# Servisi listele
grpcurl -plaintext localhost:50051 list

# TLS ile Caddy üzerinden test
grpcurl -d '{"name": "test"}' api.example.com:443 helloworld.Greeter/SayHello

# Verbose mod ile header'ları gör
grpcurl -v -d '{"name": "test"}' api.example.com:443 helloworld.Greeter/SayHello

Yaygın Sorunlar ve Çözümleri

“received http2 frame when not expecting it” hatası: Bu genellikle backend’in HTTP/2 desteklemediğine işaret eder. Backend’i h2c:// yerine düz http:// ile çağırdığınızdan emin olun ya da backend’in HTTP/2’yi etkinleştirmesi gerekiyor.

Streaming yanıtların gecikmeli gelmesi: flush_interval değerini kontrol edin. Varsayılan değer buffer’lamaya neden olabilir. Streaming için mutlaka -1 kullanın:

# Konfigürasyonun yüklendiğini doğrulayın
caddy validate --config /etc/caddy/Caddyfile

# Konfigürasyonu reload edin
sudo systemctl reload caddy
# veya
caddy reload --config /etc/caddy/Caddyfile

gRPC bağlantı kesilmeleri (RST_STREAM): Bu sorun genellikle proxy timeout’larından kaynaklanır. Özellikle uzun süreli streaming bağlantıları için:

grpc.example.com {
    @grpc {
        protocol grpc
    }
    
    reverse_proxy @grpc h2c://localhost:50051 {
        flush_interval -1
        
        transport http {
            # Uzun streaming için keepalive süresini artırın
            keepalive 300s
            # Dial timeout
            dial_timeout 10s
            # Response header timeout (gRPC için 0 yapın - sınırsız)
            response_header_timeout 0s
            versions 2
        }
    }
}

CORS sorunları (web tarayıcılarından gRPC-Web): Tarayıcıdan gRPC kullanıyorsanız (grpc-web), CORS header’larını eklemeniz gerekir:

grpcweb.example.com {
    @grpcWeb {
        header Content-Type application/grpc-web*
    }
    
    reverse_proxy @grpcWeb h2c://localhost:50051 {
        flush_interval -1
        header_up Content-Type {>Content-Type}
    }
    
    header {
        Access-Control-Allow-Origin *
        Access-Control-Allow-Methods "POST, GET, OPTIONS"
        Access-Control-Allow-Headers "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-User-Agent, X-Grpc-Web"
        Access-Control-Expose-Headers "grpc-status, grpc-message"
    }
}

Performans İpuçları

Production ortamına geçmeden önce sistem seviyesinde birkaç ayar yapmanız performansı önemli ölçüde iyileştirebilir:

# /etc/sysctl.d/99-caddy-grpc.conf
# Ağ buffer boyutlarını artır
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216

# TIME_WAIT soketlerini hızlı geri dönüştür
net.ipv4.tcp_tw_reuse = 1

# SYN backlog
net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 65535

# Değişiklikleri uygula
sudo sysctl -p /etc/sysctl.d/99-caddy-grpc.conf

Caddy’nin kendi konfigürasyonunda da bazı global ayarlar yapılabilir:

{
    # Admin endpoint
    admin localhost:2019
    
    # HTTP/2 için gerekli
    servers {
        protocols h1 h2 h3
    }
    
    # TLS cache
    storage file_system {
        root /var/lib/caddy
    }
    
    # Log seviyesi
    log {
        level INFO
    }
}

grpc.example.com {
    # Servis konfigürasyonu buraya
}

Sonuç

Caddy, gRPC ve HTTP/2 proxy senaryolarında hem kurulum kolaylığı hem de konfigürasyon sadeliği açısından gerçekten güçlü bir tercih. Özellikle otomatik TLS yönetimi ve HTTP/2 desteğinin kutudan çıkma özelliği, Nginx veya HAProxy ile karşılaştırıldığında ciddi bir zaman tasarrufu sağlıyor.

Bu yazıda ele aldığımız senaryoları özetlemek gerekirse: basit gRPC proxy’den load balancing’e, mTLS’den mixed traffic yönetimine kadar Caddy’nin reverse_proxy direktifi ve h2c:// protokol prefiksi bu işlerin büyük çoğunluğunu karşılıyor. flush_interval -1 streaming için neredeyse zorunlu, transport http bloğu ise backend bağlantı davranışını ince ayarlamak için en kritik yer.

Production’a geçmeden önce caddy validate ile konfigürasyonunuzu mutlaka doğrulayın, grpcurl ile gerçek gRPC çağrıları yaparak end-to-end testi tamamlayın ve streaming senaryolarınızı da ayrıca test etmeyi ihmal etmeyin. Timeout ve keepalive ayarları uygulama davranışınıza göre kalibre edilmeli, tek bir evrensel değer yoktur.

Mikro servis geçişlerinde veya varolan gRPC altyapınıza API gateway eklerken Caddy’yi ciddi bir alternatif olarak değerlendirmenizi öneririm.

Yorum yapın