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
- Variables
- func Check(c Candidate, req Request) error
- func DecodeRules(tj, aj json.RawMessage) (tols []Toleration, a *Affinity)
- func EncodeRules(tols []Toleration, a *Affinity) (tj, aj json.RawMessage)
- func Filter(c Candidate, req Request) string
- func FormatBytes(b int64) string
- func FormatCPU(milli int64) string
- func MaxAffinityTerms() int
- func OrdinalPeers(placed map[int]string, self int) []string
- func ParseLabels(pairs []string) (map[string]string, error)
- func ValidateAffinity(a Affinity, maxTerms int) error
- func ValidateLabels(labels map[string]string) error
- func ValidateRules(tols []Toleration, a *Affinity) error
- func ValidateTaints(taints []Taint) error
- func ValidateTolerations(tols []Toleration) error
- func ZoneOf(labels map[string]string) string
- type Affinity
- type Amount
- type Candidate
- type Capacity
- type Decision
- type Dimension
- type DomainGroup
- type DomainOverview
- type DomainRisk
- type Evaluation
- type LabelTerm
- type MatchOp
- type Placed
- type Policy
- type Rejection
- type Request
- type Resources
- type RiskInput
- type Selector
- type Simulation
- type Taint
- type TaintEffect
- type Toleration
- type WeightedNodeTerm
- type Workload
- type WorkloadTerm
- type ZoneSummary
Constants ¶
const ( RiskReplicasOneZone = "replicas_one_zone" RiskReplicasOneProvider = "replicas_one_provider" )
Domain risk kinds.
const ( LevelWarn = "warn" LevelInfo = "info" )
Risk levels.
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.
const ( DimCPU = "cpu" DimMemory = "memory" DimDisk = "disk" )
Dimension names in Evaluation.
const ( TolerationEqual = "Equal" TolerationExists = "Exists" )
Toleration operators.
const ( LabelRegion = "region" LabelZone = "zone" LabelProvider = "provider" )
Well-known node label keys that name a failure domain.
const EnvProviderSpreadWeight = "APP_PLACEMENT_PROVIDER_SPREAD_WEIGHT"
EnvProviderSpreadWeight names the env var weighting provider spread in replica scoring. 0 turns provider preference off.
const EnvRegionSpreadWeight = "APP_PLACEMENT_REGION_SPREAD_WEIGHT"
EnvRegionSpreadWeight names the env var weighting region spread in replica scoring. 0 turns region preference off.
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 ¶
var ErrNoEligibleNode = errors.New("placement: no eligible node")
ErrNoEligibleNode is returned when no candidate passes every filter.
Functions ¶
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 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 ¶
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 ¶
ParseLabels turns ["k=v", ...] into a validated map.
func ValidateAffinity ¶
ValidateAffinity checks operators, keys, weights and the number of terms. maxTerms comes from settings so the bound is not hard coded.
func ValidateLabels ¶
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 ¶
ValidateTaints checks keys, values, effects and the set size.
func ValidateTolerations ¶
func ValidateTolerations(tols []Toleration) error
ValidateTolerations checks operators and the set size.
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.
type Amount ¶
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".
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 ¶
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 ¶
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 ¶
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.
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.
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.
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.
type Selector ¶
Selector is a set of key=value pairs a node's labels must all carry.
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.
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.
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).