Envoy Original Destination Cluster 实战指南:基于 iptables REDIRECT 的透明代理与按目标地址动态路由
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
导读
本文基于 Envoy 官方示例 configs/original-dst-cluster 展开,完整讲解 Original Destination(原始目标)Cluster 的配置与端到端测试流程:通过 iptables REDIRECT 规则把发往指定网段的流量透明劫持到 Envoy 监听端口,再由 Envoy 依据连接被劫持前的"原始目标地址"动态创建上游主机并转发。读完本文,你将掌握 original_dst 监听器过滤器与ORIGINAL_DST类型集群的组合配置、网络命名空间 + veth 对 + iptables 的测试环境搭建方法,以及 Envoy 内部按需添加、自动清理动态主机的实现原理。
Original Destination Cluster 是什么
Original Destination Cluster(原始目标集群)是一种特殊的 Envoy 动态集群:它不做静态配置或 DNS 解析,而是把请求转发到该请求在被 Envoy 拦截之前的原始目标地址。其典型应用场景是透明代理——客户端并不知道代理的存在,其出站流量被 iptables REDIRECT 规则劫持后送入 Envoy,Envoy 在转发时自动恢复流量的原始目的 IP 与端口,实现"劫持后原样转发"。
整个工作链路由两个核心组件配合完成:
envoy.filters.listener.original_dst监听器过滤器(见 config.cc 与 original_dst.cc):在连接被接受时通过系统调用(Linux 下为getsockopt(SO_ORIGINAL_DST),封装于Network::Utility::getOriginalDst)取出连接被 REDIRECT 之前的原始目的地址,并把该地址恢复为 socket 的 local address。ORIGINAL_DST类型集群(见 original_dst_cluster.h 与 original_dst_cluster.cc):其负载均衡器在选主机时,根据下游连接的原始目标地址按需动态添加主机,无需在配置里列出任何上游地址。
关键事实:集群的LoadBalancer会读取下游连接上下文得到"原始目标"地址,如果该地址尚未在集群的 host 表中,就即时创建对应 host 加入集群;同时集群会周期性清理长时间没有流量、且不被任何连接池持有的过期主机(清理周期由cleanup_interval_ms配置)。这一点从类注释即可确认(original_dst_cluster.h):
The OriginalDstCluster is a dynamic cluster that automatically adds hosts as needed based on the original destination address of the downstream connection. These hosts are also automatically cleaned up after they have not seen traffic for a configurable cleanup interval time ("cleanup_interval_ms").
示例配置解析:proxy_config.yaml
仓库在 configs/original-dst-cluster/proxy_config.yaml 提供了一份可直接运行的完整示例,核心结构如下:
static_resources: listeners: - address: socket_address: address: 0.0.0.0 port_value: 10000 traffic_direction: OUTBOUND 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 stat_prefix: ingress_http route_config: name: local_service virtual_hosts: - name: backend domains: - "*" routes: - match: prefix: "/" route: cluster: cluster1 http_filters: - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router codec_type: AUTO listener_filters: - name: envoy.filters.listener.original_dst typed_config: "@type": type.googleapis.com/envoy.extensions.filters.listener.original_dst.v3.OriginalDst clusters: - name: cluster1 type: ORIGINAL_DST connect_timeout: 6s lb_policy: CLUSTER_PROVIDED dns_lookup_family: V4_ONLY cluster_manager: {} admin: address: socket_address: address: 127.0.0.1 port_value: 9901各关键字段的作用与取值说明:
| 配置项 | 取值 | 作用说明 |
|---|---|---|
listeners[].address.port_value | 10000 | Envoy 监听端口,与netns_setup.sh中ENVOY_PORT一致,是 iptables REDIRECT 的目标端口 |
traffic_direction | OUTBOUND | 标识该监听器处理的是出站流量,供 original_dst 过滤器在特定平台(如 Windows WFP 重定向)分支处理 |
listener_filters[].name | envoy.filters.listener.original_dst | 监听器过滤器,在连接建立前恢复"原始目标地址" |
clusters[].type | ORIGINAL_DST | 集群类型声明为原始目标集群,由OriginalDstClusterFactory(工厂名envoy.cluster.original_dst,见 original_dst_cluster.h)加载,同时兼容遗留的ORIGINAL_DST类型写法 |
connect_timeout | 6s | 上游连接超时。示例中特意设为 6 秒以减少偶发超时(详见下文"关于 301 与 503") |
lb_policy | CLUSTER_PROVIDED | 负载均衡策略由集群自己提供(即OriginalDstCluster::LoadBalancer),而非内置策略 |
dns_lookup_family | V4_ONLY | 目标地址按 IPv4 处理,与测试网段173.194.222.0/24匹配 |
admin.port_value | 9901 | 管理接口监听在127.0.0.1:9901,可用于观察集群状态(/clusters)与日志 |
关于use_original_dst的说明:这是早期版本常用的监听器级开关(在filter_chains之外的 listener 字段设置),现代推荐做法是在listener_filters中显式配置envoy.filters.listener.original_dst过滤器,示例配置即采用后者。原过滤器在未经过 REDIRECT 的连接上会直接放行(此时getOriginalDst返回的就是 socket 自身本地地址),不会误改流量(original_dst.cc)。
补充能力:原始目标集群的扩展配置
从 proto 定义 original_dst.proto 可以看到,ORIGINAL_DST集群除了本文示例的用法外,还支持若干可选字段(需通过cluster_type扩展配置携带):
use_http_header/http_header_name:允许从指定的 HTTP 头覆盖目标地址,实现不依赖系统 REDIRECT 的应用层目标改写;upstream_port_override:覆盖上游端口(范围 0–65535);metadata_key:从动态元数据中取目标地址。
这些能力对应 original_dst_cluster.h 中LoadBalancer持有的http_header_name_、metadata_key_、port_override_三个成员,负载均衡器按"过滤状态 → HTTP 头 → 元数据 → 原始目标"的优先级确定转发地址。
环境准备:搭建网络命名空间与 iptables 重定向
官方提供了两个配套脚本:netns_setup.sh(建环境)与netns_cleanup.sh(清理环境),均位于 configs/original-dst-cluster。脚本接收两个参数:
| 参数 | 含义 |
|---|---|
$1 | 新建网络命名空间的名称 |
$2 | 需要被重定向的目标地址或网段(如173.194.222.0/24) |
两个脚本内部均把 Envoy 监听端口硬编码为10000,与proxy_config.yaml中的port_value严格对应。
netns_setup.sh 干了什么
执行sudo ./configs/original-dst-cluster/netns_setup.sh ns1 173.194.222.0/24后,脚本依次完成(对应 netns_setup.sh):
- 创建 veth 对:
ip link add ns1-veth0 type veth peer name ns1-veth1,veth0 留在根命名空间并配置为10.0.200.2/24,作为网络出口; - 创建网络命名空间:
ip netns add ns1,把 veth1 移入命名空间并配置为10.0.200.1/24; - 配置命名空间内路由:启用 loopback,并把默认路由指向 veth0(
ip route add default via 10.0.200.2); - 注入 iptables REDIRECT 规则:
iptables -t nat -I PREROUTING --src 0/0 --dst 173.194.222.0/24 -p tcp --dport 80 -j REDIRECT --to-ports 10000这条规则挂在根命名空间 nat 表的PREROUTING钩子上:任何源地址发往173.194.222.0/24:80的 TCP 报文,都被重定向到本机10000端口(即 Envoy 监听端口)。这正是"透明劫持"的关键——目标进程完全感知不到代理的存在。
注意:脚本把 veth0 的 IP 配为
10.0.200.2而非默认网关10.0.200.1,这是为了配合命名空间内"默认路由指向 10.0.200.2"的设计(veth1 为10.0.200.1),读者按示例使用即可,无需修改。
netns_cleanup.sh 干了什么
清理脚本执行完全逆操作(netns_cleanup.sh):
iptables -t nat -D PREROUTING --src 0/0 --dst 173.194.222.0/24 -p tcp --dport 80 -j REDIRECT --to-ports 10000 ip netns delete ns1 ip link del ns1-veth0 type veth peer name ns1-veth1即删除 iptables 规则、删除网络命名空间、删除 veth 对,把网络环境恢复原状。
构建并运行 Envoy
用 Debug 模式构建
为了在日志中清晰观察主机动态增删行为,官方建议以 debug 选项构建:
bazel build //source/exe:envoy-static -c dbg产物路径为bazel-out/local-dbg/bin/source/exe/envoy-static(对应 local 配置的 dbg 编译变体)。
运行示例配置
bazel-out/local-dbg/bin/source/exe/envoy-static -c configs/original-dst-cluster/proxy_config.yaml -l debug-c指定配置文件为仓库内的 configs/original-dst-cluster/proxy_config.yaml;-l debug把日志级别设为 debug,便于观察Adding host/Keeping active host/Removing stale host等关键日志。
启动成功后,Envoy 会周期性输出类似Cleaning up stale original dst hosts.的日志——该日志来自 original_dst_cluster.cc 中cleanup()定时器回调,说明集群的过期主机清理机制已生效。
从命名空间内产生流量并观察行为
在另一个终端执行:
sudo ip netns exec ns1 curl -v 173.194.222.106:80流量走向:命名空间内 curl 的目标是173.194.222.106:80→ 经默认路由进入根命名空间 veth0 → 命中 PREROUTING REDIRECT 规则 → 被重定向到 Envoy 的0.0.0.0:10000→ original_dst 过滤器恢复原始目标地址173.194.222.106:80→ HttpConnectionManager 把请求路由到cluster1→ORIGINAL_DST集群按原始目标地址动态建主机并转发。
关于 301 与 503 两种响应
原文档明确指出可能看到两种响应(README.md):
- 最常见的是
301 Moved:173.194.222.106是 Google 的地址段(该网段即 google.com 的 IP 之一),HTTP 服务器会返回 301 重定向,这恰恰证明流量被正确转发到了真实目标; - 少数情况是
503 Service Unavailable:若上游连接超时(该网段内没有存活主机),Envoy 会返回 503。connect_timeout: 6s的设置就是为了降低这种概率,但如果目标地址根本不存在主机,无论超时设多长都会得到 503。
动态主机生命周期日志
流量产生后,每个 Envoy worker 线程都会记录如下日志序列(original_dst_cluster.cc 中addHost与cleanup的日志点):
Adding host 173.194.222.106:80:连接到达后,负载均衡器发现集群中没有该目标地址的主机,于是动态创建 host 加入集群(每个 worker 线程各记录一次);Keeping active address 173.194.222.106:80:周期性清理时,该地址近期仍被负载均衡器选中(used_位为 true),予以保留并复位标记;Removing stale address 173.194.222.106:80:流量停止后经过一个清理周期,该地址既未被选中、也没有被任何连接池持有,于是被移出集群。
整个清理逻辑的核心在cleanup()(original_dst_cluster.cc):保留条件为"主机近期被选过"或"仍被连接池使用",否则移除。used_位的"两轮延迟"设计(先标记、下一轮才删除)用于避免如下竞态:worker 释放主机与重新选中主机之间的瞬间被清理误删。这些日志是验证透明代理链路是否打通的最直观证据。
收尾:清理环境
测试完毕后,用与 setup 完全相同的参数执行清理脚本:
sudo ./configs/original-dst-cluster/netns_cleanup.sh ns1 173.194.222.0/24然后回到运行 Envoy 的终端,按^C停止 Envoy 进程。
与源码的对应关系速查
| 本文涉及的行为 | 源码位置 |
|---|---|
| original_dst 过滤器恢复原始目标地址 | original_dst.cc |
| 集群工厂与扩展配置加载 | original_dst_cluster.h |
| 动态添加主机 / 定时清理过期主机 | original_dst_cluster.cc |
扩展配置字段定义(use_http_header、upstream_port_override等) | original_dst.proto |
| 示例配置 / 建网脚本 / 清理脚本 | proxy_config.yaml、netns_setup.sh、netns_cleanup.sh |
小结
本文以仓库自带示例为主线,走通了 Envoy Original Destination Cluster 的完整链路:从 iptables REDIRECT 透明劫持、original_dst 过滤器恢复原始目标、ORIGINAL_DST集群按需动态建主机,到观察Adding host/Keeping active host/Removing stale host日志验证主机生命周期。这套机制的价值在于:上游地址完全不需要预先配置,Envoy 天然适应"目标动态变化"的透明代理、服务网格 sidecar 出站拦截等场景。需要扩展应用场景时,可进一步研究 proto 中use_http_header、upstream_port_override等扩展字段,让目标地址的决策脱离内核 REDIRECT,改由 HTTP 头或元数据驱动。
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考