Client 내부 구조
Client는 단순한 HTTP 클라이언트가 아닙니다. Tower 미들웨어 스택으로 구성된 레이어 아키텍처이며, Clone이 Arc 수준으로 가볍습니다. 내부 구조를 이해하면 타임아웃, 인증, 커스텀 미들웨어 같은 문제를 해결할 수 있습니다.
Client의 실체
pub struct Client {
inner: Buffer<Request<Body>, BoxFuture<'static, Result<Response<Body>, BoxError>>>,
default_ns: String,
valid_until: Option<Timestamp>,
}
tower::Buffer:Service를Arc로 감쌉니다.Client::clone()은 참조 카운트 증가에 불과하므로, 여러Api<K>핸들에서 같은 Client를 자유롭게 공유할 수 있습니다.- capacity 1024:
Buffer의 in-flight 요청 용량입니다. 동시에 1024개까지 요청을 큐에 넣을 수 있습니다. BoxFuture: 응답 future의 구체 타입이 지워져 있어서 내부 미들웨어 스택의 구체 타입이 외부에 노출되지 않습니다.valid_until: credential 만료 시간입니다. 만료 후 Client를 재생성해야 합니다.
Tower 미들웨어 스택
요청은 위에서 아래로, 응답은 아래에서 위로 흐릅니다. 각 레이어는 tower::Layer trait을 구현합니다.
| 레이어 | 역할 |
|---|---|
| TraceLayer | OpenTelemetry 호환 HTTP 스팬을 생성합니다. 요청/응답의 트레이싱 정보를 기록합니다. |
| extra_headers_layer | impersonation 등 커스텀 헤더를 추가합니다. |
| auth_layer | Bearer 토큰, exec 기반 인증, 토큰 자동 갱신을 처리합니다. |
| DecompressionLayer | gzip 응답을 해제합니다 (gzip feature 필요). |
| base_uri_layer | 모든 요청 URL에 cluster_url prefix를 추가합니다. |
| hyper Client | HTTP/1.1 + HTTP/2로 실제 전송합니다. |
| TimeoutConnector | connect/read/write 각각의 타임아웃을 적용합니다. |
| TLS layer | rustls-tls 또는 openssl-tls feature에 따라 TLS를 처리합니다. |
| Proxy | SOCKS5/HTTP 프록시를 통과합니다 (socks5/http-proxy feature). |
| HttpConnector | TCP 연결을 생성합니다. |
Config 추론 체인
Client::try_default()는 내부적으로 Config::infer()를 호출합니다. 다음 순서로 설정을 탐색합니다.
- kubeconfig:
$KUBECONFIG환경변수가 가리키는 파일, 또는~/.kube/config - in-cluster:
/var/run/secrets/kubernetes.io/serviceaccount/의 토큰과 CA 인증서 - 둘 다 없으면 에러를 반환합니다.
기본 타임아웃
| 설정 | 기본값 | 용도 |
|---|---|---|
connect_timeout | 30초 | TCP 연결 수립 |
read_timeout | None | 응답 대기 (무제한) |
write_timeout | None | 요청 전송 (무제한) |
이전에는 read_timeout이 295초로 설정되어 watch long-polling과 일반 API 호출에 동일하게 적용되었습니다. 이로 인해 exec, attach, port-forward 같은 장기 연결이 유휴 295초 후 끊기는 문제가 있었습니다 (kube#1798).
현재는 Go client와 동일하게 global read_timeout을 None으로 설정하고, 각 계층이 자체 타임아웃을 관리합니다:
- watcher: 서버 측
timeoutSeconds+ 마진으로 idle timeout을 자체 관리. 네트워크 장애 시 자동 재연결 - exec/attach/port-forward: 타임아웃 없음 (무기한 유휴 가능)
- reconciler 내 API 호출: 필요 시
tokio::time::timeout으로 개별 감싸기
// reconciler 내부에서 느린 호출 방어
let pod = tokio::time::timeout(
Duration::from_secs(10),
pods.get("my-pod")
).await??;
인증 처리
auth_layer가 모든 인증을 담당합니다.
| 방식 | 동작 |
|---|---|
| 정적 토큰 | Authorization: Bearer <token> 헤더를 추가합니다. |
| 클라이언트 인증서 | TLS 레이어에서 mTLS로 인증합니다. |
| exec plugin | 외부 프로그램을 호출해 토큰을 얻습니다 (AWS EKS의 aws-iam-authenticator 등). |
| 토큰 갱신 | 토큰 만료 전 자동으로 refresh합니다. |
watcher가 장시간 실행되는 동안 credential이 rotate되고 연결이 끊기면, 재연결 시 stale credential을 사용해 영구적으로 실패할 수 있습니다.
대응: Client를 재생성하거나, exec plugin 기반 인증을 사용하면 매번 새 토큰을 발급받을 수 있습니다.
Client 커스텀
ClientBuilder를 사용하면 미들웨어 스택을 커스텀할 수 있습니다.
use kube::client::ClientBuilder;
let config = Config::infer().await?;
let client = ClientBuilder::try_from(config)?
// 커스텀 Tower 레이어 추가 가능
.build();
Client 커스텀 타임아웃
global read_timeout이 None이므로 용도별 Client 분리는 더 이상 필수가 아닙니다. 필요한 경우 개별 호출에 tokio::time::timeout을 적용하거나, 특정 용도로 짧은 타임아웃 Client를 만들 수 있습니다.
// 기본 Client — 타임아웃 없음 (watcher, exec 등 모두 사용 가능)
let client = Client::try_default().await?;
// 특정 용도로 짧은 타임아웃이 필요한 경우
let mut config = Config::infer().await?;
config.read_timeout = Some(Duration::from_secs(30));
let short_timeout_client = Client::try_from(config)?;