placement

package
v0.9.2 Latest Latest
Warning

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

Go to latest
Published: Nov 6, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package placement is the multi-node scheduler: label selectors, a fit check against node CPU, memory and disk headroom, then a spread that prefers the most headroom. Taints and bin-packing are opt in; the default spreads.

Index

Constants

View Source
const (
	RiskReplicasOneZone     = "replicas_one_zone"
	RiskReplicasOneProvider = "replicas_one_provider"
)

Domain risk kinds.

View Source
const (
	LevelWarn = "warn"
	LevelInfo = "info"
)

Risk levels.

View Source
const (
	ScoreSpread  = "spread"
	ScoreBinpack = "binpack"
)

Score modes: spread keeps the most headroom, binpack fills the fullest node that still fits so the others can stay empty.

View Source
const (
	DimCPU    = "cpu"
	DimMemory = "memory"
	DimDisk   = "disk"
)

Dimension names in Evaluation.

View Source
const (
	TolerationEqual  = "Equal"
	TolerationExists = "Exists"
)

Toleration operators.

View Source
const (
	LabelRegion   = "region"
	LabelZone     = "zone"
	LabelProvider = "provider"
)

Well-known node label keys that name a failure domain.

View Source
const EnvProviderSpreadWeight = "APP_PLACEMENT_PROVIDER_SPREAD_WEIGHT"

EnvProviderSpreadWeight names the env var weighting provider spread in replica scoring. 0 turns provider preference off.

View Source
const EnvRegionSpreadWeight = "APP_PLACEMENT_REGION_SPREAD_WEIGHT"

EnvRegionSpreadWeight names the env var weighting region spread in replica scoring. 0 turns region preference off.

View Source
const TopologyNode = "node"

TopologyNode scopes a workload term to a single node; any other topology is the name of a node label such as "zone".

Variables

View Source
var ErrNoEligibleNode = errors.New("placement: no eligible node")

ErrNoEligibleNode is returned when no candidate passes every filter.

Functions

func Check

func Check(c Candidate, req Request) error

Check validates an explicit target against req, the manual-assignment path.

func DecodeRules

func DecodeRules(tj, aj json.RawMessage) (tols []Toleration, a *Affinity)

DecodeRules is EncodeRules reversed; unreadable JSON yields no rules.

func EncodeRules

func EncodeRules(tols []Toleration, a *Affinity) (tj, aj json.RawMessage)

EncodeRules turns one app's rules into the JSON the store keeps; empty rules give nil so the column stays blank.

func Filter

func Filter(c Candidate, req Request) string

Filter returns why c may not take req, or "" when it may.

func FormatBytes

func FormatBytes(b int64) string

FormatBytes renders bytes with a binary unit.

func FormatCPU

func FormatCPU(milli int64) string

FormatCPU renders millicores as cores.

func MaxAffinityTerms

func MaxAffinityTerms() int

MaxAffinityTerms is the most affinity terms one app may carry, read from APP_PLACEMENT_AFFINITY_MAX_TERMS so operators can tune it without a rebuild.

func OrdinalPeers

func OrdinalPeers(placed map[int]string, self int) []string

OrdinalPeers builds Request.Peers for an ordered workload: the nodes of every other placed ordinal, listed by ordinal so the result is stable.

func ParseLabels

func ParseLabels(pairs []string) (map[string]string, error)

ParseLabels turns ["k=v", ...] into a validated map.

func ValidateAffinity

func ValidateAffinity(a Affinity, maxTerms int) error

ValidateAffinity checks operators, keys, weights and the number of terms. maxTerms comes from settings so the bound is not hard coded.

func ValidateLabels

func ValidateLabels(labels map[string]string) error

ValidateLabels checks keys and values are short, plain identifiers.

func ValidateRules

func ValidateRules(tols []Toleration, a *Affinity) error

ValidateRules checks one app's tolerations and affinity together.

func ValidateTaints

func ValidateTaints(taints []Taint) error

