在云原生时代,容器取代虚拟机成为承载应用工作负载的主要形式。虚拟机生命周期相对较长,可能有数天,但是容器少则几分钟。这就要求负载均衡器必须能适应这种动态性。
Envoy 通过 xDS 实现了其动态配置,来应对不断变化的基础架构。
xDS 简介
Envoy通过文件系统或查询管理服务器发现其各种动态资源。这些发现服务及其相应的API统称为xDS。
资源类型
xDS API中的每个配置资源都有与之关联的类型。资源类型遵循版本控制方案。目前V2版本已经停止开发,不过会有一年的维护期。V3版本是目前主力版本。
支持以下v3 xDS资源类型:
- envoy.config.listener.v3.Listener
- envoy.config.route.v3.RouteConfiguration
- envoy.config.route.v3.ScopedRouteConfiguration
- envoy.config.route.v3.VirtualHost
- envoy.config.cluster.v3.Cluster
- envoy.config.endpoint.v3.ClusterLoadAssignment
- envoy.extensions.transport_sockets.tls.v3.Secret
- envoy.service.runtime.v3.Runtime
格式为http://type.googleapis.com/<资源类型>–例如,用于集群资源的type.googleapis.com/envoy.api.v3.Cluster。在来自Envoy的各种请求和管理服务器的响应中,都声明了资源类型URL。
这些API 实际上通过 proto3 Protocol Buffers 定义。
流式gRPC订阅
Envoy 通过订阅(_subscription_)方式来获取资源,如监控指定路径下的文件、启动 gRPC 流或轮询 REST-JSON URL。后两种方式会发送DiscoveryRequest请求消息,发现的对应资源则包含在响应消息DiscoveryResponse中。
其中流式gRPC订阅是最常使用的。
流式gRPC使用的xDS传输协议有四种变体:
- State of the World (Basic xDS):SotW,每种资源类型的单独gRPC流
- 增量xDS:每种资源类型的增量独立gRPC流
- 聚合发现服务(ADS):SotW,所有资源类型的聚合流
- 增量ADS:所有资源类型的增量聚合流
如何实现一个简单的控制平面
社区提供了两种语言的实现,在我们编写自己的控制平面时,可以直接使用:
比如go-control-plane 提供了由多个不同控制平面实现共享的基础结构。该库提供的组件是:
- API服务器_:_一种基于gRPC的通用API服务器,可实现data-plane-api中定义的xDS API 。API服务器负责将配置更新推送到Envoy。消费者应该能够在生产部署中导入该go库并按原样使用API服务器。
- 配置缓存_:_该库将在内存中缓存Envoy配置,以对Envoy提供快速响应。此库的使用者有责任将数据写入到高速缓存,并在必要时使高速缓存无效。高速缓存将基于预定义的哈希函数进行键控,该哈希函数的键基于 Node信息。
目前,此存储库将不会处理将平台(例如服务,服务实例等)的特定于资源的表示转换为Envoy样式的配置。
下面我们通过go-control-plane实现一个简单的Envoy控制平面,并且采用的是第三种xDS变体。
1:数据平面Envoy配置
虽然Envoy接受控制平面的动态资源,但是Envoy的启动需要一些静态配置,也就是引导文件。完整引导文件如下:
node:
id: node-1
cluster: edge-gateway
admin:
access_log_path: /dev/stdout
address:
socket_address: { address: 127.0.0.1, port_value: 9901 }
dynamic_resources:
ads_config:
# allows limiting the rate of discovery requests.
# for edge cases with very frequent requests or due to a bug.
rate_limit_settings:
max_tokens: 10
fill_rate: 3
# we use v3 xDS framing
transport_api_version: V3
# over gRPC
api_type: GRPC
grpc_services:
- envoy_grpc:
cluster_name: xds_cluster
# Use ADS for LDS and CDS; request V3 clusters and listeners.
lds_config: {ads: {}, resource_api_version: V3}
cds_config: {ads: {}, resource_api_version: V3}
static_resources:
clusters:
- name: xds_cluster
connect_timeout: 0.25s
type: STATIC
lb_policy: ROUND_ROBIN
# as we are using gRPC xDS we need to set the cluster to use http2
http2_protocol_options: {}
upstream_connection_options:
# important:
# configure a TCP keep-alive to detect and reconnect to the admin
# server in the event of a TCP socket half open connection
# the default values are very conservative, so you will want to tune them.
tcp_keepalive: {}
load_assignment:
cluster_name: xds_cluster
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 127.0.0.1
port_value: 9977
引导文件包含两个ConfigSource 消息,一个指示如何获取侦听器资源,另一个指示如何获取集群资源。它还包含一个单独的ApiConfigSource消息,该消息指示如何与ADS服务器通信,只要ConfigSource消息(在引导文件或从管理服务器获取的侦听器或集群资源中)包含AggregatedConfigSource消息,就会使用该消息。
在使用xDS的gRPC客户端中,仅支持ADS,并且引导文件包含ADS服务器的名称,该名称将用于所有资源。侦听器和 集群资源中的ConfigSource消息必须包含AggregatedConfigSource消息。
那么Envoy按照该引导文件启动后,会与127.0.0.1:9977 通信,获取监听器和集群资源。
2:编写控制平面
本次控制平面要实现的功能是控制Envoy实现灰度发布。由于go-control-plane 已经帮我们做了很多事情,所以我们唯一需要做的就是将灰度相关的设置转换为Envoy样式的配置。
由于代码较长,我们只贴出核心的代码:
func makeRoute(routeName string, weight uint32, clusterName1, clusterName2 string) *route.RouteConfiguration {
routeConfiguration := &route.RouteConfiguration{
Name: routeName,
VirtualHosts: []*route.VirtualHost{{
Name: "local_service",
Domains: []string{"*"},
}},
}
switch weight {
case 0:
routeConfiguration.VirtualHosts[0].Routes = []*route.Route{{
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_Prefix{
Prefix: "/",
},
},
Action: &route.Route_Route{
Route: &route.RouteAction{
ClusterSpecifier: &route.RouteAction_Cluster{
Cluster: clusterName1,
},
HostRewriteSpecifier: &route.RouteAction_HostRewriteLiteral{
HostRewriteLiteral: UpstreamHost,
},
},
},
}}
case 100:
routeConfiguration.VirtualHosts[0].Routes = []*route.Route{{
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_Prefix{
Prefix: "/",
},
},
Action: &route.Route_Route{
Route: &route.RouteAction{
ClusterSpecifier: &route.RouteAction_Cluster{
Cluster: clusterName2,
},
HostRewriteSpecifier: &route.RouteAction_HostRewriteLiteral{
HostRewriteLiteral: UpstreamHost,
},
},
},
}}
// canary-roll out:
default:
routeConfiguration.VirtualHosts[0].Routes = []*route.Route{{
Match: &route.RouteMatch{
PathSpecifier: &route.RouteMatch_Prefix{
Prefix: "/",
},
},
Action: &route.Route_Route{
Route: &route.RouteAction{
ClusterSpecifier: &route.RouteAction_WeightedClusters{
WeightedClusters: &route.WeightedCluster{
TotalWeight: &wrapperspb.UInt32Value{
Value: 100,
},
Clusters: []*route.WeightedCluster_ClusterWeight{
{
Name: clusterName1,
Weight: &wrapperspb.UInt32Value{
Value: 100 - weight,
},
},
{
Name: clusterName2,
Weight: &wrapperspb.UInt32Value{
Value: weight,
},
},
},
},
},
HostRewriteSpecifier: &route.RouteAction_HostRewriteLiteral{
HostRewriteLiteral: UpstreamHost,
},
},
},
}}
}
return routeConfiguration
}
由于我们是第三种变体,我们在变更配置的时候,需要所有资源的变更封装成cachev3.Snapshot:
func GenerateSnapshot(weight uint32) cachev3.Snapshot {
version++
nextversion := fmt.Sprintf("snapshot-%d", version)
fmt.Println("publishing version: ", nextversion)
return cachev3.NewSnapshot(
nextversion, // version needs to be different for different snapshots
[]types.Resource{}, // endpoints
[]types.Resource{makeCluster(ClusterName1), makeCluster(ClusterName2)},
[]types.Resource{makeRoute(RouteName, weight, ClusterName1, ClusterName2)},
[]types.Resource{makeHTTPListener(ListenerName, RouteName)},
[]types.Resource{}, // runtimes
[]types.Resource{}, // secrets
)
}
我们的示例比较简单,但是在生产环境,我们应该考虑哪些内容那?
3:生产环境的控制面
生产环境控制面,则需要实现:
- 核心xDS服务接口和实现
- 处理向服务注册表中注册/反注册服务的组件
- 服务注册表
- 描述Envoy配置的抽象对象模型(可选)
- 数据存储区,用于保存配置
比如Contour,实际上只有两个组成其控制平面的组件,但是,由于它仅基于Kubernetes,因此它实际上利用了许多内置的Kubernetes设施,例如Kubernetes API /存储和CRD来驱动配置。
contour
服务器init-container
引导程序
Contour使用init-container
来为Envoy生成一个静态引导程序配置文件,该文件指示在哪里可以找到xDS服务。xDS服务器是控制平面中的第二个组件。
总结
其实目前诸多基于Envoy的项目,都是采取控制面 + xDS + envoy 的模式。比如Apigateway中的gloo,ambassador,Service Mesh 中的istio,app-mesh等。
各个控制面其实基本上对接各种服务注册中心,然后再根据客户配置的转发规则,转换为xDS资源,通过gRPC流下发到Envoy中。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。