Repository navigation
HTTP_PROXY/HTTPS_PROXY ignored by load_kube_config(): proxy scheme resolved before the host is known #2681
Description
Split out of #2679 at @yliaog's request — that issue covered the kubeconfig proxy-url field, fixed in #2680. This one is about the environment variables.
What happened (please include outputs or screenshots):
HTTP_PROXY and HTTPS_PROXY are ignored when the client configuration is built by load_kube_config(). Configuration.proxy stays None and requests go direct.
Only ALL_PROXY works. That makes the commonly suggested workaround for proxy setups ("just export HTTPS_PROXY") ineffective on the load_kube_config() path, which is the path almost every user takes.
What you expected to happen:
HTTPS_PROXY (or HTTP_PROXY) is honoured for the cluster endpoint, as it is for curl, kubectl, and client-go.
How to reproduce it (as minimally and precisely as possible):
$ HTTPS_PROXY=http://127.0.0.1:8888 python -c "
from kubernetes.client import Configuration
print(repr(Configuration().proxy))
print(repr(Configuration(host='https://example:6443').proxy))"
None
'http://127.0.0.1:8888'The same asymmetry end to end, against a cluster reachable only through a proxy (the setup from #2679):
| env var set | Configuration.proxy after load_kube_config() |
API call |
|---|---|---|
HTTPS_PROXY |
None |
fails |
HTTP_PROXY |
None |
fails |
ALL_PROXY |
set | works |
Root cause
Configuration.__init__ resolves the proxy from the environment by the scheme of self.host:
python/kubernetes/client/configuration.py
Lines 339 to 347 in 9c3456a
| # urllib3 does not read proxy environment variables itself: | |
| # https://github.com/urllib3/urllib3/issues/1785 | |
| if proxy is None or no_proxy is None: | |
| proxies = getproxies() | |
| if proxy is None: | |
| scheme = urlparse(self.host).scheme | |
| proxy = proxies.get(scheme) or proxies.get("all") | |
| if no_proxy is None: | |
| no_proxy = proxies.get("no") |
scheme = urlparse(self.host).scheme
proxy = proxies.get(scheme) or proxies.get("all")But host is a property, not the constructor argument:
python/kubernetes/client/configuration.py
Lines 663 to 666 in 9c3456a
| @property | |
| def host(self) -> str: | |
| """Return generated host.""" | |
| return self.get_host_from_settings(self.server_index, variables=self.server_variables) |
@property
def host(self) -> str:
"""Return generated host."""
return self.get_host_from_settings(self.server_index, variables=self.server_variables)and server_index is set to 0 whenever no host argument is passed:
python/kubernetes/client/configuration.py
Line 247 in 9c3456a
| self.server_index = 0 if server_index is None and host is None else server_index |
self.server_index = 0 if server_index is None and host is None else server_indexWith server_index = 0, host returns get_host_settings()[0]['url'], which is "" for this spec. So urlparse("").scheme is '', proxies.get('') misses, and only the proxies.get("all") fallback can match — which is why ALL_PROXY is the one variable that works.
load_kube_config() constructs the object with no host:
python/kubernetes/base/config/kube_config.py
Lines 781 to 784 in 9c3456a
| if client_configuration is None: | |
| config = type.__call__(Configuration) | |
| loader.load_and_set(config) | |
| Configuration.set_default(config) |
if client_configuration is None:
config = type.__call__(Configuration)
loader.load_and_set(config)so it always takes the broken path. The loader assigns the real server afterwards — and the host setter does reset server_index to None — but by then the proxy has already been resolved in __init__. Passing host= explicitly is what makes the working case above work.
Possible fixes
Two options, and I'd like guidance on which is preferred before writing a patch:
- Re-evaluate the environment proxy in the loader once
hostis known — a change inkubernetes/base/config/kube_config.py, which is developer-written and patchable in this repo. - Fix the resolution order in
Configuration.__init__so it uses the effective base path rather than the generatedhostproperty. This is inkubernetes/client/configuration.py, which is generated (kubernetes/.openapi-generator/FILES), so it would need whatever hotfix/regeneration flow you prefer.
Happy to implement either. Given #2147's history I'd rather not touch generated files without agreement first.
Anything else we need to know?:
Related: #2679 / #2680 (kubeconfig proxy-url, fixed), #2520 (no_proxy env var handling).
Environment:
- Python client version: master (
9c3456a351594b59a19862d9caec1e78e7b4e287), also reproduced on released 36.0.3 - Python version: 3.12.3
- OS: Linux
/kind bug
Activity
yliaog commentedon Aug 14, 2026
@yurnov option 2. is preferred if it is done in the openapi generator repo, so the change won't be overridden by the next openapi generator run for future releases. also the fix might be applicable to other clients that uses the same openapi generator.
option 1 is also acceptable if option 2 is not viable.
yliaog commentedon Aug 14, 2026
btw, would this issue also address #2520 ?
yurnov commentedon Aug 16, 2026
Partly - they overlap but they are not the same bug.
The specific defect reported in #2520,
self.no_proxy = Noneoverwriting the value just read from the environment, is already gone: @roycaihw was right that #2459/#2513 fixed it, and the current template readsno_proxy = proxies.get("no")once.rest.pyalso gainedshould_bypass_proxies(), so the value is actually applied. On master today:What #2520 could not have noticed is that its own repro was masked by this issue: on the
load_kube_config()pathproxywas alwaysNone, so nothing was routed through a proxy andno_proxynever had anything to bypass. That is why the reporter's snippet still printsproxy: Nonetoday.So #2681 is what makes the
no_proxyhandling reachable rather than what fixes it. With the lazy-proxy patch applied, the full env-only path behaves correctly:HTTPS_PROXYNO_PROXYhttps://api.example.com:6443.internalProxyManagerhttps://api.svc.internal:6443.internalPoolManagerI'd suggest keeping #2520 open until this lands and then closing it, so there is something to point at that demonstrates the end-to-end behaviour.
(Note #2520 is
lifecycle/rottenand @Ymax27 self-assigned in August — worth a/remove-lifecycle rottenif you agree it should stay open.)yurnov commentedon Aug 16, 2026
/assign
yurnov commentedon Aug 16, 2026
Opened upstream as OpenAPITools/openapi-generator#24716 (option 2).
The proxy is re-resolved in the
hostsetter, so construction is unchanged and the existing tests pass as-is. Once it merges it reaches us with a bump ofOPENAPI_GENERATOR_COMMITinkubernetes-client/gen.yurnov commentedon Aug 18, 2026
Upstream merged: OpenAPITools/openapi-generator#24716 (
07be218c).@yliaog on kubernetes-client/gen#308 you offered the same choice for
proxy_ssl_context— revOPENAPI_GENERATOR_COMMITin a new PR, or wait for the 7.25.0 release. The two are now the same decision: #24539 landed in the same range, so one bump carries both this and #2387.Waiting looks cheap: the 7.25.0 milestone is due 2026-08-20 (23 open / 126 closed), and it would let
python.shpin a tag instead of the current SHA.Either way the bump is not isolated.
openapi/python.shis at830e9d15, 161 commits back; seven of those touch the python templates:plus
PythonClientCodegen.java.model_generic.mustacheandapi_client.mustachechange too, so the regenerated diff spans the model files, not justconfiguration.py.Both local patches also stop applying.
scripts/rest_client_patch.diffanchors onwhich #23992 rewrote to
contenttype_matches(...), andscripts/client_go_retry_patch.diffis applied with--unidiff-zero, so its line anchors shift as soon asconfiguration.pygrows. Mechanical to regenerate, but it makes the python-side PR larger than a version bump.Which raises the main question: since 37.0.0 regenerates against release-1.37 anyway — and that refreshes those patches regardless — do you want the generator bump folded into that regeneration, or landed on its own beforehand? Folding it in means one regeneration instead of two; doing it separately gets the fix onto master sooner and keeps the spec bump and the generator bump reviewable apart.
Two smaller ones:
openapi/python-aio.shcarries the same pin. The async client has no env-proxy block ({{^async}}upstream), soHTTP_PROXY/HTTPS_PROXYignored byload_kube_config(): proxy scheme resolved before the host is known #2681 doesn't need it - bump for consistency, or leave it?Happy to do the work whichever way you prefer.
cc @emmanuel-adu — #2387 rides the same bump.
yliaog commentedon Aug 18, 2026
i think it is clearer to separate the python 37.0.0 release from the generator release 1.25.0. I prefer to bump to generator 1.25.0 as soon as it releases.
btw, does your PR fix #2520 ? could you close that issue once the master has the 1.25.0 generator bump?
yurnov commentedon Aug 19, 2026
@yliaog understood — separate PR, on the release. I'll open the
kubernetes-client/genPR bumpingopenapi/python.shto the 7.25.0 tag as soon as it ships, then the regeneration PR here. 7.25.0 is due 2026-08-20 with 23 issues still open on the milestone, so it may slip a few days; I'll watch for it. Shouldopenapi/python-aio.shmove to the same tag in that PR? The async client has no env-proxy block, so it doesn't need it for this, but leaving the two pins split seems worse than bumping both.On #2520 — yes, it's covered once this lands. To be precise about why, since it's not quite "this PR fixes it": the literal defect reported there,
self.no_proxy = Noneoverwriting the value just read from the environment, was already fixed by #2459/#2513. It stayed unobservable becauseproxywas alwaysNoneon theload_kube_config()path, so nothing was routed through a proxy forno_proxyto bypass. This bump is what makes the env path work end to end, so #2520 has nothing left in it afterwards. It'slifecycle/rottenand assigned to @Ymax27 — I'll post there once master has the bump, but I don't think I have the permissions to close it myself.@craftydisso the environment is already read first —
__init__callsgetproxies()before anything else. The missing input isn't the environment, it's the scheme:That has to know whether the target is http or https to choose between
HTTP_PROXYandHTTPS_PROXY. At that pointself.hostis"", notNone—server_indexfalls back to0and the k8s spec's server URL is empty — sourlparse("").schemeis""and every lookup except theALL_PROXYfallback misses.load_kube_config()assigns the host afterwards.So no reordering inside
__init__can fix it; the host genuinely doesn't exist yet. That's why the upstream fix (OpenAPITools/openapi-generator#24716, merged) re-resolves in thehostsetter, the first point at which the scheme is known.yliaog commentedon Aug 19, 2026
thanks @yurnov , please move openapi/python-aio.sh to the same tag, so that sync and asyncio client are kept in sync to the same openapi generator release.
Pin the Python generators to openapi-generator v7.25.0 (#310)
Regenerate the synchronous client with openapi-generator 7.25.0