Help us shape the future of Go! Take the 2026 Go Developer Survey

orphans

package
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 15, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package orphans is the single definition of a leftover container or volume that desired state no longer accounts for. The reaper, the API and the CLI all classify through it so they cannot disagree.

Index

Constants

View Source
const (
	SkipTeardownPending = "app teardown is still pending"
	SkipSupersededBuild = "an older generation of a live app, owned by its reconciler"
	SkipNotOurs         = "not created by this control plane"
	SkipNoRunningCopy   = "the node it is placed on has no running copy yet"
	SkipUnrecognized    = "name does not match a managed container shape"
	SkipDatabaseVolume  = "database data volumes are never removed automatically"
	SkipMounted         = "volume is mounted by a container"
	SkipVolumesOff      = "automatic volume removal is disabled"
)

Skip reasons explain why an orphan is listed but not auto-removable.

View Source
const (
	EnvMode        = "APP_ORPHAN_REAPER"
	EnvGrace       = "APP_ORPHAN_GRACE"
	EnvReapVolumes = "APP_ORPHAN_REAP_VOLUMES"
	EnvVolumeGrace = "APP_ORPHAN_VOLUME_GRACE"
	EnvCertGrace   = "APP_ORPHAN_CERT_GRACE"
	EnvMaxRemovals = "APP_ORPHAN_MAX_REMOVALS"
	EnvInterval    = "APP_ORPHAN_INTERVAL"
)

Environment variables that tune the reaper.

Variables

This section is empty.

Functions

func DesiredServiceContainerNames

func DesiredServiceContainerNames(svc store.DesiredService) []string

DesiredServiceContainerNames is every replica container name svc's desired state can produce.

func IsManaged

func IsManaged(c docker.ContainerState, d Desired, instanceID string) bool

IsManaged is the shared "belongs to a live app or database" test the container list uses.

func ServedSet

func ServedSet(hosts []string) map[string]bool

ServedSet turns served hostnames into the storage names certificates are filed under. A wildcard cert is kept when any host it could cover is served.

Types

type CertBackend

type CertBackend interface {
	ListCertStorageKeys(ctx context.Context, prefix string, recursive bool) ([]string, error)
	DeleteCertStorageValue(ctx context.Context, key string) error
}

CertBackend lists and deletes stored certificate files. *store.DB satisfies it.

type Config

type Config struct {
	Mode        Mode
	Grace       time.Duration
	VolumeGrace time.Duration
	CertGrace   time.Duration
	ReapVolumes bool
	MaxPerPass  int
	// Interval is the minimum time between scheduled passes.
	Interval time.Duration
}

Config tunes the reaper.

func ConfigFromEnv

func ConfigFromEnv(lookup func(string) (string, bool)) Config

ConfigFromEnv reads the reaper settings, falling back to DefaultConfig for anything unset or malformed.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig is the safe default: containers reaped after 15 minutes, volumes only listed.

type Controller

type Controller struct {
	// contains filtered or unexported fields
}

Controller adapts a Reaper to the reconcile loop.

func NewController

func NewController(r *Reaper) *Controller

NewController wraps r.

func (*Controller) Name

func (c *Controller) Name() string

Name implements reconcile.Controller.

func (*Controller) Reconcile

func (c *Controller) Reconcile(ctx context.Context) (reconcile.Result, error)

Reconcile implements reconcile.Controller.

type Deps

type Deps struct {
	Store    Store
	NodeIDs  func(ctx context.Context) ([]string, error)
	NormNode func(nodeID string) string
	Resolve  func(nodeID string) (docker.Runtime, error)
	Volumes  VolumeBackend
	Certs    CertBackend
	// ServedHosts reports the hostnames ingress serves; known is false until it has applied a config.
	ServedHosts func(ctx context.Context) (hosts []string, known bool, err error)
	InstanceID  string
	Config      Config
	Logger      *slog.Logger
	Now         func() time.Time
}

Deps are the Reaper's collaborators.

type Desired

type Desired struct {
	Services       map[string]bool
	ContainerNames map[string]bool
	// ContainerNode maps each desired container name to the node it belongs on.
	ContainerNode   map[string]string
	Databases       map[string]bool
	VolumeNames     map[string]bool
	PendingTeardown map[string]bool
}

Desired is the slice of desired state classification needs.

func LoadDesired

func LoadDesired(ctx context.Context, src Source, normNode func(string) string) (Desired, error)

