acquire

package
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package acquire implements the berth-acquire helper: a small binary injected into workload pods (via the SKA-439 mutating webhook) that gates pod startup on a Berth lease and, in runtime-singleton mode, keeps the lease renewed and enforces at-most-once by stopping the main container when the lease is lost.

It talks to the Berth API lease RPCs directly (Acquire / Renew / Release) and deliberately does not depend on the BerthLease CRD — see docs/adr/0001-pod-level-gating-for-injected-singletons.md.

Index

Constants

View Source
const (
	EnvLeaseName      = "BERTH_LEASE_NAME"
	EnvLeaseNamespace = "BERTH_LEASE_NAMESPACE"
	EnvMode           = "BERTH_MODE"
	EnvEnforce        = "BERTH_ENFORCE"
	EnvSignalTarget   = "BERTH_SIGNAL_TARGET"
	EnvTTLSeconds     = "BERTH_TTL_SECONDS"
	EnvHeartbeatSecs  = "BERTH_HEARTBEAT_SECONDS"
	EnvEnforceGrace   = "BERTH_ENFORCE_GRACE_SECONDS"
	EnvReleaseOnDown  = "BERTH_RELEASE_ON_SHUTDOWN"
	EnvHolderIdentity = "BERTH_HOLDER_IDENTITY"
	EnvClusterID      = "BERTH_CLUSTER_ID"
	EnvWorkloadKind   = "BERTH_WORKLOAD_KIND"
	EnvWorkloadName   = "BERTH_WORKLOAD_NAME"
	EnvStateDir       = "BERTH_STATE_DIR"
	EnvAPIServer      = "BERTH_API_SERVER"
	EnvAPIKey         = "BERTH_API_KEY"
	EnvAPIKeyFile     = "BERTH_API_KEY_FILE"
	EnvCABundleFile   = "BERTH_CA_BUNDLE_FILE"
	EnvServerName     = "BERTH_SERVER_NAME"
	EnvInsecure       = "BERTH_INSECURE_SKIP_TLS_VERIFY"

	// Downward-API env used for holder-identity defaulting.
	EnvPodNamespace = "POD_NAMESPACE"
	EnvPodName      = "POD_NAME"
	EnvPodUID       = "POD_UID"
)

Environment variable names the injected helper reads. The mutating webhook (internal/webhook) sets these on the injected containers, and the berth-acquire binary loads them via ConfigFromEnv. This is the single source of truth for the env side of the injection contract.

View Source
const DefaultStateDir = "/berth"

DefaultStateDir is the shared volume mount where the helper writes the fencing token, holder identity, health marker, and check binary.

Variables

This section is empty.

Functions

func Hold

func Hold(ctx context.Context, cfg *Config, lc LeaseClient, state *State, log *slog.Logger) (client.AcquireResult, error)

Hold blocks until it acquires the lease, then persists the holder, fencing token, and health marker and installs the check binary so the sidecar and probe can take over. It returns the winning AcquireResult.

It retries on both transient errors and "held by another" until the context is canceled (the init container is expected to block the pod). Callers that want a bounded gate should pass a context with a deadline.

Types

type Config

type Config struct {
	// Lease target.
	LeaseName      string
	LeaseNamespace string

	// Behavior.
	Mode              Mode
	Enforce           Enforce
	TTL               time.Duration
	HeartbeatInterval time.Duration
	EnforceGrace      time.Duration
	// SignalTarget bounds enforce=signal to the workload process. It matches a
	// process by comm or executable basename (e.g. "nginx"). When empty the
	// enforcer falls back to a broad heuristic that signals every process in the
	// shared PID namespace except a small exclusion set — which can take down
	// co-located sidecars — so it warns loudly. Set it to scope the blast radius
	// to the gated workload. Ignored unless Enforce is EnforceSignal.
	SignalTarget string
	// ReleaseOnShutdown, when nil, defaults per mode (true for
	// runtime-singleton, false for startup-gate). A non-nil value is an
	// explicit override.
	ReleaseOnShutdown *bool

	// Identity. HolderIdentity, when set, completely replaces the derived
	// default. The remaining fields feed the mode-specific default.
	HolderIdentity string
	ClusterID      string
	PodNamespace   string
	PodName        string
	PodUID         string
	WorkloadKind   string
	WorkloadName   string

	// Shared state volume.
	StateDir string

	// API client / auth.
	APIServer          string
	APIKey             string
	APIKeyFile         string
	CABundleFile       string
	ServerName         string
	InsecureSkipVerify bool
}

