Envoy 代理研究及实践

Envoy 是专为大型现代 SOA(面向服务架构)架构设计的 L7 代理和通信总线,体积小,性能高,它通过一款单一的软件满足了我们的众多需求,而不需要我们去搭配一些工具混合使用。

对应用程序而言,网络应该是透明的。当网络和应用程序出现故障时,应该能够很容易确定问题的根源

Envoy 是一款由 Lyft 开源的,使用 C++ 编写的 L7 代理和通信总线,目前是 CNCF 旗下的开源项目且已经毕业,代码托管在 GitHub 上,它也是 Istio 服务网格中默认的数据平面。关于 Envoy 的详情请阅读 Envoy 中文文档。

file

特性

Envoy 包括如下特性:

  • 进程外架构,不侵入应用进程
  • 使用现代版 C++11 代码
  • L3/L4 filter 架构
  • HTTP L7 filter 架构
  • 支持 HTTP/2
  • HTTP L7 routing
  • 支持 gRPC
  • 支持 MongoDB L7
  • 动态配置
  • 最佳可观测性
  • 支持 front/edge proxy
  • 高级负载均衡
  • 健康检查
  • 服务发现
  • 支持 DynamoDB L7
    Envoy 本身无法构成一个完整的 Service Mesh,但是它可以作为 service mesh 中的应用间流量的代理,负责 service mesh 中的数据层。

负载均衡与代理

在 Matt Klein(Envoy 的作者) 的 Introduction to modern network load balancing and proxying 文章详细描述了现代的网络负载均衡与代理的特性与模式,L4 与 L7 负载均衡的区别以及 L7 负载均衡的现状,总结如下图。

file

架构

下图是 Envoy proxy 的架构图,显示了 host A 经过 Envoy 访问 host B 的过程。每个 host 上都可能运行多个 service,Envoy 中也可能有多个 Listener,每个 Listener 中可能会有多个 filter 组成了 chain。
file

三种方式

file

SideCar 模式

Envoy 是一个独立进程,设计为伴随每个应用程序服务运行。所有的 Envoy 形成一个透明的通信网格,每个应用程序发送消息到本地主机或从本地主机接收消息,不需要知道网络拓扑。

L3/L4/L7 架构

传统的网络代理,要么在 HTTP 层工作,要么在 TCP 层工作。Envoy 支持同时在 3/4 层和 7 层操作,以此应对这两种方法各自都有其实际限制的现实

动态更新

与 Nginx 等代理的热加载不同,Envoy 可以通过 API 来实现其控制平面,控制平面可以集中服务发现,并通过 API 接口动态下发规则更新数据平面的配置,不需要重启数据平面的代理。

Envoy 与 Istio 的区别

Envoy 和 Istio 是两个独立且常常一起使用的开源项目,它们在现代微服务架构中扮演不同的角色。下面是 Envoy 和 Istio 的主要区别:

  1. Envoy:

    • Envoy 是一个高性能的边缘和服务代理。它被设计用于处理网络流量的转发、负载均衡、故障恢复、限流等功能,可以作为数据平面的一部分。
    • Envoy 提供了丰富的协议支持,包括 HTTP/1.1、HTTP/2、gRPC、TCP 等,以及强大的配置和路由规则定义。它还具有动态配置能力,可以根据实时需求更新路由配置。
    • Envoy 具有可观测性和云原生特性,并提供丰富的统计信息、日志记录和追踪功能。
  2. Istio:

    • Istio 是一个面向微服务架构的服务网格平台。它利用 Envoy 作为其数据平面,提供了服务发现、智能流量管理、请求路由、A/B 测试、故障恢复、安全性等功能。
    • Istio 提供了对服务之间的通信进行管理和控制的能力,例如细粒度的流量控制和策略制定。它通过注入 Envoy 代理到应用程序容器中,实现对流量的全面控制。
    • Istio 还提供了丰富的可观测性特性,包括请求跟踪、度量指标收集、日志记录和分布式追踪,以帮助用户更好地理解和优化微服务的行为。

