Kubernetes 出站流量与代理:sidecar、egress 和 NO_PROXY
习惯传统服务器的工程师进入 Kubernetes 后,会做他一直以来都在做的事:把 HTTP_PROXY 导出到环境里,重启进程,然后等着所有出站流量都走代理。有时这确实有效。但更常见的情况是,这会弄坏半个集群,而另一半流量仍然绕过代理直接访问互联网。然后最精彩的部分开始了:为什么有些 Pod 识别了环境变量,有些没有?为什么服务之间突然无法互相访问?为什么 healthcheck 突然走了不在信任列表里的代理服务器?
本文深入剖析 Kubernetes 集群中出站流量的真实机制,以及代理应该正确嵌入在哪里。我们不会复述环境变量的基础配置——这些你早就知道了。我们要谈的是 Kubernetes 的特殊性:那些习以为常的做法在这里会产生意想不到的效果。所有示例都来自真实的清单文件片段,代理基础设施选用 Proxeon(proxeon.net)。
基础:为什么集群里的情况完全不同
从根本说起。在传统服务器上,出站流量的概念很简单:一个网络接口,一张路由表,一组系统环境变量,几乎启动的所有进程都会从父 shell 继承它们。在 profile 里导出变量,所有用户进程都能看到。
Kubernetes 里没有这个统一入口。这里有 Pod——最小部署单元,里面运行一个或多个容器。Pod 有自己的网络命名空间、自己的 IP 地址、由清单文件定义的自己的环境变量集合。根本不存在一个可以继承的 shell。环境变量只有在 Pod 规范或镜像中显式声明时,才会出现在容器里。
什么是 Pod 出站流量
当容器访问外部地址时,数据包会走一条很长的路。首先从容器网络命名空间通过虚拟接口发出,然后进入节点的网络栈,由 CNI 插件(负责集群网络的组件)处理。接着 iptables 或 eBPF 规则介入,SNAT(源地址替换)机制生效,最后数据包通过节点网络接口进入外部世界。
关键在于:从外部服务看,请求不是来自 Pod 地址,而是来自集群节点地址。Pod 被地址转换隐藏了。这是新手尝试按 IP 白名单配置访问时遇到的第一个意外:他们把 Pod 地址加到白名单,但流量却来自完全不同的地址。
两种出站流量
从一开始就要区分两种根本不同的流量:
- 东西向——集群内部服务之间的流量。一个 Pod 通过 Service、ClusterIP 或 my-service.namespace.svc.cluster.local 形式的 DNS 名称访问另一个。
- 南北向——向外部的流量,访问外部 API、数据库、合作伙伴服务、对象存储。
几乎只有南北向流量需要代理。而最常见、最痛苦的错误,是代理配置不当,意外捕获了东西向流量,切断了内部通信。正因如此,这里的排除列表比任何地方都重要。稍后详谈。
深入剖析:代理所在的三个层级
要把代理嵌入 Pod 的出站路径,正好有三个架构层级。每个层级各司其职,各有代价。我们诚实地把三者讲清楚,包括优缺点。
层级 1:容器本身
也就是容器内的应用自己知道代理的存在。它读取 HTTP_PROXY 和 HTTPS_PROXY 环境变量,或者在自身配置里写死代理地址,将 HTTP 请求导向代理。代理逻辑内置在应用自己的客户端库里。
优点。起步时极简单,不需要向集群添加任何东西,不需要额外组件。可以精确到具体 Pod 控制:你清楚知道哪个应用访问哪里。
缺点。每个应用都必须支持读取这些变量,而远非所有应用都支持。配置分散在几十个清单文件里。更新代理地址意味着要遍历所有 Deployment。很容易漏掉某个服务,它就直接出去了。没有集中化策略。
层级 2:sidecar 容器
这里在同一个 Pod 中,与主容器一起启动第二个容器——sidecar。它拦截出站流量并转发给代理。应用甚至不需要知道代理存在:它照常发请求,sidecar 悄悄代理它们。Istio 和 Linkerd 这类服务网格就是这么工作的,按同样原理也可以放一个轻量本地代理 agent。
优点。对应用透明。通过 Pod 模板实现统一策略。可以在代理之上增加可观测性:指标、追踪、重试、超时。Sidecar 在 Pod 内隔离,与它共享生命周期。
缺点。额外开销:每个 Pod 现在有两个容器,意味着更多内存和 CPU。调试变复杂——链路中多了一环。容器启动顺序很重要:如果应用比 sidecar 先启动,最初的请求可能失败。Kubernetes 1.28+ 用 原生 sidecar 容器(作为 init 容器但 restartPolicy 为 Always)解决了这个问题。
层级 3:集群 egress 网关
这是一个专用节点或 Pod,集群所有出站流量被强制导向它。数据包通过 CNI 路由到 egress 网关,再由它与外部代理通信,或者它本身就是具有稳定源 IP 的出口点。
优点。整个集群的单一控制点。稳定、可预测的源地址,方便加入外部合作伙伴的白名单。策略在一个地方修改。应用什么都不用知道。
缺点。Egress 网关成为关键单点:挂了,整个南北向就断了。需要冗余和监控。配置更复杂,需要 CNI 支持。粒度更低:没有额外规则,很难对不同应用设置不同策略。
如何选择层级
经验给出的实用指引:小型项目只有几个服务需要外部代理——用容器层级。中型集群有几十个服务且要求统一策略——用 sidecar。大型基础设施,外部合作伙伴要求固定源 IP 并审计所有流量——用 egress 网关。通常还会组合使用:egress 网关做基础控制,加上环境变量通过 Proxeon 对个别 Pod 做精细调整。
清单文件中的环境变量与 NO_PROXY 的作用
现在进入最被低估的细节。清单文件里 HTTP_PROXY、HTTPS_PROXY 和 NO_PROXY 通过容器规范的 env 块设置。经典命名习惯会同时用大写和小写,因为有些库只读小写版本。
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
spec:
replicas: 2
selector:
matchLabels:
app: worker
template:
metadata:
labels:
app: worker
spec:
containers:
- name: app
image: registry.example.com/worker:1.4.0
env:
- name: HTTP_PROXY
value: "http://gate.proxeon.net:8080"
- name: HTTPS_PROXY
value: "http://gate.proxeon.net:8080"
- name: NO_PROXY
value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.svc.cluster.local,.cluster.local,kubernetes.default"
- name: http_proxy
value: "http://gate.proxeon.net:8080"
- name: https_proxy
value: "http://gate.proxeon.net:8080"
- name: no_proxy
value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.svc.cluster.local,.cluster.local,kubernetes.default"为什么 NO_PROXY 配置不当会让集群崩溃
问题本质在这里。一旦你声明了 HTTP_PROXY,应用的 HTTP 客户端就会把所有请求都导向代理,包括访问集群内相邻服务的请求。而内部服务只在集群网络内可达——外部代理服务器物理上够不到它们。结果:对 orders.default.svc.cluster.local 的请求发到了外部代理,代理尝试解析这个名字,失败,返回错误。内部通信瞬间断裂。
NO_PROXY 是排除列表——需要绕过代理直接发送的地址和域名。普通服务器上通常放 localhost,也许还有几个内部子网。在 Kubernetes 里这个列表变得至关重要,因为它必须覆盖集群的全部内部拓扑。漏掉一个后缀,部分服务间流量就跑到外部代理去了。
集群 NO_PROXY 必须包含什么
- localhost 和 127.0.0.1——Pod 内部的访问。
- Pod CIDR 网段——Pod 地址所在子网,例如 10.0.0.0/8 或你集群更精确的网段。
- Service CIDR 网段——Service 的 ClusterIP 子网,常见为 10.96.0.0/12。
- 集群 DNS 后缀——.svc、.svc.cluster.local、.cluster.local。它们覆盖所有内部 DNS 服务名。
- kubernetes.default——API 服务器名称,应用和 agent 会访问它。
- 云元数据地址——169.254.169.254,如果在云环境中,确保元数据查询不走代理。
NO_PROXY 语法细节
这里藏着大量不明显的坑,连资深工程师都会踩。
第一,不同库对条目的解释不同。有些认为 .cluster.local 带前导点表示后缀,匹配所有子域。有些要求 cluster.local 不带点。实践做法:两种都写,带点和不带点,覆盖最多客户端。
第二,CIDR 表示法支持并不通用。Go 库理解 10.0.0.0/8,但其他语言的一些老版本客户端不支持,需要单独地址或其他格式的范围。测试你具体技术栈的行为。
第三,端口。NO_PROXY 条目如果不带端口,通常适用于该主机的任意端口。但有些客户端严格按端口匹配。内部服务使用非标准端口时,这点需要验证。
来自实践的洞察:代理上线后内部通信故障,十有八九是因为 NO_PROXY 不完整。为你的集群一次性制定一份标准列表,放进共享 ConfigMap,在所有 Deployment 中复用。这能省下几十小时的调试时间。
哪些东西不会读取环境变量
“我设了 HTTP_PROXY,所以所有流量都走代理了”——这种朴素信念在集群里错得离谱。有一整类组件直接忽略这些变量。必须提前了解。
kubelet 和系统组件
容器的环境变量只对该容器内的进程可见。kubelet——负责拉取镜像、启动容器、与 API 服务器通信的节点 agent——运行在节点层级,不是 Pod 层级。它不读清单文件里的 env。如果想让 kubelet 通过代理拉取镜像,要在 kubelet 的 systemd 服务或容器运行时配置里设置,而不是在 Pod 规范里。
# /etc/systemd/system/kubelet.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://gate.proxeon.net:8080"
Environment="HTTPS_PROXY=http://gate.proxeon.net:8080"
Environment="NO_PROXY=localhost,127.0.0.1,10.0.0.0/8,.svc,.cluster.local"容器运行时也一样——containerd 或 CRI-O。镜像拉取走它们自己的网络上下文。镜像仓库的代理配置在运行时配置里,是独立于 Pod 的另一回事。
自带配置的镜像
许多流行镜像内置网络配置,会覆盖环境变量。例如镜像内的包管理器、Web 服务器或代理工具可能读取自己的配置文件,而不是环境。如果容器内的应用使用带有显式连接参数的配置文件,你的 env 变量它根本不会注意到。
单独一类是网络客户端在读取环境之前就初始化,或启动时缓存配置的应用。不重启进程,改变量没用。
个别 SDK 和语言运行时
这是最阴险的一类。对 HTTP_PROXY 的支持是约定,不是标准。有人遵守,有人不。
- Go。标准 http.Client 通过 ProxyFromEnvironment 尊重变量。但如果代码显式设置 Proxy 为 nil 创建 transport,变量就被忽略。
- Python。requests 库默认读环境。但底层 socket、部分 gRPC 客户端和异步库不读。
- Java。JVM 使用自己的系统属性 http.proxyHost 和 https.proxyHost,默认完全不读环境变量。需要通过 JAVA_TOOL_OPTIONS 传递。
- Node.js。内置 http 模块不读环境变量。需要第三方 agent 来读取。
- gRPC。部分实现读专用变量 grpc_proxy,而非标准变量。
结论很简单:不能假设声明了变量,所有流量就会走它。每个运行时都要单独验证。例如 JVM 的清单文件是这样的:
env:
- name: JAVA_TOOL_OPTIONS
value: "-Dhttp.proxyHost=gate.proxeon.net -Dhttp.proxyPort=8080 -Dhttps.proxyHost=gate.proxeon.net -Dhttps.proxyPort=8080 -Dhttp.nonProxyHosts=localhost|127.0.0.1|*.svc|*.cluster.local"注意:JVM 的排除分隔符是竖线,不是逗号,模板用星号。这是机械复制 NO_PROXY 的又一个坑。
密钥:代理凭据用 Secret,不要放清单文件
如果代理需要认证,就会面临凭据存储问题。诱惑很大:直接写进 env 值里的 URL。不能这么做,原因如下。
Deployment 清单文件几乎总是放在版本控制里。env 里明文密码意味着凭据泄露到 Git 历史,所有有仓库访问权限的人都能看到。此外,env 值对任何能执行 kubectl describe pod 的人可见。这直接违反最小权限原则。
正确做法是使用 Secret 对象。它单独存储敏感数据,可以通过 RBAC 限制访问,并在 etcd 存储中启用加密。
apiVersion: v1
kind: Secret
metadata:
name: proxeon-credentials
type: Opaque
stringData:
proxy-user: my_account
proxy-pass: s3cr3t_token_value然后通过 secretKeyRef 把 Secret 中的值接入容器环境变量,同时让代理 URL 本身不在清单文件里暴露凭据:
env:
- name: PROXY_USER
valueFrom:
secretKeyRef:
name: proxeon-credentials
key: proxy-user
- name: PROXY_PASS
valueFrom:
secretKeyRef:
name: proxeon-credentials
key: proxy-pass
- name: HTTPS_PROXY
value: "http://$(PROXY_USER):$(PROXY_PASS)@gate.proxeon.net:8080"
- name: NO_PROXY
value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.cluster.local"Kubernetes 通过美元符号加括号的语法,从先前声明的变量插值。这样可以在运行时组装带凭据的 URL,不用把密码直接放进清单文本。注意:组装出的 HTTPS_PROXY 值仍然会在运行中容器的 env 里可见,所以还要限制谁可以对这些 Pod 执行 exec 和 describe。
Secret 使用良好实践
- 启用 etcd 静态加密——否则 Secret 只以 base64 存储,算不上保护。
- 通过 RBAC 限制 Secret 访问:Pod 只能看到自己的 Secret。
- 定期轮换代理凭据,并自动化轮换后重启 Pod。
- 考虑外部密钥管理器,通过 CSI 驱动接入,让凭据根本不进 etcd。
- 永远不要记录组装后的代理 URL——密码会泄露到日志系统。
Sidecar 方案:何时值得用,能带来什么
我们已经把 sidecar 列为三个层级之一。现在作为独立策略详细展开,因为如果你愿意为资源买单,它是最灵活的工具。
何时 sidecar 值得用
- 应用不支持读取 HTTP_PROXY,改代码不可能或太贵。
- 需要统一代理策略,不修改每个应用。
- 需要透明流量拦截,包括非 HTTP 协议。
- 需要可观测性:出站连接指标、追踪、审计。
- 需要在连接层面做重试、超时、熔断策略。
Sidecar 除了代理还能带来什么
真正的价值在这里。Sidecar 不只是包转发器。配置得当,它变成 Pod 所有出站流量的控制和观测点。
- 指标。发了多少外部请求、目标主机、延迟、错误数。没有 sidecar,这些数据得在每个应用里单独收集。
- 追踪。与入站请求关联的出站调用分布式追踪。
- 可靠性策略。幂等请求自动重试、超时、并发连接限制。
- 统一 TLS。Sidecar 可集中终止和建立安全连接。
- 审计与合规。应用访问目标的完整日志——合规不可或缺。
带 sidecar 代理的 Pod 示例
下面是简化模板,主容器把出站 HTTP 请求发到本地 sidecar,再由它通过 Proxeon 代理转发。对应用来说代理地址是 localhost,如果应用直接访问相邻服务,这自动把服务间流量排除在外部代理之外。
apiVersion: v1
kind: Pod
metadata:
name: app-with-proxy-sidecar
labels:
app: billing
spec:
containers:
- name: app
image: registry.example.com/billing:2.1.0
env:
- name: HTTP_PROXY
value: "http://127.0.0.1:3128"
- name: HTTPS_PROXY
value: "http://127.0.0.1:3128"
- name: NO_PROXY
value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.cluster.local"
- name: egress-proxy
image: registry.example.com/egress-agent:1.0.0
ports:
- containerPort: 3128
env:
- name: UPSTREAM_PROXY
value: "http://gate.proxeon.net:8080"
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi原生 sidecar 与启动顺序
经典 sidecar 的痛点是启动竞争。如果主应用先启动,在 sidecar 能接受连接之前发出第一个出站请求,请求就失败。从 Kubernetes 1.28 起支持原生 sidecar 容器:在 initContainers 块中声明,restartPolicy 为 Always,保证在主容器之前启动并保持存活。这干净地解决了竞争问题,不用延迟或启动重试之类的补丁。
spec:
initContainers:
- name: egress-proxy
image: registry.example.com/egress-agent:1.0.0
restartPolicy: Always
ports:
- containerPort: 3128Egress 网关:为集群提供稳定源地址
第三个层级值得单独讨论,因为它解决的正是通常让人们走进集群代理的原因:可预测的源 IP。
为什么需要固定源 IP
外部合作伙伴、支付网关、数据供应商经常按地址白名单工作。他们说:只接受来自这些 IP 的请求。在普通集群里,Pod 的源地址是它当前所在节点的地址。节点很多,会扩缩容、替换,自动伸缩时新增。地址不可预测。合作伙伴无法把整个节点池加进白名单,何况它还在变。
Egress 网关解决这个问题:所有出站流量汇聚到一个固定地址的点。合作伙伴把一两个稳定 IP 加入白名单——就搞定了。使用 Proxeon 作为出口点时,你得到稳定的外部地址,一次性交给合作伙伴。
流量如何路由到 egress 网关
机制取决于 CNI。有些 CNI 插件支持 egress 策略对象,描述:来自带某些标签的 Pod、向外走的流量应该通过某个节点或地址出去。插件自动配置相应的 SNAT 和路由规则。概念上像这样:
apiVersion: policy.example.io/v1
kind: EgressPolicy
metadata:
name: billing-egress
spec:
selector:
matchLabels:
egress: proxeon
egressIP: 203.0.113.10
destinationCIDRs:
- 0.0.0.0/0具体语法因 CNI 而异,请查阅你插件的文档。理念不变:标记流量经过网关的 Pod,指定稳定出口地址。
Egress 网关的可靠性
既然是单一节点,也是单点故障。生存法则:
- 冗余:至少两个网关节点,自动切换。
- 监控:每分钟单独发一个合成请求到外部,检测出口可用性。
- 关注吞吐量:所有南北向流量都走网关,瓶颈会影响全局。
- 分离策略:关键流量和后台流量最好走不同路径,避免后台负载干扰重要业务。
集群出站流量诊断
出问题时——一定会出——需要一套检查工具。这里汇集实用命令和方法。
第 1 步:查明 Pod 的真实源 IP
首先检查:Pod 对外部世界显示的是什么地址。启动临时调试容器或 exec 进现有 Pod,访问返回你公网地址的服务。
kubectl run nettest --rm -it --image=registry.example.com/nettools:1.0 --restart=Never -- sh
# 容器内
curl -s https://api.ipify.org
curl -s -x http://gate.proxeon.net:8080 https://api.ipify.org第一个请求显示不走代理的地址,第二个显式走代理。如果两者相同,而你期待不同,说明代理没生效。如果第二个返回预期的 Proxeon 地址,代理是工作的,问题在应用配置。
第 2 步:检查应用是否看到变量
kubectl exec deploy/worker -c app -- env | grep -i proxy如果输出为空——变量没到容器。检查清单文件,并确认修改后 Pod 已重建。提醒:改 env 需要重建 Pod,不会热加载。
第 3 步:检查内部 DNS 解析
很多错误伪装成代理问题,实际是 DNS。检查内部名和外部名的解析:
kubectl exec deploy/worker -c app -- nslookup orders.default.svc.cluster.local
kubectl exec deploy/worker -c app -- nslookup api.external-partner.com如果内部名不解析——问题在 CoreDNS,或者因为 NO_PROXY 缺少后缀,请求发到了外部代理。这是排除列表不完整的直接证据。
第 4 步:去哪里看失败
- 应用日志。找连接错误、超时、代理认证失败(通常是 407)。
- Sidecar 日志。如果有,能看到它接受了哪些请求、转发到哪里。
- Egress 网关指标。网关失败和延迟增长。
- Pod 事件。kubectl describe pod 显示启动问题,包括 sidecar 竞争。
- NetworkPolicy。检查网络策略是否阻断出站流量——静默失败的常见原因。
典型问题特征
- 代理返回 407——凭据错误或缺失。检查 Secret。
- 启用代理后访问内部服务超时——NO_PROXY 不完整。
- 重复请求源 IP 不同——流量没走 egress 网关,走的是普通节点 SNAT。
- 启动后最初请求失败,之后正常——sidecar 竞争,迁移到原生 sidecar。
- Java 应用忽略代理——忘了 JAVA_TOOL_OPTIONS 和系统属性。
应避免的典型错误
把最常踩的坑集中在一起。在事故之前而不是之后对照检查。
NO_PROXY 不完整
绝对第一。忘了 Service CIDR,忘了 .svc 后缀,没考虑云元数据地址——然后得到一连串莫名其妙的失败。始终从你集群的完整标准列表出发。
代理密码在清单文件中明文
泄露到 Git 和 describe 输出。只用 Secret,只限访问。
假设所有运行时都尊重变量
JVM、Node.js、部分 gRPC 客户端忽略 HTTP_PROXY。每个技术栈单独验证,用原生方式配置。
忽略 kubelet 和运行时
镜像拉取代理在节点层级配置,不是 Pod 层级。如果只配了清单文件里的 env,镜像拉不下来别惊讶。
Sidecar 启动竞争
应用先启动,头几个请求失败。解决方案是原生 sidecar 容器。
Egress 网关无冗余
没有复制的单点故障瘫痪所有出站流量。冗余并监控。
混用 CIDR 格式
给不懂 CIDR 的客户端写了 10.0.0.0/8。确认你的库支持哪种格式,并提供替代方案。
改 env 后未重建 Pod
在 Deployment 里改了变量,但旧 Pod 继续用旧值直到重启。确保滚动更新。
忘了变量小写
有些库只读小写 http_proxy。两种大小写都写。
工具与资源
处理集群出站流量时手边该有的东西。
诊断类
- kubectl exec 和 kubectl debug——从 Pod 内部检查的基础。
- 临时调试容器——无需重新构建镜像,就能给运行中 Pod 挂上一套网络工具。
- 带网络工具的镜像——curl、dig、nslookup、traceroute 打包成一个容器,快速检查。
- 公网 IP 回显服务——检查真实源地址。
基础设施类
- 标准 NO_PROXY 的 ConfigMap——所有 Deployment 的唯一事实来源。
- Secret 和外部密钥管理器——用于代理凭据。
- 服务网格——如果需要开箱即用的 sidecar 可观测性。
- CNI egress 策略——用于路由到网关。
- Proxeon(proxeon.net)——提供稳定源地址和认证访问的代理基础设施。
监控类
- 来自 sidecar 或 egress 网关的出站连接指标。
- 从集群到外部地址的合成可用性检查。
- 407 代码和出站请求超时增长的告警。
- 按目标主机分布出站流量的仪表盘。
案例与结果
分析三个概括场景,展示层级选择如何影响结果。
案例 1:支付集成与 IP 白名单
团队集成外部支付网关,它只接受约定地址的请求。起初尝试容器层级环境变量——结果发现自动伸缩时 Pod 迁到有不同地址的新节点,源 IP 仍然是节点地址,不是代理。部分请求开始被拒绝。
解决方案:把支付服务通过 Proxeon 转到具有固定源地址的 egress。合作伙伴把一个 IP 加入白名单。因未知地址导致的拒绝消失了。额外好处是支付网关所有访问的集中审计日志。
案例 2:代理上线后服务间通信断裂
公司通过共享模板一次性给所有 Deployment 加了 HTTP_PROXY。几分钟后错误涌现:服务互相看不见了。内部调用发到外部代理,代理无法解析它们。
诊断花了一些时间,直到检查 Pod 内部解析,发现 .svc.cluster.local 名称走了代理。原因:NO_PROXY 只含 localhost。制定包含 Pod CIDR、Service CIDR 和所有 DNS 后缀的完整标准列表,放进 ConfigMap,接入所有 Pod。内部通信恢复。从此标准 NO_PROXY 成为 Deployment 模板的必备部分。
案例 3:通过 sidecar 实现出站流量可观测性
安全要求:知道每个应用访问外部的确切目标,有完整日志。应用语言各异,部分不会读 HTTP_PROXY。改几十个服务的代码太贵。
在 Pod 模板中引入 sidecar 代理。应用把出站流量发给本地 agent,它通过 Proxeon 代理并记录指标:目标主机、延迟、响应码。有了按服务查看出站访问的仪表盘。还在 sidecar 层配置了超时和重试,减少了外部 API 短暂不可用时的级联故障。代价是 sidecar 的额外资源,但可观测性和可靠性的收益证明了它的价值。
FAQ:工程师常见问题
为什么访问相邻 Pod 的流量走了外部代理,明明是内部地址?
因为 HTTP 客户端不知道这是内部地址。它看到名称或地址,如果不属于 NO_PROXY,就按规则把请求发给代理。客户端自己区分不了东西向和南北向——排除列表替它做这件事。把内部后缀和子网加入 NO_PROXY。
拉取镜像也需要配置代理吗?
需要,但不是通过 Pod 的 env。镜像拉取由节点层级的容器运行时和 kubelet 执行。仓库代理配置在运行时配置或 kubelet 的 systemd unit 里。容器环境变量完全不影响它。
要稳定源 IP,sidecar 和 egress 网关哪个更重要?
Egress 网关。Sidecar 代理单个 Pod 的流量,但源地址仍取决于连接后续走向。要保证外部合作伙伴接受的固定地址,需要 egress 网关,或者通过具有固定地址的外部出口点,例如 Proxeon。
为什么 Java 应用忽略 HTTP_PROXY?
JVM 出于历史原因不读代理环境变量。它使用自己的系统属性 http.proxyHost、https.proxyHost 和排除项 http.nonProxyHosts。通过 JAVA_TOOL_OPTIONS 传递。记住:JVM 的排除分隔符是竖线,模板用星号,不是 CIDR。
如何安全存储代理密码?
放在 Secret 对象里,通过 RBAC 限制访问,启用 etcd 加密。通过 secretKeyRef 接入值。不要把密码明文写进清单文件,否则会泄露到 Git 和 describe 输出。更高要求时通过 CSI 使用外部密钥管理器。
改了环境变量,行为没变——为什么?
环境变量在容器启动时固定。Pod 未重建前,一直用旧值。更新 Deployment 让 Pod 滚动更新。同时确认应用确实读取环境,而不是初始化时缓存配置。
怎么知道我用的库需要什么 NO_PROXY 格式?
经验法:配好代理,从 Pod 内访问内部名,看请求是走代理还是直连。如果走了代理——格式没被识别。尝试带前导点和不带点的变体,用单独地址替代 CIDR,检查变量名大小写。具体客户端的文档是最好的参考。
为了代理是否应该总是用服务网格?
不该。服务网格是带可观测性和策略的强大工具,但带来明显的开销和运维复杂性。如果任务只是把出站流量导向代理,轻量 sidecar agent 或 egress 网关更便宜。当你确实需要它全部能力时再上网格。
完全非 HTTP 的流量怎么办?
HTTP_PROXY 变量只对尊重它们的 HTTP 和 HTTPS 客户端有效。任意 TCP 连接需要在 sidecar 或 egress 网关层做透明拦截,配上相应路由规则。这里环境变量无能为力。
能对不同 Pod 设置不同代理策略吗?
可以。容器层级——在不同 Deployment 用不同 env。Egress 层级——通过 egress 策略中的标签选择器,把标记的 Pod 导向所需出口。组合使用带来灵活性:基础出口走网关,加个别服务的独立配置。
结语:整理上线清单
我们从为什么“像普通服务器那样配置”在集群里不工作,走到代理嵌入的三个层级、NO_PROXY 细节、Secret、sidecar、egress 网关和诊断。主要结论:在 Kubernetes 里,出站流量不是一个变量,而是一个分层系统,最重要的不是如何启用代理,而是如何不破坏内部通信。
整理最终上线清单,每次实施时放在眼前。
集群代理上线清单
- 确定层级。有意识地根据具体任务选择容器、sidecar 或 egress 网关。
- 制定完整 NO_PROXY。