ValidateTaints checks keys, values, effects and the set size.

func ValidateTolerations

func ValidateTolerations(tols []Toleration) error

ValidateTolerations checks operators and the set size.

func ZoneOf

func ZoneOf(labels map[string]string) string

ZoneOf returns the node's failure domain: its zone label, or "" when unlabeled.

Types

type Affinity

type Affinity struct {
	NodeRequired  []LabelTerm        `json:"node_required,omitempty" yaml:"node_required,omitempty"`
	NodePreferred []WeightedNodeTerm `json:"node_preferred,omitempty" yaml:"node_preferred,omitempty"`
	Together      []WorkloadTerm     `json:"together,omitempty" yaml:"together,omitempty"`
	Apart         []WorkloadTerm     `json:"apart,omitempty" yaml:"apart,omitempty"`
}

Affinity is the optional rule set of one workload. Together attracts it to nodes (or zones) running matching workloads, Apart repels it.

func (Affinity) Empty

func (a Affinity) Empty() bool

Empty reports whether the rule set asks for nothing.

type Amount

type Amount struct {
	Value int64 `json:"value"`
	Known bool  `json:"known"`
}

Amount is a measured quantity. Known is false when the node never reported it: unknown is not zero (nothing fits) and not infinite (everything fits), it is "cannot be verified".

func Measured

func Measured(v int64) Amount

Measured returns a known Amount.

type Candidate

type Candidate struct {
	ID          string
	Name        string
	Labels      map[string]string
	Schedulable bool
	Online      bool
	// Load is how many apps and databases already run there.
	Load int
	// Capacity is what the node reported; Requested is what workloads
	// already placed on it asked for.
	Capacity  Capacity
	Requested Resources
	// Taints repel workloads that do not tolerate them.
	Taints []Taint
	// Workloads are the labels of each workload already on the node, used by
	// affinity rules. The caller leaves out the workload being placed.
	Workloads []map[string]string
}

Candidate is one node as the scheduler sees it.

type Capacity

type Capacity struct {
	CPUMilli       Amount `json:"cpu_milli"`
	MemoryBytes    Amount `json:"memory_bytes"`
	DiskTotalBytes Amount `json:"disk_total_bytes"`
	DiskFreeBytes  Amount `json:"disk_free_bytes"`
}

Capacity is what a node reports about itself.

type Decision

type Decision struct {
	NodeID     string       `json:"node_id"`
	Why        string       `json:"why"`
	Needs      Resources    `json:"needs"`
	Policy     Policy       `json:"policy"`
	Considered []Evaluation `json:"considered"`
	Rejected   []Rejection  `json:"rejected"`
	Warnings   []string     `json:"warnings,omitempty"`
}

Decision is the chosen node and the full account of how it was chosen.

func Pinned

func Pinned(cands []Candidate, req Request, nodeID string) Decision

Pinned records an operator's explicit choice. It is honored even when the node lacks room, and the shortfall is carried as a warning.

func Spread

func Spread(cands []Candidate, req Request) (Decision, error)

Spread filters out nodes that cannot take req, then picks the one that keeps the most headroom. Nodes whose capacity is unknown rank after every verified node; ties fall to load, then node id, so the choice is stable.

func (Decision) CapacityBlocked

func (d Decision) CapacityBlocked() bool

CapacityBlocked reports whether any node was refused for lack of room.

type Dimension

type Dimension struct {
	Name        string `json:"name"`
	Allocatable *int64 `json:"allocatable"`
	Requested   int64  `json:"requested"`
	Need        int64  `json:"need"`
	Free        *int64 `json:"free"`
	Shortfall   int64  `json:"shortfall"`
}

Dimension is one resource on one node, in the unit the operator reads. Allocatable is nil when the node never reported it. For disk, Requested is measured use and Allocatable already excludes the free-space floor.

func (Dimension) Describe

func (d Dimension) Describe() string

Describe renders d's shortfall or state for an operator.

type DomainGroup