LoadDesired snapshots desired state from src. normNode maps a stored node ID to the ID the scanner uses for it ("" for the control plane's own node).

func (Desired) Empty

func (d Desired) Empty() bool

Empty reports whether nothing at all is desired.

type Failure

type Failure struct {
	Orphan
	Error string `json:"error"`
}

Failure is an orphan the reaper could not remove.

type Finding

type Finding struct {
	Orphan
	FirstSeenAt *time.Time `json:"first_seen_at,omitempty"`
	ReapAfter   *time.Time `json:"reap_after,omitempty"`
	Due         bool       `json:"due"`
}

Finding is an Orphan plus where it stands against the grace period.

type Kind

type Kind string

Kind is the type of resource an Orphan describes.

const (
	KindContainer Kind = "container"
	KindVolume    Kind = "volume"
	// KindCertificate is a stored TLS certificate for a hostname nothing serves.
	KindCertificate Kind = "certificate"
)

Resource kinds.

type Mode

type Mode string

Mode is how the reaper acts on what it finds.

const (
	ModeOn     Mode = "on"
	ModeDryRun Mode = "dry-run"
	ModeOff    Mode = "off"
)

Reaper modes.

type Orphan

type Orphan struct {
	Kind      Kind   `json:"kind"`
	Name      string `json:"name"`
	ID        string `json:"id,omitempty"`
	NodeID    string `json:"node_id"`
	Reason    Reason `json:"reason"`
	Image     string `json:"image,omitempty"`
	SizeBytes int64  `json:"size_bytes,omitempty"`
	// PlacedOn is the node desired state now places a misplaced container on.
	PlacedOn string `json:"placed_on,omitempty"`
	// Skip is non-empty when the reaper must leave this resource alone.
	Skip string `json:"skip,omitempty"`
}

Orphan is one leftover resource on one node.

func ClassifyCertificate

func ClassifyCertificate(issuer, domain string, served map[string]bool) (Orphan, bool)

ClassifyCertificate reports whether the certificate filed under domain (a Caddy storage directory name) is for a hostname nothing serves.

func ClassifyContainer

func ClassifyContainer(c docker.ContainerState, d Desired, instanceID, nodeID string) (Orphan, bool)

ClassifyContainer reports whether c is an orphan. instanceID is this control plane's own instance label, empty when instances are not scoped.

func ClassifyVolume

func ClassifyVolume(v docker.NamedVolume, d Desired, nodeID string, reapVolumes bool) (Orphan, bool)

ClassifyVolume reports whether v is an orphan. reapVolumes is the operator's opt-in for automatic volume removal.

func (Orphan) Reapable

func (o Orphan) Reapable() bool

Reapable reports whether the reaper may remove o once its grace elapsed.

type Reaper

type Reaper struct {
	Deps
	// contains filtered or unexported fields
}

Reaper removes orphans once they have stayed orphaned past the grace period.

func New

func New(d Deps) *Reaper

New builds a Reaper.

func (*Reaper) Reap

func (r *Reaper) Reap(ctx context.Context, dryRun bool) (Report, error)

Reap runs one pass: it records sightings, then removes every due orphan unless dryRun. At most Config.MaxPerPass resources are removed per pass.

func (*Reaper) Scan

func (r *Reaper) Scan(ctx context.Context) (Report, error)

Scan lists every orphan and its grace status without removing anything or writing any state.

type Reason

type Reason string

Reason says why a resource is considered an orphan.

const (
	ReasonServiceDeleted  Reason = "service_deleted"
	ReasonDatabaseDeleted Reason = "database_deleted"
	ReasonMisplaced       Reason = "misplaced"
	ReasonStaleGeneration Reason = "stale_generation"
	ReasonUnmanaged       Reason = "unmanaged"
	ReasonForeignInstance Reason = "foreign_instance"
	ReasonUnrecognized    Reason = "unrecognized_name"
	ReasonUnreferenced    Reason = "unreferenced_volume"
	ReasonUnserved        Reason = "unserved_certificate"
)

Orphan reasons.

type Report

type Report struct {
	DryRun   bool      `json:"dry_run"`
	Findings []Finding `json:"findings"`
	Removed  []Orphan  `json:"removed"`
	Failed   []Failure `json:"failed,omitempty"`
	// Unreachable lists nodes that could not be scanned this pass.
	Unreachable []string `json:"unreachable,omitempty"`
	// Halted explains why removal was withheld, empty when it was not.
	Halted string `json:"halted,omitempty"`
}

Report is what one pass saw and did.

type Source

type Source interface {
	ListDesiredServices(ctx context.Context) ([]store.DesiredService, error)
	ListDesiredDatabases(ctx context.Context) ([]store.DesiredDatabase, error)
	ListPendingTeardowns(ctx context.Context) ([]store.PendingTeardown, error)
}

Source is the persistence LoadDesired reads. *store.DB satisfies it.

type Store

type Store interface {
	Source
	RecordOrphanSighting(ctx context.Context, kind, nodeID, name string, at time.Time) error
	ClearOrphanSighting(ctx context.Context, kind, nodeID, name string) error
	ListOrphanSightings(ctx context.Context) ([]store.OrphanSighting, error)
	SaveAuditEntry(ctx context.Context, e store.AuditEntry) error
}

Store is the persistence the Reaper needs. *store.DB satisfies it.

type VolumeBackend

type VolumeBackend interface {
	ListNamedVolumes(ctx context.Context) ([]docker.NamedVolume, error)
	RemoveVolume(ctx context.Context, name string) error
}

VolumeBackend lists and removes named volumes on the control plane's own node. *docker.Client satisfies it. Remote nodes are containers-only.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL