Fork me on GitHub
Envoy 是专为大型现代 SOA(面向服务架构)架构设计的 L7 代理和通信总线

Envoy实现.NET架构的网关(一)静态配置与文件动态配置

 

什么是Gateway

在微服务体系结构中,如果每个微服务通常都会公开一组精细终结点,这种情况可能会有以下问题

  1. 如果没有 API 网关模式,客户端应用将与内部微服务相耦合
  2. 在客户端应用中,单个页面/屏幕可能需要多次调用多个服务。 
  3. 如果没有网关,所有微服务必定会暴露在“外部世界”中。
  4. 每个公开发布的微服务都必须处理授权和 SSL 等问题。

而Gateway可以为微服务组提供单一入口点,API 网关位于客户端应用和微服务之间。 它充当反向代理,将请求从客户端路由到服务。 它还可以提供其他跨领域功能,例如身份验证、SSL 终止和缓存

什么是Envoy

Envoy 是专为大型现代 SOA(面向服务架构)架构设计的 L7 代理和通信总线,它有以下优势

  1. C++11编写,原生代码高性能
  2. L3/L4 filter架构,例如TCP代理
  3. HTTP L7 filter架构,缓存,限速,路由/转发
  4. 顶级HTTP2与GRPC支持
  5. 服务发现与动态配置
  6. 健康检查
  7. 高级负载均衡

我们可以借助Envoy实现API Gateway。Envoy通过yaml配置文件来组织网关的信息。下面来说说Envoy中的核心概念

.NET网关与Gateway实战-Envoy与kong课程希望大家支持  https://ke.qq.com/course/4033027?tuin=1271860f

 

Listener

一个命名的网络地址,可以被下游客户端连接,它的配置样式如下:

复制代码
static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        address: 0.0.0.0
        port_value: 10000
复制代码

此配置说明Envoy监听在10000端口,下游客户端可以通过此端口与Envoy交互

L3/L4过滤器Filter

L3/L4过滤器Filter可以帮我们实现如:HTTP连接管理,限速,TCP代理等功能,它的配置样式如下:

复制代码
    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
          scheme_header_transformation:
            scheme_to_overwrite: http
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  host_rewrite_literal: 192.168.43.94
                  cluster: service_envoyproxy_io
          http_filters:
          - name: envoy.filters.http.router
复制代码

此配置说明通过HttpConnectionManager这个过滤器来接受HTTP请求,并将请求通过router过滤器的配置转发到service_envoyproxy_io这个上游集群

Upstream Cluster

Envoy 的集群管理器管理所有配置的上游集群,用来真正处理Envoy接受的请求,其配置样式如下:

复制代码
clusters:
  - name: service_envoyproxy_io
    connect_timeout: 30s
    type: strict_dns
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: service_envoyproxy_io
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 192.168.43.94
                port_value: 5000
复制代码

此配置说明Envoy会将请求转发到192.168.43.94:5000这个地址。

调用逻辑我们总结如下,Listener接受请求,将请求交给过滤器,过滤器处理完后,根据路由规则将请求转发给上游集群,上游集群中的endpoint会真正处理请求。

运行Envoy

我们通过docker运行一个默认Envoy容器

docker run --rm -it -p 9901:9901 -p 10000:10000 envoyproxy/envoy-dev

访问http://localhost:10000/,发现其跳转到Envoy官网

 

 

 我们进入容器查看其配置,发现其最终会将请求转发到www.envoyproxy.io

cat /etc/envoy/envoy.yaml
复制代码
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: www.envoyproxy.io
                port_value: 443
复制代码

静态文件配置

我们现在通过Envoy来实现我们自己的网关。静态文件配置是我们把配置信息提前配置好,Envoy启动后不可修改配置内容

准备服务

我们准备两个.NET WebAPI,server1与server2,其中分别创建NameController,并新建Get方法

Server1

复制代码
      [HttpGet]
        public string Get()
        {
            _logger.LogInformation("call server1");
            var req = Request;
            return "server1";
        }
复制代码

Server2

复制代码
        [HttpGet]
        public string Get()
        {
            _logger.LogInformation("call server2");
            var req = Request;
            return "server2";
        }
复制代码

并将server1的启动端口指定为5000,将server2的启动端口指定为5001

Server1

webBuilder.UseUrls("http://*:5555").UseStartup<Startup>();

Server2

webBuilder.UseUrls("http://*:5001/").UseStartup<Startup>();

我们启动Server1与Server2

准备Envoy配置

我们将上节课的默认Envoy配置文件从容器中取出,并作修改如下

复制代码
admin:
  address:
    socket_address:
      protocol: TCP
      address: 0.0.0.0
      port_value: 9901
