Files
juwan-backend/ENVOY_GATEWAY_GUIDE.md
T
wwweww fdbcde13b2 add:
2026-02-23 20:36:21 +08:00

15 KiB
Raw Blame History

Envoy Gateway 配置指南(带 JWT 认证)

📋 目录

  1. 快速开始
  2. 添加新服务
  3. JWT 认证配置
  4. 分级访问控制
  5. 故障排查

快速开始

前置条件

  • K8s 集群正在运行(已验证
  • Envoy Gateway Pod 处于 Running 状态
  • 所有后端服务已部署

当前网关状态

# 查看 Envoy Pod
kubectl get pods -n juwan -l app=envoy-gateway

# 查看网关 Service
kubectl get svc -n juwan envoy-gateway

# 查看 ConfigMap
kubectl get cm -n juwan envoy-config

访问网关

# 通过 kubectl 端口转发(本地测试)
kubectl port-forward -n juwan svc/envoy-gateway 8080:80 &

# 测试
curl http://localhost:8080/api/users/login

添加新服务

场景:添加 Product 服务

1. 创建服务的 K8s 部署清单

编辑或创建 deploy/k8s/service/product/product-api.yaml

apiVersion: v1
kind: Namespace
metadata:
  name: juwan
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: product-api-config
  namespace: juwan
data:
  product-api.yaml: |
    Name: product-api
    Host: 0.0.0.0
    Port: 8890
    Database:
      DataSource: postgres://user:pass@pg-dx:5432/juwan
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: product-api
  namespace: juwan
  labels:
    app: product-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: product-api
  template:
    metadata:
      labels:
        app: product-api
    spec:
      containers:
      - name: api
        image: your-registry/product-api:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8890
          name: http
        volumeMounts:
        - name: config
          mountPath: /etc/product-api
        env:
        - name: TZ
          value: "Asia/Shanghai"
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 500m
            memory: 512Mi
        livenessProbe:
          httpGet:
            path: /health
            port: 8890
          initialDelaySeconds: 10
          periodSeconds: 10
      volumes:
      - name: config
        configMap:
          name: product-api-config
---
apiVersion: v1
kind: Service
metadata:
  name: product-api-svc
  namespace: juwan
spec:
  selector:
    app: product-api
  ports:
  - port: 8890
    targetPort: 8890
    name: http
  type: ClusterIP

2. 在 Envoy 网关中添加路由

编辑 deploy/k8s/envoy-gateway.yaml,在 route_configroutes 部分添加:

# ... 在现有路由下方添加:
- match:
    prefix: /api/products
  route:
    cluster: product_api_cluster
    timeout: 30s

3. 在 Envoy 网关中添加上游集群

编辑 deploy/k8s/envoy-gateway.yaml,在 clusters 部分添加:

- name: product_api_cluster
  connect_timeout: 5s
  type: STRICT_DNS
  dns_lookup_family: V4_ONLY
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: product_api_cluster
    endpoints:
      - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: product-api-svc.juwan.svc.cluster.local
                  port_value: 8890
  health_checks:
    - timeout: 3s
      interval: 10s
      unhealthy_threshold: 2
      healthy_threshold: 2
      http_health_check:
        path: /health

4. 部署到集群

# 部署 Product API
kubectl apply -f deploy/k8s/service/product/product-api.yaml

# 更新 Envoy 配置
kubectl apply -f deploy/k8s/envoy-gateway.yaml

# 重启 Envoy Pod 以加载新配置
kubectl delete pods -n juwan -l app=envoy-gateway

# 验证
kubectl get pods -n juwan

# 测试新接口
curl http://localhost:8080/api/products

JWT 认证配置

1. 生成 JWT 密钥并存储

# 执行设置脚本
bash deploy/envoy/setup-jwt-auth.sh

# 或手动执行
JWT_SECRET=$(openssl rand -hex 32)
echo "保存这个密钥: $JWT_SECRET"

# 创建 K8s Secret
kubectl create secret generic jwt-secret \
  --from-literal=key=$JWT_SECRET \
  -n juwan

2. 配置 Envoy JWT 认证

编辑 deploy/k8s/envoy-gateway.yaml,更新 http_filters 部分:

http_filters:
  # JWT 认证过滤器(必须在 router 之前)
  - name: envoy.filters.http.jwt_authn
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.jwt_authn.v3.JwtAuthentication
      
      providers:
        default:
          issuer: "juwan"
          audiences: "api"
          # 使用 ConfigMap 中的 JWKS(已通过 volumeMount 挂载)
          local_jwks:
            filename: /etc/envoy/jwks.json
      
      rules:
        # 规则1: 登录端点不需要认证
        - match:
            prefix: /api/users/login
          allow_missing_or_failed: true
        
        # 规则2: 注册端点不需要认证
        - match:
            prefix: /api/users/register
          allow_missing_or_failed: true
        
        # 规则3: 获取公开商品列表不需要认证
        - match:
            prefix: /api/products
            case_sensitive: false
          methods: ["GET"]  # 仅 GET 不需要认证
          allow_missing_or_failed: true
        
        # 规则4: 其他所有路由需要认证
        - match:
            prefix: "/"
          requires:
            provider_name: "default"
  
  # 路由过滤器(在 JWT 认证之后)
  - name: envoy.filters.http.router
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

3. 在 Envoy Deployment 中挂载 JWKS

编辑 deploy/k8s/envoy-gateway.yaml 的 Deployment 部分:

spec:
  # ... 其他配置 ...
  template:
    spec:
      containers:
      - name: envoy
        # ... 其他配置 ...
        volumeMounts:
        - name: envoy-config
          mountPath: /etc/envoy
        - name: jwks-config      # ← 新增
          mountPath: /etc/envoy
      
      volumes:
      - name: envoy-config
        configMap:
          name: envoy-config
      - name: jwks-config        # ← 新增
        configMap:
          name: jwks-config
          items:
          - key: jwks.json
            path: jwks.json

4. 在 API 服务中生成 JWT Token

在 User API 的 login 端点(app/users/api/internal/logic/user/loginlogic.go):

package user

import (
    "context"
    "time"
    "github.com/golang-jwt/jwt/v4"
    "app/users/api/internal/svc"
    "app/users/api/internal/types"
)

type LoginLogic struct {
    ctx    context.Context
    svcCtx *svc.ServiceContext
}

func (l *LoginLogic) Login(req *types.LoginReq) (*types.LoginResp, error) {
    // TODO: 验证用户名和密码
    
    // 从配置中获取 JWT 密钥
    jwtSecret := l.svcCtx.Config.JwtSecret
    if jwtSecret == "" {
        jwtSecret = "default-secret"  // 开发环境默认值
    }
    
    // 生成 JWT Token
    claims := jwt.MapClaims{
        "userId":   1,  // 实际应从数据库获取
        "username": req.Username,
        "exp":      time.Now().Add(24 * time.Hour).Unix(),
        "iat":      time.Now().Unix(),
    }
    
    token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    tokenString, err := token.SignedString([]byte(jwtSecret))
    if err != nil {
        return nil, err
    }
    
    return &types.LoginResp{
        Token:   tokenString,
        Expires: time.Now().Add(24 * time.Hour).Unix(),
        UserId:  1,
        Username: req.Username,
        Email:   "user@example.com",
    }, nil
}

5. 在 API 配置中设置 JWT 密钥

编辑 app/users/api/etc/user-api.yaml

Name: user-api
Host: 0.0.0.0
Port: 8888

JwtSecret: "${JWT_SECRET}"  # 环境变量

Database:
  DataSource: postgres://...

UserRpc:
  Endpoints:
    - user-rpc-svc.juwan.svc.cluster.local:50051

编辑 app/users/api/internal/config/config.go

package config

import "github.com/zeromicro/go-zero/rest"

type Config struct {
    rest.RestConf
    JwtSecret string `json:"jwtSecret"`
}

在 K8s Deployment 中设置环境变量:

spec:
  template:
    spec:
      containers:
      - name: api
        env:
        - name: JWT_SECRET
          valueFrom:
            secretKeyRef:
              name: jwt-secret
              key: key

分级访问控制

场景1: 获取用户信息(有权限区分)

如果用户查看自己的信息 → 返回完整数据
如果用户查看他人信息 → 返回部分数据

在 RPC 服务中实现

编辑 app/users/rpc/internal/logic/getUsersByIdLogic.go

func (l *GetUsersByIdLogic) GetUsersById(ctx context.Context, in *pb.GetUsersByIdReq) (*pb.GetUsersByIdResp, error) {
    // 获取请求者的 userId(由 API 层通过 context 传递)
    requesterID, ok := ctx.Value("userId").(int64)
    if !ok {
        requesterID = 0  // 未认证用户
    }
    
    targetID := in.Id
    
    // 查询数据库
    user := l.svcCtx.UserModel.FindOne(ctx, targetID)
    if user == nil {
        return nil, status.Error(codes.NotFound, "user not found")
    }
    
    resp := &pb.GetUsersByIdResp{
        Users: &pb.Users{
            UserId:    user.UserId,
            Username:  user.Username,
            CreatedAt: user.CreatedAt,
        },
    }
    
    // 权限检查:自己可以看全部,别人只能看部分
    if requesterID == targetID {
        resp.Users.Email = user.Email      // ✅ 自己可见
        resp.Users.Phone = user.Phone      // ✅ 自己可见
        resp.Users.Passwd = ""             // ❌ 密码永远不返回
    }
    // else: 只返回基本信息(username, userId
    
    return resp, nil
}

在 API 层调用时传递 userId

编辑 app/users/api/internal/logic/user/getUserInfoLogic.go

func (l *GetUserInfoLogic) GetUserInfo(req *types.GetUserInfoReq) (*types.UserInfo, error) {
    // 从 context 获取当前认证用户
    currentUserID, ok := l.ctx.Value("userId").(int64)
    if !ok {
        // 未认证 → 只能查看公开信息
        currentUserID = 0
    }
    
    // 调用 RPC,传递 userId
    ctx := context.WithValue(l.ctx, "userId", currentUserID)
    rpcResp, err := l.svcCtx.UserRpc.GetUsersById(ctx, &pb.GetUsersByIdReq{
        Id: req.UserId,
    })
    if err != nil {
        return nil, err
    }
    
    return &types.UserInfo{
        UserId:   rpcResp.Users.UserId,
        Username: rpcResp.Users.Username,
        Email:    rpcResp.Users.Email,
        Phone:    rpcResp.Users.Phone,
        CreateAt: rpcResp.Users.CreatedAt,
    }, nil
}

场景2: 修改用户信息(只能修改自己)

func (l *UpdateUserInfoLogic) UpdateUserInfo(req *types.UpdateUserInfoReq) (*types.UpdateUserInfoResp, error) {
    // 获取当前认证用户
    currentUserID, ok := l.ctx.Value("userId").(int64)
    if !ok {
        return nil, errors.New("unauthorized")
    }
    
    // 权限检查:只能修改自己的信息
    if currentUserID != req.UserId {
        return nil, errors.New("forbidden: can only update your own info")
    }
    
    // 更新用户信息
    // ...
    
    return &types.UpdateUserInfoResp{
        Message: "更新成功",
    }, nil
}

故障排查

问题1: Envoy Pod 启动失败

# 查看日志
kubectl logs -n juwan -l app=envoy-gateway --tail=100

# 常见错误及解决
# Error: "no such field"
#   → YAML 字段名拼写错误或与 Envoy 版本不兼容
#   → 检查 Envoy 版本并查看官方文档

# Error: "unknown cluster"
#   → envoy-gateway.yaml 中缺少 cluster 定义
#   → 确保添加了所有需要的 cluster 部分

# Error: "unknown extension type"
#   → 使用了 Envoy 不支持的扩展类型
#   → 检查 "@type" 字段是否正确

问题2: JWT 认证失败

# 验证 JWKS ConfigMap 是否存在
kubectl get cm -n juwan jwks-config

# 查看 JWKS 内容
kubectl get cm jwks-config -n juwan -o jsonpath='{.data.jwks\.json}'

# 验证 Envoy 能否读取 JWKS
kubectl exec -it {envoy-pod-name} -n juwan -- ls -la /etc/envoy/

# 测试没有 Token 的请求(应返回 401)
curl -v http://localhost/api/users/1

# 测试有效 Token 的请求
TOKEN="your-jwt-token"
curl -H "Authorization: Bearer $TOKEN" http://localhost/api/users/1

问题3: 后端服务无法访问

# 查看 Service 是否存在
kubectl get svc -n juwan

# 测试 DNS 解析
kubectl exec -it {pod-name} -n juwan -- \
  nslookup product-api-svc.juwan.svc.cluster.local

# 查看 Pod 是否正确运行
kubectl get pods -n juwan -l app=product-api

# 查看后端服务日志
kubectl logs -n juwan -l app=product-api --tail=50

# Envoy 检查上游集群状态
kubectl exec -it {envoy-pod-name} -n juwan -- \
  curl localhost:9901/clusters | grep -A5 product_api_cluster

问题4: 跨域请求失败

如果前端遇到 CORS 问题:

# 在 Envoy 配置中添加 CORS 过滤器
http_filters:
  - name: envoy.filters.http.cors
    typed_config:
      "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
  
  # JWT 认证过滤器(在 CORS 之后)
  - name: envoy.filters.http.jwt_authn
    # ...

配置更新流程

每次修改 envoy-gateway.yaml 后的完整更新步骤:

# 1. 验证 YAML 语法
kubectl apply -f deploy/k8s/envoy-gateway.yaml --dry-run=client

# 2. 应用配置
kubectl apply -f deploy/k8s/envoy-gateway.yaml

# 3. 监控 Pod 重启(应该自动重新加载)
kubectl get pods -n juwan -l app=envoy-gateway -w

# 4. 查看最新日志确认无错误
kubectl logs -n juwan -l app=envoy-gateway --tail=50

# 5. 测试新配置
curl http://localhost/api/your-new-endpoint

总结

任务 文件 说明
添加新 API desc/api/, app/*/api/ 定义接口并实现业务逻辑
添加新 RPC desc/rpc/, app/*/rpc/ 内部服务通信(不通过网关)
更新网关路由 deploy/k8s/envoy-gateway.yaml 添加路由、集群、认证规则
配置认证 deploy/envoy/setup-jwt-auth.sh 生成和管理 JWT 密钥
部署到 K8s deploy/k8s/service/ 创建服务的 Deployment 和 Service

需要更多帮助?查看 PROJECT_GUIDE.md 了解完整的项目架构和工作流!