总结来说,Envoy 是一个功能强大的边缘和服务代理,而 Istio 是构建在 Envoy 之上的服务网格平台,用于管理和控制微服务架构中的流量、安全性和可观测性。使用 Istio 可以更方便地配置和管理 Envoy 代理,并获得更高级别的服务治理功能。

问:两者在部署上有什么区别?Envoy能实现 A/B 测试、灰度发布、智能流量管理吗?
Envoy 和 Istio 在部署上有一些区别。

部署 Envoy 比部署 Istio 简单,因为 Envoy 是一个独立的代理,可以很容易地与多种微服务框架和平台集成。要部署 Envoy,只需将代理放在应用程序和后端服务之间,然后进行必要的配置即可。

Istio 的部署需要将 Envoy 代理注入到应用程序容器中,并且还需要配置 Istio 控制平面。由于 Istio 需要比 Envoy 实现更多的功能,因此在部署 Istio 时需要考虑更多复杂性。

至于 A/B 测试、灰度发布、智能流量管理等功能,Envoy 可以实现它们。Envoy 可以配置和管理多个路由规则,并根据诸如头文件、路径、查询参数等条件将流量路由到不同的目标服务中。这使得 Envoy 能够支持基于权重的灰度发布、A/B 测试和智能流量管理等功能。

不过,使用 Envoy 去实现这些功能需要相应的配置和管理工作。而 Istio 提供了对这些功能的更高级别的抽象,使得用户更方便地管理这些功能。例如,Istio 中的 VirtualService 可以更直观地描述路由规则,使得管理者能够更容易地实现服务版本控制、流量管理等复杂功能。

Envoy 可以实现如 Istio 中的 VirtualService 吗?

是的,Envoy 可以实现类似于 Istio 中 VirtualService 的功能,虽然 Envoy 不提供直接的 VirtualService 抽象,但可以通过配置 Envoy 代理来实现相似的路由规则和流量管理。

在 Envoy 中,通过配置 Cluster、Listener 和 Route 来定义路由规则和控制流量。下面是一个示例 Envoy 配置,展示了如何实现类似于 Istio 的 VirtualService 功能:

static_resources:
  listeners:
    - name: my_listener
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 8080
      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
                route_config:
                  name: my_route
                  virtual_hosts:
                    - name: my_virtual_host
                      domains: ["example.com"]
                      routes:
                        - match:
                            prefix: "/api/v1"
                          route:
                            cluster: backend_service_v1
                        - match:
                            prefix: "/api/v2"
                          route:
                            cluster: backend_service_v2
                http_filters:
                  - name: envoy.filters.http.router

在上述配置中,通过定义 virtual_hostsroutes,我们可以针对特定的域名、URL 前缀等条件将请求路由到不同的后端服务。

这个示例配置将 /api/v1 前缀的请求路由到名为 backend_service_v1 的集群,将 /api/v2 前缀的请求路由到名为 backend_service_v2 的集群。这种方式就类似于 Istio 中 VirtualService 的功能。

需要注意的是,Envoy 的配置语法可能会比较复杂,但通过正确的配置可以实现类似于 Istio 中 VirtualService 的路由规则和流量管理功能。

如何通过 api来操作可实现上边的路由规则?

可以通过 Envoy 的管理 API 来动态地更新 Envoy 的路由规则。Envoy 的管理 API 可以使用 gRPC 或者 RESTful 接口进行访问,您可以使用任何支持这些协议的编程语言来调用这个 API。

下面是一个使用 Python 调用 Envoy 管理 API,添加或修改路由规则的示例代码:

import grpc
from envoy.api.v2 import RouteConfiguration, ClusterLoadAssignment
from envoy.config.endpoint.v3 import LocalityLbEndpoints, LbEndpoint
from envoy.service.discovery.v3 import DeltaDiscoveryRequest, DiscoveryResponse
from google.protobuf.wrappers_pb2 import UInt32Value, StringValue
from google.protobuf.duration_pb2 import Duration

# 创建 gRPC channel,并创建 Envoy 管理 API 客户端
channel = grpc.insecure_channel('envoy_admin:19000')
routes_client = RouteDiscoveryServiceStub(channel)
clusters_client = ClusterDiscoveryServiceStub(channel)

