Envoy Proxy 动态API的使用总结

Envoy Proxy和其它L4/L7反向搭理工具最大的区别就是原生支持动态配置。 首先来看一下Envoy的大致架构

从上图可以简单理解:Listener负责接受外部的请求,然后经过Filter/Router处理之后,在转发到具体的Cluster。 其中Listener,Router,Cluster和Host地址都是可以动态配置的,配置这些数据的服务就称之为X Discovery Services,简称XDS。

本文主要描述如何编写XDS Server更新逻辑。

Envoy Porxy XDS Service通过GRPC服务进行数据更新,所有Proto文件可以参考 https://github.com/envoyproxy/envoy/tree/master/api/envoy/api/v2 。 用户可以根据proto文件自行生成相对应语言的GRPC代码文件。如果使用golang来实现的话,Envoy已经提供了一份编译好的GRPC代码,地址在这里: https://github.com/envoyproxy/go-control-plane/tree/master/envoy/api/v2

每个XDS Service都有两种GRPC服务, StreamDeltaStream用来更新全量数据,Delta用来更新增量数据。下面以RDS Service为例来看看如何实现一个 XDS Service。

RDS Service可以提供所有的Route信息,一个简化后的典型Route配置如下:

# 完整的Route API定义参考 https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/rds.proto#envoy-api-msg-routeconfiguration

                route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
require_tls: NONE
routes:
- match:
prefix: "/MyService"
route: { cluster: my-grpc-svc_cluster }

上面的配置语义为: 当收到一个Path前缀为/MyService的请求后,将此请求转发到my-grpc-svc_cluster. (my-grpc-svc_cluster表示的是后端Upstream信息,可以是STATIC类型也可以由CDS Service动态提供)

RDS Service的作用就是动态生成类似上面的语义配置。 先来看相对简单的StreamRoutes如何实现。

GRPC描述文件中,对此函数的定义如下:

service RouteDiscoveryService {
rpc StreamRoutes(stream DiscoveryRequest) returns (stream DiscoveryResponse) {
} rpc DeltaRoutes(stream DeltaDiscoveryRequest) returns (stream DeltaDiscoveryResponse) {
} rpc FetchRoutes(DiscoveryRequest) returns (DiscoveryResponse) {
option (google.api.http) = {
post: "/v2/discovery:routes"
body: "*"
};
}
}

从返回值可以看出StreamRoutes是一个流函数,RDS会通过这个流实时将数据推送给Envoy。 所以大致的实现模型就是如下的样子:

func (r rds) StreamRoutes(ls envoy_api_v2.RouteDiscoveryService_StreamRoutesServer) error {
for{
select{
case x <- c>:
ls.Send(xxx)
} }
}

Send函数接受的是DiscoveryResponse指针,而这个DiscoveryResponse从定义来看是自解释动态结构体。 具体数据类型由typeUrl属性来决定。 具体到Route来说,typeURL是"type.googleapis.com/envoy.api.v2.RouteConfiguration". (类型说明参见 https://www.envoyproxy.io/docs/envoy/latest/api-docs/xds_protocol.html?highlight=type url#resource-types

数据则由Resource来保存。

Resource[]*any.Any类型,说白了就是万能的Interface{}。所以创建any.Any时需要指定具体的数据类型("type.googleapis.com/envoy.api.v2.RouteConfiguration"). data则是经过ProtoMessage编码后的二进制数据。 所以创建any.Any应该是下面的样子:


data, err := proto.Marshal(xxxx)
if err != nil {
logrus.Errorf("Marshal Error. %s", err)
continue
} any := &any.Any{
TypeUrl: "type.googleapis.com/envoy.api.v2.Cluster",
Value: data,
})

xxxx是RDS需要返回给Envoy的路由数据,也就是RouteConfiguration。所以下面来看如何构建RouteConfiguration。 通过API定义可知,有一些数据是必输项(通过proto校验描述文件也可以获取必输项,但不如看API文档来的直接)。 假设我们要实现开篇简单的Route配置,那么 RouteConfiguration 应该这样定义:

      r:=&envoy_api_v2.RouteConfiguration{
Name: "local_route",
VirtualHosts: []*route.VirtualHost{
&route.VirtualHost{
Name: "local_service",
Domains: []string{
"*",
},
Routes: []*route.Route{
&route.Route{
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_Prefix{Prefix: "/MyService"},
},
Action: &route.Route_Route{Route: &route.RouteAction{
ClusterSpecifier: &route.RouteAction_Cluster{Cluster: "my-grpc-svc_cluster"},
}},
},
},
},
},
},

