본문으로 건너뛰기

최적화

대규모 클러스터에서 컨트롤러가 효율적으로 동작하도록 각 레이어별 최적화 방법을 다룹니다.

최적화 순서

성능 문제가 발생했을 때 어디서부터 시작해야 하는지 우선순위입니다. 위에서 아래로, 효과가 크고 부작용이 적은 순서입니다.

순서작업효과위험도
1진단 — 실제 병목 확인방향 설정없음
2selector 축소 — label/field selector 추가API 서버 부하, 네트워크, 메모리 동시 감소낮음
3predicate_filter — 불필요한 reconcile 제거reconcile 호출 횟수 감소낮음 (predicate 조합 주의)
4metadata_watcher — spec/status 수신 생략메모리 사용량 감소중간 (reconciler에서 전체 객체 필요 시 get 필요)
5reflector 정리.modify()로 불필요한 필드 제거Store 메모리 감소낮음
6reconciler 튜닝 — debounce, concurrency, 캐시 활용API 호출 감소, 처리량 조절낮음
7샤딩 — 네임스페이스/라벨 기반 분배수평 확장높음 (운영 복잡도 증가)

1단계 진단이 가장 중요합니다. 메모리가 문제인지, reconcile 지연이 문제인지, API 서버 throttling이 문제인지에 따라 접근이 달라집니다. RUST_LOG=kube=debug로 로그를 확인하고, 모니터링의 메트릭으로 reconcile 횟수와 소요 시간을 측정합니다. 메모리가 의심되면 jemalloc 프로파일링으로 Store 크기를 확인합니다. 증상별 진단은 트러블슈팅을 참고합니다.

Watcher 최적화

감시 범위 축소

label selector와 field selector로 API 서버가 필터링하게 합니다. 네트워크 트래픽과 메모리를 모두 절약합니다.

use kube::runtime::watcher;

let wc = watcher::Config::default()
.labels("app=myapp") // label selector
.fields("metadata.name=specific-one"); // field selector

metadata_watcher

spec과 status가 필요 없고 메타데이터만 필요한 경우 metadata_watcher()를 사용합니다. PartialObjectMeta만 수신하므로 메모리 사용량이 크게 줄어듭니다.

use kube::runtime::watcher::metadata_watcher;
use kube::core::PartialObjectMeta;

let stream = metadata_watcher(api, wc).default_backoff();

큰 spec을 가진 리소스(Secret, ConfigMap 등)에서 효과적입니다. 단, reconciler에서 전체 객체가 필요하면 별도 get() 호출이 필요합니다.

StreamingList

Watcher 상태 머신에서 다룬 StreamingList 전략을 사용하면 초기 목록 로드 시 메모리 피크를 낮출 수 있습니다.

let wc = watcher::Config::default().streaming_lists();

Kubernetes 1.27 이상이 필요합니다. LIST 대신 WATCH로 초기 목록을 스트리밍하므로 전체 목록을 한 번에 메모리에 올리지 않습니다.

page_size 조절

기본 page_size는 500입니다 (client-go와 동일).

클러스터 규모권장이유
소규모 (수백 개)더 크게 (1000+)API 호출 수 감소
대규모 (수만 개)더 작게 (100~300)메모리 피크 감소
let wc = watcher::Config::default().page_size(100);

Reflector 최적화

불필요한 필드 제거

Reflector와 Store에 캐시되는 객체에서 불필요한 필드를 제거하면 메모리를 절약합니다.

use kube::runtime::WatchStreamExt;

let stream = watcher(api, wc)
.default_backoff()
.modify(|obj| {
// managedFields 제거 — 상당한 메모리 절약
obj.managed_fields_mut().clear();
// last-applied-configuration annotation — SSA 이전 방식의 큰 annotation
obj.annotations_mut()
.remove("kubectl.kubernetes.io/last-applied-configuration");
});
modify는 Store에 저장되기 전에 적용됩니다

modify로 제거한 필드는 reconciler에서도 접근할 수 없습니다. reconciler에서 필요한 필드는 제거하지 않도록 주의합니다.

메모리 추정

Store에 캐시된 객체 수와 평균 크기로 메모리를 추정합니다:

항목계산
기본 사용량객체 수 x 평균 크기
re-list 스파이크old store + new buffer + 스트림 버퍼 = 최대 2~3배

jemalloc과 MALLOC_CONF="prof:true"로 힙 프로파일링을 하면 실제 메모리 사용 패턴을 확인할 수 있습니다.

Reconciler 최적화

불필요한 reconcile 방지

Reconciler 패턴에서 다룬 것처럼, status 변경으로 인한 자기 trigger를 방지합니다.

use kube::runtime::{predicates, watcher, WatchStreamExt};
use kube::runtime::utils::predicate::PredicateConfig;

// watcher 스트림에 predicate_filter를 적용한 후 Controller에 주입
let (reader, writer) = reflector::store();
let stream = reflector(writer, watcher(api.clone(), wc))
.applied_objects()
.predicate_filter(predicates::generation, PredicateConfig::default());

Controller::for_stream(stream, reader)

status만 변경된 이벤트는 generation이 바뀌지 않으므로 필터링됩니다. finalizer를 사용한다면 predicates::generation.combine(predicates::finalizers)로 조합합니다.

