一、问题背景
在 Kubernetes 集群中部署的 Java 应用(Spring Boot),受集群出网策略限制,必须通过 HTTP 代理(192.168.0.96:3128)访问外部互联网服务。
在业务运行过程中,部分接口(如行政区划查询)访问外部 HTTP/HTTPS 服务失败,导致业务异常。
涉及技术组件包括:
- Kubernetes Deployment / Pod
- Java 8 + Spring Boot
- Apache HttpClient
- Nacos(gRPC / Netty)
- HTTP Proxy(Squid)
二、问题现象
1. 业务层异常表现
接口调用失败,日志显示连接超时:
org.apache.http.conn.HttpHostConnectException: Connect to restapi.amap.com:443 failed: Operation timed out
异常调用链集中在:
com.lishicloud.ygj.common.utils.HttpClientUtil.doGet org.apache.http.impl.client.CloseableHttpClient.execute
- 关键特征现象
- Pod 内使用
wget / curl访问外部站点 正常 - JVM 启动参数中已正确注入代理配置
- Java 应用中的 Apache HttpClient 请求 仍然直连外网并超时
- 早期曾出现 Nacos 访问内网服务被代理拦截(403),后续已解决
三、排查过程与分析路径
1. JVM 层代理参数验证
通过 Pod 内检查 Java 进程启动参数:
ps -ef | grep java
确认 JVM 已生效的系统属性包括:
-Dhttp.proxyHost=192.168.0.96 -Dhttp.proxyPort=3128 -Dhttps.proxyHost=192.168.0.96 -Dhttps.proxyPort=3128 -Dhttp.nonProxyHosts=...
结论:
JVM 系统属性层面的代理配置完全生效,排除 JVM 参数未注入的问题。
2. 集群出网策略验证
在 Pod 内进行对照测试:
curl https://restapi.amap.com curl -x http://192.168.0.96:3128 https://restapi.amap.com
结果:
- 直连外网请求超时
- 通过代理访问成功
结论:
集群出网策略要求所有外部流量必须走代理
3. Pod 环境变量与 JVM 行为对比
Deployment 中配置了以下环境变量:
http_proxyhttps_proxy
验证结论:
-
wget / curl会自动使用http(s)_proxy -
JVM 不会自动读取 Pod 中的
http_proxy / https_proxy环境变量 -
JVM 只识别:
-Dhttp.proxyHost-Dhttps.proxyHost- 或应用代码中显式配置的代理
➡ Pod 层代理环境变量 ≠ JVM 代理配置
4. Nacos gRPC 问题(阶段性问题)
曾出现异常:
HttpProxyHandler$HttpProxyConnectException => nacos.middleware:9848, status: 403 Forbidden
原因分析:
- Nacos gRPC 基于 Netty
- Netty 会优先读取
http_proxy / https_proxy - 未配置
NO_PROXY,导致内网服务也被错误地代理
处理措施:
- 补齐
NO_PROXY / no_proxy - 覆盖
.svc / .cluster.local / 内网网段 / nacos.middleware
结果:
Nacos 相关异常消失。该问题与 Apache HttpClient 外网访问问题根因不同
四、根因分析
根因一:Pod 中配置的 http(s)_proxy 环境变量不会自动代入 JVM
这是本次问题中最容易被误判的关键点。
事实结论:
-
http_proxy / https_proxy:- 对
curl / wget / Netty / 部分 C 库生效
- 对
-
JVM 不会自动使用这些环境变量
-
JVM 只认:
- 启动参数
-Dhttp.proxyHost/-Dhttps.proxyHost - 或代码级别代理配置
- 启动参数
因此,仅在 Deployment 中配置 http(s)_proxy 不足以保证 Java 应用出网
根因二:Apache HttpClient 默认不使用 JVM 代理系统属性
项目中 HttpClientUtil 构建 HttpClient 的方式为默认模式:
HttpClients.createDefault(); // 或 HttpClients.custom().build();
该模式下:
- Apache HttpClient 不会自动读取 JVM 的代理系统属性
- 即使 JVM 中已存在
-Dhttp.proxyHost,HttpClient 仍然直连
根因三:错误的工程认知假设
错误假设:
“只要在 Pod 或 JVM 里配了代理,所有 Java HTTP 请求都会自动走代理”
事实:
- JVM、Apache HttpClient、Netty、Nacos 各自独立决定是否使用代理
- 代理行为 不会跨层自动继承
五、解决方案
方案一:让 Apache HttpClient 使用 JVM 系统代理
在 HttpClientUtil 中构建 client 时显式启用:
CloseableHttpClient httpClient = HttpClients.custom() .useSystemProperties() .build();
效果:
- Apache HttpClient 读取 JVM 的
http.proxyHost / https.proxyHost - 行为与 JVM 参数保持一致
- 最小侵入、最易维护
方案二:在代码中显式指定代理
适用于 所有外网请求必须强制走代理 的场景。
HttpHost proxy = new HttpHost("192.168.0.96", 3128); RequestConfig config = RequestConfig.custom() .setProxy(proxy) .build(); CloseableHttpClient httpClient = HttpClients.custom() .setDefaultRequestConfig(config) .build();
六、最终结论
- Pod 层
http(s)_proxy环境变量 不会自动代入 JVM - JVM 代理系统属性已正确生效
- Apache HttpClient 默认 不会使用 JVM 代理
- 导致 Java 应用部分 HTTP 请求直连外网并超时
- 问题根因在 应用代码中 HttpClient 的构建方式
七、经验总结与工程建议
1. 明确三层代理作用域
Pod http_proxy | JVM 不继承 | 仅对 curl、wget、Netty 等生效 |
JVM -Dhttp.proxyHost | HttpClient 不自动用 | 需显式开启 |
| 代码级代理配置 | 内建 | 行为最可控 |
2. Kubernetes + Java 出网推荐实践
-
Pod 层:
http_proxy / https_proxyNO_PROXY明确排除内网
-
JVM 层:
- 使用
JAVA_TOOL_OPTIONS注入-Dhttp.proxyHost
- 使用
-
代码层:
- Apache HttpClient 必须
useSystemProperties()或显式setProxy()
- Apache HttpClient 必须
3. 排查方法论沉淀
- 先验证网络路径是否通(curl / wget)
- 再验证 JVM 参数是否真实生效
- 最后审计具体客户端库的代理使用逻辑