需要注意两个地方:

  1. Name: "local_route"。 这里的Name一定要和Listener中定义的RouteConfig Name保持一致。 如果不一致,Listener不会加载这段Route配置(换言之,这个Name就是双方的关联主键)
  2. Cluster 名称也要保持一致。 同理,如果不一致,后续请求转发时就会找不到UPstream

经过这些步骤,一个近似完整的Route DiscoveryResponse就定义完成了。 而后就可以通过调用Send来发送给Envoy。

然而此时事情并没有结束, 开篇说过Stream同步全量,Delta同步增量。 再详细一点,在StreamRoutes中每次都需要传输当前所有的Route配置,而不仅仅是发生过变更的数据 . 个人感觉这种处理方式,对于数据组织来说很麻烦,但对于Envoy数据更新来说确很方便(每次都是全量数据,不用做merge了)。 merge总是一件耗时费力的事情,就看事情谁来做,这次envoy决定让用户来做了。

所以我们需要调整一下StreamRoutes实现模型:

func (r rds) StreamRoutes(ls envoy_api_v2.RouteDiscoveryService_StreamRoutesServer) error {
for{
select{
case x <- c>:
// x表示变动的数据
n := merge(x) //对x进行merge操作,返回当前最新全量数据n var srvRoute []*route.Route
for _, d := range n{
srvRoute = append(srvRoute, &route.Route{
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_Prefix{Prefix: xxxx},
},
Action: &route.Route_Route{Route: &route.RouteAction{
ClusterSpecifier: &route.RouteAction_Cluster{Cluster: xxxx},
}},
})
} rc := []*envoy_api_v2.RouteConfiguration{
&envoy_api_v2.RouteConfiguration{
Name: "local_route",
VirtualHosts: []*route.VirtualHost{
&route.VirtualHost{
Name: "local_service",
Domains: []string{
"*",
},
Routes: srvRoute,
},
},
},
} var resource []*any.Any for _, rca := range rc {
data, err := proto.Marshal(rca)
if err != nil {
return err
} resource = append(resource, &any.Any{
TypeUrl: "type.googleapis.com/envoy.api.v2.RouteConfiguration",
Value: data,
})
} ls.Send(&envoy_api_v2.DiscoveryResponse{
VersionInfo: xxx,
Resources: resource,
Canary: false,
TypeUrl: "type.googleapis.com/envoy.api.v2.RouteConfiguration",
Nonce: time.Now().String(),
})
} }
}

调整之后,每次就会返回Envoy最新的Route数据。 上面的模型仅考虑了单Envoy实例的情况,并未考虑多实例。 当多实例链接RDS Service时, 从c获取数据,就会变成非幂等事件,从而无法保证所有Envoy实例数据保持一致。

实现StreamRoutes之后,在来看如何实现DeltaRoutes

Delta是用来同步增量数据的,从函数原型来看,入参也是一个Stream,所以函数原型应该和StreamRoutes差不多。 如果你也这样想,就错了

Delta的stream只是用来传输数据的(猜测是为了提高数据传输效率,而并不是为了保持长连接)。 每次传输完成之后,Envoy都会主动断开这个链接。 也就是说,Envoy是定时调用DeltaRoutes来获取增量更新数据的。如果按照stream的实现模型来编写逻辑,将会发现经过一段时间后,这个stream会莫名的变成closed状态。 原因就是envoy接收到此次事件后,主动关闭了stream。

所以如果要使用Delta模式,那么会无法保证Envoy无法实时响应数据变化(因为这个定时调用的存在)。 而如果使用Stream模式,那么用户需要自行维护数据正确性(如果merge很复杂,正确性就会下降)。

所以选择Stream还是Delta对于用户来说是个问题。