predicate_filter는 스트림 메서드입니다

predicate_filter()Controller의 메서드가 아니라 WatchStreamExt trait의 메서드입니다. for_stream()과 함께 사용해야 합니다.

debounce

짧은 시간 내 동일 객체에 대한 중복 trigger를 흡수합니다.

use kube::runtime::Config;

Controller::new(api, wc)
.with_config(Config::default().debounce(Duration::from_secs(1)))

Deployment 업데이트 시 여러 ReplicaSet 이벤트가 연쇄적으로 발생하는 경우 등에서 효과적입니다.

concurrency 제한

Controller::new(api, wc)
.with_config(Config::default().concurrency(10))
설정동작
0 (기본)제한 없음
N최대 N개 동시 reconcile

API 서버 부하를 제어하려면 적절한 값을 설정합니다. 같은 객체에 대한 동시 reconcile은 Controller 파이프라인에서 Runner가 자동으로 방지합니다.

reconciler 내부 최적화

async fn reconcile(obj: Arc<MyResource>, ctx: Arc<Context>) -> Result<Action, Error> {
// 1. Store에서 읽기 (API 호출 대신 캐시 활용)
let related = ctx.store.get(&ObjectRef::new("related-name").within("ns"));

// 2. 변경 필요 없으면 patch 건너뛰기
let current_cm = cm_api.get("my-cm").await?;
if current_cm.data == desired_cm.data {
// patch 불필요 → API 호출 절약
} else {
cm_api.patch("my-cm", &pp, &patch).await?;
}

// 3. 독립적인 API 호출 병렬화
let (secret, service) = tokio::try_join!(
secret_api.get("my-secret"),
svc_api.get("my-service"),
)?;

Ok(Action::requeue(Duration::from_secs(300)))
}

대규모 클러스터 고려사항

네임스페이스 분리

클러스터 전체 대신 특정 네임스페이스만 감시하면 부하를 크게 줄일 수 있습니다.

// 클러스터 전체 (부하 높음)
let api = Api::<MyResource>::all(client.clone());

// 특정 네임스페이스만 (부하 낮음)
let api = Api::<MyResource>::namespaced(client.clone(), "target-ns");

여러 네임스페이스를 처리해야 하면 네임스페이스별 Controller 인스턴스를 실행할 수 있습니다.

re-list 메모리 스파이크

객체 수평균 크기기본 메모리re-list 피크
1,00010KB10MB~30MB
10,00010KB100MB~300MB
100,00010KB1GB~3GB

완화 방법:

  • StreamingList로 피크 감소
  • metadata_watcher()로 객체 크기 축소
  • .modify()로 불필요한 필드 제거
  • label selector로 대상 축소

API 서버 부하

owns()watches()를 추가할 때마다 별도 watch 연결이 생깁니다. 각 watch는 API 서버와 지속적인 HTTP 연결을 유지합니다.

가능하면 unstable-runtime feature의 shared reflector로 여러 컨트롤러가 같은 watch를 공유할 수 있습니다.

Leader election

HA 배포에서는 여러 인스턴스 중 하나만 active로 동작해야 합니다. leader election의 메커니즘, 서드파티 크레이트, shutdown 연계에 대한 자세한 내용은 가용성에서 다룹니다.

스케일링 전략

단일 인스턴스의 처리량이 부족할 때의 확장 전략을 다룹니다.

수직 확장

가장 먼저 시도할 방법입니다. reconcile 자체가 병렬이므로 CPU/메모리를 늘리면 throughput이 증가합니다.

조절 항목효과
CPU request/limit 증가reconciler 동시 실행 수용량 증가
메모리 증가Store 캐시 + re-list 스파이크 수용
Config::concurrency(N) 증가동시 reconcile 수 확장

수직 확장의 한계는 watcher 하나가 처리할 수 있는 이벤트 처리량입니다. watch 연결 하나의 throughput이 병목이면 샤딩으로 전환합니다.

명시적 샤딩

리소스를 여러 컨트롤러 인스턴스에 분배합니다. 각 인스턴스는 담당 범위만 watch합니다.

네임스페이스별 샤딩

가장 단순한 방법입니다. 각 인스턴스가 다른 네임스페이스를 담당합니다:

// 환경변수로 담당 네임스페이스 결정
let ns = std::env::var("WATCH_NAMESPACE").unwrap_or("default".into());
let api = Api::<MyResource>::namespaced(client, &ns);

라벨 기반 샤딩

FluxCD에서 사용하는 패턴입니다. 리소스에 샤드 라벨을 부여하고, 각 인스턴스가 해당 라벨만 감시합니다:

// 샤드별 label selector
let shard_id = std::env::var("SHARD_ID").unwrap_or("0".into());
let wc = watcher::Config::default()
.labels(&format!("controller.example.com/shard={}", shard_id));
전략장점단점
네임스페이스별구현이 간단, 격리가 자연스러움네임스페이스 수에 의존
라벨 기반유연한 분배라벨 관리 필요, 재분배 시 reconcile 중복

각 샤드에 leader election을 조합하면 HA + 수평 확장을 동시에 달성할 수 있습니다. 자세한 내용은 가용성 — Elected Shards를 참고합니다.