news 2026/9/14 3:39:27

Cilium Service Mesh:用 CiliumEnvoyConfig 实现 L7 负载均衡与 URL 重写完整实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cilium Service Mesh:用 CiliumEnvoyConfig 实现 L7 负载均衡与 URL 重写完整实践

Cilium Service Mesh:用 CiliumEnvoyConfig 实现 L7 负载均衡与 URL 重写完整实践

【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium

本文基于 Cilium 官方文档《L7 Load Balancing and URL re-writing》展开,介绍 Cilium Service Mesh 中CiliumEnvoyConfig/CiliumClusterwideEnvoyConfigCRD 的完整实战流程:部署测试工作负载、通过 Hubble 观测流量、添加 L7 网络策略让内置 Envoy 代理介入流量路径,最终用CiliumClusterwideEnvoyConfig定义 Envoy Listener、Route 和 Cluster 资源,在两个后端服务之间做 50/50 加权负载均衡并将路径/foo重写为/。读完本文,你可以掌握在 Cilium 集群中用声明式 xDS 资源实现 L7 流量管理(负载均衡、URL 重写、重试策略、异常检测)的全部操作步骤,并理解 Envoy 资源在 Cilium Agent 中的加载方式与校验限制。

Cilium Service Mesh 定义了CiliumEnvoyConfigCRD,允许用户为嵌入 Cilium Agent 的 Envoy 组件设置配置。本文的示例场景就是:配置一个 Envoy Listener,将流量在两个后端服务echo-service-1echo-service-2之间负载均衡。

部署测试应用

先部署测试工作负载,对应的清单文件位于 examples/kubernetes/servicemesh/envoy/test-application.yaml:

kubectl apply -f examples/kubernetes/servicemesh/envoy/test-application.yaml

该清单包含以下资源:

  • 两个客户端 Deployment:clientclient2(使用quay.io/cilium/alpine-curl镜像,以sleep方式常驻,供kubectl exec发起请求);
  • 两个 Echo 服务:echo-service-1echo-service-2(基于quay.io/cilium/json-mock镜像,监听 8080 端口,返回 JSON 回显;echo-service-1额外带other=echo标签并设置了与 client 的 podAffinity,两个 echo Pod 内还各带一个 sidecar CoreDNS 容器用于 DNS 测试);
  • 两个NodePort类型的 Service:echo-service-1echo-service-2,均暴露 8080/TCP。

查看这些 Pod 的信息:

kubectl get pods --show-labels -o wide

输出示例(kind=client,name=client2,other=client这类标签组合决定了后续策略的选择器):

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS client-7568bc7f86-dlfqr 1/1 Running 0 100s 10.0.1.8 minikube-m02 <none> <none> kind=client,name=client,pod-template-hash=7568bc7f86 client2-8b4c4fd75-xn25d 1/1 Running 0 100s 10.0.1.24 minikube-m02 <none> <none> kind=client,name=client2,other=client,pod-template-hash=8b4c4fd75 echo-service-1-97748874-4sztx 2/2 Running 0 100s 10.0.1.86 minikube-m02 <none> <none> kind=echo,name=echo-service-1,other=echo,pod-template-hash=97748874 echo-service-2-76c584c4bf-p4z4w 2/2 Running 0 100s 10.0.1.16 minikube-m02 <none> <none> kind=echo,name=echo-service-2,pod-template-hash=76c584c4bf

可以注意到:只有client2带有other=client标签——这个标签稍后会用在CiliumNetworkPolicyendpointSelector中,使 L7 策略只作用于client2这个端点。

client2的 Pod 名保存到环境变量中,后文命令会反复用到:

export CLIENT2=$(kubectl get pods -l name=client2 -o jsonpath='{.items[0].metadata.name}')

用 Hubble 开始观测流量

先按照 Hubble 安装文档的说明在集群中启用 Hubble(见 Hubble 安装文档)。然后在另一个终端启用 Hubble 端口转发,并持续观测来自client2Pod 的流量:

