如何为Golang gRPC网关服务动态配置自定义Ingress Controller规则
实现gRPC网关的动态Ingress规则配置
我正在弃用Traefik Ingress Controller,改用Golang自行实现gRPC网关。当前代码中硬编码了gRPC方法白名单,需要替换为动态配置的Ingress规则,支持通过gRPC请求更新规则,并根据规则将请求转发到对应后端gRPC服务。现有硬编码代码如下:
var whitelistMethods = [...]string{`/auth.Auth/Authenticate`, `/xvm.xvmService/NewClientID`,`/xvm.xvmService/Authenticate`, `/xvm.xvmService/DeleteClientID`} func gatewayDirector() proxy.StreamDirector { return func(ctx context.Context, method string) (context.Context, *grpc.ClientConn, error) { meta, ok := metadata.FromIncomingContext(ctx) if !ok { return ctx, nil, errInvalidMeta } var clientAddr string if clientPeer, ok := peer.FromContext(ctx); ok { addr, _, err := net.SplitHostPort(clientPeer.Addr.String()) if err != nil { log.DErrorf("erred while parsing client address, error:%v", err) } else { clientAddr = addr } } if isWhitelistMethod(method) { log.Debugf("whitelist method:%v, forward to security service", method) return ctx, securityClientConn, nil } var clientConn *grpc.ClientConn // handle regular RPC methods and get the corresponding backend client clientConn, err = handleRegularMethods(method, meta) if err != nil { return ctx, nil, err } return ctx, clientConn, nil } } func isWhitelistMethod(method string) bool { for _, m := range whitelistMethods { if m == method { return true } } return false }
核心解决方案
替换硬编码白名单,实现三个核心模块:线程安全的动态规则存储、外部可调用的gRPC配置接口、基于规则的动态路由逻辑。
1. 定义动态规则与线程安全存储
首先定义规则结构体和并发安全的存储容器:
import ( "google.golang.org/grpc" "sync" "strings" ) // IngressRule 存储单条Ingress规则 type IngressRule struct { MethodPattern string // 匹配的gRPC方法,支持精确匹配(如"/auth.Auth/Authenticate")或前缀匹配(如"/xvm.xvmService/*") BackendAddr string // 后端gRPC服务地址 Conn *grpc.ClientConn // 已建立的后端连接 } // RuleStore 线程安全的规则存储容器 type RuleStore struct { sync.RWMutex rules map[string]*IngressRule } var globalRuleStore = &RuleStore{ rules: make(map[string]*IngressRule), }
2. 实现规则的增删改查操作
为RuleStore添加线程安全的操作方法,包含连接的自动管理:
// AddOrUpdateRule 添加或更新规则,自动重建后端连接 func (rs *RuleStore) AddOrUpdateRule(pattern string, backendAddr string) error { rs.Lock() defer rs.Unlock() // 检查规则是否存在,地址未变更则直接返回 if rule, exists := rs.rules[pattern]; exists { if rule.BackendAddr == backendAddr { return nil } // 关闭旧连接 _ = rule.Conn.Close() } // 建立新的后端连接 conn, err := grpc.Dial(backendAddr, grpc.WithInsecure()) // 根据实际需求添加TLS等配置 if err != nil { return err } rs.rules[pattern] = &IngressRule{ MethodPattern: pattern, BackendAddr: backendAddr, Conn: conn, } return nil } // RemoveRule 删除指定规则并关闭对应连接 func (rs *RuleStore) RemoveRule(pattern string) error { rs.Lock() defer rs.Unlock() rule, exists := rs.rules[pattern] if !exists { return nil } _ = rule.Conn.Close() delete(rs.rules, pattern) return nil } // MatchRule 根据请求方法匹配规则,优先精确匹配,再尝试前缀匹配 func (rs *RuleStore) MatchRule(method string) (*IngressRule, bool) { rs.RLock() defer rs.RUnlock() // 精确匹配 if rule, exists := rs.rules[method]; exists { return rule, true } // 前缀匹配(针对带*的规则) for pattern, rule := range rs.rules { if len(pattern) > 0 && pattern[len(pattern)-1] == '*' && strings.HasPrefix(method, pattern[:len(pattern)-1]) { return rule, true } } return nil, false }
3. 提供gRPC配置接口
定义外部可调用的gRPC服务,用于动态更新规则。首先编写proto文件:
syntax = "proto3"; package gatewayconfig; service GatewayConfigService { rpc AddOrUpdateRule(AddRuleRequest) returns (AddRuleResponse); rpc RemoveRule(RemoveRuleRequest) returns (RemoveRuleResponse); } message AddRuleRequest { string method_pattern = 1; string backend_addr = 2; } message AddRuleResponse { bool success = 1; string message = 2; } message RemoveRuleRequest { string method_pattern = 1; } message RemoveRuleResponse { bool success = 1; string message = 2; }
然后实现服务逻辑:
import ( "context" pb "your/proto/path/gatewayconfig" ) type gatewayConfigService struct { pb.UnimplementedGatewayConfigServiceServer } func (s *gatewayConfigService) AddOrUpdateRule(ctx context.Context, req *pb.AddRuleRequest) (*pb.AddRuleResponse, error) { err := globalRuleStore.AddOrUpdateRule(req.MethodPattern, req.BackendAddr) if err != nil { return &pb.AddRuleResponse{Success: false, Message: err.Error()}, nil } return &pb.AddRuleResponse{Success: true, Message: "Rule updated successfully"}, nil } func (s *gatewayConfigService) RemoveRule(ctx context.Context, req *pb.RemoveRuleRequest) (*pb.RemoveRuleResponse, error) { err := globalRuleStore.RemoveRule(req.MethodPattern) if err != nil { return &pb.RemoveRuleResponse{Success: false, Message: err.Error()}, nil } return &pb.RemoveRuleResponse{Success: true, Message: "Rule removed successfully"}, nil } // 在网关启动时注册该配置服务 func RegisterConfigService(s *grpc.Server) { pb.RegisterGatewayConfigServiceServer(s, &gatewayConfigService{}) }
4. 修改网关路由逻辑
替换原有的硬编码白名单判断,改为从动态规则中匹配:
func gatewayDirector() proxy.StreamDirector { return func(ctx context.Context, method string) (context.Context, *grpc.ClientConn, error) { meta, ok := metadata.FromIncomingContext(ctx) if !ok { return ctx, nil, errInvalidMeta } var clientAddr string if clientPeer, ok := peer.FromContext(ctx); ok { addr, _, err := net.SplitHostPort(clientPeer.Addr.String()) if err != nil { log.DErrorf("erred while parsing client address, error:%v", err) } else { clientAddr = addr } } // 从动态规则中匹配后端服务 if rule, matched := globalRuleStore.MatchRule(method); matched { log.Debugf("matched rule for method:%v, forward to backend:%v", method, rule.BackendAddr) return ctx, rule.Conn, nil } // 处理未匹配规则的请求(保留原有逻辑) clientConn, err := handleRegularMethods(method, meta) if err != nil { return ctx, nil, err } return ctx, clientConn, nil } }
5. 可选优化点
- 连接健康检查:为后端连接添加心跳检测,断开时自动重建
- 规则持久化:将规则存储到Etcd、Redis或本地文件,避免网关重启后规则丢失
- 配置权限控制:在配置接口中添加身份验证,防止非法修改规则
内容的提问来源于stack exchange,提问作者Abhishek Karigar
相关产品推荐
相关产品推荐