type DomainGroup struct {
	Name          string   `json:"name"`
	Region        string   `json:"region,omitempty"`
	Providers     []string `json:"providers,omitempty"`
	NodeIDs       []string `json:"node_ids"`
	Nodes         int      `json:"nodes"`
	Online        int      `json:"online_nodes"`
	Schedulable   int      `json:"schedulable_nodes"`
	Workloads     int      `json:"workloads"`
	CPUMilli      int64    `json:"cpu_milli"`
	MemoryBytes   int64    `json:"memory_bytes"`
	DiskFreeBytes int64    `json:"disk_free_bytes"`
	Unverified    int      `json:"unverified_nodes"`
}

DomainGroup is the capacity of every node sharing one provider, region or zone. Capacity sums only what nodes reported; Unverified counts the nodes that reported nothing, so a total is never mistaken for the whole fleet.

type DomainOverview

type DomainOverview struct {
	Providers []DomainGroup `json:"providers"`
	Regions   []DomainGroup `json:"regions"`
	Zones     []DomainGroup `json:"zones"`
	Unlabeled int           `json:"unlabeled_nodes"`
	Total     int           `json:"total_nodes"`
}

DomainOverview is the fleet grouped three ways. Unlabeled nodes are counted separately instead of being hidden in an empty-named group.

func Domains

func Domains(cands []Candidate) DomainOverview

Domains groups candidates by provider, region and zone. Zones are keyed by region and zone together because zone names repeat across regions.

type DomainRisk

type DomainRisk struct {
	Kind         string   `json:"kind"`
	Level        string   `json:"level"`
	Name         string   `json:"name"`
	NodeID       string   `json:"node_id"`
	Replicas     int      `json:"replicas"`
	Zone         string   `json:"zone,omitempty"`
	Region       string   `json:"region,omitempty"`
	Provider     string   `json:"provider,omitempty"`
	Alternatives []string `json:"alternatives"`
	Message      string   `json:"message"`
}

DomainRisk says a workload's replicas share one failure domain although another domain could host a copy. All replicas of a workload run on one node today, so a workload with two or more replicas always shares fate.

func DomainRisks

func DomainRisks(cands []Candidate, in []RiskInput) []DomainRisk

DomainRisks lists workloads with two or more replicas whose node sits in a labeled zone or provider while an eligible node exists in a different one. The result is sorted by name so the same fleet always gives the same list.

type Evaluation

type Evaluation struct {
	NodeID     string      `json:"node_id"`
	Name       string      `json:"name"`
	Zone       string      `json:"zone,omitempty"`
	Region     string      `json:"region,omitempty"`
	Provider   string      `json:"provider,omitempty"`
	Eligible   bool        `json:"eligible"`
	Reason     string      `json:"reason,omitempty"`
	Capacity   bool        `json:"capacity_blocked,omitempty"`
	Unverified bool        `json:"unverified,omitempty"`
	Headroom   *int        `json:"headroom_permille,omitempty"`
	Load       int         `json:"load"`
	Dimensions []Dimension `json:"dimensions"`
	// SoftTaints counts untolerated PreferNoSchedule taints; fewer ranks first.
	SoftTaints int `json:"untolerated_soft_taints,omitempty"`
	// AffinityScore sums the weights of the preferences the node satisfies.
	AffinityScore int `json:"affinity_score,omitempty"`
	// contains filtered or unexported fields
}

Evaluation is how one node fared for one placement request.

func Eligible

func Eligible(cands []Candidate, req Request) []Evaluation

Eligible returns the evaluation of every candidate that may take req, in node id order, without picking a winner. A per-node workload runs on all of them.

func EligibleSplit

func EligibleSplit(cands []Candidate, req Request) (fit, short []Evaluation)

EligibleSplit also returns the nodes that pass every structural filter but lack room, so a per-node instance can wait there with the fit reason instead of moving elsewhere. Both lists are sorted by node id.

func Evaluate

func Evaluate(c Candidate, req Request) Evaluation

