Envoy 可以将流量拆分为两个或多个路由,分别将流量导向不同的集群,这里有两个常见的用例:
- 版本升级,路由的流量逐渐从一个集群转移到另一个集群。
- A/B 测试,同一服务,不同版本之间的测试,路由分配不同比例的流量进入不同版本的集群。
先准备两个版本的 API 服务。
首先 v1 版本,main_v1.go :
package main
import (
"github.com/emicklei/go-restful"
"io"
"log"
"net/http"
)
// This example shows the minimal code needed to get a restful.WebService working.
//
// GET http://localhost:8081/hello
func main() {
ws := new(restful.WebService)
ws.Route(ws.GET("/hello").To(hello))
restful.Add(ws)
log.Println("server start in 8081")
log.Fatal(http.ListenAndServe(":8081", nil))
}
func hello(req *restful.Request, resp *restful.Response) {
log.Println("request hello")
_, _ = io.WriteString(resp, "world v1")
}
然后 v2 版本,main_v2.go :
package main
import (
"github.com/emicklei/go-restful"
"io"
"log"
"net/http"
)
// This example shows the minimal code needed to get a restful.WebService working.
//
// GET http://localhost:8082/hello
func main() {
ws := new(restful.WebService)
ws.Route(ws.GET("/hello").To(hello))
restful.Add(ws)
log.Println("server start in 8082")
log.Fatal(http.ListenAndServe(":8082", nil))
}
func hello(req *restful.Request, resp *restful.Response) {
log.Println("request hello")
_, _ = io.WriteString(resp, "world v2")
}
编译并运行 v1 版本:
$ go build main_v1.go
$ ./main_v1
编译并运行 v2 版本:
$ go build main_v2.go
$ ./main_v2
然后编写 envoy 配置文件 envoy-config.yaml :
admin:
access_log_path: /dev/stdout
address:
socket_address:
address: 0.0.0.0
port_value: 20000
node:
cluster: hello-service
id: node1
static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 82
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
codec_type: auto
stat_prefix: ingress_http
access_log:
name: envoy.file_access_log
typed_config:
"@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog
path: /dev/stdout
route_config:
name: local_route
virtual_hosts:
- name: service
domains:
- "*"
routes:
- match:
prefix: "/hello"
runtime_fraction:
default_value:
numerator: 90
denominator: HUNDRED
runtime_key: routing.traffic_shift.hello
route:
cluster: hello-v1
- match:
prefix: "/hello"
route:
cluster: hello-v2
http_filters:
- name: envoy.filters.http.router
clusters:
- name: hello-v1
connect_timeout: 0.25s
type: strict_dns
lb_policy: ROUND_ROBIN
hosts:
- socket_address:
address: 127.0.0.1
port_value: 8081
- name: hello-v2
connect_timeout: 0.25s
type: strict_dns
lb_policy: ROUND_ROBIN
hosts:
- socket_address:
address: 127.0.0.1
port_value: 8082
这里 runtime_key
是可以自定义的,denominator
(分母)为 HUNDRED
(一百),numerator
(分子)为 90。
启动:
$ sudo getenvoy run standard:1.14.1 -- --config-path ./envoy-config.yaml
测试:
$ curl http://127.0.0.1:82/hello
访问 10 次,会访问一次 v2 版本。
介绍一下其中的流程:
Envoy 会对路由策略进行匹配,如果路由具有 runtime_fraction
配置,则会根据 runtime_fraction
值,另外匹配路由。
上面的示例中,两条路由有相同的匹配策略,第一条路由中指定了 runtime_fraction 对象,可以通过更改runtime_fraction值来实现流量转移。
以下是完成任务所需的大概操作顺序:
- 首先,如果设置
routing.traffic_shift.hello
的值为 100%,则所有请求都会路由到 hello-v1 集群。 - 如果想慢慢将流量慢慢转移到 hello-v2 集群,可以逐步减小
routing.traffic_shift.hello
的值 。这一步可以通过 xDS 来实现动态更新。 - 当
routing.traffic_shift.hello
设定为 0 时,则所有流量都会导入到 hello-v2
继续使用上面的两个服务。
下面按比例在两个服务之间分配流量,比如分配 90% 的流量给 hello-v1,剩余 10% 的流量分配给 hello-v2。可以通过 weighted_clusters
来实现分流的效果。
在上面转移流量的示例中,需要配置多个路由,而在 weighted_clusters
中,只需要配置单个路由,就可以在多个 cluster 之间按权重分配流量。
修改 Envoy 的配置文件如下:
admin:
access_log_path: /dev/stdout
address:
socket_address:
address: 0.0.0.0
port_value: 20000
node:
cluster: hello-service
id: node1
static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 82
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.config.filter.network.http_connection_manager.v2.HttpConnectionManager
codec_type: auto
stat_prefix: ingress_http
access_log:
name: envoy.file_access_log
typed_config:
"@type": type.googleapis.com/envoy.config.accesslog.v2.FileAccessLog
path: /dev/stdout
route_config:
name: local_route
virtual_hosts:
- name: service
domains:
- "*"
routes:
- match:
prefix: "/hello"
route:
weighted_clusters:
runtime_key_prefix: routing.traffic_split.hello
total_weight: 100
clusters:
- name: hello-v1
weight: 90
- name: hello-v2
weight: 10
http_filters:
- name: envoy.filters.http.router
clusters:
- name: hello-v1
connect_timeout: 0.25s
type: strict_dns
lb_policy: ROUND_ROBIN
hosts:
- socket_address:
address: 127.0.0.1
port_value: 8081
- name: hello-v2
connect_timeout: 0.25s
type: strict_dns
lb_policy: ROUND_ROBIN
hosts:
- socket_address:
address: 127.0.0.1
port_value: 8082
默认情况下,权重之和必须恰好为 total_weight
指定的值,这里是 100。在 v2 API 中,总权重默认为100,如果想要更精细的粒度,可以修改这里的 total_weight
。
启动:
$ sudo getenvoy run standard:1.14.1 -- --config-path ./envoy-config.yaml
测试:
$ curl http://127.0.0.1:82/hello
和转移中的效果一致,访问 10 次这条 URL,会访问一次 v2 版本的服务。