kubectl -n kube-system port-forward deployment/hubble-relay 4245:4245 & hubble observe --from-pod $CLIENT2 -f

此时从client2直接向两个后端服务发请求,应该都能各自获得响应(尚未经过代理):

kubectl exec -it $CLIENT2 -- curl -v echo-service-1:8080/ kubectl exec -it $CLIENT2 -- curl -v echo-service-2:8080/

观察 Hubble 输出,你会发现这些 Pod 之间的所有流都标记为to/from-stackto/from-overlayto/from-endpoint——此时还没有任何流量被标记为流向/来自代理(to/from-proxy)。这一观察结果成立的前提是集群中尚不存在其他会影响该流量的 L7 策略。

再验证一个基线事实:向这两个服务的不存在 URL/foo发请求会得到 404 响应:

kubectl exec -it $CLIENT2 -- curl -v echo-service-1:8080/foo kubectl exec -it $CLIENT2 -- curl -v echo-service-2:8080/foo

添加 L7 策略:让 Envoy 代理进入流量路径

添加 L7 策略会使 Envoy 代理被引入该流量的转发路径。需要应用两个策略文件:

kubectl apply -f examples/kubernetes/servicemesh/envoy/client-egress-l7-http.yaml kubectl apply -f examples/kubernetes/servicemesh/envoy/client-egress-only-dns.yaml

第一个文件 client-egress-l7-http.yaml 是对 L7 策略的完整定义,值得逐段解读:

apiVersion: "cilium.io/v2" kind: CiliumNetworkPolicy metadata: name: client-egress-l7-http spec: description: "Allow GET one.one.one.one:80/ and GET <echo>:8080/ from client2" endpointSelector: matchLabels: other: client # 只选中 client2(唯一带 other=client 标签的 Pod) egress: # 允许 GET / 请求流向 echo Pod - toEndpoints: - matchLabels: k8s:kind: echo toPorts: - ports: - port: "8080" protocol: TCP rules: http: - method: "GET" path: "/" # 只允许 GET 方法 + 根路径,其他路径全部丢弃 # 仅允许对 one.one.one.one 的 GET / 请求(80 端口) - toFQDNs: - matchName: "one.one.one.one" toPorts: - ports: - port: "80" protocol: TCP rules: http: - method: "GET" path: "/"

第二个文件 client-egress-only-dns.yaml 允许所有kind: client端点向 kube-system 命名空间中的 kube-dns/coredns 端点发起任意 DNS 查询(matchPattern: "*",53 端口 ANY 协议),它启用的是 DNS 层解析能力,是toFQDNs类策略能工作的前提。

添加 L7 策略后再次发请求:

kubectl exec -it $CLIENT2 -- curl -v echo-service-1:8080/ kubectl exec -it $CLIENT2 -- curl -v echo-service-2:8080/foo

此时 Hubble 输出出现了关键变化:

  • 出现了to-proxy流向——说明流量被重定向到了本地 Envoy 代理,L7 可见性被激活;
  • 输出中出现了第 7 层的 HTTP 协议信息,例如HTTP/1.1 GET http://echo-service-1:8080/

关于 X-Forwarded-For 与 Envoy 头部清理(Sanitizing)

需要注意:Envoy 出于安全考虑会对部分 HTTP 头部做清理(sanitize),例如丢弃入站请求中由客户端伪造的X-Forwarded-For。如果后端服务需要感知真实客户端 IP,可以让 Envoy 信任前序跳数、阻止其重写这些头部。做法是通过 Helm values 设置:

  • envoy.xffNumTrustedHopsL7PolicyIngress:ingress L7 策略 Envoy 监听器信任的跳数;
  • envoy.xffNumTrustedHopsL7PolicyEgress:egress L7 策略 Envoy 监听器信任的跳数。