# 创建一个新的路由规则
route_config = RouteConfiguration(
    name='my_route',
    virtual_hosts=[{
        'name': 'my_virtual_host',
        'domains': ['example.com'],
        'routes': [{
            'match': {
                'prefix': '/api/v1'
            },
            'route': {
                'cluster': 'backend_service_v1'
            }
        }, {
            'match': {
                'prefix': '/api/v2'
            },
            'route': {
                'cluster': 'backend_service_v2'
            }
        }]
    }]
)

# 将路由规则发送给 Envoy
routes_request = DeltaDiscoveryRequest(
    node={'id': 'my_node'},
    type_url='type.googleapis.com/envoy.api.v2.RouteConfiguration',
    resource_names=['my_route'],
    response_nonce='',
    resource_versions={},
    error_detail=None,
    initial_fetch_request=False,
    raw_response=False,
    envoy_api_version=UInt32Value(value=2),
    node_context_params=None,
    delta_origin=None,
    inline_entries=[DiscoveryResponse(type_url='type.googleapis.com/envoy.api.v2.RouteConfiguration',
                                       version='v1',
                                       resource=route_config)]
)

response = routes_client.DeltaRoutes(routes_request)

# 创建一个新的集群,指向后端服务的地址
cluster_address = LbEndpoint(endpoint=Endpoint(address=Address(socket_address=SocketAddress(address='backend_service', port_value=8080))))
locality_lb_endpoints = LocalityLbEndpoints(lb_endpoints=[cluster_address])
endpoints = ClusterLoadAssignment(endpoints=[locality_lb_endpoints])
cluster = Cluster(name='backend_service_v1',
                  type=Cluster.STATIC,
                  connect_timeout=Duration(seconds=5),
                  lb_policy=Cluster.ROUND_ROBIN,
                  load_assignment=endpoints)

# 将集群信息发送给 Envoy
clusters_request = DeltaDiscoveryRequest(
    node={'id': 'my_node'},
    type_url='type.googleapis.com/envoy.api.v2.Cluster',
    resource_names=['backend_service_v1'],
    response_nonce='',
    resource_versions={},
    error_detail=None,
    initial_fetch_request=False,
    raw_response=False,
    envoy_api_version=UInt32Value(value=2),
    node_context_params=None,
    delta_origin=None,
    inline_entries=[DiscoveryResponse(type_url='type.googleapis.com/envoy.api.v2.Cluster',
                                       version='v1',
                                       resource=cluster)]
)

response = clusters_client.DeltaClusters(clusters_request)

上面的示例代码中,我们使用了 Envoy 的 Delta xDS API,可以使我们在不重启 Envoy 的情况下,动态地更新路由规则和后端服务集群的信息。

需要注意的是,上述示例代码仅供参考,实际中,您需要根据具体的 Envoy 配置和需求来编写代码。同时,确保您的请求遵循 Envoy 管理 API 的协议和数据格式要求。

k8s部署管理

envoy 部署在k8s,如何部署,如何通过 api 来访问?

要在 Kubernetes 中部署 Envoy,你可以使用 Helm 来管理安装。Helm 是 Kubernetes 的一个包管理工具,可用于轻松管理和部署应用程序。

以下是在 Kubernetes 群集中使用 Helm 部署 Envoy 的步骤:

  1. 安装 Helm:
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
  1. 添加 Envoy Helm chart 存储库:
helm repo add envoy https://envoy.github.io/helm-charts
  1. 更新 Helm 存储库:
helm repo update
  1. 创建名为 my-envoy 的新命名空间:
kubectl create namespace my-envoy
  1. 使用 Helm 安装 Envoy:
helm install my-envoy envoy/envoy -n my-envoy

以上命令将在 my-envoy 命名空间中安装 Envoy,其中 envoy/envoy 是从 Envoy 的 Helm 存储库中提取的最新 Helm chart。

