You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The previous commit on this branch enabled HTTP/2 on the rustls
connector by switching ALPN from `enable_http1()` alone to
`enable_http1().enable_http2()`. REST traffic, watch streams and
log streaming benefit from HTTP/2 multiplexing, but exec, attach
and port-forward use HTTP/1.1 connection upgrades, which are
unrepresentable on an HTTP/2 connection. After ALPN negotiates
`h2`, the apiserver answered upgrade requests with 4xx/5xx and the
integration suite started failing with
`UpgradeConnection(ProtocolSwitch(...))`.
Fix this by giving `Client` two transports with separate connection
pools and ALPN policies:
* a primary, h2-capable transport for normal traffic, configured
with `TokioTimer` and HTTP/2 keep-alive PINGs (interval 30s,
while-idle on) so watch streams survive idle-killing
intermediaries such as HAProxy;
* an HTTP/1.1-only transport used by `Client::connect()`, the only
caller of `hyper::upgrade::on` in the workspace, hard-routed
there via a new `upgrade_inner` field on `Client`.
A subtle point governs the h1-only path on rustls. hyper-rustls'
builder asserts that `alpn_protocols.is_empty()` when accepting a
rustls config, and only `enable_http2()` populates the list. The
`enable_http1()`-only builder path therefore leaves ALPN empty,
which means no ALPN extension is sent on the wire and a modern
apiserver may still negotiate HTTP/2 -- the very condition we are
trying to avoid. The h1-only sibling must advertise `http/1.1`
*explicitly*.
The natural workaround -- `HttpsConnector::from((http,
Arc::new(rustls_config)))` with `alpn_protocols =
vec![b"http/1.1".to_vec()]` -- bypasses the builder's assertion but
also drops `Config::tls_server_name`, because the `From` impl
constructs the connector with the default server-name resolver and
the resolver field is private. Rather than depend on an upstream
hyper-rustls change, ship a small `H1OnlyHttpsConnector<H>` in
`kube-client/src/client/tls.rs` that mirrors the public TCP-then-TLS
dance from `hyper_rustls::HttpsConnector::call`, sets the explicit
ALPN advertisement, and resolves SNI via `Config::tls_server_name`
when set or from the URI host otherwise. This adds `tokio-rustls`
as a direct dep gated on `rustls-tls` (already in the dep tree
transitively via hyper-rustls).
Other invariants worth preserving:
* `Config::auth_layer()` and `Config::extra_headers_layer()` are
computed once and *cloned* into both transport stacks. Calling
`auth_layer()` twice would mint independent `RefreshableToken`
state, so each transport would refresh tokens on its own and
could diverge under exec-plugin or token-file auth. `AuthLayer`
gains `#[derive(Clone)]` for this; the inner `Either` was
already trivially cloneable.
* `Config` gains a public `disable_http2: bool` field as a runtime
escape hatch. When set, the primary transport falls back to the
h1-only client; the two-client shape stays the same so the upgrade
path is unaffected. HTTP/2 stays on by default; the field
matches the existing `disable_compression` style.
* `ClientBuilder` grows `with_upgrade_service`, paired with a new
`Client::new_with_upgrade` constructor for custom-service users
who supply their own service stack and need to opt in to the
split. The single-service `Client::new` keeps its signature; it
internally clones the buffered handle into `upgrade_inner` so
back-compat is preserved.
Verified against k3d v1.34.1 with the integration suite, the
`pod_exec`, `pod_attach`, `pod_portforward*`, `pod_shell*` and
`log_stream` examples, and a full 1200-combo `cargo hack` feature
powerset.
Signed-off-by: Andrew McDermott <aim@frobware.com>
0 commit comments