从 install/kubernetes/values.yaml.tmpl 可见,这两个值默认都是0(不信任任何前序跳数)。语义上的区别是:

  • egress 策略中,前序跳数就是源 Pod 本身;
  • ingress 策略中,前序跳数可能是源 Pod、"egress policy transparent proxy"、Cilium Ingress Controller、Cilium Gateway API,或任何其他 Ingress 代理/基础设施。

是否信任前序跳数存在安全含义(客户端可伪造头部),应结合部署环境自行评估。

验证 L7 策略强制执行

由于策略只允许对/路径的 GET 请求,对其他任何 URL 的请求都会被丢弃。例如:

kubectl exec -it $CLIENT2 -- curl -v echo-service-1:8080/foo

Hubble 输出会显示该 HTTP 请求被丢弃:

Jul 7 08:40:15.076: default/client2-8b4c4fd75-6pgvl:58586 -> default/echo-service-1-97748874-n7758:8080 http-request DROPPED (HTTP/1.1 GET http://echo-service-1:8080/foo)

同时 curl 端会收到403 Forbidden响应。至此验证了:L7 策略由代理路径上的 Envoy 实例强制执行,未匹配的请求在 L7 层被拦截。

添加 Envoy 负载均衡与 URL 重写

现在应用核心配置文件 envoy-traffic-management-test.yaml,它定义了一个CiliumClusterwideEnvoyConfig(集群级 CRD,与命名空间级的CiliumEnvoyConfig对应,CRD 定义见 examples/crds/v2/ciliumclusterwideenvoyconfigs.yaml):

kubectl apply -f examples/kubernetes/servicemesh/envoy/envoy-traffic-management-test.yaml

该配置的完整内容如下(这也是理解 Cilium 如何声明 Envoy xDS 资源的最佳范例):

apiVersion: cilium.io/v2 kind: CiliumClusterwideEnvoyConfig metadata: name: envoy-lb-listener spec: services: - name: echo-service-1 namespace: default - name: echo-service-2 namespace: default resources: # 1) Listener:HTTP 连接管理器,指向名为 lb_route 的路由配置 - "@type": type.googleapis.com/envoy.config.listener.v3.Listener name: envoy-lb-listener 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: envoy-lb-listener rds: route_config_name: lb_route use_remote_address: true skip_xff_append: true http_filters: - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router # 2) RouteConfiguration:按前缀 / 匹配,加权转发并做正则重写 - "@type": type.googleapis.com/envoy.config.route.v3.RouteConfiguration name: lb_route virtual_hosts: - name: "lb_route" domains: [ "*" ] routes: - match: prefix: "/" route: weighted_clusters: clusters: - name: "default/echo-service-1" weight: 50 - name: "default/echo-service-2" weight: 50 retry_policy: retry_on: 5xx num_retries: 3 per_try_timeout: 1s regex_rewrite: pattern: google_re2: { } regex: "^/foo.*$" substitution: "/" # 3) 两个 EDS Cluster:后端由 Cilium 自动同步 - "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster name: "default/echo-service-1" connect_timeout: 5s lb_policy: ROUND_ROBIN type: EDS outlier_detection: split_external_local_origin_errors: true consecutive_local_origin_failure: 2 - "@type": type.googleapis.com/envoy.config.cluster.v3.Cluster name: "default/echo-service-2" connect_timeout: 3s lb_policy: ROUND_ROBIN type: EDS outlier_detection: split_external_local_origin_errors: true consecutive_local_origin_failure: 2

结合 CRD schema(examples/crds/v2/ciliumenvoyconfigs.yaml)可以明确spec中三个关键字段的分工:

字段作用
services指定流量应被重定向到 Envoy Listener 做 L7 负载均衡的 Kubernetes Service。这些服务的后端会自动通过 EDS 同步到 Envoy;listener字段可指定重定向到哪个 Listener(缺省用resources中的第一个 Listener);ports可限定仅重定向哪些前端端口(缺省全部)
backendServices指定后端自动通过 EDS 同步到 Envoy 的 Service,但将这些服务的流量转发到 Listener——即允许自定义 Listener 做加权负载均衡,同时保留 Cilium 原生的 Service 负载均衡,二者并存
resourcesEnvoy xDS 资源列表,仅接受五种类型:envoy.config.listener.v3.Listenerenvoy.config.route.v3.RouteConfigurationenvoy.config.cluster.v3.Clusterenvoy.config.endpoint.v3.ClusterLoadAssignmentenvoy.extensions.transport_sockets.tls.v3.Secret

另外两个与集群级 CRD 相关的细节来自 schema 描述:namespace字段在CiliumEnvoyConfig中默认取 CEC 自身命名空间,而在CiliumClusterwideEnvoyConfig中默认取defaultnodeSelector(标准 K8s 标签选择器)可限定配置生效的节点,为 nil 时作用于所有节点。

注意(官方警告):这些 Envoy 资源完全不被 Kubernetes 校验(schema 使用x-kubernetes-preserve-unknown-fields: true)。Envoy 资源中的任何错误只会在 Cilium Agent 观察这些 CRD 时暴露——kubectl apply会报告成功,而节点本地 Envoy 实例的解析/安装可能已经失败。目前唯一的验证手段是查看 Cilium Agent 日志中的错误与警告(Agent 还会对集群中相互冲突的 Envoy 资源打印警告日志)。此外,Cilium Ingress Controller 会在底层自行配置所需的 Envoy 资源;如果你显式创建 Envoy 资源,务必检查 Agent 日志确认没有冲突。原文见 warning.rst。

配置效果:50/50 负载均衡 + 路径重写

该配置监听发往两个echo-服务之一的流量,并:

  • 在两个后端echo-服务之间做 50/50 加权负载均衡;
  • 将路径/foo重写为/(正则^/foo.*$→ 替换为/);
  • 附带retry_policy:遇到 5xx 时最多重试 3 次,单试超时 1 秒;
  • 两个 Cluster 均配置了outlier_detection(连续 2 次本地发起失败即将端点剔除),并采用ROUND_ROBIN负载均衡与 EDS 端点发现。

由于路径重写,之前被 403/404 挡住的/foo请求现在应该成功:

kubectl exec -it $CLIENT2 -- curl -v echo-service-1:8080/foo

但网络策略仍然会拦截任何没有被重写成/的路径。例如这个请求会导致数据包被丢弃并返回 403:

kubectl exec -it $CLIENT2 -- curl -v echo-service-1:8080/bar

Hubble 输出:

Jul 7 08:43:47.165: default/client2-8b4c4fd75-6pgvl:33376 -> default/echo-service-2-76c584c4bf-874dm:8080 http-request DROPPED (HTTP/1.1 GET http://echo-service-1:8080/bar)

注意这里还有一个微妙但重要的行为:请求虽然发往echo-service-1,但策略判定依据的 HTTP 目标仍然是原始 URIhttp://echo-service-1:8080/bar——重写发生在 Envoy 转发层,策略评估看到的是代理解析出的 L7 请求,二者协作才实现"重写放行、未重写拦截"的效果。

观察负载均衡行为

对同一个后端服务连续发起多次请求,你会在 Hubble 输出中观察到大约一半的请求实际由另一个后端处理,例如:

Jul 7 08:45:25.807: default/client2-8b4c4fd75-6pgvl:37388 -> kube-system/coredns-64897985d-8jhhn:53 L3-L4 REDIRECTED (UDP) Jul 7 08:45:25.807: default/client2-8b4c4fd75-6pgvl:37388 -> kube-system/coredns-64897985d-8jhhn:53 to-proxy FORWARDED (UDP) Jul 7 08:45:25.807: default/client2-8b4c4fd75-6pgvl:37388 -> kube-system/coredns-64897985d-8jhhn:53 dns-request FORWARDED (DNS Query echo-service-1.default.svc.cluster.local. AAAA) Jul 7 08:45:25.807: default/client2-8b4c4fd75-6pgvl:37388 -> kube-system/coredns-64897985d-8jhhn:53 dns-request FORWARDED (DNS Query echo-service-1.default.svc.cluster.local. A) Jul 7 08:45:25.808: default/client2-8b4c4fd75-6pgvl:57942 -> default/echo-service-1:8080 none REDIRECTED (TCP Flags: SYN) Jul 7 08:45:25.808: default/client2-8b4c4fd75-6pgvl:57942 -> default/echo-service-1:8080 to-proxy FORWARDED (TCP Flags: SYN) Jul 7 08:45:25.808: default/client2-8b4c4fd75-6pgvl:57942 -> default/echo-service-1:8080 to-proxy FORWARDED (TCP Flags: ACK) Jul 7 08:45:25.808: default/client2-8b4c4fd75-6pgvl:57942 -> default/echo-service-1:8080 to-proxy FORWARDED (TCP Flags: ACK, PSH) Jul 7 08:45:25.809: default/client2-8b4c4fd75-6pgvl:57942 -> default/echo-service-2-76c584c4bf-874dm:8080 L3-L4 REDIRECTED (TCP Flags: SYN) Jul 7 08:45:25.809: default/client2-8b4c4fd75-6pgvl:57942 -> default/echo-service-2-76c584c4bf-874dm:8080 to-endpoint FORWARDED (TCP Flags: SYN) Jul 7 08:45:25.809: default/client2-8b4c4fd75-6pgvl:57942 -> default/echo-service-2-76c584c4bf-874dm:8080 to-endpoint FORWARDED (TCP Flags: ACK) Jul 7 08:45:25.809: default/client2-8b4c4fd75-6pgvl:57942 -> default/echo-service-2-76c584c4bf-874dm:8080 to-endpoint FORWARDED (TCP Flags: ACK, PSH) Jul 7 08:45:25.809: default/client2-8b4c4fd75-6pgvl:57942 -> default/echo-service-2-76c584c4bf-874dm:8080 http-request FORWARDED (HTTP/1.1 GET http://echo-service-1:8080/) Jul 7 08:45:25.811: default/client2-8b4c4fd75-6pgvl:57942 -> default/echo-service-1:8080 to-proxy FORWARDED (TCP Flags: ACK, FIN) Jul 7 08:45:25.811: default/client2-8b4c4fd75-6pgvl:57942 -> default/echo-service-1:8080 to-proxy FORWARDED (TCP Flags: ACK) Jul 7 08:45:30.811: default/client2-8b4c4fd75-6pgvl:57942 -> default/echo-service-2-76c584c4bf-874dm:8080 to-endpoint FORWARDED (TCP Flags: ACK, FIN)

这段输出清晰地还原了整条数据路径:客户端 SYN 被REDIRECTED到本地代理(to-proxy),Envoy 通过weighted_clusters把连接交给echo-service-2的端点(to-endpoint),L7 请求http-request FORWARDED中显示的 URI 仍为重写前的http://echo-service-1:8080/。这就是"客户端只看到一个 Service,实际流量被 Envoy 按权重分摊到两个 Service"的完整证据链。

小结:这套机制的关键点

  1. 两层职责分离:L7 网络策略(CiliumNetworkPolicyrules.http)负责"允许/丢弃",CiliumEnvoyConfig负责"如何转发"(加权、重写、重试、剔除异常端点)——二者叠加生效,路径重写不能绕过策略评估。
  2. 声明式 xDS:Cilium 把 Envoy 的 Listener/Route/Cluster 作为 CRD 字段直接暴露(spec.resources),后端端点通过 EDS 自动同步,无需手写ClusterLoadAssignment,也无需独立部署 Envoy 控制面。
  3. 无 schema 校验的现实约束resources字段不经过 K8s 校验,配置正确性最终靠 Cilium Agent 日志确认,这是运维该特性时最重要的检查动作。
  4. 可观测性闭环:Hubble 的to-proxy/http-request事件让"代理是否介入、L7 内容是什么、策略在哪一层丢弃了请求"全部可视,是调试 L7 流量管理问题的第一入口。

相关文件索引:

  • 文档源文件:envoy-traffic-management.rst、warning.rst
  • 示例清单:test-application.yaml、client-egress-l7-http.yaml、client-egress-only-dns.yaml、envoy-traffic-management-test.yaml
  • CRD 定义:ciliumenvoyconfigs.yaml、ciliumclusterwideenvoyconfigs.yaml
  • 相关主题(同一 servicemesh 目录下的延伸阅读):Envoy 负载均衡、L7 流量管理、流量染色/灰度

【免费下载链接】ciliumeBPF-based Networking, Security, and Observability项目地址: https://gitcode.com/GitHub_Trending/ci/cilium

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 3:39:06

MATLAB点云建模全攻略:从散点到可交付模型

简介&#xff1a;这一MATLAB三维点云建模源码包&#xff0c;面向计算机图形学、机器视觉领域的学生与研究者&#xff0c;围绕三维点云数据的预处理、特征匹配、基础矩阵估计、三角化与三维结构恢复等核心环节提供可运行的实现&#xff0c;可用于快速搭建点云建模与三维重建实验…

作者头像 李华
网站建设 2026/9/14 3:38:39

WordPress子主题样式加载与覆盖实战:RiPro小八子v8.1文件族解析

简介&#xff1a;这款RiPro子主题VV8.1开心版&#xff08;别名“小八子主题”&#xff09;是一套专门为RiPro主主题设计的WordPress子主题包&#xff0c;主要面向资源下载类网站站长和前端开发人员&#xff0c;用于在不改动主主题的情况下完成界面美化、布局调整与功能扩展。压…

作者头像 李华
网站建设 2026/9/14 3:38:25

RS编码原理与MATLAB实现:从GF(2^m)到参数设计

简介&#xff1a;针对Reed-Solomon编码的MATLAB实现与学习资源包&#xff0c;适合通信工程、数据存储方向的学生及开发者。RS编码广泛用于光盘、磁盘阵列、深空通信等场景&#xff0c;本包通过源码和文档帮助读者从伽罗华域运算到编码解码流程建立完整认识。压缩包共71个文件&a…

作者头像 李华
网站建设 2026/9/14 3:38:24

共享充电桩小程序开发:订单状态机与计费联动实战解析

简介&#xff1a;面向计算机相关专业毕业设计的共享充电桩微信小程序项目&#xff0c;是一套98分的高分毕设系统&#xff0c;适合正在准备论文与答辩的学生、需要项目实战练习的开发者&#xff0c;也可直接用于课程设计或期末大作业。项目覆盖充电桩查找、使用、计费及后台管理…

作者头像 李华
网站建设 2026/9/14 3:37:08

Windows下直连齐信开通宝实现OBD总里程读取实战

1. 项目概述&#xff1a;这不是“调用OBD”&#xff0c;而是打通Windows与汽车诊断协议的实操链路“一文读懂&#xff1a;如何在Windows下调用齐信开通宝OBD的开源代码&#xff1f;”——这个标题里藏着三个关键认知陷阱&#xff0c;我得先帮你踩平。第一&#xff0c;“调用”这…

作者头像 李华
网站建设 2026/9/14 3:37:06

PMSM无感FOC实战:从Simulink仿真到STM32调试的完整复盘

去年冬天我把手里一套PMSM无感FOC项目从V1.0推到重来&#xff0c;重新搭了控制框架、重画了驱动板、把转子初始位置检测和无感启动策略全部换掉。这个版本我在内部代号叫FOC-P2-DRAFT_V2.0&#xff0c;P2是第二阶段的缩写&#xff0c;DRAFT说明它还带着草稿性质&#xff0c;但到…

作者头像 李华