Skip to main content

Client Internals

Client is not a simple HTTP client. It is a layered architecture built from a Tower middleware stack, and cloning it is as cheap as cloning an Arc. Understanding its internals helps you solve issues around timeouts, authentication, and custom middleware.

What Client Really Is​

kube-client/src/client/mod.rs (simplified)
pub struct Client {
inner: Buffer<Request<Body>, BoxFuture<'static, Result<Response<Body>, BoxError>>>,
default_ns: String,
valid_until: Option<Timestamp>,
}
  • tower::Buffer: Wraps the Service in an Arc. Client::clone() is just a reference count increment, so the same Client can be freely shared across multiple Api<K> handles.
  • capacity 1024: The in-flight request capacity of Buffer. Up to 1024 requests can be queued simultaneously.
  • BoxFuture: The concrete type of the response future is erased, so the concrete types of the internal middleware stack are not exposed externally.
  • valid_until: The credential expiration time. The Client must be recreated after expiration.

Tower Middleware Stack​

Requests flow from top to bottom, responses flow from bottom to top. Each layer implements the tower::Layer trait.

LayerRole
TraceLayerCreates OpenTelemetry-compatible HTTP spans. Records tracing information for requests and responses.
extra_headers_layerAdds custom headers such as impersonation headers.
auth_layerHandles Bearer tokens, exec-based authentication, and automatic token refresh.
DecompressionLayerDecompresses gzip responses (requires the gzip feature).
base_uri_layerPrepends the cluster_url prefix to all request URLs.
hyper ClientPerforms the actual transport over HTTP/1.1 + HTTP/2.
TimeoutConnectorApplies separate timeouts for connect, read, and write operations.
TLS layerHandles TLS based on the rustls-tls or openssl-tls feature.
ProxyRoutes through SOCKS5/HTTP proxies (socks5/http-proxy features).
HttpConnectorCreates TCP connections.

Config Inference Chain​

Client::try_default() internally calls Config::infer(). It searches for configuration in the following order.

  1. kubeconfig: The file pointed to by the $KUBECONFIG environment variable, or ~/.kube/config
  2. in-cluster: The token and CA certificate from /var/run/secrets/kubernetes.io/serviceaccount/
  3. If neither is found, an error is returned.

Default Timeouts​

SettingDefaultPurpose
connect_timeout30sTCP connection establishment
read_timeoutNoneWaiting for a response (unlimited)
write_timeoutNoneSending a request (unlimited)
Timeout design — per-layer separation

Previously, read_timeout was set to 295 seconds, applying uniformly to both watch long-polling and regular API calls. This caused long-lived connections like exec, attach, and port-forward to break after 295 seconds of idle time (kube#1798).

Now, matching the Go client behavior, the global read_timeout defaults to None, and each layer manages its own timeouts:

  • watcher: Self-manages idle timeout based on server-side timeoutSeconds + margin. Auto-reconnects on network failure.
  • exec/attach/port-forward: No timeout (can idle indefinitely)
  • API calls in reconciler: Wrap with tokio::time::timeout as needed
// Guard against slow calls inside reconciler
let pod = tokio::time::timeout(
Duration::from_secs(10),
pods.get("my-pod")
).await??;

Authentication Handling​

The auth_layer handles all authentication.

MethodBehavior
Static tokenAdds an Authorization: Bearer <token> header.
Client certificateAuthenticates via mTLS at the TLS layer.
exec pluginCalls an external program to obtain a token (e.g., aws-iam-authenticator for AWS EKS).
Token refreshAutomatically refreshes the token before it expires.
Credential rotation during long-running processes

If credentials are rotated while a watcher is running for an extended period and the connection drops, the reconnection may use stale credentials and fail permanently.

Mitigation: Recreate the Client, or use exec plugin-based authentication to obtain a fresh token on every call.

Customizing Client​

ClientBuilder allows you to customize the middleware stack.

use kube::client::ClientBuilder;

let config = Config::infer().await?;
let client = ClientBuilder::try_from(config)?
// Custom Tower layers can be added here
.build();

Custom Client Timeouts​

Since the global read_timeout defaults to None, separating clients by purpose is no longer necessary. You can wrap individual calls with tokio::time::timeout as needed, or create a short-timeout client for specific use cases.

// Default Client — no timeout (works for watcher, exec, etc.)
let client = Client::try_default().await?;

// If you need a short timeout for a specific use case
let mut config = Config::infer().await?;
config.read_timeout = Some(Duration::from_secs(30));
let short_timeout_client = Client::try_from(config)?;