static_resources:
  listeners:
  - name: listener_0
    address:
      socket_address:
        protocol: TCP
        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
          scheme_header_transformation:
            scheme_to_overwrite: http
          stat_prefix: ingress_http
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match:
                  prefix: "/"
                route:
                  host_rewrite_literal: 192.168.43.94
                  cluster: service_envoyproxy_io
          http_filters:
          - name: envoy.filters.http.router
  clusters:
  - name: service_envoyproxy_io
    connect_timeout: 30s
    type: static
    # Comment out the following line to test on v6 networks
    dns_lookup_family: V4_ONLY
    lb_policy: ROUND_ROBIN
    load_assignment:
      cluster_name: service_envoyproxy_io
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 192.168.43.94
                port_value: 5000
        - endpoint:
            address:
              socket_address:
                address: 192.168.43.94
                port_value: 5001
复制代码

我们启动Envoy,验证配置是否正确

docker run --rm -it -p 9901:9901 -p 10000:10000 -v D:/gateway/envoy/config/static/envoy.yaml:/etc/envoy/envoy.yaml -v D:/gateway/envoy/logs:/logs envoyproxy/envoy-dev  -c /etc/envoy/envoy.yaml --log-path logs/custom.log

调用api,发现其实现了负载

http://localhost:10000/Name

动态文件配置

动态文件可以帮助我们实现当文件发生更改时,Envoy 将自动更新其配置。

修改静态文件,将其中的cluster提取到cds.yaml文件中

复制代码
resources:
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: example_proxy_cluster
  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:
        http_protocol_options: {}
  load_assignment:
    cluster_name: example_proxy_cluster
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: 192.168.43.94
              port_value: 5000
复制代码

将listener提取到lds.yaml文件中

复制代码
resources:
- "@type": type.googleapis.com/envoy.config.listener.v3.Listener
  name: listener_0
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 10000
  filter_chains:
  - filters:
    - name: envoy.http_connection_manager
      typed_config:
        "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
        stat_prefix: ingress_http
        http_filters:
        - name: envoy.filters.http.router
        route_config:
          name: local_route
          virtual_hosts:
          - name: local_service
            domains:
            - "*"
            routes:
            - match:
                prefix: "/envoyapi/"
              route:
                prefix_rewrite: "/"
                host_rewrite_literal: 192.168.43.94
                cluster: example_proxy_cluster
复制代码

修改envoy.yaml让其引用lds.yaml与cds.yaml文件

复制代码
admin:
  address:
    socket_address:
      protocol: TCP
      address: 0.0.0.0
      port_value: 9902
node:
  cluster: test-cluster
  id: test-id
dynamic_resources:
  cds_config:
    path: /etc/envoy/cds.yaml
  lds_config:
    path: /etc/envoy/lds.yaml
复制代码

启动Envoy

docker run --rm -it -p 9902:9902 -p 10000:10000 -v D:/gateway/envoy/config/dynamic/:/etc/envoy/ -v D:/gateway/envoy/logs:/logs envoyproxy/envoy-dev  -c /etc/envoy/envoy.yaml --log-path logs/custom.log

调用api,发现调用成功

http://localhost:10000/envoyapi/Name

修改动态文件配置

修改cds.yaml,将endpoint端口设置为5001

复制代码
resources:
- "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster
  name: example_proxy_cluster
  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:
        http_protocol_options: {}
  load_assignment:
    cluster_name: example_proxy_cluster
    endpoints:
    - lb_endpoints:
      - endpoint:
          address:
            socket_address:
              address: 192.168.43.94
              port_value: 5001
复制代码

进入容器内部强制更新文件

# cd /etc/envoy
# mv cds.yaml tmp
# mv tmp cds.yaml

调用api,发现在不重启Envoy的情况下,实现了配置信息的动态更新

 

至此,我们已经通过Envoy的静态配置与文件动态配置实现了一个网关来代理我们的.NET程序

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

 

什么是控制面板

这篇我们来看看如何通过控制平面(Control Panel)来配置Envoy。

控制平面就是一个提供Envoy配置信息的单独服务,Envoy可以通过调用这个服务的api来加载配置。

配置控制面板

官方为我们提供了两种已经实现好的控制面板。

go控制面板:https://github.com/envoyproxy/go-control-plane

java控制面板:https://github.com/envoyproxy/java-control-plane

我们下载官方的go语言控制面板,先来看看go控制面板中提供的样例结构。

其中有三个核心文件,main.go,server.go,与resource.go

1.main.go:启动go-controll-panel的入口

2.server.go:定义go-controll-panel的grpc服务

3.resource.go:定义了envoy相关的资源配置,包括cluster,listener,endpoint等

其中resource.go组成了一个结构化的配置信息,并通过server.go中的grpc提供给envoy,而main.go则是启动了grpc,因此我们需要修改的就是resource.go中的资源配置,需要将里面的cluster,listener,endpoint等设置为我们自己的服务信息。下面我们就来将resource.go中的配置信息改成我们自己的测试项目配置。

