Open-Source Wikis

/

Kubernetes

/

Systems

/

Garbage collection

kubernetes/kubernetes

Garbage collection

The Kubernetes garbage collector (pkg/controller/garbagecollector/) is what makes "delete the parent and the children disappear" work. It is a generic, dynamically-typed engine that walks the live cluster, builds a graph of OwnerReferences, and reacts to deletes by deleting dependents.

The GC verbs

Every Kubernetes Delete request carries an optional propagationPolicy:

  • Foreground — block the parent's actual removal until all dependents are gone. The parent is marked with metadata.finalizers: [foregroundDeletion]; the GC removes dependents, then removes the finalizer.
  • Background — return success immediately; remove dependents asynchronously. Default for most kinds.
  • Orphan — remove the controller: true OwnerReference from each dependent so they survive the parent's deletion. Default for ReplicaSets in legacy releases.

The choice is enforced by the apiserver (in staging/src/k8s.io/apiserver/pkg/registry/generic/registry/store.go) and by the GC at runtime.

The graph

graph LR
    Deploy[Deployment] --> RS1[ReplicaSet rev 1]
    Deploy --> RS2[ReplicaSet rev 2]
    RS1 --> P1[Pod a]
    RS1 --> P2[Pod b]
    RS2 --> P3[Pod c]
    PVC[PVC] --> Pod[Pod with claim]

The GC keeps an in-memory graph keyed by (ownerUID, dependentUID). Nodes are loaded from informers across every API group; the discovery loop refreshes the type list periodically (pkg/controller/garbagecollector/garbagecollector.go's runGCController).

The two queues

  • Attempt-to-delete queue — fed by parent-deletes. Items here represent "this dependent's owner is gone; consider deleting the dependent."
  • Attempt-to-orphan queue — fed by Orphan-mode deletes. Items here represent "remove me from this dependent's OwnerRef set."

A dedicated worker drains each queue. Both use the rate-limited workqueue pattern from controller-framework.

Rebuild on resync

Periodically the GC re-fetches the discovery document. New API groups bring new informers online; removed groups have their informers torn down. This is what lets the GC handle CRDs that come and go without restarting.

Failure modes

  • Orphaned by accident. A controller forgets to set the OwnerRef. Children never get cleaned up. Workaround: kubectl get pods -l app=foo then kubectl delete pod.
  • Stuck terminating. A foreground-deletion finalizer is hung because some dependents have their own finalizers that won't release. Inspect metadata.finalizers of the dependents.
  • Discovery thrash. If a CRD is constantly added and removed, the discovery resync churns informers. Rare in practice; reflected in garbagecollector_resync_* metrics.

Cluster-scope vs namespace-scope

Cluster-scoped objects can own cluster-scoped or namespace-scoped dependents. Namespace-scoped objects can only own dependents in the same namespace. The apiserver's owner-ref validation (in staging/src/k8s.io/apimachinery/pkg/api/validation) rejects cross-namespace owner references on namespace-scoped owners.

Key source files

File Purpose
pkg/controller/garbagecollector/garbagecollector.go Top-level controller, runs the workers
pkg/controller/garbagecollector/graph_builder.go Builds the in-memory ownership graph from informer events
pkg/controller/garbagecollector/dump.go Debug dump of the graph
pkg/controller/garbagecollector/operations.go DeletionPolicy resolution helpers
pkg/controller/garbagecollector/uid_cache.go Per-UID cache to dedupe events

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Garbage collection – Kubernetes wiki | Factory