可能一些图片不见请见原文章:https://www.yuque.com/simonalong/ruanjian/zk5nr4
早先公司提出OS中枢概念,希望能有一个系统进行对整个OS进行感知并加以控制,后来由另外一个同事率先落地实施,规划其实挺好,只是一期之后就暂停直到现在,诸多功能以及定位其实都没有实现,其原因有多种,但是站在现在来看,该项目对OS中枢系统的实现其实偏应用层而非系统层,对多语言、多底层系统而言适配性并没有考虑到,而多语言和底层系统这种在我们公司却是很重要的考虑项。鉴于中枢系统的重要性,以及对新技术的初始调研方面,考虑对中枢系统进行重新调研和设计,也对新技术做了一些探索。其中就是Envoy。
在介绍Envoy之前,更应该对Envoy出现的历史说明一下。
Sidecar:边车模式
边车模式是一种分布式架构的设计模式。
分布式架构越来越复杂和微服务越拆越细,就越来越迫切的希望有一个统一的控制面来管理所有的微服务,来帮助维护和管理所有微服务(其实就是OS中枢的定位)。
那传统的实现是怎么做的,服务注册、服务发现、监控、流量控制、日志记录、服务限流和服务服务熔断等功能全部都是在应用层上面,通过SDK或者lib的方式,在开发时引入软件包依赖,使其与应用包结合起来,这种方式对代码有侵入,而且对整个公司层面的技术规范的统一化要求很高,比如需统一的语言、统一的框架、统一的规范,此外当依赖有bug需要修复和升级时候,业务代码需要重新编译和发布,还有依赖的包停止维护等等,都会给系统维护带来很大的成本。
边车模式是什么,就是给每一个服务都配置一个“代理”,这个代理其实就是一个“边车(Sidecar)”,该服务所有的通信都是通过这个“代理”来完成的,这个“代理”同服务一起创建,一起销毁。由该代理完成服务的各种功能:服务注册、服务发现、监控、流量控制、日志记录、服务限流和服务服务熔断。这样可以真正实现业务的逻辑和控制的分离。
ServiceMesh:服务网格
Sidecar对业务的逻辑与控制进行了解耦,其实该模式很早就已经实践,只是对其标准化并未真正的落地,随着分布式和微服务架构越来越多的接受,对该需求就更多,这就诞生了ServiceMesh(服务网格),服务网格是CNCF(云原生基金会)目前主推的新一代为服务架构。
ServiceMesh将底层的网络通信进行统一管理,比如:流控、访问控制等。
非网格服务
数据面控制面模式
我们从整个集群上面来看,就是如下这种最典型的服务网格架构,数据面使用边车模式,控制面板单独部署
将业务层的数据剥离后,控制层和数据层的部分:实现是相互调用,虚线是双方通信,控制层管控数据层
这种ServiceMesh思想在业内也经过了一些时间的沉淀,目前业内有多种实现方式,这里做了汇总和整理
功能 | Linkerd | Envoy | Istio | Conduit |
---|---|---|---|---|
代理 | 1.x:Fingagle + Jetty2.x:新的Proxy | C++实现的Envoy | C++实现的Envoy | Rust实现的Conduit |
语言 | 数据层:1.X:jvm语言Scala;2.X:Rust控制层:Go | 数据层:C++ | 数据层:C++控制层:Go | 数据层:Rust;控制层:Go |
熔断 | 支持 | 支持 | 支持 | 不支持 |
动态路由 | 支持 | 支持 | 支持 | 不支持 |
流量分流 | 支持 | 支持 | 支持 | 不支持 |
服务发现 | 支持,支持扩展 | 支持,支持扩展 | 支持,支持扩展 | 只支持k8s |
负载均衡 | 支持 | 支持 | 支持 | 只支持http的 |
安全通信 | 支持TLS | 支持TLS | 支持TLS | 支持TLS |
访问控制 | 不支持 | 不支持 | 只是,基于RBAC | 不支持 |
链路追踪 | 支持,Zipkin | 支持,Zipkin | 支持,Zipkin | 不支持 |
数据指标 | 支持,InfluxDb、Prometheus、statsd | 支持,statsd | 支持,Prometheus、statsd,支持监控:NewRepic、Stackdriver | 支持,Prometheus |
部署模式 | sidecar或者pre-host | sidecar | sidecar | sidecar |
控制平面 | 包含,Namerd | 无 | 包含,Pilot,Mixer,Citadel | Conduit |
协议支持 | http1、http2、gRpc | http1、http2、gRpc、tcp | http1、http2、gRpc、tcp | http1、http2、gRpc、tcp |
运行平台 | 1.X:平台无关;2.X:支持k8s较好 | 平台无关 | 支持k8s,未来会平台无关 | 只支持k8s |
在寻找适合我们操作系统的控制面和数据面的探索中,有这么几点原则我觉得我们系统长期来说是需要的
- 轻量化:加入后对现有资源的占用,不能太多
- 影响小:数据面本身是会对系统有影响,要保证非常小的影响,资源占用、耗时影响、性能损耗
- 少侵入:这里侵入两层意思,引入数据面和控制面后,第一:我们系统后续的改造;第二:业务改造
- 平台无关化
- 灵活化:方便定制,我们自己的系统并不是主流业务,为了方便我们后续做更多定制化,建议只包含基本数据面和控制面即可
基于以上我们对ServiceMesh系统的进行考量,在考虑到功能的灵活化以及平台无关化这两个方面时候,建议使用Envoy作为我们自己系统的服务网格基础,因为它足够简单,基本功能足够,边界很清晰,如果使用envoy来作为OS的中枢的话,长期来说可以逐渐打磨中枢系统,最后可以形成公司的中枢控制系统的核心基座。基于以上考量下面就对Envoy进行了各种探索
envoy介绍
前面表格中也说明了Envoy是数据面的实现部分,数据控制面是用来控制当前节点中的所有网络出入请求的,那这是怎么实现的,我们能想到用来控制网络出入请求的只有防火墙,而防火墙是怎么实现的,采用的是内核的netfilter,envoy也是采用netfilter实现的,只是netfilter是内核的,要调用该软件采用的是用户软件iptables,该软件来调用netfilter,那netfilter是啥,以及怎么控制的,以前写过一点文章,这里重新介绍下。
envoy网络拦截原理
iptables 是一个工具软件,一个操作linux防火墙软件netfilter的命令软件,netfilter位于内核中,而iptables位于用户控制层,我们用iptables来操作防火墙软件netfilter
Netfilter是Linux操作系统核心层内部的一个数据包处理模块,它具有如下功能:
- 网络地址转换(Network Address Translate)
- 数据包内容修改
- 以及数据包过滤的防火墙功能
Netfilter主要是根据规则办事的,而iptable就是给这些规则进行增删改查,规则就是对如下的规则进行判断,判断OK,然后就放行(accept)、拒绝(reject)和丢弃(drop)
- 源地址
- 目的地址
- 传输协议
- 服务类型
- …等
根据规则的分类,这里又分了几个类,叫做表,每个表都有很多规则配置,这里采用的是链式模式,顺序执行链中的规则,进而判断处理,将整个流程全部细化,为如下部分
iptables的工作流程是如下的
将iptable与envoy结合,整体的流程是如下的
借鉴一下,istio的部分,后续我们如果要做,也是要借鉴这个,目前iptables的一些相关处理还没有做,需要补充
envoy拦截流程
上面介绍了网络拦截的原理,知道envoy能够对网络拦截了,那么envoy怎么进行对数据管控的,为了说明整个envoy的构成与原理,这里画了一个详细的图。
envoy对数据包的流程,划分了基本的四步
- 监听器:端口等监听的匹配
- 路由:请求url的路径的匹配和映射
- 集群:集群的负载均衡等配置
- 端点:待请求的服务的ip和端口
以上的步骤,最后在配置上是如下的配置
在真实的流程上,详细的为如下配置。
在业务A这边请求B,A服务所在节点经过一层envoy的配置转换到,外部网络到达B服务所在节点,B节点的envoy节点接收后转发到服务B。
这里面有一些概念,列举下,方面后续理解
- Downstream(下游):下游主机连接到 Envoy,发送请求并接收响应,即发送请求的主机。
- Upstream(上游):上游主机接收来自Envoy的链接和请求,并返回响应,即接收请求的主机。
- LDS:监听器的发现服务
- Listener(监听器):监听器是对外暴露的地址,包括端口、网址等,调用方(下游)可以连接这些监听器。
- RDS:路由的发现服务;路由是由各种路由规则组成,这个规则包括http头部修改,虚拟主机以及每个虚拟主机的映射规则,这些都可以通过RDS进行动态修改
- CDS:集群发现服务;集群指Envoy连接的一组逻辑相同的上游主机,可通过CDS发现
- EDS:端点发现服务;其中端点即上游主机,是一个或多个集群的成员,可通过EDS发现
- ADS:聚合的发现服务,是对CDS、RDS、LDS、EDS服务的统一封装,为了解决CDS、RDS、LDS、EDS信息更新顺序依赖的问题,从而保证以一定的顺序同步各类配置信息
- xDS:所有发现服务的总称,xDS中的x是一个代词,指代LDS、RDS、CDS等
envoy动态化配置
envoy的很多配置都是直接位于数据层的,如果需要变化肯定不能让业务感知到,envoy是支持动态的变更这些xDS的配置的,就是“数据层”直接通过gRpc或者http2与“数据层”通信,在配置变化时候,修改“数据层”的envoy的一些配置,就可以实现控制。如下是控制层的一些核心代码部分;提示:自己使用的envoy的V3版本的api
控制层代码
控制层envoy本身没有提供,但是提供了grpc的接口,也提供了对应的go和java的客户端,我们这里基于go的客户端进行了调研和部分开发,将其中的核心代码列出如下
func runGrpcServer(ctx context.Context, snapshotCacheData cache.SnapshotCache, port int) {
var grpcOptions []grpc.ServerOption
grpcOptions = append(grpcOptions,
grpc.MaxConcurrentStreams(grpcMaxConcurrentStreams),
grpc.KeepaliveParams(keepalive.ServerParameters{
Time: grpcKeepaliveTime,
Timeout: grpcKeepaliveTimeout,
}),
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: grpcKeepaliveMinTime,
PermitWithoutStream: true,
}),
)
grpcServer := grpc.NewServer(grpcOptions...)
cb := &test.Callbacks{Debug: true}
// 创建grpc
srv := server.NewServer(ctx, snapshotCacheData, cb)
// 注册LDS
listenerservice.RegisterListenerDiscoveryServiceServer(grpcServer, srv)
// 注册RDS
routeservice.RegisterRouteDiscoveryServiceServer(grpcServer, srv)
// 注册CDS
clusterservice.RegisterClusterDiscoveryServiceServer(grpcServer, srv)
// 注册EDS
endpointservice.RegisterEndpointDiscoveryServiceServer(grpcServer, srv)
// 注册ADS
discoverygrpc.RegisterAggregatedDiscoveryServiceServer(grpcServer, srv)
// 注册SDS
secretservice.RegisterSecretDiscoveryServiceServer(grpcServer, srv)
// 注册RTDS
runtimeservice.RegisterRuntimeDiscoveryServiceServer(grpcServer, srv)
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
logger.Error("启动端口失败: %d", port, err)
return
}
go func() {
logger.Info("grpc服务启动监听端口:%v", port)
if err = grpcServer.Serve(lis); err != nil {
logger.Error("启动grpc异常", err)
}
}()
}
func SendEnvoyData(envoyDataInsert *dto.EnvoyDataInsert) {
// 配置节点名,这样可以发送到指定的“数据层”
node := corev3.Node{Id: envoyDataInsert.Id, Cluster: envoyDataInsert.ClusterName}
ctx := context.Background()
resourcesMap := map[resource.Type][]types.Resource{}
var listeners []types.Resource
if len(envoyDataInsert.Listeners) != 0 {
for _, info := range envoyDataInsert.Listeners {
listeners = append(listeners, AddListener(info, envoyDataInsert.ClusterName))
}
}
// LDS的配置
resourcesMap[resource.ListenerType] = listeners
var routers []types.Resource
if len(envoyDataInsert.Routers) != 0 {
for _, info := range envoyDataInsert.Routers {
routers = append(routers, AddRouter(info))
}
}
// RDS的配置
resourcesMap[resource.RouteType] = routers
var clusters []types.Resource
if len(envoyDataInsert.Clusters) != 0 {
for _, info := range envoyDataInsert.Clusters {
clusters = append(clusters, AddCluster(info))
}
}
// CDS的配置
resourcesMap[resource.ClusterType] = clusters
var endpoints []types.Resource
if len(envoyDataInsert.Endpoints) != 0 {
for _, info := range envoyDataInsert.Endpoints {
endpoints = append(endpoints, addEndpoint(info))
}
}
// EDS的配置
resourcesMap[resource.EndpointType] = endpoints
snap, _ := cache.NewSnapshot(isc.ToString(envoyDataInsert.Version), resourcesMap)
if err := snap.Consistent(); err != nil {
logger.Error("参数检查异常:%v", err)
return
}
// 发送到数据层
if err := CacheData.SetSnapshot(ctx, node.GetId(), snap); err != nil {
logger.Error("数据发送异常:%v", err)
}
}
func GetListener(listenerName string, route string) *listener.Listener {
return &listener.Listener{
// 监听器名称
Name: "",
// 监听器地址,必须唯一
Address: GetListenerAddress("0.0.0.0", ListenerPort),
// stat_prefix:用于侦听器统计信息的可选前缀
StatPrefix: "",
// 访问日志配置
AccessLog: nil,
// -------------------------------- 过滤器 --------------------------------
// 过滤器链子
FilterChains: Filter(route, ""),
// 默认过滤器链
DefaultFilterChain: nil,
// 监听器过滤器
ListenerFilters: nil,
// 监听器过滤器超时时间
ListenerFiltersTimeout: nil,
// 当侦听器过滤器超时时是否应该创建连接。默认false
ContinueOnListenerFiltersTimeout: false,
// -------------------------------- tcp配置 --------------------------------
// 侦听器是否应接受 TCP 快速打开 (TFO) 连接,这里是对应的长度
TcpFastOpenQueueLength: nil,
// tcp 侦听器的挂起连接队列可以增长到的最大长度,linux上默认为128
TcpBacklogSize: nil,
// 是否启动Mptcp,这个
EnableMptcp: true,
// -------------------------------- udp配置 --------------------------------
// udp监听器配置
UdpListenerConfig: nil,
// -------------------------------- 套接字配置 --------------------------------
// 套接字配置
SocketOptions: nil,
// 侦听器是否应设置为透明套接字
Transparent: nil,
// 侦听器是否应设置 *IP_FREEBIND* 套接字选项
Freebind: nil,
// 套接字重用配置,默认为true,连接重用
EnableReusePort: nil,
// 侦听器是否应绑定到端口。未绑定的侦听器只能接收从将 use_original_dst 设置为 true 的其他侦听器重定向的连接。默认为true
BindToPort: &wrapperspb.BoolValue{Value: true},
// -------------------------------- 连接配置 --------------------------------
// 每个连接缓存限值,默认1Mb
PerConnectionBufferLimitBytes: nil,
// 连接平衡配置,不太懂????
ConnectionBalanceConfig: nil,
// 如果使用 iptables 重定向连接,则代理接收它的端口可能与原始目标地址不同
UseOriginalDst: nil,
// -------------------------------- 其他 --------------------------------
// 请求的拉取类型,没太懂??后续测试下
DrainType: listener.Listener_DEFAULT,
// 指定流量相对于本地 Envoy 的预期方向,对于使用原始目标过滤器的侦听器,Windows 上需要此属性
TrafficDirection: 0,
// 用于表示一个 API 监听器,用于非代理客户端。向非代理应用程序公开的 API 类型取决于 API 侦听器的类型。设置此字段时,不应设置除名称以外的其他字段。
ApiListener: nil,
}
}
func GetRouter(routeName string, clusterName string) *route.RouteConfiguration {
return &route.RouteConfiguration{
// 路由名称
Name: routeName,
// ----------------------------------- 虚拟主机 -----------------------------------
// 组成路由表的虚拟主机数组
VirtualHosts: getHost(clusterName),
// 一组虚拟主机将通过 VHDS API 动态加载
Vhds: nil,
// ----------------------------------- 请求和响应 -----------------------------------
// 指定应该添加到由 HTTP 连接管理器路由的每个请求的 HTTP 标头列表。
RequestHeadersToAdd: nil,
// 指定应从 HTTP 连接管理器路由的每个请求中删除的 HTTP 标头列表。
RequestHeadersToRemove: nil,
// 指定应添加到连接管理器编码的每个响应的 HTTP 标头列表。
ResponseHeadersToAdd: nil,
// 指定应该从连接管理器编码的每个响应中删除的 HTTP 标头列表。
ResponseHeadersToRemove: nil,
// 响应的最大尺寸
MaxDirectResponseBodySizeBytes: &wrappers.UInt32Value{Value: 4096},
// 要允许在路由或虚拟主机级别设置覆盖为true
MostSpecificHeaderMutationsWins: false,
//(可选)指定连接管理器将仅视为内部的 HTTP 标头列表。
InternalOnlyHeaders: nil,
// ----------------------------------- 其他 -----------------------------------
// 一个可选的布尔值,指定路由表引用的集群是否将由集群管理器验证
ValidateClusters: &wrappers.BoolValue{Value: true},
// 插件列表
ClusterSpecifierPlugins: nil,
}
}
func GetCluster(clusterName string) *cluster.Cluster {
return &cluster.Cluster{
// 集群名称
Name: clusterName,
// 控制层的连接超时时间
ConnectTimeout: durationpb.New(5 * time.Second),
// 集群类型,这里使用集群名字解析出来的第一个ip,算是逻辑ip
ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_LOGICAL_DNS},
// -------------------------- dns解析 ---------------------------------------
// dns的域名解析策略
DnsLookupFamily: cluster.Cluster_AUTO,
// dns刷新率, 如果指定了DNS刷新率且群集类型为STRICT_DNS或LOGICAL_DNS,则此值将用作群集的DNS刷新率。 如果未指定此设置,则默认值为5000毫秒。 对于除STRICT_DNS和LOGICAL_DNS之外的集群类型,将忽略此设置。
DnsRefreshRate: nil,
// dns失败刷新率,只有type为strict或者logic时候且指定了该失败率才会生效,否则,则使用dns刷新率
DnsFailureRefreshRate: nil,
// 相当于dns,设置此选项是指定STATIC,STRICT_DNS或LOGICAL_DNS集群的成员所必需的
LoadAssignment: makeEndpoint(clusterName),
// eds的一些配置,只有上面的cluster_type为eds的时候才有效
EdsClusterConfig: &cluster.Cluster_EdsClusterConfig{},
// 用于设置集群的 DNS 刷新率的可选配置。如果该值设置为 true,则集群的 DNS 刷新率将设置为来自 DNS 解析的资源记录的 TTL
RespectDnsTtl: false,
// dns解析配置
TypedDnsResolverConfig: nil,
// -------------------------- 负载均衡---------------------------------------
// 选择主机时的负载均衡策略
LbPolicy: cluster.Cluster_ROUND_ROBIN,
// LbPolicy进行选择指定的配置类型,比如上面的,Round,则该配置需要为 Cluster_RoundRobinLbConfig_
LbConfig: nil,
// 负载均衡子集配置
LbSubsetConfig: nil,
// 负载均衡配置,如果这个值配置了,则会替换LbPolicy
LoadBalancingPolicy: nil,
// -------------------------- 上游配置 ---------------------------------------
// 清理上游的host的时间
CleanupInterval: nil,
// 上游配置
UpstreamConfig: nil,
// 上游绑定的配置
UpstreamBindConfig: nil,
// 用于上游连接的自定义传输套接字实现,可选
TransportSocket: nil,
// 上游游链接配置
UpstreamConnectionOptions: nil,
// 扩展协议,该字段用于为上游连接提供特定于扩展的协议选项。密钥应与扩展过滤器名称匹配,例如“envoy.filters.network.thrift_proxy”。有关特定选项的详细信息,请参阅扩展的文档。
TypedExtensionProtocolOptions: nil,
// -------------- 上游健康检查配置 --------------
// 集群的可选活动健康检查配置。如果未指定配置,则不会进行健康检查,并且所有集群成员将始终被认为是健康的。
HealthChecks: getHealthCheck(),
// 上游不健康时候是否关闭所有链接
CloseConnectionsOnHostHealthFailure: true,
// 上游移除的时候,将不再考虑他的健康值
IgnoreHealthOnHostRemoval: true,
// -------------- 上游链接配置 --------------
// 集群连接读写缓冲区大小的软限制。如果未指定,则应用实现定义的默认值 (1MiB),单位应该是Byte
PerConnectionBufferLimitBytes: &wrappers.UInt32Value{Value: 32768},
// 集群的预链接配置
PreconnectPolicy: nil,
// 断路器,其实也是连接池的一些配置
CircuitBreakers: nil,
// -------------------------- 下游配置 ---------------------------------------
// 为true,则集群将为每个下游连接使用单独的连接池
ConnectionPoolPerDownstreamConnection: true,
// -------------------------- 信息追踪配置 ---------------------------------------
// 用于跟踪可选集群统计信息的配置
TrackClusterStats: nil,
// -------------------------- 其他配置 ---------------------------------------
// 用于在预热时阻止集群就绪的可选配置
WaitForWarmOnInit: nil,
// 元数据字段:元数据字段可用于提供有关群集的其他信息
Metadata: nil,
// 一个(可选的)网络过滤器链,按应用过滤器的顺序列出。该链将应用于 Envoy 与该集群的上游服务器建立的所有传出连接。
Filters: nil,
}
}
func GetEndpoint() *endpoint.Endpoint {
return &endpoint.Endpoint{
// 上游主机地址
Address: nil,
// 选的健康检查配置用作健康检查器与健康检查主机联系的配置
HealthCheckConfig: nil,
// 此端点关联的主机名
Hostname: "",
}
}
数据层配置
数据层其实就是envoy进程启动时候配置的,envoy的配置有静态配置和动态配置,静态配置是envoy启动时候直接写死的,动态配置是运行中动态修改的,是看不到的,其中的静态配置如下,以及
# node是对集群进行区分的,动态配置也是根据这个来进行改配置
node:
cluster: default
id: biz-d-service
# 查看相关的envoy的配置信息
admin:
access_log_path: /var/log/envoy/admin_access.log
profile_path: /var/log/envoy/envoy.prof
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 9901
dynamic_resources:
ads_config:
api_type: GRPC
transport_api_version: V3
grpc_services:
- envoy_grpc:
cluster_name: cluster_xds
cds_config:
resource_api_version: V3
ads: {}
lds_config:
resource_api_version: V3
ads: {}
static_resources:
clusters:
- type: STRICT_DNS
typed_extension_protocol_options:
envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
"@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
explicit_http_config:
http2_protocol_options: {}
# envoy控制层的集群
name: cluster_xds
load_assignment:
cluster_name: cluster_xds
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
# 控制层的服务
address: isc-envoy-controller-service
# 控制层服务的端口
port_value: 11000
# 本机的服务某个服务,目前只有一个,因此配置的就该服务
- name: cluster_local
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: cluster_local
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
# 本机某个服务的端口,比如服务B
port_value: 18003
# 数据搜集服务,包括链路数据等
- name: data-collector
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: zipkin
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: isc-envoy-data-collector
port_value: 18080
listeners:
- name: listener_ingress
address:
socket_address:
address: 0.0.0.0
# 所有服务对外的端口,这里采用10000
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
tracing:
provider:
name: envoy.tracers.zipkin
typed_config:
"@type": type.googleapis.com/envoy.config.trace.v3.ZipkinConfig
collector_cluster: data-collector
collector_endpoint: "/api/v2/spans"
collector_endpoint_version: HTTP_JSON
stat_prefix: http
route_config:
# 配置流量的输入监听
name: route_local
virtual_hosts:
- name: route_local
domains:
- "*"
routes:
- match:
prefix: "/api/d/"
route:
cluster: cluster_local
decorator:
operation: checkAvailability
http_filters:
- name: envoy.filters.http.router
generate_request_id: true
在调研中我们使用了go和java两种业务来进行开发,这里做一个示例,使用的是docker进行部署,就是在docker里面运行envoy和业务两个进程
.
├── Dockerfile
├── envoy
│ ├── envoy-config.yaml
│ └── start_service.sh
├── go.mod
├── go.sum
├── ...
└── ...
#!/usr/bin/env bash
/app/server &
envoy -c /etc/envoy/envoy.yaml --service-cluster biz-b-service
#build stage
FROM golang:1.18 as builder
RUN go version
ENV GO111MODULE=on
ENV GOPROXY=https://goproxy.cn
WORKDIR /app
COPY . /app/
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /app
EXPOSE 18001
#image stage
FROM envoyproxy/envoy:v1.21.2
WORKDIR /app
ENV TZ=Asia/Shanghai
ENV ZONEINFO=/app/zoneinfo.zip
COPY --from=builder /app/application.yaml /app/application.yaml
COPY --from=builder /app/biz-b-service /app/server
COPY --from=builder /usr/local/go/lib/time/zoneinfo.zip /app
COPY ./envoy/start_service.sh /usr/local/bin/start_service.sh
COPY ./envoy/envoy-config.yaml /etc/envoy/envoy.yaml
ADD ./envoy/log /var/log/envoy
RUN chmod u+x /usr/local/bin/start_service.sh
ENTRYPOINT /usr/local/bin/start_service.sh
#CMD ["./server"]
.
├── Dockerfile
├── envoy
│ ├── envoy-config.yaml
│ └── start_service.sh
├── pom.xml
├── ...
└── ...
#!/usr/bin/env bash
java -jar /usr/local/opt/server.jar & envoy -c /etc/envoy/envoy.yaml --service-cluster biz-a-service
#image stage
FROM envoyproxy/envoy:v1.21.2
WORKDIR /app
ENV TZ=Asia/Shanghai
ENV ZONEINFO=/app/zoneinfo.zip
RUN mkdir /usr/local/java8
COPY ./jdk/* /usr/local/java8/
# 配置JAVA的环境变量
ENV JAVA_HOME=/usr/local/java8
# 配置到PAHT中
ENV PATH=$PATH:$JAVA_HOME/bin
COPY ./jar/biz-a-service.jar /usr/local/opt/server.jar
COPY ./envoy/start_service.sh /usr/local/bin/start_service.sh
COPY ./envoy/envoy-config.yaml /etc/envoy/envoy.yaml
ADD ./envoy/log /var/log/envoy
RUN chmod u+x /usr/local/bin/start_service.sh
RUN chmod u+x /usr/local/java8/bin/*
ENTRYPOINT /usr/local/bin/start_service.sh
运行中的envoy配置
使用envoy的admin端口,查看config_dump
http:xxx:9901/config_dump
{
"configs": [
{
"@type": "type.googleapis.com/envoy.admin.v3.BootstrapConfigDump",
"bootstrap": {
"node": {
"id": "biz-a-service",
"cluster": "default",
"user_agent_name": "envoy",
"user_agent_build_version": {
"version": {
"major_number": 1,
"minor_number": 21,
"patch": 2
},
"metadata": {
"revision.sha": "dc7f46eb44e54d5646301aa5ab4ba01f662fdf75",
"build.type": "RELEASE",
"revision.status": "Clean",
"ssl.version": "BoringSSL"
}
},
"extensions": [
{
"name": "envoy.grpc_credentials.aws_iam",
"category": "envoy.grpc_credentials"
},
{
"name": "envoy.grpc_credentials.default",
"category": "envoy.grpc_credentials"
},
{
"name": "envoy.grpc_credentials.file_based_metadata",
"category": "envoy.grpc_credentials"
},
{
"name": "preserve_case",
"category": "envoy.http.stateful_header_formatters"
},
{
"name": "envoy.http.original_ip_detection.custom_header",
"category": "envoy.http.original_ip_detection"
},
{
"name": "envoy.http.original_ip_detection.xff",
"category": "envoy.http.original_ip_detection"
},
{
"name": "envoy.http.stateful_session.cookie",
"category": "envoy.http.stateful_session"
},
{
"name": "envoy.tls.cert_validator.default",
"category": "envoy.tls.cert_validator"
},
{
"name": "envoy.tls.cert_validator.spiffe",
"category": "envoy.tls.cert_validator"
},
{
"name": "envoy.cluster.eds",
"category": "envoy.clusters"
},
{
"name": "envoy.cluster.logical_dns",
"category": "envoy.clusters"
},
{
"name": "envoy.cluster.original_dst",
"category": "envoy.clusters"
},
{
"name": "envoy.cluster.static",
"category": "envoy.clusters"
},
{
"name": "envoy.cluster.strict_dns",
"category": "envoy.clusters"
},
{
"name": "envoy.clusters.aggregate",
"category": "envoy.clusters"
},
{
"name": "envoy.clusters.dynamic_forward_proxy",
"category": "envoy.clusters"
},
{
"name": "envoy.clusters.redis",
"category": "envoy.clusters"
},
{
"name": "envoy.key_value.file_based",
"category": "envoy.common.key_value"
},
{
"name": "envoy.ip",
"category": "envoy.resolvers"
},
{
"name": "default",
"category": "envoy.dubbo_proxy.route_matchers"
},
{
"name": "envoy.transport_sockets.alts",
"category": "envoy.transport_sockets.upstream"
},
{
"name": "envoy.transport_sockets.quic",
"category": "envoy.transport_sockets.upstream"
},
{
"name": "envoy.transport_sockets.raw_buffer",
"category": "envoy.transport_sockets.upstream"
},
{
"name": "envoy.transport_sockets.starttls",
"category": "envoy.transport_sockets.upstream"
},
{
"name": "envoy.transport_sockets.tap",
"category": "envoy.transport_sockets.upstream"
},
{
"name": "envoy.transport_sockets.tcp_stats",
"category": "envoy.transport_sockets.upstream"
},
{
"name": "envoy.transport_sockets.tls",
"category": "envoy.transport_sockets.upstream"
},
{
"name": "envoy.transport_sockets.upstream_proxy_protocol",
"category": "envoy.transport_sockets.upstream"
},
{
"name": "raw_buffer",
"category": "envoy.transport_sockets.upstream"
},
{
"name": "starttls",
"category": "envoy.transport_sockets.upstream"
},
{
"name": "tls",
"category": "envoy.transport_sockets.upstream"
},
{
"name": "envoy.rbac.matchers.upstream_ip_port",
"category": "envoy.rbac.matchers"
},
{
"name": "envoy.quic.crypto_stream.server.quiche",
"category": "envoy.quic.server.crypto_stream"
},
{
"name": "composite-action",
"category": "envoy.matching.action"
},
{
"name": "skip",
"category": "envoy.matching.action"
},
{
"name": "envoy.client_ssl_auth",
"category": "envoy.filters.network"
},
{
"name": "envoy.echo",
"category": "envoy.filters.network"
},
{
"name": "envoy.ext_authz",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.client_ssl_auth",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.connection_limit",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.direct_response",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.dubbo_proxy",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.echo",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.ext_authz",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.http_connection_manager",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.local_ratelimit",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.mongo_proxy",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.ratelimit",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.rbac",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.redis_proxy",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.sni_cluster",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.sni_dynamic_forward_proxy",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.tcp_proxy",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.thrift_proxy",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.wasm",
"category": "envoy.filters.network"
},
{
"name": "envoy.filters.network.zookeeper_proxy",
"category": "envoy.filters.network"
},
{
"name": "envoy.http_connection_manager",
"category": "envoy.filters.network"
},
{
"name": "envoy.mongo_proxy",
"category": "envoy.filters.network"
},
{
"name": "envoy.ratelimit",
"category": "envoy.filters.network"
},
{
"name": "envoy.redis_proxy",
"category": "envoy.filters.network"
},
{
"name": "envoy.tcp_proxy",
"category": "envoy.filters.network"
},
{
"name": "envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
"category": "envoy.upstream_options"
},
{
"name": "envoy.upstreams.http.http_protocol_options",
"category": "envoy.upstream_options"
},
{
"name": "request-headers",
"category": "envoy.matching.http.input"
},
{
"name": "request-trailers",
"category": "envoy.matching.http.input"
},
{
"name": "response-headers",
"category": "envoy.matching.http.input"
},
{
"name": "response-trailers",
"category": "envoy.matching.http.input"
},
{
"name": "envoy.resource_monitors.fixed_heap",
"category": "envoy.resource_monitors"
},
{
"name": "envoy.resource_monitors.injected_resource",
"category": "envoy.resource_monitors"
},
{
"name": "envoy.retry_host_predicates.omit_canary_hosts",
"category": "envoy.retry_host_predicates"
},
{
"name": "envoy.retry_host_predicates.omit_host_metadata",
"category": "envoy.retry_host_predicates"
},
{
"name": "envoy.retry_host_predicates.previous_hosts",
"category": "envoy.retry_host_predicates"
},
{
"name": "dubbo.hessian2",
"category": "envoy.dubbo_proxy.serializers"
},
{
"name": "envoy.transport_sockets.alts",
"category": "envoy.transport_sockets.downstream"
},
{
"name": "envoy.transport_sockets.quic",
"category": "envoy.transport_sockets.downstream"
},
{
"name": "envoy.transport_sockets.raw_buffer",
"category": "envoy.transport_sockets.downstream"
},
{
"name": "envoy.transport_sockets.starttls",
"category": "envoy.transport_sockets.downstream"
},
{
"name": "envoy.transport_sockets.tap",
"category": "envoy.transport_sockets.downstream"
},
{
"name": "envoy.transport_sockets.tcp_stats",
"category": "envoy.transport_sockets.downstream"
},
{
"name": "envoy.transport_sockets.tls",
"category": "envoy.transport_sockets.downstream"
},
{
"name": "raw_buffer",
"category": "envoy.transport_sockets.downstream"
},
{
"name": "starttls",
"category": "envoy.transport_sockets.downstream"
},
{
"name": "tls",
"category": "envoy.transport_sockets.downstream"
},
{
"name": "envoy.filters.udp.dns_filter",
"category": "envoy.filters.udp_listener"
},
{
"name": "envoy.filters.udp_listener.udp_proxy",
"category": "envoy.filters.udp_listener"
},
{
"name": "envoy.internal_redirect_predicates.allow_listed_routes",
"category": "envoy.internal_redirect_predicates"
},
{
"name": "envoy.internal_redirect_predicates.previous_routes",
"category": "envoy.internal_redirect_predicates"
},
{
"name": "envoy.internal_redirect_predicates.safe_cross_scheme",
"category": "envoy.internal_redirect_predicates"
},
{
"name": "envoy.filters.connection_pools.tcp.generic",
"category": "envoy.upstreams"
},
{
"name": "envoy.filters.listener.http_inspector",
"category": "envoy.filters.listener"
},
{
"name": "envoy.filters.listener.original_dst",
"category": "envoy.filters.listener"
},
{
"name": "envoy.filters.listener.original_src",
"category": "envoy.filters.listener"
},
{
"name": "envoy.filters.listener.proxy_protocol",
"category": "envoy.filters.listener"
},
{
"name": "envoy.filters.listener.tls_inspector",
"category": "envoy.filters.listener"
},
{
"name": "envoy.listener.http_inspector",
"category": "envoy.filters.listener"
},
{
"name": "envoy.listener.original_dst",
"category": "envoy.filters.listener"
},
{
"name": "envoy.listener.original_src",
"category": "envoy.filters.listener"
},
{
"name": "envoy.listener.proxy_protocol",
"category": "envoy.filters.listener"
},
{
"name": "envoy.listener.tls_inspector",
"category": "envoy.filters.listener"
},
{
"name": "envoy.matching.common_inputs.environment_variable",
"category": "envoy.matching.common_inputs"
},
{
"name": "envoy.dog_statsd",
"category": "envoy.stats_sinks"
},
{
"name": "envoy.graphite_statsd",
"category": "envoy.stats_sinks"
},
{
"name": "envoy.metrics_service",
"category": "envoy.stats_sinks"
},
{
"name": "envoy.stat_sinks.dog_statsd",
"category": "envoy.stats_sinks"
},
{
"name": "envoy.stat_sinks.graphite_statsd",
"category": "envoy.stats_sinks"
},
{
"name": "envoy.stat_sinks.hystrix",
"category": "envoy.stats_sinks"
},
{
"name": "envoy.stat_sinks.metrics_service",
"category": "envoy.stats_sinks"
},
{
"name": "envoy.stat_sinks.statsd",
"category": "envoy.stats_sinks"
},
{
"name": "envoy.stat_sinks.wasm",
"category": "envoy.stats_sinks"
},
{
"name": "envoy.statsd",
"category": "envoy.stats_sinks"
},
{
"name": "envoy.access_loggers.file",
"category": "envoy.access_loggers"
},
{
"name": "envoy.access_loggers.http_grpc",
"category": "envoy.access_loggers"
},
{
"name": "envoy.access_loggers.open_telemetry",
"category": "envoy.access_loggers"
},
{
"name": "envoy.access_loggers.stderr",
"category": "envoy.access_loggers"
},
{
"name": "envoy.access_loggers.stdout",
"category": "envoy.access_loggers"
},
{
"name": "envoy.access_loggers.tcp_grpc",
"category": "envoy.access_loggers"
},
{
"name": "envoy.access_loggers.wasm",
"category": "envoy.access_loggers"
},
{
"name": "envoy.file_access_log",
"category": "envoy.access_loggers"
},
{
"name": "envoy.http_grpc_access_log",
"category": "envoy.access_loggers"
},
{
"name": "envoy.open_telemetry_access_log",
"category": "envoy.access_loggers"
},
{
"name": "envoy.stderr_access_log",
"category": "envoy.access_loggers"
},
{
"name": "envoy.stdout_access_log",
"category": "envoy.access_loggers"
},
{
"name": "envoy.tcp_grpc_access_log",
"category": "envoy.access_loggers"
},
{
"name": "envoy.wasm_access_log",
"category": "envoy.access_loggers"
},
{
"name": "envoy.access_loggers.extension_filters.cel",
"category": "envoy.access_logger.extension_filters"
},
{
"name": "envoy.compression.brotli.decompressor",
"category": "envoy.compression.decompressor"
},
{
"name": "envoy.compression.gzip.decompressor",
"category": "envoy.compression.decompressor"
},
{
"name": "envoy.filters.dubbo.router",
"category": "envoy.dubbo_proxy.filters"
},
{
"name": "auto",
"category": "envoy.thrift_proxy.protocols"
},
{
"name": "binary",
"category": "envoy.thrift_proxy.protocols"
},
{
"name": "binary/non-strict",
"category": "envoy.thrift_proxy.protocols"
},
{
"name": "compact",
"category": "envoy.thrift_proxy.protocols"
},
{
"name": "twitter",
"category": "envoy.thrift_proxy.protocols"
},
{
"name": "envoy.dynamic.ot",
"category": "envoy.tracers"
},
{
"name": "envoy.lightstep",
"category": "envoy.tracers"
},
{
"name": "envoy.tracers.datadog",
"category": "envoy.tracers"
},
{
"name": "envoy.tracers.dynamic_ot",
"category": "envoy.tracers"
},
{
"name": "envoy.tracers.lightstep",
"category": "envoy.tracers"
},
{
"name": "envoy.tracers.opencensus",
"category": "envoy.tracers"
},
{
"name": "envoy.tracers.skywalking",
"category": "envoy.tracers"
},
{
"name": "envoy.tracers.xray",
"category": "envoy.tracers"
},
{
"name": "envoy.tracers.zipkin",
"category": "envoy.tracers"
},
{
"name": "envoy.zipkin",
"category": "envoy.tracers"
},
{
"name": "envoy.watchdog.abort_action",
"category": "envoy.guarddog_actions"
},
{
"name": "envoy.watchdog.profile_action",
"category": "envoy.guarddog_actions"
},
{
"name": "envoy.request_id.uuid",
"category": "envoy.request_id"
},
{
"name": "envoy.quic.proof_source.filter_chain",
"category": "envoy.quic.proof_source"
},
{
"name": "envoy.network.dns_resolver.cares",
"category": "envoy.network.dns_resolver"
},
{
"name": "envoy.retry_priorities.previous_priorities",
"category": "envoy.retry_priorities"
},
{
"name": "dubbo",
"category": "envoy.dubbo_proxy.protocols"
},
{
"name": "envoy.bandwidth_limit",
"category": "envoy.filters.http"
},
{
"name": "envoy.buffer",
"category": "envoy.filters.http"
},
{
"name": "envoy.cors",
"category": "envoy.filters.http"
},
{
"name": "envoy.csrf",
"category": "envoy.filters.http"
},
{
"name": "envoy.ext_authz",
"category": "envoy.filters.http"
},
{
"name": "envoy.ext_proc",
"category": "envoy.filters.http"
},
{
"name": "envoy.fault",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.adaptive_concurrency",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.admission_control",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.alternate_protocols_cache",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.aws_lambda",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.aws_request_signing",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.bandwidth_limit",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.buffer",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.cache",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.cdn_loop",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.composite",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.compressor",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.cors",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.csrf",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.decompressor",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.dynamic_forward_proxy",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.dynamo",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.ext_authz",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.ext_proc",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.fault",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.grpc_http1_bridge",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.grpc_http1_reverse_bridge",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.grpc_json_transcoder",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.grpc_stats",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.grpc_web",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.header_to_metadata",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.health_check",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.ip_tagging",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.jwt_authn",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.local_ratelimit",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.lua",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.oauth2",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.on_demand",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.original_src",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.ratelimit",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.rbac",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.router",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.set_metadata",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.stateful_session",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.tap",
"category": "envoy.filters.http"
},
{
"name": "envoy.filters.http.wasm",
"category": "envoy.filters.http"
},
{
"name": "envoy.grpc_http1_bridge",
"category": "envoy.filters.http"
},
{
"name": "envoy.grpc_json_transcoder",
"category": "envoy.filters.http"
},
{
"name": "envoy.grpc_web",
"category": "envoy.filters.http"
},
{
"name": "envoy.health_check",
"category": "envoy.filters.http"
},
{
"name": "envoy.http_dynamo_filter",
"category": "envoy.filters.http"
},
{
"name": "envoy.ip_tagging",
"category": "envoy.filters.http"
},
{
"name": "envoy.local_rate_limit",
"category": "envoy.filters.http"
},
{
"name": "envoy.lua",
"category": "envoy.filters.http"
},
{
"name": "envoy.rate_limit",
"category": "envoy.filters.http"
},
{
"name": "envoy.router",
"category": "envoy.filters.http"
},
{
"name": "match-wrapper",
"category": "envoy.filters.http"
},
{
"name": "envoy.formatter.metadata",
"category": "envoy.formatter"
},
{
"name": "envoy.formatter.req_without_query",
"category": "envoy.formatter"
},
{
"name": "envoy.filters.thrift.header_to_metadata",
"category": "envoy.thrift_proxy.filters"
},
{
"name": "envoy.filters.thrift.rate_limit",
"category": "envoy.thrift_proxy.filters"
},
{
"name": "envoy.filters.thrift.router",
"category": "envoy.thrift_proxy.filters"
},
{
"name": "envoy.rate_limit_descriptors.expr",
"category": "envoy.rate_limit_descriptors"
},
{
"name": "envoy.compression.brotli.compressor",
"category": "envoy.compression.compressor"
},
{
"name": "envoy.compression.gzip.compressor",
"category": "envoy.compression.compressor"
},
{
"name": "envoy.wasm.runtime.null",
"category": "envoy.wasm.runtime"
},
{
"name": "envoy.wasm.runtime.v8",
"category": "envoy.wasm.runtime"
},
{
"name": "envoy.matching.matchers.consistent_hashing",
"category": "envoy.matching.input_matchers"
},
{
"name": "envoy.matching.matchers.ip",
"category": "envoy.matching.input_matchers"
},
{
"name": "envoy.bootstrap.wasm",
"category": "envoy.bootstrap"
},
{
"name": "envoy.extensions.network.socket_interface.default_socket_interface",
"category": "envoy.bootstrap"
},
{
"name": "envoy.health_checkers.redis",
"category": "envoy.health_checkers"
},
{
"name": "envoy.extensions.http.cache.simple",
"category": "envoy.http.cache"
},
{
"name": "auto",
"category": "envoy.thrift_proxy.transports"
},
{
"name": "framed",
"category": "envoy.thrift_proxy.transports"
},
{
"name": "header",
"category": "envoy.thrift_proxy.transports"
},
{
"name": "unframed",
"category": "envoy.thrift_proxy.transports"
}
]
},
"static_resources": {
"listeners": [
{
"name": "listener_ingress",
"address": {
"socket_address": {
"address": "0.0.0.0",
"port_value": 10000
}
},
"filter_chains": [
{
"filters": [
{
"name": "envoy.filters.network.http_connection_manager",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"stat_prefix": "http",
"route_config": {
"name": "route_local",
"virtual_hosts": [
{
"name": "route_local",
"domains": [
"*"
],
"routes": [
{
"match": {
"prefix": "/api/a/"
},
"route": {
"cluster": "cluster_local"
},
"decorator": {
"operation": "checkAvailability"
}
}
]
}
]
},
"http_filters": [
{
"name": "envoy.filters.http.router"
}
],
"tracing": {
"provider": {
"name": "envoy.tracers.zipkin",
"typed_config": {
"@type": "type.googleapis.com/envoy.config.trace.v3.ZipkinConfig",
"collector_cluster": "zipkin",
"collector_endpoint": "/api/v2/spans",
"collector_endpoint_version": "HTTP_JSON"
}
}
},
"generate_request_id": true
}
}
]
}
]
}
],
"clusters": [
{
"name": "cluster_xds",
"type": "STRICT_DNS",
"load_assignment": {
"cluster_name": "cluster_xds",
"endpoints": [
{
"lb_endpoints": [
{
"endpoint": {
"address": {
"socket_address": {
"address": "192.168.8.66",
"port_value": 11000
}
}
}
}
]
}
]
},
"typed_extension_protocol_options": {
"envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
"@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
"explicit_http_config": {
"http2_protocol_options": {}
}
}
}
},
{
"name": "cluster_local",
"type": "STRICT_DNS",
"load_assignment": {
"cluster_name": "cluster_local",
"endpoints": [
{
"lb_endpoints": [
{
"endpoint": {
"address": {
"socket_address": {
"address": "127.0.0.1",
"port_value": 18000
}
}
}
}
]
}
]
}
},
{
"name": "jaeger",
"type": "STRICT_DNS",
"load_assignment": {
"cluster_name": "jaeger",
"endpoints": [
{
"lb_endpoints": [
{
"endpoint": {
"address": {
"socket_address": {
"address": "jaeger",
"port_value": 9411
}
}
}
}
]
}
]
}
},
{
"name": "zipkin",
"type": "STRICT_DNS",
"load_assignment": {
"cluster_name": "zipkin",
"endpoints": [
{
"lb_endpoints": [
{
"endpoint": {
"address": {
"socket_address": {
"address": "zipkin",
"port_value": 9411
}
}
}
}
]
}
]
}
}
]
},
"dynamic_resources": {
"lds_config": {
"ads": {},
"resource_api_version": "V3"
},
"cds_config": {
"ads": {},
"resource_api_version": "V3"
},
"ads_config": {
"api_type": "GRPC",
"grpc_services": [
{
"envoy_grpc": {
"cluster_name": "cluster_xds"
}
}
],
"transport_api_version": "V3"
}
},
"admin": {
"access_log_path": "/var/log/envoy/admin_access.log",
"profile_path": "/var/log/envoy/envoy.prof",
"address": {
"socket_address": {
"address": "0.0.0.0",
"port_value": 9901
}
}
}
},
"last_updated": "2022-08-01T13:43:52.585Z"
},
{
"@type": "type.googleapis.com/envoy.admin.v3.ClustersConfigDump",
"version_info": "0",
"static_clusters": [
{
"cluster": {
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "cluster_local",
"type": "STRICT_DNS",
"load_assignment": {
"cluster_name": "cluster_local",
"endpoints": [
{
"lb_endpoints": [
{
"endpoint": {
"address": {
"socket_address": {
"address": "127.0.0.1",
"port_value": 18000
}
}
}
}
]
}
]
}
},
"last_updated": "2022-08-01T13:43:52.607Z"
},
{
"cluster": {
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "cluster_xds",
"type": "STRICT_DNS",
"load_assignment": {
"cluster_name": "cluster_xds",
"endpoints": [
{
"lb_endpoints": [
{
"endpoint": {
"address": {
"socket_address": {
"address": "192.168.8.66",
"port_value": 11000
}
}
}
}
]
}
]
},
"typed_extension_protocol_options": {
"envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
"@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
"explicit_http_config": {
"http2_protocol_options": {}
}
}
}
},
"last_updated": "2022-08-01T13:43:52.606Z"
},
{
"cluster": {
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "jaeger",
"type": "STRICT_DNS",
"load_assignment": {
"cluster_name": "jaeger",
"endpoints": [
{
"lb_endpoints": [
{
"endpoint": {
"address": {
"socket_address": {
"address": "jaeger",
"port_value": 9411
}
}
}
}
]
}
]
}
},
"last_updated": "2022-08-01T13:43:52.612Z"
},
{
"cluster": {
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "zipkin",
"type": "STRICT_DNS",
"load_assignment": {
"cluster_name": "zipkin",
"endpoints": [
{
"lb_endpoints": [
{
"endpoint": {
"address": {
"socket_address": {
"address": "zipkin",
"port_value": 9411
}
}
}
}
]
}
]
}
},
"last_updated": "2022-08-01T13:43:52.614Z"
}
],
"dynamic_active_clusters": [
{
"version_info": "0",
"cluster": {
"@type": "type.googleapis.com/envoy.config.cluster.v3.Cluster",
"name": "cluster_d",
"type": "LOGICAL_DNS",
"connect_timeout": "5s",
"load_assignment": {
"cluster_name": "cluster_d",
"endpoints": [
{
"lb_endpoints": [
{
"endpoint": {
"address": {
"socket_address": {
"address": "biz-d-service",
"port_value": 10000
}
}
}
}
]
}
]
}
},
"last_updated": "2022-08-01T13:43:53.117Z"
}
]
},
{
"@type": "type.googleapis.com/envoy.admin.v3.ListenersConfigDump",
"version_info": "0",
"static_listeners": [
{
"listener": {
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
"name": "listener_ingress",
"address": {
"socket_address": {
"address": "0.0.0.0",
"port_value": 10000
}
},
"filter_chains": [
{
"filters": [
{
"name": "envoy.filters.network.http_connection_manager",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"stat_prefix": "http",
"route_config": {
"name": "route_local",
"virtual_hosts": [
{
"name": "route_local",
"domains": [
"*"
],
"routes": [
{
"match": {
"prefix": "/api/a/"
},
"route": {
"cluster": "cluster_local"
},
"decorator": {
"operation": "checkAvailability"
}
}
]
}
]
},
"http_filters": [
{
"name": "envoy.filters.http.router"
}
],
"tracing": {
"provider": {
"name": "envoy.tracers.zipkin",
"typed_config": {
"@type": "type.googleapis.com/envoy.config.trace.v3.ZipkinConfig",
"collector_cluster": "zipkin",
"collector_endpoint": "/api/v2/spans",
"collector_endpoint_version": "HTTP_JSON"
}
}
},
"generate_request_id": true
}
}
]
}
]
},
"last_updated": "2022-08-01T13:43:52.635Z"
}
],
"dynamic_listeners": [
{
"name": "listener_egress_d",
"active_state": {
"version_info": "0",
"listener": {
"@type": "type.googleapis.com/envoy.config.listener.v3.Listener",
"name": "listener_egress_d",
"address": {
"socket_address": {
"address": "0.0.0.0",
"port_value": 18003
}
},
"filter_chains": [
{
"filters": [
{
"name": "envoy.filters.network.http_connection_manager",
"typed_config": {
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"stat_prefix": "http",
"rds": {
"config_source": {
"api_config_source": {
"api_type": "GRPC",
"grpc_services": [
{
"envoy_grpc": {
"cluster_name": "cluster_xds"
}
}
],
"set_node_on_first_message_only": true,
"transport_api_version": "V3"
},
"resource_api_version": "V3"
},
"route_config_name": "route_d"
},
"http_filters": [
{
"name": "envoy.filters.http.router"
}
],
"tracing": {
"provider": {
"name": "envoy.tracers.zipkin",
"typed_config": {
"@type": "type.googleapis.com/envoy.config.trace.v3.ZipkinConfig",
"collector_cluster": "zipkin",
"collector_endpoint": "/api/v2/spans",
"shared_span_context": false,
"collector_endpoint_version": "HTTP_JSON"
}
}
},
"generate_request_id": true
}
}
]
}
]
},
"last_updated": "2022-08-01T13:43:53.127Z"
}
}
]
},
{
"@type": "type.googleapis.com/envoy.admin.v3.ScopedRoutesConfigDump"
},
{
"@type": "type.googleapis.com/envoy.admin.v3.RoutesConfigDump",
"static_route_configs": [
{
"route_config": {
"@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"name": "route_local",
"virtual_hosts": [
{
"name": "route_local",
"domains": [
"*"
],
"routes": [
{
"match": {
"prefix": "/api/a/"
},
"route": {
"cluster": "cluster_local"
},
"decorator": {
"operation": "checkAvailability"
}
}
]
}
]
},
"last_updated": "2022-08-01T13:43:52.634Z"
}
],
"dynamic_route_configs": [
{
"version_info": "0",
"route_config": {
"@type": "type.googleapis.com/envoy.config.route.v3.RouteConfiguration",
"name": "route_d",
"virtual_hosts": [
{
"name": "route_d",
"domains": [
"*"
],
"routes": [
{
"match": {
"prefix": "/api/d/"
},
"route": {
"cluster": "cluster_d"
}
}
]
}
]
},
"last_updated": "2022-08-01T13:43:53.132Z"
}
]
},
{
"@type": "type.googleapis.com/envoy.admin.v3.SecretsConfigDump"
}
]
}
envoy对系统影响如何
为了对envoy在性能还有资源占用方面进行基准测试
系统方面有
- 两个业务组成的小型服务
- 6个java/go组成的业务中型服务
包含envoy方面
- 没有envoy
- 有envoy
观察指标
- cpu影响
- 内存影响
- 耗时影响
小型服务
小型服务:有一些功能性的尝试,更多也是非功能性,针对单个接口的多次平均测试,最后得到如下
- 耗时(统计后计算):平均每次增加3.02066675ms
- cpu:这个是大批量的变化统计:是增加6.60
- 内存:这个跟两个因素有关,内存平均增加:13.41mb
- 默认内存分配:内存分配方式采用TCMalloc,实际占用内存会比分配的更多,建议设置内存上限
- 内存占用与Listener和Cluster个数呈线性关系,需要在业务上对这两个个数管控,还是要在大集群规模中进行测试,本次简化版是每个服务只有一个Listener和一个Cluster
具体衡量部分
非功能调研的数据:两个服务
中型服务
这里java和go的服务都有
全量版部署图
中型服务:一些功能性的尝试,也有非功能性的尝试,更多的是不同链路接口长度,对整体耗时的影响。(对于这种长链的中型服务,内存和cpu的统计会更加精确)
- 耗时:平均增加2.10ms
- cpu:这里有java和go的服务,两者对cpu的使用差距较大,因此分开进行了统计。总体来说cpu变化并不大
- java的cpu变化:-2.07
- go的cpu变化:1.31
- 总的变化:-0.38
- 内存:平均增加12.89mb
具体衡量部分
非功能调研:多个服务
项目代码:
控制层代码
- https://github.com/simonalong/envoy-control-service
业务测试代码
- https://github.com/simonalong/biz_envoy_a
- https://github.com/simonalong/biz_envoy_b
- https://github.com/simonalong/biz_envoy_c
- https://github.com/simonalong/biz_envoy_d
- https://github.com/simonalong/biz_envoy_e
- https://github.com/simonalong/biz_envoy_f
整体上来说
- 耗时:少量服务单接口多次大批量更为精准,平均增加:3.02066675ms
- 内存:大量服务多接口一次大批量更为精准,平均增加:12.89mb
- 即使是长连接和文件上传,也是增加12.8mb(一个12.83mb,一个是12.81mb)
- cpu:两者统计来看差距都不大,暂时忽略
envoy功能有哪些
先看下envoy本身说的提供的功能
- 安全:服务之间如何授权
- 链路:如何轻量化接入全公司的链路
- 数据采集:能够采集哪些数据供展示
- 服务发现与负载均衡:是否符合公司本身的技术方向
- 熔断、限流:
- 更多功能:…
根据以上的功能我们主要探索以上几个重要功能
安全
安全意思就是我可以在一定情况下,让某个服务不让其他服务访问。这里面有两类,一个粒度粗的服务级别,一个粒度细的接口级别
粗粒度的,比如服务B,我不让服务A访问,那么这种在envoy这边就可以通过动态的RDS,来动态修改路由规则,让服务A的envoy数据层没有向服务B的路由集群配置
细粒度的,比如服务B,我的接口/api/app/b/test/no/a,不让服务A访问。这种可以动态修改路由让服务A的envoy里面没有接口匹配的/api/app/b/test/no/a这个接口,不过这样的话,目前来实现只能通过穷举,目前暂时没有看到有exclude字段
listeners:
- name: listener_ingress
address:
socket_address:
address: 0.0.0.0
# 所有服务对外的端口,这里采用10000
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
stat_prefix: http
route_config:
# 配置流量的输入监听
name: route_local
virtual_hosts:
- name: route_local
domains:
- "*"
routes:
- match:
# 这里就是前缀的配置
prefix: "/api/d/"
route:
cluster: cluster_local
decorator:
operation: checkAvailability
http_filters:
- name: envoy.filters.http.router
generate_request_id: true
对应代码方面就是上面的api
func getHost(clusterName string) []*route.VirtualHost {
return []*route.VirtualHost{{
Name: "local_service",
Domains: []string{"*"},
Routes: []*route.Route{{
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_Prefix{
Prefix: "/",
},
},
Action: &route.Route_Route{
Route: &route.RouteAction{
ClusterSpecifier: &route.RouteAction_Cluster{
Cluster: clusterName,
},
HostRewriteSpecifier: &route.RouteAction_HostRewriteLiteral{
HostRewriteLiteral: UpstreamHost,
},
},
},
}},
}}
}
链路
1. envoy配置链路
目前上面envoy的默认的静态配置中就添加了zipkin的,zipkin也是可以使用jeager接口
- name: data-collector
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: zipkin
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
# 搜集埋点数据的服务
address: isc-data-collector-service
port_value: 18080
listeners:
- name: listener_ingress
address:
socket_address:
address: 0.0.0.0
port_value: 10000
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
tracing:
provider:
name: envoy.tracers.zipkin
typed_config:
"@type": type.googleapis.com/envoy.config.trace.v3.ZipkinConfig
# 配置搜集的
collector_cluster: data-collector
collector_endpoint: "/api/v2/spans"
collector_endpoint_version: HTTP_JSON
stat_prefix: http
# 配置自动生成request_id
generate_request_id: true
2. 服务埋点
我们遵守jaeger的规范,需要将head中的数据传递下去,那么每个服务向下发送需要带上如下的head
● x-request-id:其实就是traceId
● x-b3-traceid:一个工作单元(rpc 调用)的唯一标识。
● x-b3-spanid:一个工作单元(rpc 调用)的唯一标识。
● x-b3-parentspanid:一个工作单元(rpc 调用)的唯一标识。
● x-b3-sampled:是否被抽样为输出的标志,1 为需要被输出,0 为不需要被输出。
● x-b3-flags:
● x-ot-span-context:
示例:
// 一定要判断值是否为空,不空再做处理,否则会造成链断开
headers := netHttp.Header{}
if c.GetHeader("x-request-id") != "" {
headers["x-request-id"] = []string{c.GetHeader("x-request-id")}
headers["x-b3-traceid"] = []string{c.GetHeader("x-b3-traceid")}
headers["x-b3-spanid"] = []string{c.GetHeader("x-b3-spanid")}
headers["x-b3-parentspanid"] = []string{c.GetHeader("x-b3-parentspanid")}
headers["x-b3-sampled"] = []string{c.GetHeader("x-b3-sampled")}
headers["x-b3-flags"] = []string{c.GetHeader("x-b3-flags")}
headers["x-ot-span-context"] = []string{c.GetHeader("x-ot-span-context")}
}
// .....
// 发送示例:data, err := http.GetOfStandard(config.GetValueString("biz.url.f") + "/api/f/cf/" + haveMysql, headers, map[string]string{})
// ......
@Slf4j
@Component
public class DataFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
// 请务必判断为空
String reqId = httpServletRequest.getHeader("x-request-id");
if (null == reqId || "".equals(reqId)) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
return;
}
Map<String, Object> headMap = new HashMap<>();
headMap.put("x-request-id", httpServletRequest.getHeader("x-request-id"));
headMap.put("x-b3-traceid", httpServletRequest.getHeader("x-b3-traceid"));
headMap.put("x-b3-spanid", httpServletRequest.getHeader("x-b3-spanid"));
headMap.put("x-b3-parentspanid", httpServletRequest.getHeader("x-b3-parentspanid"));
headMap.put("x-b3-sampled", httpServletRequest.getHeader("x-b3-sampled"));
headMap.put("x-b3-flags", httpServletRequest.getHeader("x-b3-flags"));
headMap.put("x-ot-span-context", httpServletRequest.getHeader("x-ot-span-context"));
// 保存到阿里的替换ThreadLocal的Ttl中(TransmittableThread)
// ContextHelper.setData(headMap);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
3. 链路搜集服务
我们这里使用的是我们自己的埋点数据搜集服务,链路使用jaeger的标准接口,只需实现如下接口即可
post api/v2/spans
比如,我们这里使用数据搜集服务“isc-data-collector-service”实现了该接口
func main() {
server.Post("api/v2/spans", spansData)
server.Run()
}
func spansData(c *gin.Context) {
var spans2 []map[string]interface{}
data, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
logger.Error("数据异常, %v", err.Error())
rsp.FailedOfStandard(c, 500, "解析异常:"+err.Error())
return
}
err = json.Unmarshal(data, &spans2)
if err != nil {
logger.Error("数据异常, %v", err.Error())
rsp.FailedOfStandard(c, 53, "解析异常:"+err.Error())
return
}
// str就是我们要的数据
str, err := json.Marshal(spans2)
rsp.SuccessOfStandard(c, "value")
}
在多服务的复杂调用中,测试接口的调用示例:
a -> d -> e -> f
服务语言类型:
- a:java
- d:go
- e:java
- f:go
得到如下数据:一共7条数据,接收的顺序无所谓,根据其中的parentId和id就可以建立树形结构
- a:2条
- d:2条
- e:2条
- f:1条
[{"annotations":[{"timestamp":1659353374125652,"value":"ss"}],"duration":59895,"id":"5211e6310573d8f2","kind":"SERVER","localEndpoint":{"ipv4":"172.25.0.103","port":0,"serviceName":"biz-d-service"},"name":"checkAvailability","parentId":"0e2327a579634223","shared":true,"tags":{"component":"proxy","downstream_cluster":"-","guid:x-request-id":"3296709e-de77-9931-8b45-fb777c01773c","http.method":"GET","http.protocol":"HTTP/1.1","http.status_code":"200","http.url":"http://localhost:18003/api/d/ad/false","node_id":"biz-d-service","peer.address":"172.25.0.100","request_size":"0","response_flags":"-","response_size":"42","upstream_cluster":"cluster_local","upstream_cluster.name":"cluster_local","user_agent":"okhttp/3.14.7"},"timestamp":1659353374064811,"traceId":"0e2327a579634223"}]
[{"annotations":[{"timestamp":1659353374131938,"value":"ss"}],"duration":126252,"id":"0e2327a579634223","kind":"SERVER","localEndpoint":{"ipv4":"172.25.0.100","port":0,"serviceName":"biz-a-service"},"name":"checkAvailability","shared":true,"tags":{"component":"proxy","downstream_cluster":"-","guid:x-request-id":"3296709e-de77-9931-8b45-fb777c01773c","http.method":"GET","http.protocol":"HTTP/1.1","http.status_code":"200","http.url":"http://localhost:10000/api/a/front/adef/false/false","node_id":"biz-a-service","peer.address":"172.25.0.1","request_size":"0","response_flags":"-","response_size":"22","upstream_cluster":"cluster_local","upstream_cluster.name":"cluster_local","user_agent":"apifox/1.0.0 (https://www.apifox.cn)"},"timestamp":1659353374000938,"traceId":"0e2327a579634223"}]
[{"annotations":[{"timestamp":1659353374112755,"value":"ss"}],"duration":7005,"id":"af68aeb64876ac17","kind":"SERVER","localEndpoint":{"ipv4":"172.25.0.105","port":0,"serviceName":"biz-f-service"},"name":"checkAvailability","parentId":"391882d9b20b1c1b","shared":true,"tags":{"component":"proxy","downstream_cluster":"-","guid:x-request-id":"3296709e-de77-9931-8b45-fb777c01773c","http.method":"GET","http.protocol":"HTTP/1.1","http.status_code":"200","http.url":"http://localhost:18005/api/f/ef/false","node_id":"biz-f-service","peer.address":"172.25.0.104","request_size":"0","response_flags":"-","response_size":"42","upstream_cluster":"cluster_local","upstream_cluster.name":"cluster_local","user_agent":"okhttp/3.14.7"},"timestamp":1659353374105164,"traceId":"0e2327a579634223"}]
[{"annotations":[{"timestamp":1659353374118792,"value":"ss"}],"duration":44736,"id":"391882d9b20b1c1b","kind":"SERVER","localEndpoint":{"ipv4":"172.25.0.104","port":0,"serviceName":"biz-e-service"},"name":"checkAvailability","parentId":"5211e6310573d8f2","shared":true,"tags":{"component":"proxy","downstream_cluster":"-","guid:x-request-id":"3296709e-de77-9931-8b45-fb777c01773c","http.method":"GET","http.protocol":"HTTP/1.1","http.status_code":"200","http.url":"http://localhost:18004/api/e/de/false","node_id":"biz-e-service","peer.address":"172.25.0.103","request_size":"0","response_flags":"-","response_size":"42","upstream_cluster":"cluster_local","upstream_cluster.name":"cluster_local","user_agent":"Go-http-client/1.1"},"timestamp":1659353374073455,"traceId":"0e2327a579634223"}]
[{"annotations":[{"timestamp":1659353374126043,"value":"ss"}],"duration":64958,"id":"5211e6310573d8f2","kind":"SERVER","localEndpoint":{"ipv4":"172.25.0.100","port":0,"serviceName":"biz-a-service"},"name":"localhost:18003","parentId":"0e2327a579634223","tags":{"component":"proxy","downstream_cluster":"-","guid:x-request-id":"3296709e-de77-9931-8b45-fb777c01773c","http.method":"GET","http.protocol":"HTTP/1.1","http.status_code":"200","http.url":"http://localhost:18003/api/d/ad/false","node_id":"biz-a-service","peer.address":"127.0.0.1","request_size":"0","response_flags":"-","response_size":"42","upstream_cluster":"cluster_d","upstream_cluster.name":"cluster_d","user_agent":"okhttp/3.14.7"},"timestamp":1659353374060944,"traceId":"0e2327a579634223"}]
[{"annotations":[{"timestamp":1659353374113019,"value":"ss"}],"duration":8149,"id":"af68aeb64876ac17","kind":"SERVER","localEndpoint":{"ipv4":"172.25.0.104","port":0,"serviceName":"biz-e-service"},"name":"localhost:18005","parentId":"391882d9b20b1c1b","tags":{"component":"proxy","downstream_cluster":"-","guid:x-request-id":"3296709e-de77-9931-8b45-fb777c01773c","http.method":"GET","http.protocol":"HTTP/1.1","http.status_code":"200","http.url":"http://localhost:18005/api/f/ef/false","node_id":"biz-e-service","peer.address":"127.0.0.1","request_size":"0","response_flags":"-","response_size":"42","upstream_cluster":"cluster_f","upstream_cluster.name":"cluster_f","user_agent":"okhttp/3.14.7"},"timestamp":1659353374104764,"traceId":"0e2327a579634223"}]
[{"annotations":[{"timestamp":1659353374119114,"value":"ss"}],"duration":46159,"id":"391882d9b20b1c1b","kind":"SERVER","localEndpoint":{"ipv4":"172.25.0.103","port":0,"serviceName":"biz-d-service"},"name":"localhost:18004","parentId":"5211e6310573d8f2","tags":{"component":"proxy","downstream_cluster":"-","guid:x-request-id":"3296709e-de77-9931-8b45-fb777c01773c","http.method":"GET","http.protocol":"HTTP/1.1","http.status_code":"200","http.url":"http://localhost:18004/api/e/de/false","node_id":"biz-d-service","peer.address":"127.0.0.1","request_size":"0","response_flags":"-","response_size":"42","upstream_cluster":"cluster_e","upstream_cluster.name":"cluster_e","user_agent":"Go-http-client/1.1"},"timestamp":1659353374072874,"traceId":"0e2327a579634223"}]
根据上面数据,我们就可以建立服务之间的链路关系,并呈现链路数据,我们可以根据以上数据展示我们想要的画面即可。
也可以参考zipkin拿上面这些数据展示的形式
整条链路数据
查看服务依赖关系
某个服务的调用次数和异常
数据指标采集
每一个envoy进程,都有一个admin端口,直接访问该端口,如下
我们这里关注stats/prometheus即可,然后在prometheus配置下即可
envoy定义了很多metric,可以直接与prometheus将端口配置下即可,如下
注意:
prometheus的默认接口是:/metrics,envoy这里是:/stats/prometheus,因此在配置prometheus的配置时候,要注意配置如下
global:
scrape_interval: 60s
evaluation_interval: 60s
scrape_configs:
- job_name: prometheus
static_configs:
- targets: ['localhost:9090']
labels:
instance: prometheus
- job_name: biz-c-service
# 这个很重要,看了官网才找到如下字段,否则就是默认路径: /metrics
metrics_path: /stats/prometheus
static_configs:
- targets: ['192.168.8.66:9902']
labels:
instance: localhost
配置好后就可以使用了
服务发现
服务发现:属于数据动态变更和EDS部分,其中当某个服务需要指向某个服务时候,就可以配置EDS。
func GetEndpoint() *endpoint.Endpoint {
return &endpoint.Endpoint{
// 上游主机地址
Address: nil,
// 选的健康检查配置用作健康检查器与健康检查主机联系的配置
HealthCheckConfig: nil,
// 此端点关联的主机名
Hostname: "",
}
}
负载均衡
负载均衡,属于CDS的范围,其中负载均衡如下
- Cluster_ROUND_ROBIN:轮询
- Cluster_LEAST_REQUEST:最小请求
- Cluster_RING_HASH:经典一致性哈希的换均衡
- Cluster_RANDOM:随机均衡
- Cluster_MAGLEV:Maglev算法的一致性均衡
- Cluster_CLUSTER_PROVIDED:配置集群情况下的集群负载均衡
- Cluster_LOAD_BALANCING_POLICY_CONFIG:弃用
比如如下配置cluster地方就可以配置负载均衡策略,这里只截取一部分
func GetCluster(clusterName string) *cluster.Cluster {
return &cluster.Cluster{
// 集群名称
Name: clusterName,
// 控制层的连接超时时间
ConnectTimeout: durationpb.New(5 * time.Second),
// ...
// -------------------------- 负载均衡---------------------------------------
// 选择主机时的负载均衡策略
LbPolicy: cluster.Cluster_ROUND_ROBIN,
// LbPolicy进行选择指定的配置类型,比如上面的,Round,则该配置需要为 Cluster_RoundRobinLbConfig_
LbConfig: nil,
// 负载均衡子集配置
LbSubsetConfig: nil,
// 负载均衡配置,如果这个值配置了,则会替换LbPolicy
LoadBalancingPolicy: nil,
// ...
}
}
熔断
熔断是分布式系统的重要组成部分。快速失败并尽快给下游施加压力,该配置也是在CDS集群中,主要是对上游链接的一些配置
func GetCluster(clusterName string) *cluster.Cluster {
return &cluster.Cluster{
// 集群名称
Name: clusterName,
// ...
// -------------- 上游链接配置 --------------
// ...
// 熔断: 断路器,其实也是连接池的一些配置
CircuitBreakers: nil,
// ...
}
}
其中circuit_breakers就是熔断器的配置,官方示例:
circuit_breakers:
thresholds:
- priority: "DEFAULT"
max_requests: 75
max_pending_requests: 35
retry_budget:
budget_percent:
value: 25.0
min_retry_concurrency: 10
其中
- Priority:指定的断路器设置适用于,目前有两个:0:DEFAULT,1:HIGH
- MaxConnections:Envoy 将与上游集群建立的最大连接数。默认 1024
- MaxPendingRequests:Envoy 对上游集群的待处理请求的最大数量。默认1024
- MaxRequests:Envoy 将向上游集群发出的最大并行请求数。默认1024
- MaxRetries:Envoy 允许上游集群的最大并行重试次数。默认为 3
- RetryBudget:(可选)指定与活动请求数相关的并发重试限制
- TrackRemaining:为true:则将发布统计信息,显示在断路器打开之前剩余的资源数量。默认false
- MaxConnectionPools:Envoy 将同时支持的每个集群的最大连接池数。默认无限制
限流
限流是RDS的部分,也就是路由配置的部分。限流在envoy这里支持两种限流
- 普通限流:就是在路由规则里面配置
- 全局限流:就是提供额外的一个限流服务,每次请求进行判断
普通限流
func GetRouter(routeName string, clusterName string) *route.RouteConfiguration {
return &route.RouteConfiguration{
// 路由名称
Name: routeName,
// ----------------------------------- 虚拟主机 -----------------------------------
// 组成路由表的虚拟主机数组
VirtualHosts: getHost(clusterName),
// ...
}
}
func getHost(clusterName string) []*route.VirtualHost {
return []*route.VirtualHost{{
Name: "local_service",
Domains: []string{"*"},
// ...
// 限速配置
RateLimits: nil,
}}
}
全局限流
全局限速,这里没有做尝试,摘录了一张图,如下
基于envoy要做的事情
如果公司要真的基于envoy来做中枢控制系统,那么长期来说,可以做如下规划
从以上规划来说,对人力物力和资源投入还是很大的,尤其是第一期,不过目前已经部分做了技术尝试,一期的话,更多的是作为一个尝试,在三期后,我们希望能达到这样一个成果,并能够在各种细节上面做更多控制
参考:
envoy中文文档:https://cloudnative.to/envoy/about_docs.html
https://zhuanlan.zhihu.com/p/153105848?from_voters_page=true