K8s Gateway is a high-performance, Kubernetes-native API Gateway built with .NET 9 and YARP (Yet Another Reverse Proxy). It's designed to serve as the backbone of microservices architectures with near-zero latency overhead, comprehensive security, and enterprise-grade observability.
This solution follows Clean Architecture principles with clear separation of concerns:
K8sGateway/
├── src/
│ ├── K8sGateway.Core/ # Domain abstractions and constants
│ │ ├── Interfaces/ # ICorrelationIdProvider, ISecurityHeadersProvider
│ │ └── Constants/ # HeaderNames, RateLimitPolicies
│ │
│ ├── K8sGateway.Infrastructure/ # Implementation of core abstractions
│ │ ├── Providers/ # CorrelationIdProvider, SecurityHeadersProvider
│ │ └── DependencyInjection.cs
│ │
│ └── K8sGateway.Host/ # ASP.NET Core host (API Gateway)
│ ├── Middleware/ # SecurityHeaders, CorrelationId, GlobalExceptionHandler
│ ├── Program.cs # YARP configuration and middleware pipeline
│ └── appsettings.json # Route and cluster configuration
│
├── k8s/ # Kubernetes manifests
└── Dockerfile # Multi-stage container build
- Configuration-driven routing (no code recompilation needed)
- Hot-reloadable routes and clusters from
appsettings.json - Support for HTTP/1.1, HTTP/2, HTTP/3 (QUIC)
- Built-in gRPC and WebSocket support
- Service discovery via DNS (e.g.,
http://service-name.namespace.svc.cluster.local) - Health checks for liveness (
/health/live) and readiness (/health/ready) - Graceful shutdown support
- Resource-aware (respects K8s CPU/memory limits)
- Token Bucket algorithm for global rate limiting
- Fixed Window, Sliding Window policies for per-route limiting
- Concurrency limiting
- Automatic rejection with RFC 7807 Problem Details
- Strict security headers (HSTS, CSP, X-Frame-Options, etc.)
- Correlation ID tracking for request tracing
- Global exception handler (prevents crashes)
- Non-root container execution
- Request/response logging with structured logging (Serilog)
- Structured logging with Serilog
- Correlation IDs for distributed tracing
- Request logging with timing and status codes
- Health check endpoints for monitoring
- Server Garbage Collection enabled
- Tiered JIT compilation
- Connection pooling (MaxConnectionsPerServer: 1000)
- Kestrel tuning for high throughput
- No artificial concurrency limits
{
"ReverseProxy": {
"Routes": {
"route_name": {
"ClusterId": "cluster_name",
"RateLimiterPolicy": "FixedWindowPolicy",
"Match": { "Path": "/api/service/{**remainder}" },
"Transforms": [
{ "PathRemovePrefix": "/api/service" },
{ "RequestHeader": "X-Forwarded-Prefix", "Set": "/api/service" }
]
}
},
"Clusters": {
"cluster_name": {
"Destinations": {
"dest1": {
"Address": "http://service-name.namespace.svc.cluster.local:80"
}
},
"HttpClient": {
"MaxConnectionsPerServer": 1000
}
}
}
}
}No code changes required! Simply update appsettings.json:
{
"ReverseProxy": {
"Routes": {
"my_new_service": {
"ClusterId": "my_service_cluster",
"RateLimiterPolicy": "TokenBucketPolicy",
"Match": { "Path": "/api/myservice/{**remainder}" },
"Transforms": [
{ "PathRemovePrefix": "/api/myservice" }
]
}
},
"Clusters": {
"my_service_cluster": {
"Destinations": {
"dest1": {
"Address": "http://my-service.default.svc.cluster.local:80"
}
}
}
}
}
}# Restore dependencies
dotnet restore
# Build solution
dotnet build
# Run the gateway (listens on http://localhost:5000)
dotnet run --project src/K8sGateway.HostVisit http://localhost:5000/health/live to verify the gateway is running.
# Build Docker image
docker build -t k8sgateway:latest .
# Run container
docker run -p 80:80 \
-e ASPNETCORE_ENVIRONMENT=Production \
k8sgateway:latestCreate a ConfigMap for your routes (example in k8s/configmap.yaml):
kubectl apply -f k8s/The request flows through the following middleware in order:
- GlobalExceptionHandlerMiddleware - Catches all exceptions, returns RFC 7807 Problem Details
- CorrelationIdMiddleware - Ensures all requests have correlation IDs for tracing
- SerilogRequestLogging - Structured request/response logging
- SecurityHeadersMiddleware - Adds strict security headers to all responses
- RateLimiter - Enforces rate limiting policies
- MapReverseProxy - YARP routing to destination services
- Tokens: 100 per client IP
- Replenishment: 20 tokens every 10 seconds
- Queue: 10 requests queued per client
- Limit: 100 requests per minute
- Queue: 10 requests
- Limit: 100 requests per minute (across 6 segments)
- Queue: 10 requests
- Tokens: 100 per route
- Replenishment: 20 tokens every 10 seconds
- Queue: 10 requests
- Limit: 50 concurrent requests
- Queue: 25 requests
Rejected requests return HTTP 429 Too Many Requests with a Retry-After header.
All responses include:
| Header | Value | Purpose |
|---|---|---|
Strict-Transport-Security |
max-age=31536000; includeSubDomains; preload | Enforce HTTPS |
X-Content-Type-Options |
nosniff | Prevent MIME sniffing |
X-Frame-Options |
DENY | Prevent clickjacking |
Content-Security-Policy |
default-src 'self'; frame-ancestors 'none' | Strict CSP |
Referrer-Policy |
strict-origin-when-cross-origin | Control referrer |
Permissions-Policy |
(cameras, microphone, payment, etc. disabled) | Restrict browser features |
X-Permitted-Cross-Domain-Policies |
none | Block Flash/PDF embedding |
The gateway never crashes. All errors return HTTP 502 Bad Gateway with RFC 7807 Problem Details:
{
"type": "https://httpstatuses.com/502",
"title": "Gateway Error",
"status": 502,
"detail": "An error occurred while processing your request.",
"instance": "/api/users/123",
"correlationId": "a1b2c3d4e5f6g7h8",
"traceId": "0hm15r5gjj5n3s8p:00000001"
}Every error is logged with its correlation ID for troubleshooting.
livenessProbe:
httpGet:
path: /health/live
port: 80
initialDelaySeconds: 5
periodSeconds: 30readinessProbe:
httpGet:
path: /health/ready
port: 80
initialDelaySeconds: 5
periodSeconds: 10View structured logs with correlation ID:
# All logs for a specific correlation ID
kubectl logs <pod> | grep "a1b2c3d4e5f6g7h8"
# Following logs
kubectl logs -f <pod>The gateway is configured for maximum throughput:
serverOptions.Limits.MaxConcurrentConnections = null; // No limit
serverOptions.Limits.MaxRequestBodySize = 100MB;
serverOptions.Limits.MinRequestBodyDataRate = null; // Allow WebSockets
serverOptions.Limits.KeepAliveTimeout = 2 minutes;
serverOptions.ConfigureEndpointDefaults(listenOptions =>
listenOptions.Protocols = Http1AndHttp2AndHttp3);Each cluster maintains a pool of up to 1,000 connections to destination services, reducing SNAT port exhaustion.
- Server GC enabled for better throughput
- Tiered JIT enabled for faster startup and better long-term performance
- WAF Protection: For public-facing deployments, place a WAF (Cloudflare, Azure Front Door) in front of the gateway.
- Rate Limiting: The default policy is aggressive but configurable. Adjust based on your upstream service capacity.
- HTTPS: Always use TLS in production. Modify
appsettings.jsonto use HTTPS endpoints. - RBAC: The gateway runs as non-root user in containers.
- Network Policies: Use Kubernetes NetworkPolicies to restrict traffic to/from the gateway.
- Check gateway logs:
kubectl logs <gateway-pod> - Verify destination service DNS:
kubectl exec <gateway-pod> -- nslookup service-name.namespace.svc.cluster.local - Check rate limiting: Is the client hitting the rate limit? (HTTP 429)
- Monitor connection pooling:
kubectl top pod - Check downstream service response times
- Increase
MaxConnectionsPerServerif SNAT exhaustion is detected
- Monitor garbage collection: Add GC logging in appsettings
- Check for memory leaks in rate limiter queue
- Verify large request bodies aren't being buffered
This project uses strict warning-as-errors compilation. All code must:
- Follow C# naming conventions
- Use nullable reference types (
#nullable enable) - Include XML documentation on public types
- Pass code analysis
[Your License Here]
For issues or questions, please open an issue or contact the team.