MonkeyCode构建API网关:从零到Kong/Nginx的全链路实战

所有微服务的统一入口:认证、限流、日志、灰度。API网关做对了,后端才能真正专心做业务。

API网关是什么?

客户端请求
    ↓
┌─────────────────┐
│   API Gateway   │ ← 所有请求的单一入口
│  ┌───────────┐  │
│  │ 认证鉴权  │  │
│  │ 限流熔断  │  │
│  │ 路由转发  │  │
│  │ 日志监控  │  │
│  │ 协议转换  │  │
│  │ 灰度发布  │  │
│  └───────────┘  │
└─────────────────┘
    ↓     ↓     ↓
用户服务 订单服务 支付服务

方案对比:选型指南

方案 适用规模 性能 配置方式 学习成本
Nginx + Lua 中小型(<100QPS) 极高 conf文件
Kong 中大型 REST API + DB
APISIX 大型(云原生) Admin API
Traefik K8s原生 注解/ConfigMap
自研(Go/Node) 特定需求 代码

实战一:Nginx + Kong(最成熟方案)

Kong + Docker Compose一键启动

# docker-compose.kong.yml
version: '3.8'
services:
  kong-database:
    image: postgres:15
    environment:
      POSTGRES_USER: kong
      POSTGRES_PASSWORD: kongpass
      POSTGRES_DB: kong
    volumes: [pgdata:/var/lib/postgresql/data]
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "kong"]
    networks: [kong-net]

  kong:
    image: kong:3.4
    depends_on: [kong-database]
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-database
      KONG_PG_USER: kong
      KONG_PG_PASSWORD: kongpass
      KONG_ADMIN_LISTEN: 0.0.0.0:8001
      KONG_PROXY_LISTEN: 0.0.0.0:8000
      KONG_LOG_LEVEL: info
    ports: ["8000:8000", "8443:8443", "8001:8001"]
    networks: [kong-net]

  konga:
    image: pantsel/konga:latest
    depends_on: [kong]
    environment:
      NODE_ENV: production
      DB_ADAPTER: postgres
      DB_HOST: kong-database
      DB_PORT: 5432
      DB_USER: kong
      DB_PASSWORD: kongpass
      DB_DATABASE: kong
    ports: ["1337:1337"]
    networks: [kong-net]

volumes:
  pgdata:

networks:
  kong-net:
    driver: bridge

Kong Admin配置路由和服务

# 1. 创建上游服务(user-service)
curl -X POST http://localhost:8001/services/ \
  -d "name=user-service" \
  -d "url=http://user-service:8001" \
  -d "port=8001" \
  -d "protocol=http"

# 2. 添加路由
curl -X POST http://localhost:8001/services/user-service/routes/ \
  -d "name=user-routes" \
  -d "paths[]=/api/users" \
  -d "methods=GET,POST,PUT,DELETE" \
  -d "strip_path=false"

# 3. 配置JWT认证插件
curl -X POST http://localhost:8001/services/user-service/plugins/ \
  -d "name=jwt" \
  -d "config.uri_param_names=jwt" \
  -d "config.cookie_names=jwt" \
  -d "config.claims_to_verify=exp"

# 4. 配置限流插件(每个IP每分钟100次)
curl -X POST http://localhost:8001/routes/user-routes/plugins/ \
  -d "name=rate-limiting" \
  -d "config.minute=100" \
  -d "config.policy=redis" \
  -d "config.redis_host=redis" \
  -d "config.redis_port=6379" \
  -d "config.hide_client_headers=false"

# 5. 配置IP黑白名单
curl -X POST http://localhost:8001/routes/user-routes/plugins/ \
  -d "name=ip-restriction" \
  -d "config.allow=127.0.0.1,10.0.0.0/8"

实战二:Nginx配置(轻量场景)

不需要Kong,用Nginx也能做:

# /etc/nginx/conf.d/api-gateway.conf

upstream user_service {
    least_conn;  # 最少连接优先
    server user-service-1:8001 weight=3;
    server user-service-2:8001 weight=2;
    keepalive 32;
}

upstream order_service {
    server order-service-1:8002;
    server order-service-2:8002 backup;  # 热备
}

# 限流配置
limit_req_zone $binary_remote_addr zone=user_limit:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=addr:10m;

server {
    listen 80;
    server_name api.example.com;
    
    # ─── 日志 ───
    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    'rt=$request_time uct="$upstream_connect_time" '
                    'uht="$upstream_header_time" urt="$upstream_response_time"';
    access_log /var/log/nginx/api-gateway.log main;
    
    # ─── 全局限流 ───
    limit_req zone=user_limit burst=20 nodelay;
    limit_conn addr 10;
    
    # ─── 路由 ───
    location /api/users/ {
        proxy_pass http://user_service/;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Connection "";
        
        # 超时配置
        proxy_connect_timeout 5s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
        
        # 重试配置
        proxy_next_upstream error timeout http_502 http_503;
    }
    
    location /api/orders/ {
        proxy_pass http://order_service/;
        # 其他配置同上...
    }
    
    # ─── 健康检查 ───
    location /health {
        access_log off;
        return 200 "OK\n";
        add_header Content-Type text/plain;
    }
}

实战三:自研Go网关(最高灵活度)

// gateway/main.go
package main

import (
    "net/http"
    "net/http/httputil"
    "net/url"
    "time"
    "github.com/gin-gonic/gin"
    "golang.org/x/time/rate"
)

// 服务路由配置
var routes = map[string]*ServiceConfig{
    "/api/users":    {Target: "http://user-service:8001", RateLimit: 100},
    "/api/orders":   {Target: "http://order-service:8002", RateLimit: 50},
    "/api/payments": {Target: "http://payment-service:8003", RateLimit: 20},
}