Evaluate checks c against req: the structural filter first, then each resource. An unknown dimension never fails the node by itself, it marks the node Unverified (or fails it when the policy rejects unknowns).

type LabelTerm

type LabelTerm struct {
	Key    string   `json:"key" yaml:"key"`
	Op     MatchOp  `json:"op" yaml:"op"`
	Values []string `json:"values,omitempty" yaml:"values,omitempty"`
}

LabelTerm tests one label key.

func (LabelTerm) Matches

func (t LabelTerm) Matches(labels map[string]string) bool

Matches reports whether labels satisfy the term.

func (LabelTerm) String

func (t LabelTerm) String() string

type MatchOp

type MatchOp string

MatchOp is how a LabelTerm compares a label to its values.

const (
	OpIn           MatchOp = "In"
	OpNotIn        MatchOp = "NotIn"
	OpExists       MatchOp = "Exists"
	OpDoesNotExist MatchOp = "DoesNotExist"
)

Label term operators.

type Placed

type Placed struct {
	Name     string `json:"name"`
	From     string `json:"from_node_id,omitempty"`
	NodeID   string `json:"node_id,omitempty"`
	NodeName string `json:"node_name,omitempty"`
	Zone     string `json:"zone,omitempty"`
	Why      string `json:"why,omitempty"`
	// Unplaced carries the refusal when no node could take the workload.
	Unplaced string `json:"unplaced,omitempty"`
}

Placed is one simulated workload and where the scheduler would put it.

type Policy

type Policy struct {
	CPUOvercommitPercent    int  `json:"cpu_overcommit_percent"`
	MemoryOvercommitPercent int  `json:"memory_overcommit_percent"`
	MinFreeDiskPercent      int  `json:"min_free_disk_percent"`
	RejectUnknown           bool `json:"reject_unknown"`
	// ScoreMode is ScoreSpread (default) or ScoreBinpack.
	ScoreMode string `json:"score_mode,omitempty"`
}

Policy is the operator's overcommit and headroom setting. Percent 100 means requests may add up to the node's measured total and no further; above 100 allows overcommit. MinFreeDiskPercent keeps that share of the disk unplaceable. RejectUnknown filters nodes whose capacity cannot be verified instead of ranking them last.

func DefaultPolicy

func DefaultPolicy() Policy

DefaultPolicy never overcommits, keeps 10 percent of disk free and ranks unverifiable nodes last rather than refusing them.

func (Policy) Validate

func (p Policy) Validate() error

Validate rejects percentages outside their useful range.

type Rejection

type Rejection struct {
	NodeID string `json:"node_id"`
	Reason string `json:"reason"`
}

Rejection says why one candidate was filtered out.

type Request

type Request struct {
	Selector Selector
	// Exclude is skipped outright, typically the node being drained.
	Exclude string
	// Needs are the workload's total requests; zero asks for nothing.
	Needs  Resources
	Policy Policy
	// Prefer wins when it is eligible, e.g. the node that serves ingress.
	Prefer string
	// Peers are node IDs already running this workload's other replicas.
	Peers []string
	// Tolerations let the workload land on tainted nodes.
	Tolerations []Toleration
	// Affinity is the workload's optional placement rules.
	Affinity Affinity
	// contains filtered or unexported fields
}

Request is what one placement decision needs.

type Resources

type Resources struct {
	CPUMilli    int64 `json:"cpu_milli"`
	MemoryBytes int64 `json:"memory_bytes"`
	DiskBytes   int64 `json:"disk_bytes"`
}

Resources is what a workload asks for, or what placed workloads add up to.

func (Resources) Add

func (r Resources) Add(o Resources) Resources

Add returns r plus o.

func (Resources) Times

func (r Resources) Times(n int64) Resources

Times returns r multiplied by n, the replica count.

type RiskInput

type RiskInput struct {
	Name     string
	NodeID   string
	Replicas int
	Selector Selector
}

RiskInput is one workload for DomainRisks.

type Selector

