Enterprise API Gateway
Part 1: Core Gateway Implementation
In this first part, we’ll build the foundation of our API gateway with basic routing and proxy functionality. We’ll use Envoy as our data plane and implement a control plane in Go.
Architecture Overview
The core gateway consists of:
- Control Plane: Go service that manages configuration
- Data Plane: Envoy proxy handling requests
- Configuration API: gRPC interface for dynamic updates
Setting Up the Project
Let’s start by setting up our Go module and basic project structure:
mkdir api-gateway
cd api-gateway
go mod init github.com/yourusername/api-gateway
Control Plane Implementation
The control plane is responsible for managing Envoy’s configuration and providing service discovery:
// cmd/control-plane/main.go
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
"github.com/envoyproxy/go-control-plane/pkg/server/v3"
"github.com/yourusername/api-gateway/internal/controlplane"
)
func main() {
ctx := context.Background()
// Create the control plane server
cp := controlplane.NewServer()
// Start the xDS server
grpcServer := grpc.NewServer()
server.RegisterServer(grpcServer, cp, cp.log)
listener, err := net.Listen("tcp", ":18000")
if err != nil {
log.Fatalf("Failed to listen: %v", err)
}
log.Println("Control plane listening on :18000")
if err := grpcServer.Serve(listener); err != nil {
log.Fatalf("Failed to serve: %v", err)
}
}
Service Discovery
We implement a simple service discovery mechanism that watches for changes in upstream services:
// internal/controlplane/discovery.go
package controlplane
import (
"context"
"sync"
"time"
core "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
endpoint "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
)
type ServiceDiscovery struct {
mu sync.RWMutex
services map[string]*Service
watchers []chan struct{}
}
type Service struct {
Name string
Endpoints []Endpoint
}
type Endpoint struct {
Address string
Port uint32
Health HealthStatus
}
func (sd *ServiceDiscovery) RegisterService(service *Service) {
sd.mu.Lock()
defer sd.mu.Unlock()
sd.services[service.Name] = service
sd.notifyWatchers()
}
func (sd *ServiceDiscovery) GetEndpoints(serviceName string) []*endpoint.LocalityLbEndpoints {
sd.mu.RLock()
defer sd.mu.RUnlock()
service, exists := sd.services[serviceName]
if !exists {
return nil
}
var endpoints []*endpoint.LbEndpoint
for _, ep := range service.Endpoints {
endpoints = append(endpoints, &endpoint.LbEndpoint{
HostIdentifier: &endpoint.LbEndpoint_Endpoint{
Endpoint: &endpoint.Endpoint{
Address: &core.Address{
Address: &core.Address_SocketAddress{
SocketAddress: &core.SocketAddress{
Protocol: core.SocketAddress_TCP,
Address: ep.Address,
PortSpecifier: &core.SocketAddress_PortValue{
PortValue: ep.Port,
},
},
},
},
},
},
})
}
return []*endpoint.LocalityLbEndpoints{
{
LbEndpoints: endpoints,
},
}
}
Envoy Configuration
We generate Envoy configuration dynamically based on discovered services:
// internal/controlplane/config.go
package controlplane
import (
route "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
hcm "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
"github.com/envoyproxy/go-control-plane/pkg/wellknown"
)
func (cp *Server) generateRouteConfiguration() *route.RouteConfiguration {
return &route.RouteConfiguration{
Name: "local_route",
VirtualHosts: []*route.VirtualHost{
{
Name: "local_service",
Domains: []string{"*"},
Routes: []*route.Route{
{
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_Prefix{
Prefix: "/api/v1/users",
},
},
Action: &route.Route_Route{
Route: &route.RouteAction{
ClusterSpecifier: &route.RouteAction_Cluster{
Cluster: "user-service",
},
},
},
},
{
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_Prefix{
Prefix: "/api/v1/products",
},
},
Action: &route.Route_Route{
Route: &route.RouteAction{
ClusterSpecifier: &route.RouteAction_Cluster{
Cluster: "product-service",
},
},
},
},
},
},
},
}
}
Testing the Core Gateway
We can test our basic gateway functionality:
# Start the control plane
go run cmd/control-plane/main.go
# Start Envoy with our configuration
envoy -c envoy.yaml -l debug
# Test routing
curl -H "Host: api.example.com" http://localhost:8080/api/v1/users
Next Steps
In the next part, we’ll add authentication and authorization capabilities to secure our API gateway, including:
- JWT token validation
- OAuth2 integration
- Role-based access control
- API key management
The core gateway provides the foundation for all these advanced features.