一旦 Envoy 安装完成,你可以通过 API 访问 Envoy。Envoy 提供了多种 API,包括管理 API、统计信息 API 和健康检查 API。其中最常用的是管理 API,它可以用于动态调整 Envoy 的配置。

要使用 Envoy 管理 API,可以通过类似以下方式的 HTTP POST 请求来发送配置至 Envoy:

curl -X POST -H "Content-Type: application/json" -d @/path/to/config.json http://envoy-proxy:8001/api/v1/config_dump

其中 /path/to/config.json 是包含新配置的 JSON 文件的路径,envoy-proxy 是已安装 Envoy 的 Kubernetes Service 的名称。默认情况下,Envoy 管理 API 监听 8001 端口,并提供以下 URL:

  • http://<envoy-service-name>.<namespace>:<port>/healthcheck: Envoy 健康检查 API。
  • http://<envoy-service-name>.<namespace>:<port>/clusters: 获取 Envoy 集群的运行状况。
  • http://<envoy-service-name>.<namespace>:<port>/stats: 获取 Envoy 统计信息。

请确保将 <envoy-service-name> 替换为你在 Kubernetes 中为 Envoy 安装创建的 Service 名称,<namespace> 替换为你选择的命名空间,<port> 替换为 Service 暴露出的端口。

如何通过 python 来操作k8s 部署的 Envoy 管理 路由规则,请给出示例

要通过 Python 操作 Kubernetes 中部署的 Envoy 来管理路由规则,你可以使用 Kubernetes Python 客户端库(例如 kubernetesopenshift)来与 Kubernetes API 进行交互。下面是一个示例代码,演示如何使用 kubernetes 客户端库来添加和更新 Envoy 的路由规则:

首先,确保已安装 kubernetes 客户端库:

pip install kubernetes

然后,使用以下代码来添加和更新路由规则:

from kubernetes import client, config

def add_route(namespace, route_config):
    config.load_kube_config()
    api = client.CustomObjectsApi()

    group = 'getenvoy.io'
    version = 'v1'
    plural = 'routegroups'

    group_version = f"{group}/{version}"

    try:
        response = api.get_namespaced_custom_object(group, version, namespace, plural, 'default')
        routes = response['spec']['routes']
        routes.append(route_config)
        response['spec']['routes'] = routes
        api.replace_namespaced_custom_object(group, version, namespace, plural, 'default', response)
        print("路由规则添加成功")
    except Exception as e:
        print("路由规则添加失败:", str(e))

# 新的路由规则
new_route_config = {
    "name": "local_route",
    "virtual_hosts": [
        {
            "name": "default",
            "domains": ["*"],
            "routes": [
                {
                    "match": {"prefix": "/api"},
                    "route": {"cluster": "backend_service_cluster"}
                }
            ]
        }
    ]
}

# 调用函数来添加路由规则
add_route("my-envoy", new_route_config)

在上述示例代码中,我们使用 kubernetes 客户端库的 CustomObjectsApi 类来与 Kubernetes 的自定义资源 routegroups 进行交互。通过调用 get_namespaced_custom_object 方法获取现有的路由规则,然后将新的路由规则添加到列表中,并使用 replace_namespaced_custom_object 方法将更新后的路由规则提交到 Kubernetes API。

请确保将 "my-envoy" 替换为你在 Kubernetes 中为 Envoy 部署的命名空间,new_route_config 替换为你要添加或更新的新路由配置。

这样,你就可以使用 Python 代码来操作 Kubernetes 中部署的 Envoy,进行路由规则的管理。


相关文章:
envoy代理官网
envoy代理官方文档
github | envoyproxy/envoy
云原生 | Envoy中文指南
jimmysong.io | Envoy 介绍
envoyproxy/java-control-plane
掘金 | K8S Envoy 接管 Pod 的流量(2)
掘金 | Envoy 入门介绍 (1)
云原生时代的流量入口:Envoy Gateway
史上最全的高性能代理服务器 Envoy 中文实战教程 !(强烈建议收藏)
Envoy Gateway 首个正式开源版本介绍
Envoy Gateway 官网
java | envoyproxy/java-control-plane

为者常成,行者常至