본문으로 건너뛰기

관계와 Finalizer

Controller가 여러 리소스의 변경을 감지하는 방법(owns, watches)과, 리소스 삭제 전 정리 작업을 보장하는 finalizer의 동작 원리를 다룹니다. owns와 watches의 내부 trigger 메커니즘은 Controller 파이프라인에서 자세히 설명합니다.

소유 관계 — owns

controller.owns::<ConfigMap>(api, wc)

내부 동작:

  1. ConfigMap에 대한 별도 watcher를 생성합니다
  2. ConfigMap이 변경되면 metadata.ownerReferences를 순회합니다
  3. 부모의 kind/apiVersion이 Controller 주 리소스와 일치하면
  4. 부모의 ObjectRefReconcileRequest를 생성합니다

ownerReference 설정

reconciler에서 자식 리소스를 생성할 때 ownerReference를 설정합니다:

let owner_ref = obj.controller_owner_ref(&()).unwrap();
let cm = ConfigMap {
metadata: ObjectMeta {
name: Some("my-config".into()),
namespace: obj.namespace(),
owner_references: Some(vec![owner_ref]),
..Default::default()
},
data: Some(BTreeMap::from([("key".into(), "value".into())])),
..Default::default()
};
메서드controller 필드용도
controller_owner_ref()true하나의 컨트롤러만 소유. Controller에서 사용
owner_ref()미설정여러 소유자 가능

자동 가비지 컬렉션

ownerReference가 설정된 리소스는 부모 삭제 시 Kubernetes가 자동으로 삭제합니다. propagationPolicy에 따라 Foreground, Background, Orphan 중 선택할 수 있습니다.

감시 관계 — watches

ownerReference로 관계를 표현할 수 없을 때 watches를 사용합니다.

controller.watches::<Secret>(api, wc, |secret| {
// Secret에서 관련 주 리소스의 ObjectRef 목록 반환
let name = secret.labels().get("app")?.clone();
let ns = secret.namespace()?;
Some(ObjectRef::new(&name).within(&ns))
})
ownswatches
관계 정의리소스의 ownerReferences에 기록코드의 mapper 함수에 정의
매핑자동 (ownerReferences 순회)수동 (mapper 함수 작성)
가비지 컬렉션Kubernetes가 자동 처리직접 처리
사용 사례부모-자식 관계참조 관계 (Secret → 리소스)

Finalizer 상태 머신

finalizer는 리소스 삭제 전 정리 작업을 보장합니다. watch 이벤트의 Delete는 네트워크 단절로 유실될 수 있지만, finalizer가 있으면 Kubernetes가 삭제를 지연시키므로 정리 작업을 확실히 실행할 수 있습니다.

네 가지 상태:

finalizer삭제 중?동작
없음아님JSON Patch로 finalizer를 추가합니다
있음아님Event::Apply → 정상 reconcile
있음삭제 중Event::Cleanup → 정리 후 finalizer 제거
없음삭제 중아무것도 하지 않습니다 (이미 정리됨)

finalizer 제거 시 JSON Patch에 Test operation이 포함됩니다. 다른 프로세스가 이미 finalizer를 제거했다면 Patch가 실패해 동시성 문제를 방지합니다.

사용 패턴

use kube::runtime::finalizer::{finalizer, Event};

const FINALIZER_NAME: &str = "myapp.example.com/cleanup";

async fn reconcile(obj: Arc<MyResource>, ctx: Arc<Context>) -> Result<Action, Error> {
let api = Api::<MyResource>::namespaced(
ctx.client.clone(),
&obj.namespace().unwrap(),
);

finalizer(&api, FINALIZER_NAME, obj, |event| async {
match event {
Event::Apply(obj) => apply(obj, &ctx).await,
Event::Cleanup(obj) => cleanup(obj, &ctx).await,
}
}).await
}

async fn apply(obj: Arc<MyResource>, ctx: &Context) -> Result<Action, Error> {
// 정상 reconcile 로직
Ok(Action::requeue(Duration::from_secs(300)))
}

async fn cleanup(obj: Arc<MyResource>, ctx: &Context) -> Result<Action, Error> {
// 외부 리소스 정리
// 이 함수가 성공하면 finalizer가 제거됩니다
Ok(Action::await_change())
}

주의사항

cleanup 실패 시 객체가 삭제되지 않습니다

deletionTimestamp는 설정되었지만 finalizer가 남아있으므로 Kubernetes가 실제 삭제를 수행하지 않습니다. cleanup은 반드시 최종적으로 성공하도록 설계해야 합니다. 영구적으로 실패하면 kubectl delete --force로 강제 삭제할 수 있지만, 정리 작업은 건너뛰게 됩니다.

finalizer 이름은 도메인 형식이어야 합니다

"myapp.example.com/cleanup" 형식입니다. 다른 컨트롤러의 finalizer와 충돌하지 않도록 고유한 이름을 사용합니다.

클러스터 스코프 부모 + 네임스페이스 스코프 자식

클러스터 스코프 CR이 네임스페이스 스코프 자식을 owns할 때, 부모의 namespace가 None이고 자식의 namespace가 Some("ns")이므로 ObjectRef 매칭에 문제가 생길 수 있습니다. ownerReferences는 같은 namespace 또는 클러스터 스코프 리소스만 참조할 수 있습니다.

finalizer + predicate_filter 상호작용

finalizer 추가/제거는 generation을 변경하지 않습니다. predicates::generation만 사용하면 finalizer 관련 이벤트를 놓칩니다.

// ✗ finalizer 이벤트를 놓칠 수 있음
.predicate_filter(predicates::generation)

// ✓ finalizer 변경도 감지
.predicate_filter(predicates::generation.combine(predicates::finalizers))

정리 전략 매트릭스

관계 유형에 따라 정리 방법이 달라집니다:

관계 유형설정 방법정리 방법Finalizer 필요?
Owned (owns)ownerReferences 설정Kubernetes 자동 GC보통 불필요
Watched (watches)mapper 함수로 매핑reconciler에서 직접 삭제필요
External (클러스터 외부)cleanup에서 외부 API 호출필요
  • Owned: ownerReferences가 있으므로 부모 삭제 시 Kubernetes가 자식을 자동 삭제합니다. 외부 리소스를 동시에 관리하지 않는 한 finalizer가 필요 없습니다.
  • Watched: 소유 관계가 아니므로 자동 GC가 동작하지 않습니다. finalizer의 Event::Cleanup에서 관련 리소스를 직접 삭제해야 합니다.
  • External: 클러스터 밖의 리소스(DNS 레코드, 클라우드 로드밸런서 등)는 Kubernetes가 관리하지 않으므로, finalizer로 삭제 전 외부 API를 호출해 정리합니다.