Documentation
¶
Overview ¶
Package spendcap holds per-app monthly spend and egress guardrails: the pure threshold math and the level-triggered controller step that decides whether an app is throttled or paused. Persistence and the ingress effect live with the callers.
Index ¶
- Constants
- Variables
- func NextPeriodStart(t time.Time) time.Time
- func OverrideTTL() time.Duration
- func ParseBytes(s string) (int64, error)
- func ParseWarnPercents(raw string, fallback []int) []int
- func Period(t time.Time) string
- func ValidateGuardMode(m string) error
- type AppLoad
- type Breach
- type Cap
- type Condition
- type Controller
- type Enforcement
- type Eval
- type Event
- type Override
- type Preview
- type PreviewApp
- type State
- type Status
- type Store
- type Usage
- type UsageSource
Constants ¶
const ( EventThreshold = "threshold" EventEnforced = "enforced" EventReleased = "released" EventRollover = "rollover" EventConfigured = "configured" EventCleared = "cleared" EventOverride = "override" )
Event kinds recorded in an app's history.
const ( ActionNone = "none" ActionThrottle = "throttle" ActionPause = "pause" )
Hard actions taken when a limit is reached.
const ( DefaultThrottleRPS = 5 MaxThrottleRPS = 100000 MaxThresholds = 8 )
Limits on what an operator can configure.
const ( GuardAlert = "alert" GuardBlockDeploys = "block_deploys" )
Guard modes decide whether reaching the limit also refuses new deploys.
const ( ReasonWithinBudget = "WithinBudget" ReasonThresholdReached = "ThresholdReached" ReasonLimitAlertOnly = "LimitReachedAlertOnly" ReasonThrottled = "Throttled" ReasonPaused = "Paused" ReasonDeploysBlocked = "DeploysBlocked" ReasonEnforcementPending = "EnforcementPending" )
Condition reasons.
const EnvOverrideTTL = "APP_SPEND_OVERRIDE_TTL"
EnvOverrideTTL sets how long a root override of the deploy guard lasts, as a Go duration such as "1h".
const EnvWarnPercents = "APP_SPEND_WARN_PERCENTS"
EnvWarnPercents overrides the default alert levels, for example "80,100".
const (
// MaxReasonLen bounds the audited reason text.
MaxReasonLen = 500
)
Variables ¶
var DefaultThresholds = ParseWarnPercents(os.Getenv(EnvWarnPercents), []int{50, 80, 100})
DefaultThresholds are the alert levels, in percent of the limit.
var ErrNotFound = errors.New("spendcap: not found")
ErrNotFound is returned when an app has no cap.
Functions ¶
func NextPeriodStart ¶
NextPeriodStart is the first instant of the month after t.
func OverrideTTL ¶
OverrideTTL reads the override lifetime from the environment.
func ParseBytes ¶
ParseBytes reads 500GiB (binary) or 500GB (decimal) into bytes.
func ParseWarnPercents ¶
ParseWarnPercents reads comma separated whole percents. Anything invalid falls back, so a typo in the environment can never disable alerting.
func ValidateGuardMode ¶
ValidateGuardMode rejects an unknown guard mode.
Types ¶
type AppLoad ¶
AppLoad is what a pause would stop for one app. Reclaimable cost is the part a pause removes (compute and egress); storage and idle node cost stay.
type Cap ¶
type Cap struct {
App string `json:"app"`
MonthlyEgressBytes int64 `json:"monthly_egress_bytes"`
MonthlyCostCeiling float64 `json:"monthly_cost_ceiling"`
Thresholds []int `json:"thresholds"`
Action string `json:"action"`
ThrottleRPS int `json:"throttle_rps"`
GuardMode string `json:"guard_mode"`
}
Cap is one app's monthly guardrail. A zero limit is unset.
type Condition ¶
type Condition struct {
Healthy bool `json:"healthy"`
Reason string `json:"reason"`
Message string `json:"message"`
}
Condition is the controller's status for one app.
type Controller ¶
type Controller struct {
Store Store
Usage UsageSource
Nudge func(ctx context.Context) error
Logger *slog.Logger
Now func() time.Time
}
Controller converges each capped app's enforcement on its usage. It holds no state of its own: every tick re-derives the decision from the store and usage, so a restart or a missed tick changes nothing.
func (*Controller) Breaches ¶
func (c *Controller) Breaches(ctx context.Context) ([]Breach, error)
Breaches lists the highest threshold each capped app crossed this month, for the budget_threshold alert rule.
type Enforcement ¶
Enforcement is one app's action ingress must apply now.
type Eval ¶
type Eval struct {
EgressPct float64
CostPct float64
Pct float64
Crossed []int
Exhausted bool
// Desired is the action that should be in force right now.
Desired string
}
Eval is the position of usage against a cap.
type Event ¶
type Event struct {
ID int64 `json:"id"`
App string `json:"app"`
Period string `json:"period"`
Kind string `json:"kind"`
Detail string `json:"detail"`
Pct float64 `json:"pct"`
At time.Time `json:"at"`
}
Event is one entry of an app's history.
type Override ¶
type Override struct {
ID int64 `json:"id"`
App string `json:"app"`
Period string `json:"period"`
Reason string `json:"reason"`
Actor string `json:"actor"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at"`
}
Override is one audited, expiring permission to deploy a blocked app.
func NewOverride ¶
NewOverride validates the reason and stamps the expiry.
type Preview ¶
type Preview struct {
Apps []PreviewApp `json:"apps"`
TotalReplicas int `json:"total_replicas"`
RemainingDays float64 `json:"remaining_days"`
EstimatedSaving float64 `json:"estimated_saving"`
DryRun bool `json:"dry_run"`
}
Preview is what pausing a scope would do. It is computed, never applied.
type PreviewApp ¶
type PreviewApp struct {
Name string `json:"name"`
Replicas int `json:"replicas"`
DailyRunRate float64 `json:"daily_run_rate"`
EstimatedSaving float64 `json:"estimated_saving"`
}
PreviewApp is one app's line in a pause preview.
type State ¶
type State struct {
App string
Period string
// Enforced is "" or the action ingress must apply; Nudged is the value
// ingress was last told about.
Enforced string
Nudged string
Highest int
Pct float64
EgressBytes int64
Cost float64
UpdatedAt time.Time
}
State is what the controller last decided for an app in a period.
type Status ¶
type Status struct {
Cap
Period string `json:"period"`
EgressBytes int64 `json:"egress_bytes"`
Cost float64 `json:"cost"`
EgressPct float64 `json:"egress_pct"`
CostPct float64 `json:"cost_pct"`
Pct float64 `json:"pct"`
Enforced string `json:"enforced"`
ResetsAt time.Time `json:"resets_at"`
Condition Condition `json:"condition"`
}
Status is one app's cap with live usage and condition.
type Store ¶
type Store interface {
ListSpendCaps(ctx context.Context) ([]Cap, error)
GetSpendCap(ctx context.Context, app string) (Cap, error)
SaveSpendCap(ctx context.Context, c Cap, ev Event, now time.Time) error
DeleteSpendCap(ctx context.Context, app string, ev Event) error
GetSpendCapState(ctx context.Context, app string) (State, bool, error)
SaveSpendCapState(ctx context.Context, st State, events []Event) error
ListSpendCapEvents(ctx context.Context, app string, limit int) ([]Event, error)
}
Store is the persistence the controller needs; *store.DB satisfies it.