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

618 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Envoy Gateway 配置指南(带 JWT 认证)
## 📋 目录
1. [快速开始](#快速开始)
2. [添加新服务](#添加新服务)
3. [JWT 认证配置](#jwt-认证配置)
4. [分级访问控制](#分级访问控制)
5. [故障排查](#故障排查)
---
## 快速开始
### 前置条件
- K8s 集群正在运行(已验证 ✅)
- Envoy Gateway Pod 处于 Running 状态
- 所有后端服务已部署
### 当前网关状态
```bash
# 查看 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
```
### 访问网关
```bash
# 通过 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`
```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_config``routes` 部分添加:
```yaml
# ... 在现有路由下方添加:
- match:
prefix: /api/products
route:
cluster: product_api_cluster
timeout: 30s
```
#### 3. 在 Envoy 网关中添加上游集群
编辑 `deploy/k8s/envoy-gateway.yaml`,在 `clusters` 部分添加:
```yaml
- 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. 部署到集群
```bash
# 部署 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
# 执行设置脚本
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` 部分:
```yaml
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 部分:
```yaml
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`):
```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`
```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`
```go
package config
import "github.com/zeromicro/go-zero/rest"
type Config struct {
rest.RestConf
JwtSecret string `json:"jwtSecret"`
}
```
在 K8s Deployment 中设置环境变量:
```yaml
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`
```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`
```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: 修改用户信息(只能修改自己)
```go
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 启动失败
```bash
# 查看日志
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 认证失败
```bash
# 验证 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: 后端服务无法访问
```bash
# 查看 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 问题:
```yaml
# 在 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` 后的完整更新步骤:
```bash
# 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` 了解完整的项目架构和工作流!