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
- func Hold(ctx context.Context, cfg *Config, lc LeaseClient, state *State, ...) (client.AcquireResult, error)
- type Config
- type Enforce
- type Enforcer
- type HealthResult
- type HealthVerdict
- type LeaseClient
- type Mode
- type Renewer
- type State
- func (s *State) CheckPath() string
- func (s *State) HealthyPath() string
- func (s *State) InstallCheckBinary() error
- func (s *State) IsFresh(maxAge time.Duration) HealthResult
- func (s *State) IsHealthy() bool
- func (s *State) MarkHealthy() error
- func (s *State) MarkUnhealthy() error
- func (s *State) ReadHolder() (string, error)
- func (s *State) ReadToken() (int32, error)
- func (s *State) WriteAcquired(holder string, token int32) error
Constants ¶
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.
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 ¶
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 ¶
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.
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 ¶
NewRenewer builds a Renewer, selecting the enforcer from cfg.Enforce.
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 (*State) HealthyPath ¶
HealthyPath is exported so the probe command can be pointed at it.
func (*State) InstallCheckBinary ¶
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 ¶
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 ¶
MarkHealthy creates the health marker; the injected liveness probe passes while it exists.
func (*State) MarkUnhealthy ¶
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 ¶
ReadHolder returns the holder identity the init container persisted.