Go ile Sıfırdan gRPC Servisi Nasıl Yazılır? Adım Adım Production Rehberi
REST API'ler hâlâ iş görüyor, fakat gerçek zamanlı veri akışının ve düşük gecikmenin (latency) her şey olduğu ortamlarda — loglama, metrik toplama, IoT sinyalleri — artık gRPC standardı öne çıkıyor. Google, Netflix, Spotify ve Cloudflare gibi devler tüm dahili iletişimlerini gRPC üzerine kurdu. Peki neden REST yetmiyor ve Go ile kendi gRPC servisinizi sıfırdan nasıl yazarsınız?
Bu rehberde, Eresus Security'nin açık kaynak olarak yayınladığı EresusLog projesini referans alarak, Go ile tam bir production-grade gRPC servisi inşa edeceğiz. Protobuf tanımlamadan, veritabanı entegrasyonuna, kimlik doğrulama (Auth), istek loglama ve hız sınırlama (Rate Limiting) interceptor'lerine kadar her şeyi adım adım kodlayacağız.
1. gRPC Nedir ve REST'ten Farkı Ne?
gRPC, Google'ın geliştirdiği açık kaynaklı bir Remote Procedure Call (Uzak Prosedür Çağrısı) framework'üdür. REST API'lerle karşılaştırıldığında:
| Özellik | REST (JSON/HTTP) | gRPC (Protobuf/HTTP/2) | | :--- | :--- | :--- | | Veri Formatı | JSON (text) | Protocol Buffers (binary) | | Aktarım Hızı | Yavaş (JSON serialization) | Çok hızlı (ikili sıkıştırma) | | Streaming | Yok (WebSocket lazım) | Doğal destek (4 farklı pattern) | | Tip Güvenliği | Yok | Derleme zamanında kontrol | | HTTP Versiyonu | HTTP/1.1 | HTTP/2 (multiplexing) |
Eğer servisler arası iletişimde (microservice-to-microservice) milisaniyeler önemliyse, gRPC tek doğru cevaptır.
2. Adım 1: Protobuf Servis Tanımı
Her gRPC projesi bir .proto dosyasıyla başlar. Bu dosya hem veri yapılarınızı (message) hem de API metodlarınızı (service) tanımlar:
syntax = "proto3";
package logger;
option go_package = "github.com/EresusSecurity/eresuslog/api/proto;logger";
service LoggerService {
// Tek bir log gönder
rpc Log(LogRequest) returns (LogResponse) {}
// İstemciden sunucuya log akışı (client-streaming)
rpc StreamLogs(stream LogRequest) returns (LogResponse) {}
// Veritabanındaki logları sorgula
rpc FetchLogs(FetchRequest) returns (FetchResponse) {}
// Gerçek zamanlı log dinle (server-streaming)
rpc SubscribeLogs(SubscribeRequest) returns (stream LogEntry) {}
}
message LogRequest {
string service_name = 1;
string level = 2;
string message = 3;
int64 timestamp = 4;
map<string, string> metadata = 5;
}
Dikkat edin: 4 farklı RPC pattern'ı tek bir servis altında tanımlandı — Unary, Client-Streaming, Server-Streaming ve gerekirse Bidirectional.
protoc ile Go kodlarını üretiyoruz:
protoc --go_out=. --go_opt=paths=source_relative \
--go-grpc_out=. --go-grpc_opt=paths=source_relative \
api/proto/logger.proto
Bu komut logger.pb.go (veri yapıları) ve logger_grpc.pb.go (servis arayüzleri) dosyalarını otomatik oluşturur.
3. Adım 2: Veritabanı Katmanı (PostgreSQL + GORM)
Loglarımızı kalıcı bir şekilde depolamak için GORM ORM'ini kullanıyoruz:
// internal/db/models.go
type Log struct {
gorm.Model
ServiceName string `gorm:"index"`
Level string `gorm:"index"`
Message string
Timestamp time.Time
Metadata string // JSON encoded
}
Repository katmanı ile veritabanı işlemleri temiz bir şekilde izole edilir:
// internal/db/repository.go
func (r *Repository) SaveLog(ctx context.Context, serviceName, level, message string,
timestamp int64, metadata map[string]string) error {
metadataJson, _ := json.Marshal(metadata)
log := &Log{
ServiceName: serviceName,
Level: level,
Message: message,
Timestamp: time.Unix(timestamp, 0),
Metadata: string(metadataJson),
}
return r.db.WithContext(ctx).Create(log).Error
}
4. Adım 3: gRPC Sunucu Implementasyonu
Servisimizi UnimplementedLoggerServiceServer üzerine inşa ediyoruz. İşte real-time pub/sub mekanizmasının kalp kodu:
// internal/server/server.go
type LoggerServer struct {
pb.UnimplementedLoggerServiceServer
repo *db.Repository
subscribers []chan *pb.LogEntry
mu sync.RWMutex
}
func (s *LoggerServer) Log(ctx context.Context, req *pb.LogRequest) (*pb.LogResponse, error) {
err := s.repo.SaveLog(ctx, req.ServiceName, req.Level, req.Message,
req.Timestamp, req.Metadata)
if err != nil {
return &pb.LogResponse{Success: false, Message: err.Error()}, nil
}
// Tüm aktif dinleyicilere yayınla (broadcast)
s.broadcast(&pb.LogEntry{
ServiceName: req.ServiceName,
Level: req.Level,
Message: req.Message,
Timestamp: req.Timestamp,
Metadata: req.Metadata,
})
return &pb.LogResponse{Success: true, Message: "Log saved"}, nil
}
SubscribeLogs metoduyla herhangi bir istemci canlı log akışına abone olabilir — Tıpkı bir tail -f gibi, ama ağ üzerinde ve tip-güvenli (type-safe).
5. Adım 4: Interceptor Zinciri (Güvenlik Katmanları)
gRPC'nin en güçlü silahı Interceptor'lerdir. REST'teki middleware konseptinin karşılığıdır. EresusLog'da 3 katmanlı bir zincir kuruyoruz:
5.1 Request Logger Interceptor
Her gelen isteğin IP adresini, hangi metodu çağırdığını, ne kadar sürdüğünü ve sonucunu loglar:
func (r *RequestLoggerInterceptor) Unary() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
resp, err := handler(ctx, req)
clientIP := "unknown"
if p, ok := peer.FromContext(ctx); ok {
clientIP = p.Addr.String()
}
st, _ := status.FromError(err)
log.Printf("[gRPC] %s | %s | %s | %v",
info.FullMethod, clientIP, st.Code(), time.Since(start))
return resp, err
}
}
5.2 Rate Limiter Interceptor
Her IP adresi için kayan pencere (sliding window) hız sınırlaması — DDoS ve log spam saldırılarını engeller:
rl := server.NewRateLimiter(100, 10*time.Second) // IP başına 10sn'de 100 istek
5.3 Auth Interceptor (API Key)
gRPC metadata'sından Authorization: Bearer <key> token'ını doğrular:
func (i *AuthInterceptor) authorize(ctx context.Context) error {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return status.Errorf(codes.Unauthenticated, "metadata is not provided")
}
values := md["authorization"]
if len(values) == 0 {
return status.Errorf(codes.Unauthenticated, "token is missing")
}
token := strings.TrimPrefix(values[0], "Bearer ")
if token != i.validAPIKey {
return status.Errorf(codes.Unauthenticated, "invalid API key")
}
return nil
}
Bunları sunucuya zincir halinde bağlıyoruz:
s := grpc.NewServer(
grpc.ChainUnaryInterceptor(
reqLogger.Unary(),
rateLimiter.UnaryInterceptor(),
authInterceptor.Unary(),
),
grpc.ChainStreamInterceptor(
reqLogger.Stream(),
rateLimiter.StreamInterceptor(),
authInterceptor.Stream(),
),
)
6. Adım 5: Health Check ve Production Hazırlığı
Kubernetes veya AWS ALB arkasında çalışacak her gRPC servisi bir Health Check endpoint'ine sahip olmalıdır:
healthServer := health.NewServer()
healthpb.RegisterHealthServer(s, healthServer)
healthServer.SetServingStatus("logger.LoggerService",
healthpb.HealthCheckResponse_SERVING)
Terminal'den test:
grpcurl -plaintext localhost:50051 grpc.health.v1.Health/Check
7. Sonuç: Tüm Kodu Alın ve Çalıştırın
Bu rehberdeki tüm kodu tek bir yerde görmek ve hemen çalıştırmak isterseniz, EresusLog'un tüm kaynak koduna açık kaynak olarak erişebilirsiniz:
git clone https://github.com/EresusSecurity/eresuslog.git
cd eresuslog
cp .env.example .env
go run cmd/server/main.go
Projeye GitHub üzerinden yıldız (⭐) vererek destek olabilir, katkıda bulunabilirsiniz!
Eresus Security olarak sistemlerinizin güvenliğini otonom yapay zeka ajanlarıyla korumak, DevSecOps altyapınızı güçlendirmek veya sızma testi (pentest) yaptırmak için bizimle iletişime geçebilirsiniz.