type ServiceConfig struct {
    Target    string
    RateLimit int
    limiter   *rate.Limiter
}

func main() {
    gin.SetMode(gin.ReleaseMode)
    r := gin.New()
    r.Use(gin.Recovery(), middlewareLogger(), AuthMiddleware(), RateLimitMiddleware())
    
    // 动态路由:匹配前缀后转发
    for prefix, cfg := range routes {
        target, _ := url.Parse(cfg.Target)
        proxy := httputil.NewSingleHostReverseProxy(target)
        proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
            // 错误处理:降级或熔断
            w.WriteHeader(http.StatusBadGateway)
            w.Write([]byte(`{"error":"service unavailable"}`))
        }
        
        cfg.limiter = rate.NewLimiter(rate.Limit(cfg.RateLimit), cfg.RateLimit*2)
        
        r.Any(prefix+"/*path", createProxyHandler(proxy))
    }
    
    r.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"status": "ok"}) })
    
    r.Run(":8000")
}

func createProxyHandler(proxy *httputil.ReverseProxy) gin.HandlerFunc {
    return func(c *gin.Context) {
        // 修改请求路径
        c.Request.URL.Path = "/" + c.Param("path")[1:]
        c.Request.URL.Host = proxy.Director(c.Request).URL.Host
        
        // 添加追踪头
        traceID := generateTraceID()
        c.Request.Header.Set("X-Trace-ID", traceID)
        c.Header("X-Trace-ID", traceID)
        
        // 执行代理
        proxy.ServeHTTP(c.Writer, c.Request)
    }
}

func RateLimitMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        for prefix, cfg := range routes {
            if c.Request.URL.Path HasPrefix(prefix) {
                if !cfg.limiter.Allow() {
                    c.AbortWithStatusJSON(http.StatusTooManyRequests,
                        gin.H{"error": "rate limit exceeded", "retry_after": "1s"})
                    return
                }
                break
            }
        }
        c.Next()
    }
}

认证鉴权(网关统一处理)

# Nginx + Lua 实现JWT验证
# 放在 /etc/nginx/nginx.conf 或通过OpenResty加载

access_by_lua_block {
    local jwt = require("resty.jwt")
    local cjson = require("cjson")
    
    local token = ngx.var.arg_jwt  -- URL参数
    if not token then
        -- 从Header取
        local auth_header = ngx.var.http_authorization
        if auth_header and string.match(auth_header, "Bearer%s+(.+)") then
            token = string.match(auth_header, "Bearer%s+(.+)")
        end
    end
    
    if not token then
        ngx.exit(ngx.HTTP_UNAUTHORIZED)
        return
    end
    
    local jwt_obj = jwt:verify("your-secret-key", token)
    if not jwt_obj.valid then
        ngx.exit(ngx.HTTP_UNAUTHORIZED)
        return
    end
    
    -- 把用户信息注入到请求头,透传给后端
    ngx.req.set_header("X-User-ID", jwt_obj.payload.sub)
    ngx.req.set_header("X-User-Role", jwt_obj.payload.role)
}

灰度发布(按Header/IP/权重)

# Nginx灰度:Header-based
geo $backend {
    default         user-service-stable;
    ~* "^v2"       user-service-v2;
}

# Nginx灰度:Weight-based
upstream user_service_stable {
    server 10.0.0.11:8001;
}
upstream user_service_v2 {
    server 10.0.0.21:8001;
}
upstream user_service_all {
    server 10.0.0.11:8001 weight=7;
    server 10.0.0.21:8001 weight=3;  # 30%流量到v2
}
// 自研网关灰度策略
type GrayStrategy struct {
    headerRules map[string]string  // Header值 → 版本
    ipRules    map[string]string   // IP段 → 版本
    weightV2   float64             // v2权重(0.0~1.0)
}

func (g *GrayStrategy) Route(r *http.Request) *url.URL {
    // 1. Header优先
    if v := r.Header.Get("X-Canary"); v != "" {
        if target := g.headerRules[v]; target != "" {
            return parseURL(target)
        }
    }
    
    // 2. IP白名单
    clientIP := getClientIP(r)
    if target := g.ipRules[clientIP]; target != "" {
        return parseURL(target)
    }
    
    // 3. 权重
    if rand.Float64() < g.weightV2 {
        return parseURL("http://user-service-v2")
    }
    
    return parseURL("http://user-service-stable")
}

监控指标(接入Prometheus)

# Nginx开启status + Prometheus采集
location /metrics {
    stub_status on;
    access_log off;
}

# Prometheus配置
- job_name: 'nginx'
  static_configs:
    - targets: ['nginx:80']
  metrics_path: '/metrics'

MonkeyCode Prompt模板

帮我的微服务架构设计API网关,需求:
1. 服务列表:[列出所有后端服务]
2. 认证方案:[JWT/Cookie/OAuth2]
3. 限流要求:[QPS/用户/IP维度]
4. 灰度策略:[按Header/权重/IP]
5. 方案选型:Nginx/Kong/APISIX/自研,给出推荐
6. 生成完整的配置文件或代码
7. 配置健康检查和降级策略

总结

API网关三大职责:路由、过滤、可观测

  • Nginx:轻量、成熟,适合中小型
  • Kong:功能全、UI友好,适合中大型
  • 自研:最灵活,适合有特殊需求的场景

MonkeyCode能帮你选型、配置、写代码,一站式搞定API网关。

posted @ 2026-05-29 21:50  机房管理员  阅读(18)  评论(0)    收藏  举报