type Selector map[string]string

Selector is a set of key=value pairs a node's labels must all carry.

func (Selector) Matches

func (sel Selector) Matches(labels map[string]string) bool

Matches reports whether labels satisfy every pair in sel.

func (Selector) String

func (sel Selector) String() string

String renders sel as sorted "k=v,k=v".

type Simulation

type Simulation struct {
	Placed   []Placed      `json:"placed"`
	Unplaced int           `json:"unplaced"`
	Zones    []ZoneSummary `json:"zones"`
}

Simulation is a dry run: nothing is written, the candidates are copied.

func FailNodes

func FailNodes(cands []Candidate, workloads []Workload, dead func(Candidate) bool) Simulation

FailNodes simulates losing the nodes for which dead returns true: every workload on them is rescheduled onto survivors in a stable order (largest memory first, then name) so the same fleet always gives the same answer.

func Simulate

func Simulate(cands []Candidate, req Request, count int) Simulation

Simulate places count identical workloads one after another, each seeing the load the previous ones added, exactly as real creations would.

type Taint

type Taint struct {
	Key    string      `json:"key" yaml:"key"`
	Value  string      `json:"value,omitempty" yaml:"value,omitempty"`
	Effect TaintEffect `json:"effect" yaml:"effect"`
}

Taint marks a node so only workloads that tolerate it are placed there.

func (Taint) String

func (t Taint) String() string

String renders the taint as key=value:Effect.

type TaintEffect

type TaintEffect string

TaintEffect says what an untolerated taint does to a placement.

const (
	EffectNoSchedule       TaintEffect = "NoSchedule"
	EffectPreferNoSchedule TaintEffect = "PreferNoSchedule"
	EffectNoExecute        TaintEffect = "NoExecute"
)

Taint effects. NoExecute filters like NoSchedule here; moving workloads already running on the node is left to the drain flow.

type Toleration

type Toleration struct {
	Key      string      `json:"key,omitempty" yaml:"key,omitempty"`
	Operator string      `json:"operator,omitempty" yaml:"operator,omitempty"`
	Value    string      `json:"value,omitempty" yaml:"value,omitempty"`
	Effect   TaintEffect `json:"effect,omitempty" yaml:"effect,omitempty"`
}

Toleration lets a workload ignore matching taints. An empty Effect matches every effect and an empty Key with the Exists operator matches every taint.

func (Toleration) Tolerates

func (tol Toleration) Tolerates(t Taint) bool

Tolerates reports whether the toleration covers the taint.

type WeightedNodeTerm

type WeightedNodeTerm struct {
	Weight int       `json:"weight" yaml:"weight"`
	Term   LabelTerm `json:"term" yaml:"term"`
}

WeightedNodeTerm is a node label preference; the highest total weight wins.

type Workload

type Workload struct {
	Name   string
	NodeID string
	Needs  Resources
	// Selector is the label constraint the workload was placed under.
	Selector Selector
}

Workload is something already running that a failure would displace.

type WorkloadTerm

type WorkloadTerm struct {
	Match    []LabelTerm `json:"match" yaml:"match"`
	Topology string      `json:"topology,omitempty" yaml:"topology,omitempty"`
	Weight   int         `json:"weight,omitempty" yaml:"weight,omitempty"`
}

WorkloadTerm selects other workloads by label and applies within a topology. Weight 0 makes the term required; 1 to 100 makes it a preference.

type ZoneSummary

type ZoneSummary struct {
	Zone      string   `json:"zone"`
	Region    string   `json:"region,omitempty"`
	Providers []string `json:"providers,omitempty"`
	NodeIDs   []string `json:"node_ids"`
	Online    int      `json:"online_nodes"`
	Workloads int      `json:"workloads"`
}

ZoneSummary is one failure domain with the workloads it carries.

func Zones

func Zones(cands []Candidate) []ZoneSummary

Zones groups candidates by zone (unlabeled nodes share the "" zone).

Jump to

Keyboard shortcuts

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