Config is the fully-resolved helper configuration. The cmd layer gathers values from flags / env / downward API and hands a Config to this package; defaulting and validation live here so both the binary and its tests share one source of truth.

func ConfigFromEnv

func ConfigFromEnv(get func(string) string) (*Config, error)

ConfigFromEnv builds a Config from the BERTH_*/POD_* environment using the supplied getter (os.Getenv in production, a stub in tests). It does not apply defaults or validate — callers layer flag overrides on top and then call Validate. It returns an error only for malformed values (non-integer seconds, an unparseable boolean) so a typo surfaces at startup rather than silently falling back to a default.

func (*Config) ApplyDefaults

func (c *Config) ApplyDefaults()

ApplyDefaults fills mode-specific and derived defaults for any unset field. It is idempotent and is called by Validate.

func (*Config) Holder

func (c *Config) Holder() string

Holder returns the holder identity for Acquire / Renew / Release. An explicit HolderIdentity wins; otherwise the default is mode-specific (see the design doc "Holder Identity Defaulting").

Runtime-singleton folds in the Pod UID so different Pod incarnations never share a holder by accident. Startup-gate prefers a workload-level identity because it only proves startup admission.

func (*Config) NewClient

func (c *Config) NewClient() (*client.Client, error)

NewClient builds a Berth API client from the configured endpoint and auth, reusing the shared clientauth helpers. When APIKeyFile is set the returned cleanup-free client reads a refreshing token on every request.

func (*Config) Validate

func (c *Config) Validate() error

Validate applies defaults and then checks the invariants the helper depends on. The webhook (SKA-439) enforces most of these at admission; re-checking here keeps the binary safe when run directly.

type Enforce

type Enforce string

Enforce selects how the runtime-singleton sidecar stops the main container on lease loss. See ADR-0003.

const (
	// EnforceProbe removes a shared health marker so an injected exec
	// liveness probe fails and the kubelet kills the container.
	EnforceProbe Enforce = "probe"
	// EnforceSignal sends SIGTERM then SIGKILL to the main process via a
	// shared process namespace.
	EnforceSignal Enforce = "signal"
)

type Enforcer

type Enforcer interface {
	// Hold stops the main container and keeps it stopped.
	Hold(ctx context.Context) error
	// Release lets the main container run again after a reacquire.
	Release(ctx context.Context) error
}

Enforcer stops (and later re-allows) the main container when the lease is lost or reacquired. Hold is re-invoked on every renew tick while the lease is not held, so implementations must be idempotent and re-entrant (a kubelet-restarted container must be re-gated). See ADR-0003.

type HealthResult added in v0.4.0

type HealthResult struct {
	Verdict HealthVerdict
	// Age of the marker, set when it could be stat'ed.
	Age time.Duration
	// MaxAge applied; zero means the freshness check was not requested.
	MaxAge time.Duration
	// Err is the underlying error for HealthIndeterminate.
	Err error
}

HealthResult is the verdict plus the evidence behind it, so a failing probe can report *why* rather than only that it failed.

func EvaluateMarker added in v0.4.0

func EvaluateMarker(path string, maxAge time.Duration) HealthResult

EvaluateMarker is the single freshness predicate. Both the sidecar's State.IsHealthy and the probe's check subcommand call it, so the two can never disagree about what "healthy" means.

A maxAge of zero or less requests presence-only evaluation, preserving the behavior of releases that had no freshness bound.

Freshness compares the marker's modification time against the caller's own clock. Both the writing sidecar and the reading probe observe one node kernel clock through the shared volume, so the result never depends on two container clocks agreeing.

func (HealthResult) OK added in v0.4.0

func (r HealthResult) OK() bool

OK reports whether the marker should be treated as healthy.

func (HealthResult) Reason added in v0.4.0

func (r HealthResult) Reason(path string) string

Reason is a short human-readable explanation suitable for probe stderr, which the kubelet surfaces on the pod's probe-failure event.

type HealthVerdict added in v0.4.0

type HealthVerdict int

HealthVerdict is why the health marker did or did not pass. Absent and Stale are deliberately distinct: they are different operational stories. Absent means the sidecar removed the marker, so the lease was lost — an expected failover. Stale means nobody removed it and nobody refreshed it, so the sidecar is dead or wedged — an incident.