新手学分布式 - Envoy Proxy XDS Server动态配置的一点使用心得的更多相关文章

  1. 新手学分布式-动态修改Nginx配置的一些想法

    本人是分布式的新手,在实际工作中遇到了需要动态修改nginx的需求,因此写下实现过程中的想法.Nginx功能强大且灵活,所以这些权当抛砖引玉,希望可以得到大家的讨论和指点.(具体代码在 https:/ ...

  2. Envoy实现.NET架构的网关(二)基于控制平面的动态配置

    什么是控制平面 上一篇我们讲了文件系统的动态配置,这次我们来看看通过Control Panel来配置Envoy.控制平面就是一个提供Envoy配置信息的单独服务,我们可以通过这个服务来修改Envoy的 ...

  3. 云原生之旅 - 9)云原生时代网关的后起之秀Envoy Proxy 和基于Envoy 的 Emissary Ingress

    前言 前一篇文章讲述了基于Nginx代理的Kuberenetes Ingress Nginx[云原生时代的网关 Ingress Nginx]这次给大家介绍下基于Envoy的 Emissary Ingr ...

  4. 自学Zabbix13.1 分布式监控proxy介绍

    点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 自学Zabbix13.1 分布式监控proxy介绍 zabbix2.4版本之前,zabbix提供了 ...

  5. 自学Zabbix13.2 分布式监控proxy配置

    点击返回:自学Zabbix之路 点击返回:自学Zabbix4.0之路 点击返回:自学zabbix集锦 自学Zabbix13.2 分布式监控proxy配置 分为两部分: 安装proxy 配置proxy ...

  6. java中Proxy(代理与动态代理)

    转自: https://blog.csdn.net/pangqiandou/article/details/52964066 一.代理的概念 动态代理技术是整个java技术中最重要的一个技术,它是学习 ...

  7. (转)[SQL Server] 动态sql给变量赋值(或返回值给变量)

    本文转载自:http://blog.csdn.net/xiaoxu0123/article/details/5684680 [SQL Server] 动态sql给变量赋值(或返回值给变量) decla ...

  8. [Python]再学 socket 之非阻塞 Server

    再学 socket 之非阻塞 Server 本文是基于 python2.7 实现,运行于 Mac 系统下 本篇文章是上一篇初探 socket 的续集, 上一篇文章介绍了:如何建立起一个基本的 sock ...

  9. 学习Spring必学的Java基础知识(2)----动态代理

    Spring AOP使用动态代理技术在运行期织入增强的代码,为了揭示Spring AOP底层的工作机理,有必要对涉及到的Java知识进行学习.Spring AOP使用了两种代理机制:一种是基于JDK的 ...

随机推荐

  1. Android H5混合开发(3):原生Android项目里嵌入Cordova

    前言 如果安卓项目已经存在了,那么如何使用Cordova做混合开发? 方案1(适用于插件会持续增加或变化的项目): 新建Cordova项目并添加Android平台,把我们的安卓项目导入Android平 ...

  2. 前端技术之:webpack热模块替换(HMR)

    第一步:安装HMR中间件: npm install --save-dev webpack-hot-middleware   第二步:webpack配置中引入webpack对象     const we ...

  3. 建议收藏:.net core 使用导入导出Excel详细案例,精心整理源码已更新至开源模板

    还记得刚曾经因为导入导出不会做而发愁的自己吗?我见过自己前同事因为一个导出改了好几天,然后我们发现虽然有开源的库但是用起来却不得心应手,主要是因为百度使用方案的时候很多方案并不能解决问题. 尤其是尝试 ...

  4. N42期-qq-林友埙-第一周作业

    1.按系列罗列Linux的发行版,并描述不同发行版之间的联系与区别. 基于Dpkg (Debian系) 商业发行版 o Ubuntu,一个非常流行的桌面发行版. 社区发行版 o Debian,一个强烈 ...

  5. VM安装CentOS系统

    本篇文章主要介绍了VMware安装Centos7超详细过程(图文) 1.软硬件准备 软件:推荐使用VMwear,我用的是VMwear 12 镜像:CentOS7 ,如果没有镜像可以在官网下载 :htt ...

  6. ORACLE存储过程的创建和执行的简单示例和一些注意点

    此示例的主要目的主要是为了了解在PL/SQL环境下怎么创建和执行存储过程. 存储过程所涉及的DataTable: 第一步:创建游标变量 游标是ORACLE系统在内存中开辟的一个工作区,主要用来存储SE ...

  7. Java-100天知识进阶-基本类型-知识铺(一)

    知识铺: 致力于打造轻知识点,持续更新每次的知识点较少,阅读不累.不占太多时间,不停地来唤醒你记忆深处的知识点. Java的两大数据类型: 一.内置数据类型 二.引用数据类型 内置数据类型 Java语 ...

  8. DNS服务反向解析及过程中一些小问题解决

    在此需要了解一下,反向解析的作用是根据IP地址查找到对应的主机名(域名),在区域文件(named.rfc1912.zones)中默认已存在一些注释内容与区域信息,可不需要删除上面实验及默认区域信息,直 ...

  9. MySQL添加主键和外键

    查看表的字段信息:desc 表名; 查看表的所有信息:show create table 表名; 添加主键约束:alter table 表名 add constraint 主键 (形如:PK_表名) ...

  10. Spring资源下载(官网)

    Spring 资源jar包官网下载地址: 点击Spring