Envoy 代理研究及实践
Envoy 是专为大型现代 SOA(面向服务架构)架构设计的 L7 代理和通信总线,体积小,性能高,它通过一款单一的软件满足了我们的众多需求,而不需要我们去搭配一些工具混合使用。
对应用程序而言,网络应该是透明的。当网络和应用程序出现故障时,应该能够很容易确定问题的根源
Envoy 是一款由 Lyft 开源的,使用 C++ 编写的 L7 代理和通信总线,目前是 CNCF 旗下的开源项目且已经毕业,代码托管在 GitHub 上,它也是 Istio 服务网格中默认的数据平面。关于 Envoy 的详情请阅读 Envoy 中文文档。
特性
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 负载均衡的现状,总结如下图。
架构
下图是 Envoy proxy 的架构图,显示了 host A 经过 Envoy 访问 host B 的过程。每个 host 上都可能运行多个 service,Envoy 中也可能有多个 Listener,每个 Listener 中可能会有多个 filter 组成了 chain。
三种方式
SideCar 模式
Envoy 是一个独立进程,设计为伴随每个应用程序服务运行。所有的 Envoy 形成一个透明的通信网格,每个应用程序发送消息到本地主机或从本地主机接收消息,不需要知道网络拓扑。
L3/L4/L7 架构
传统的网络代理,要么在 HTTP 层工作,要么在 TCP 层工作。Envoy 支持同时在 3/4 层和 7 层操作,以此应对这两种方法各自都有其实际限制的现实
动态更新
与 Nginx 等代理的热加载不同,Envoy 可以通过 API 来实现其控制平面,控制平面可以集中服务发现,并通过 API 接口动态下发规则更新数据平面的配置,不需要重启数据平面的代理。
Envoy 与 Istio 的区别
Envoy 和 Istio 是两个独立且常常一起使用的开源项目,它们在现代微服务架构中扮演不同的角色。下面是 Envoy 和 Istio 的主要区别:
-
Envoy:
- Envoy 是一个高性能的边缘和服务代理。它被设计用于处理网络流量的转发、负载均衡、故障恢复、限流等功能,可以作为数据平面的一部分。
- Envoy 提供了丰富的协议支持,包括 HTTP/1.1、HTTP/2、gRPC、TCP 等,以及强大的配置和路由规则定义。它还具有动态配置能力,可以根据实时需求更新路由配置。
- Envoy 具有可观测性和云原生特性,并提供丰富的统计信息、日志记录和追踪功能。
-
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_hosts
和 routes
,我们可以针对特定的域名、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 的步骤:
- 安装 Helm:
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
- 添加 Envoy Helm chart 存储库:
helm repo add envoy https://envoy.github.io/helm-charts
- 更新 Helm 存储库:
helm repo update
- 创建名为
my-envoy
的新命名空间:
kubectl create namespace my-envoy
- 使用 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 客户端库(例如 kubernetes
或 openshift
)来与 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
为者常成,行者常至
自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)