const (
	// HealthOK: the marker exists and is within the freshness bound.
	HealthOK HealthVerdict = iota
	// HealthAbsent: the marker does not exist.
	HealthAbsent
	// HealthStale: the marker exists but has not been refreshed in time.
	HealthStale
	// HealthIndeterminate: the marker exists but could not be examined.
	// Treated as unhealthy — an unreadable marker is not evidence of health.
	HealthIndeterminate
)

type LeaseClient

type LeaseClient interface {
	Acquire(ctx context.Context, namespace, name, holder string, ttl time.Duration) (client.AcquireResult, error)
	Renew(ctx context.Context, namespace, name, holder string, fencingToken int32, ttl time.Duration) (client.AcquireResult, error)
	Release(ctx context.Context, namespace, name, holder string, fencingToken int32) error
}

LeaseClient is the subset of the Berth API client the helper uses. It is an interface so tests can substitute a fake without a live server. *client.Client satisfies it.

type Mode

type Mode string

Mode selects the injected helper behavior. See the design doc "Injected Modes" section.

const (
	// ModeStartupGate blocks pod start until an initial Acquire succeeds,
	// with no runtime lease guarantee after the init container exits.
	ModeStartupGate Mode = "startup-gate"
	// ModeRuntimeSingleton blocks start, then keeps a sidecar renewing the
	// lease and stops the main container if the lease is lost.
	ModeRuntimeSingleton Mode = "runtime-singleton"
)

type Renewer

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

Renewer is the runtime-singleton sidecar. It renews the lease the init container acquired and, on loss, drives the configured Enforcer to stop the main container until it can reacquire.

func NewRenewer

func NewRenewer(cfg *Config, lc LeaseClient, state *State, log *slog.Logger) *Renewer

NewRenewer builds a Renewer, selecting the enforcer from cfg.Enforce.

func (*Renewer) Run

func (r *Renewer) Run(ctx context.Context) error

Run renews until the context is canceled, then performs a best-effort release when configured. It returns nil on graceful shutdown.

type State

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

State manages the files the helper shares between its init container, its sidecar, and the injected liveness probe via an emptyDir volume.

<dir>/holder   holder identity the init container acquired with
<dir>/token    fencing token from the successful Acquire
<dir>/healthy  presence-based health marker (probe enforcement)
<dir>/check    a copy of this binary so the probe can exec it without
               needing a shell in the (possibly distroless) main image

func NewState

func NewState(dir string) *State

NewState returns a State rooted at dir.

func (*State) CheckPath

func (s *State) CheckPath() string

CheckPath is the path of the copied check binary the probe execs.

func (*State) HealthyPath

func (s *State) HealthyPath() string

HealthyPath is exported so the probe command can be pointed at it.

func (*State) InstallCheckBinary

func (s *State) InstallCheckBinary() error

InstallCheckBinary copies the currently-running executable to <dir>/check so the injected probe can exec it in the main container, which may be distroless and lack a shell.

func (*State) IsFresh added in v0.4.0

func (s *State) IsFresh(maxAge time.Duration) HealthResult

IsFresh reports whether the health marker is present and was refreshed within maxAge, using the same predicate the probe applies.

func (*State) IsHealthy

func (s *State) IsHealthy() bool

IsHealthy reports whether the health marker is present. It is the sidecar-side view and deliberately shares EvaluateMarker with the probe so the two cannot drift apart.

func (*State) MarkHealthy

func (s *State) MarkHealthy() error

MarkHealthy creates the health marker; the injected liveness probe passes while it exists.

func (*State) MarkUnhealthy

func (s *State) MarkUnhealthy() error

MarkUnhealthy removes the health marker so the kubelet kills the main container. Absence of the marker is the unhealthy signal, so a missing file is treated as success.

func (*State) ReadHolder

func (s *State) ReadHolder() (string, error)

ReadHolder returns the holder identity the init container persisted.

func (*State) ReadToken

func (s *State) ReadToken() (int32, error)

ReadToken returns the fencing token the init container persisted.

func (*State) WriteAcquired

func (s *State) WriteAcquired(holder string, token int32) error

WriteAcquired records a successful Acquire: it persists the holder and fencing token for the sidecar to renew with, then marks the pod healthy.

Jump to

Keyboard shortcuts

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