首先修改upstream信息,指定我们的上游服务的ip与两个上游服务的端口

1
2
3
4
5
6
7
8
9
const (
    ClusterName   = "example_proxy_cluster"
    RouteName     = "local_route"
    ListenerName  = "listener_0"
    ListenerPort  = 10000
    UpstreamHost  = "192.168.43.94"
    UpstreamPort  = 5000
    UpstreamPort2 = 5001
)

然后我们修改dns类型,将其修改为静态dns解析

1
2
3
4
5
6
7
8
9
10
func makeCluster(clusterName string) *cluster.Cluster {
    return &cluster.Cluster{
        Name:                 clusterName,
        ConnectTimeout:       ptypes.DurationProto(5 * time.Second),
        ClusterDiscoveryType: &cluster.Cluster_Type{Type: cluster.Cluster_STATIC},
        LbPolicy:             cluster.Cluster_ROUND_ROBIN,
        LoadAssignment:       makeEndpoint(clusterName),
        DnsLookupFamily:      cluster.Cluster_V4_ONLY,
    }
}

修改makeEndpoint方法,指定两个测试server,server1与server2的地址为Endpoint

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
func makeEndpoint(clusterName string) *endpoint.ClusterLoadAssignment {
    return &endpoint.ClusterLoadAssignment{
        ClusterName: clusterName,
        Endpoints: []*endpoint.LocalityLbEndpoints{{
            LbEndpoints: []*endpoint.LbEndpoint{{
                HostIdentifier: &endpoint.LbEndpoint_Endpoint{
                    Endpoint: &endpoint.Endpoint{
                        Address: &core.Address{
                            Address: &core.Address_SocketAddress{
                                SocketAddress: &core.SocketAddress{
                                    Protocol: core.SocketAddress_TCP,
                                    Address:  UpstreamHost,
                                    PortSpecifier: &core.SocketAddress_PortValue{
                                        PortValue: UpstreamPort,
                                    },
                                },
                            },
                        },
                    },
                },
            },
                {
                    HostIdentifier: &endpoint.LbEndpoint_Endpoint{
                        Endpoint: &endpoint.Endpoint{
                            Address: &core.Address{
                                Address: &core.Address_SocketAddress{
                                    SocketAddress: &core.SocketAddress{
                                        Protocol: core.SocketAddress_TCP,
                                        Address:  UpstreamHost,
                                        PortSpecifier: &core.SocketAddress_PortValue{
                                            PortValue: UpstreamPort2,
                                        },
                                    },
                                },
                            },
                        },
                    },
                },
            },
        }},
    }
}

启动控制平面

 

 控制平面监听在18000端口

配置Envoy

.NET网关与Gateway实战-Envoy与kong课程希望大家支持  https://ke.qq.com/course/4033027?tuin=1271860f

 

我们需要配置Envoy.yaml让Envoy从控制平面中获取详细的配置信息。我们可以参考官网的推荐配置。

1.需要在dynamic_resources中配置ads_config,并让cds_config与lds_config从中读取

2.另外我们需要配置xds cluster,让Envoy知道控制平面的地址。

具体配置信息如下:

复制代码
admin:
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 9902
      
node:
  cluster: test-cluster
  id: test-id

dynamic_resources:
  ads_config:
    api_type: GRPC
    transport_api_version: V3
    grpc_services:
    - envoy_grpc:
        cluster_name: xds_cluster
  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: {}
    name: xds_cluster
    load_assignment:
      cluster_name: xds_cluster
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 192.168.43.94
                port_value: 18000
复制代码

运行Envoy

我们通过docker运行Envoy

docker run --rm -it -p 9902:9902 -p 10000:10000 -v D:/gateway/envoy/config/dynamic-plane/:/etc/envoy/ -v D:/gateway/envoy/logs:/logs envoyproxy/envoy-dev  -c /etc/envoy/envoy.yaml

然后启动我们上一节的server1与server2

测试

 我们通过http://localhost:10000/Name来判断我们的配置是不是配置成功

可以看到其中一次调用返回了server1,另一次调用返回了server2,控制面板验证成功!

二次开发

目前的样例代码中提供的配置都是硬编码的方式,把配置写死在代码中,如果生产环境中我们想使用这种方式来配置envoy,我们可以基于现在的代码二次开发。开发的步骤如下:

1.新增数据库表结构组织Envoy中的资源,比如Listener表,Cluster表,Endpoint表,Route表

2.新增接口来对Listener,Cluster,Endpoint,Route等资源进行增删改查

3.新增UI来调用增删改查接口,对资源进行管理

4.定时刷新Snapshot信息

 这样我们就可以实现一个可视化的界面来动态的配置我们的Envoy。

posted on 2022-01-18 08:53  HackerVirus  阅读(278)  评论(0)    收藏  举报