You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过控制平面推送EnvoyProxy动态配置?Go实现增量xDS解惑

基于Delta xDS实现大规模Envoy实例的增量更新控制平面

Envoy侧的Delta xDS订阅配置

要让Envoy使用Delta xDS订阅,需在bootstrap配置中指定对应资源类型的API模式为DELTA_GRPC,并指向控制平面服务地址:

dynamic_resources:
  lds_config:
    api_config_source:
      api_type: DELTA_GRPC
      grpc_services:
        envoy_grpc:
          cluster_name: xds_cluster
  cds_config:
    api_config_source:
      api_type: DELTA_GRPC
      grpc_services:
        envoy_grpc:
          cluster_name: xds_cluster
clusters:
- name: xds_cluster
  connect_timeout: 0.25s
  type: STRICT_DNS
  lb_policy: ROUND_ROBIN
  load_assignment:
    cluster_name: xds_cluster
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: <你的控制平面IP>
              port_value: 18000

Envoy启动后会主动向控制平面发起DeltaDiscoveryRequest,请求携带当前已持有的资源版本、目标资源类型(如LISTENER、CLUSTER)及客户端node标识。

Go控制平面的Delta xDS实现

基于envoyproxy/go-control-plane SDK实现增量推送,核心是实现DeltaDiscoveryServer接口并维护客户端资源状态:

1. 实现DeltaDiscoveryServer核心接口

核心方法StreamDeltaResources用于处理Envoy长连接请求,维护每个客户端的资源版本快照:

package main

import (
	"context"
	"sync"

	v3 "github.com/envoyproxy/go-control-plane/envoy/service/discovery/v3"
	"google.golang.org/grpc"
)

type deltaServer struct {
	v3.UnimplementedDeltaDiscoveryServer
	// 存储每个客户端的资源版本状态,key为node.id
	clientStates sync.Map
	// 全局资源版本库,key为"资源类型/资源名",value为版本号
	globalResources sync.Map
}

func (s *deltaServer) StreamDeltaResources(stream v3.DeltaDiscoveryService_StreamDeltaResourcesServer) error {
	for {
		req, err := stream.Recv()
		if err != nil {
			return err
		}

		nodeID := req.GetNode().GetId()
		// 初始化或获取客户端状态
		state, _ := s.clientStates.LoadOrStore(nodeID, &clientState{
			resourceVersions: make(map[string]string),
		})
		clientState := state.(*clientState)

		resp := &v3.DeltaDiscoveryResponse{
			TypeUrl: req.GetTypeUrl(),
			Nonce:   generateNonce(),
		}

		// 对比全局资源与客户端版本,筛选增量内容
		s.globalResources.Range(func(key, value interface{}) bool {
			resType, resName := parseResourceKey(key.(string))
			if resType != req.GetTypeUrl() {
				return true
			}
			currentVersion := value.(string)
			clientVersion, exists := clientState.resourceVersions[resName]

			if !exists || clientVersion != currentVersion {
				if res := getResource(resType, resName); res != nil {
					resp.Resources = append(resp.Resources, res)
					clientState.resourceVersions[resName] = currentVersion
				}
			}
			return true
		})

		// 存在增量则推送
		if len(resp.Resources) > 0 {
			if err := stream.Send(resp); err != nil {
				return err
			}
		}
	}
}

type clientState struct {
	resourceVersions map[string]string
}

// 生成唯一Nonce,用于Envoy确认更新
func generateNonce() string {
	return "nonce-" + uuid.NewString()
}

// 解析资源key(格式为"type/name")
func parseResourceKey(key string) (string, string) {
	// 实现解析逻辑,按分隔符拆分类型与名称
}

// 从存储中获取序列化后的资源内容
func getResource(resType, resName string) *v3.Resource {
	// 从你的配置存储中取出对应资源并序列化
}

2. 增量更新触发逻辑

当资源有小更新时,只需更新全局资源库中的版本号,后续客户端请求会自动触发增量推送:

// 更新指定资源的版本号
func (s *deltaServer) UpdateResource(resType, resName, version string) {
	key := resType + "/" + resName
	s.globalResources.Store(key, version)
}

该逻辑下,仅持有旧版本资源的客户端会收到更新,且仅推送变化的资源条目,大幅降低带宽消耗。

关键注意事项

  • 客户端状态准确性:必须精准记录每个Envoy实例的资源版本,避免重复推送或漏推。
  • Nonce机制:每次推送需携带唯一Nonce,Envoy会在后续请求中返回该值,用于控制平面确认更新已被接收。
  • 资源删除处理:若需删除资源,在DeltaDiscoveryResponse的RemovedResources字段中添加目标资源名称即可,Envoy会自动移除对应资源。

内容的提问来源于stack exchange,提问作者Maxwell You

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 21:23:08