Documentation ¶
Index ¶
- Constants
- Variables
- func NewKubernetesCoster(interval time.Duration, config *Config, client kubernetes.Interface, ...) (*coster, error)
- func RunningPodFilter(p *core_v1.Pod) bool
- type BufferingCostExporter
- type Config
- type CostData
- type CostDataKey
- type CostExporter
- type CostItem
- type CostTable
- type CostTableEntry
- func (e *CostTableEntry) CPUCostMicroCents(millicpu float64, duration time.Duration) int64
- func (e *CostTableEntry) GPUCostMicroCents(gpus float64, duration time.Duration) int64
- func (e *CostTableEntry) Match(labels Labels) bool
- func (e *CostTableEntry) MemoryCostMicroCents(membytes float64, duration time.Duration) int64
- type Coster
- type Labels
- type Mapper
- type Mapping
- type PodFilter
- type PodFilters
- type PricingStrategy
- type PricingStrategyFunc
- type PubsubCostExporter
- type ResourceCostKind
- type StatsCostExporter
Constants ¶
const ( // StrategyNameCPU is used whenever we derive a cost metric using the CPUPricingStrategy. StrategyNameCPU = "CPUPricingStrategy" // StrategyNameMemory is used whenever we derive a cost metric using the MemoryPricingStrategy. StrategyNameMemory = "MemoryPricingStrategy" // StrategyNameNode is used whenever we derive a cost metric using the NodePricingStrategy. StrategyNameNode = "NodePricingStrategy" // StrategyNameWeighted is used whenever we derive a cost metric using the WeightedPricingStrategy. StrategyNameWeighted = "WeightedPricingStrategy" // StrategyNameGPU is used whenever we derive a cost metric using the GPUPricingStrategy. StrategyNameGPU = "GPUPricingStrategy" // ResourceGPU is used for gpu resources, coinciding with modern versions of the nvidia-device-plugin. ResourceGPU = core_v1.ResourceName("nvidia.com/gpu") )
Variables ¶
var ( // ResourceCostCPU is a cost metric derived from CPU utilization. ResourceCostCPU = ResourceCostKind("cpu") // ResourceCostMemory is a cost metric derived from memory utilization. ResourceCostMemory = ResourceCostKind("memory") // ResourceCostGPU is a cost metric derived from GPU utilization. At the present // time kostanza assumes all GPU's in your cluster are homogenous. ResourceCostGPU = ResourceCostKind("gpu") // ResourceCostWeighted is a cost metric derived from a weighted average of memory and cpu utilization. ResourceCostWeighted = ResourceCostKind("weighted") // ResourceCostNode represents the overall cost of a node. ResourceCostNode = ResourceCostKind("node") // TagStatus indicates the success or failure of an operation. TagStatus, _ = tag.NewKey("status") )
var ( // ErrNoPodNode may be returned during races where the pod containing a given // node has disappeared. ErrNoPodNode = errors.New("could not lookup node for pod") // ErrSenselessInterval is returned if the difference since our last run time // is less than 0. Obviously, this should never since time moves forward. ErrSenselessInterval = errors.New("senseless interval since last calculation") )
var ( // MeasureCost is the stat for tracking costs in millionths of a cent. MeasureCost = stats.Int64("kostanza/measures/cost", "Cost in millionths of a cent", "µ¢") // MeasureCycles is the number of tracking loops conducted. MeasureCycles = stats.Int64("kostanza/measures/cycles", "Iterations executed", stats.UnitDimensionless) // MeasureLag is the discrepancy between the ideal interval and actual interval between calculations. MeasureLag = stats.Float64("kostanza/measures/lag", "Lag time in calculation intervals", stats.UnitMilliseconds) )
var CPUPricingStrategy = PricingStrategyFunc(func(table CostTable, duration time.Duration, pods []*core_v1.Pod, nodes []*core_v1.Node) []CostItem { nm := buildNodeMap(nodes) cis := []CostItem{} for _, p := range pods { cpu := sumPodResource(p, core_v1.ResourceCPU) node, ok := nm[p.Spec.NodeName] if !ok { log.Log.Warnw("could not find nodeResourceMap for node", zap.String("nodeName", p.Spec.NodeName)) continue } te, err := table.FindByLabels(node.Labels) if err != nil { log.Log.Warnw("could not find pricing entry for node", zap.String("nodeName", node.ObjectMeta.Name)) continue } ci := CostItem{ Kind: ResourceCostCPU, Value: te.CPUCostMicroCents(float64(cpu), duration), Pod: p, Node: node, Strategy: StrategyNameCPU, } log.Log.Debugw( "generated cost item", zap.String("pod", ci.Pod.ObjectMeta.Name), zap.String("strategy", ci.Strategy), zap.Int64("value", ci.Value), ) cis = append(cis, ci) } return cis })
CPUPricingStrategy calculates the cost of a pod based strictly on it's share of CPU requests as a fraction of all CPU available on the node onto which it is allocated.
var ( // ErrNoCostEntry is returned when we cannot find a suitable CostEntry in a CostTable. ErrNoCostEntry = errors.New("could not find an appropriate cost entry") )
var GPUPricingStrategy = PricingStrategyFunc(func(table CostTable, duration time.Duration, pods []*core_v1.Pod, nodes []*core_v1.Node) []CostItem { nm := buildNodeMap(nodes) cis := []CostItem{} for _, p := range pods { gpu := sumPodResource(p, ResourceGPU) node, ok := nm[p.Spec.NodeName] if gpu == 0 { log.Log.Debugw("skipping pod that does not utilize gpu", zap.String("pod", p.ObjectMeta.Name)) continue } if !ok { log.Log.Warnw("could not find nodeResourceMap for node", zap.String("nodeName", p.Spec.NodeName)) continue } te, err := table.FindByLabels(node.Labels) if err != nil { log.Log.Warnw("could not find pricing entry for node", zap.String("nodeName", node.ObjectMeta.Name)) continue } ci := CostItem{ Kind: ResourceCostGPU, Value: te.GPUCostMicroCents(float64(gpu), duration), Pod: p, Node: node, Strategy: StrategyNameGPU, } log.Log.Debugw( "generated cost item", zap.String("pod", ci.Pod.ObjectMeta.Name), zap.String("strategy", ci.Strategy), zap.Int64("value", ci.Value), ) cis = append(cis, ci) } return cis })
GPUPricingStrategy generates cost metrics that account for the cost of GPUs consumed by pods.
var ( // MeasurePubsubPublishErrors tracks publishing errors in the PubsubCostExporter. MeasurePubsubPublishErrors = stats.Int64("kostanza/measures/pubsub_errors", "Number of pubsub publish error", stats.UnitDimensionless) )
var MemoryPricingStrategy = PricingStrategyFunc(func(table CostTable, duration time.Duration, pods []*core_v1.Pod, nodes []*core_v1.Node) []CostItem { nm := buildNodeMap(nodes) cis := []CostItem{} for _, p := range pods { mem := sumPodResource(p, core_v1.ResourceMemory) node, ok := nm[p.Spec.NodeName] if !ok { log.Log.Warnw("could not find nodeResourceMap for node", zap.String("nodeName", p.Spec.NodeName)) continue } te, err := table.FindByLabels(node.Labels) if err != nil { log.Log.Warnw("could not find pricing entry for node", zap.String("nodeName", node.ObjectMeta.Name)) continue } ci := CostItem{ Kind: ResourceCostMemory, Value: te.MemoryCostMicroCents(float64(mem), duration), Pod: p, Node: node, Strategy: StrategyNameMemory, } log.Log.Debugw( "generated cost item", zap.String("pod", ci.Pod.ObjectMeta.Name), zap.String("strategy", ci.Strategy), zap.Int64("value", ci.Value), ) cis = append(cis, ci) } return cis })
MemoryPricingStrategy calculates the cost of a pod based strictly on it's share of memory requests as a fraction of all memory on the node onto which it was scheduled.
var NodePricingStrategy = PricingStrategyFunc(func(table CostTable, duration time.Duration, pods []*core_v1.Pod, nodes []*core_v1.Node) []CostItem { cis := []CostItem{} for _, n := range nodes { te, err := table.FindByLabels(n.Labels) if err != nil { log.Log.Warnw("could not find pricing entry for node", zap.String("nodeName", n.ObjectMeta.Name)) continue } c := n.Status.Capacity.Cpu() if c == nil { log.Log.Warnw("could not get node cpu capacity, skipping", zap.String("nodeName", n.ObjectMeta.Name)) continue } m := n.Status.Capacity.Memory() if m == nil { log.Log.Warnw("could not get node memory capacity, skipping", zap.String("nodeName", n.ObjectMeta.Name)) continue } memcost := te.MemoryCostMicroCents(float64(m.MilliValue())/1000, duration) cpucost := te.CPUCostMicroCents(float64(c.MilliValue()), duration) gpucost := int64(0) if g := gpuCapacity(&n.Status.Capacity); g != nil { gpucost = te.GPUCostMicroCents(float64(g.Value()), duration) } ci := CostItem{ Kind: ResourceCostNode, Value: memcost + cpucost + gpucost, Node: n, Strategy: StrategyNameNode, } log.Log.Debugw( "generated cost item", zap.String("node", ci.Node.ObjectMeta.Name), zap.String("strategy", ci.Strategy), zap.Int64("value", ci.Value), ) cis = append(cis, ci) } return cis })
NodePricingStrategy generates cost metrics that represent the cost of an active node, regardless of pod. This is generally used to provide an overall cost metric that can be compared to per-pod costs.
var WeightedPricingStrategy = PricingStrategyFunc(func(table CostTable, duration time.Duration, pods []*core_v1.Pod, nodes []*core_v1.Node) []CostItem { nrm := buildNormalizedNodeResourceMap(pods, nodes) cis := []CostItem{} for _, p := range pods { cpu := sumPodResource(p, core_v1.ResourceCPU) mem := sumPodResource(p, core_v1.ResourceMemory) gpu := sumPodResource(p, ResourceGPU) nr, ok := nrm[p.Spec.NodeName] if !ok { log.Log.Warnw("could not find nodeResourceMap for node", zap.String("nodeName", p.Spec.NodeName)) continue } te, err := table.FindByLabels(nr.node.Labels) if err != nil { log.Log.Warnw("could not find pricing entry for node", zap.String("nodeName", nr.node.ObjectMeta.Name)) continue } cpucost := te.CPUCostMicroCents(float64(cpu)*nr.CPUScale(), duration) memcost := te.MemoryCostMicroCents(float64(mem)*nr.MemoryScale(), duration) gpucost := te.GPUCostMicroCents(float64(gpu)*nr.GPUScale(), duration) ci := CostItem{ Kind: ResourceCostWeighted, Value: cpucost + memcost + gpucost, Pod: p, Node: nr.node, Strategy: StrategyNameWeighted, } log.Log.Debugw( "generated cost item", zap.String("pod", ci.Pod.ObjectMeta.Name), zap.String("strategy", ci.Strategy), zap.Int64("value", ci.Value), ) cis = append(cis, ci) } return cis })
WeightedPricingStrategy calculates the cost of a pod based on it's average use of the CPU and Memory requests as a fraction of all CPU and memory requests on the node onto which it has been allocated. This strategy ensures that unallocated resources do not go unattributed and has a tendency to punish pods that may occupy oddly shaped resources or those that frequently churn.
Functions ¶
func NewKubernetesCoster ¶
func NewKubernetesCoster( interval time.Duration, config *Config, client kubernetes.Interface, prometheusExporter *prometheus.Exporter, listenAddr string, costExporters []CostExporter, ) (*coster, error)
NewKubernetesCoster returns a new coster that talks to a kubernetes cluster via the provided client.
func RunningPodFilter ¶
RunningPodFilter returns true if the Pod is running.
Types ¶
type BufferingCostExporter ¶
type BufferingCostExporter struct {
// contains filtered or unexported fields
}
BufferingCostExporter is an exporter that locally merges similarly dimensioned data on the client before emitting to other exporters.
func NewBufferingCostExporter ¶
func NewBufferingCostExporter(ctx context.Context, interval time.Duration, next CostExporter) (*BufferingCostExporter, error)
NewBufferingCostExporter returns a BufferingCostExporter that flushes on the provided interval. The backgrounded flush procedure can be cancelled by cancelling the provided context. On every interval we emit aggregated cost metrics to the provided `next` CostExporter.
func (*BufferingCostExporter) ExportCost ¶
func (bce *BufferingCostExporter) ExportCost(cd CostData)
ExportCost enqueues the CostData provided for subsequent emission to the next cost exporter. This serves to debounce repeated cost events and reduce load on the system.
type Config ¶
Config contains the configuration data necessary to drive the kubernetesCoster. It includes both mapping information to teach it how to derive metric dimensions from pod labels, as well as as a pricing table to instruct it how expensive nodes are.
type CostData ¶
type CostData struct { // The kind of cost figure represented. Kind ResourceCostKind // The strategy the yielded this CostItem. Strategy string // The value in microcents that it costs. Value int64 // Additional dimensions associated with the cost. Dimensions map[string]string // The interval for which this metric was created. EndTime time.Time }
CostData models pubsub-exported cost metadata.
func (*CostData) MarshalLogObject ¶
func (c *CostData) MarshalLogObject(enc zapcore.ObjectEncoder) error
MarshalLogObject exports CostData fields for the zap logger.
type CostDataKey ¶
type CostDataKey struct { // The kind of cost figure represented. Kind ResourceCostKind // The strategy the yielded this CostItem. Strategy string // Additional dimensions associated with the cost. Dimensions string }
CostDataKey groups related cost data. Note: this isn't very space efficient at the moment given the duplication between it and the CostDataRow. We could, for example use a hashing function instead but this ought to be friendly for debugging in the short term.
type CostExporter ¶
type CostExporter interface {
ExportCost(cd CostData)
}
CostExporter emits CostItems - for example, as a metric or to a third-party system.
type CostItem ¶
type CostItem struct { // The kind of cost figure represented. Kind ResourceCostKind // The strategy the yielded this CostItem. Strategy string // The value in microcents that it costs. Value int64 // Kubernetes pod metadata associated with the pod which we're pricing out. Pod *core_v1.Pod // Kubernetes pod metadata associated with the node which we're pricing out. Node *core_v1.Node }
CostItem models the metadata associated with a pod and/or node cost. Generally, this is subsequently utilized in order to emit an associated cost metric with dimensions derived from an appropriately configured Mapper.
type CostTable ¶
type CostTable struct {
Entries []*CostTableEntry
}
CostTable is a collection of CostTableEntries, generally used to look up pricing data via a set of labels provided callers of it's FindByLabels method. The order of of entries determines precedence of potentially multiple applicable matches.
func (*CostTable) FindByLabels ¶
func (ct *CostTable) FindByLabels(labels Labels) (*CostTableEntry, error)
FindByLabels returns the first matching CostTableEntry whose labels are a subset of those provided.
A CostTableEntry with labels:
{"size": "large", "region": usa"}
will match:
{"size": "large", "region": "usa"}
an will also match:
{"size": "large", "region": "usa", "foo": "bar"}
but will not match:
{"region": "usa"}
type CostTableEntry ¶
type CostTableEntry struct { Labels Labels HourlyMemoryByteCostMicroCents float64 HourlyMilliCPUCostMicroCents float64 HourlyGPUCostMicroCents float64 }
CostTableEntry models the cost of a nodes resources. The labels are used to identify nodes.
func (*CostTableEntry) CPUCostMicroCents ¶
func (e *CostTableEntry) CPUCostMicroCents(millicpu float64, duration time.Duration) int64
CPUCostMicroCents returns the cost of the provided cpu over a given duration in millionths of a cent.
func (*CostTableEntry) GPUCostMicroCents ¶
func (e *CostTableEntry) GPUCostMicroCents(gpus float64, duration time.Duration) int64
GPUCostMicroCents returns the cost of the provided number of gpus over a given duration in millionths of a cent.
func (*CostTableEntry) Match ¶
func (e *CostTableEntry) Match(labels Labels) bool
Match returns true if all of the CostTableEntry's labels match some subeset of the labels provided.
Additional labels can be used to increase the specificity of the selector and are generally useful for refining cost table configurations - e.g. from global, to per region pricing by using labels. For example, in GCP the following labels may be added to nodes in Kubernetes 1.11: - beta.kubernetes.io/instance-type: n1-standard-16 - failure-domain.beta.kubernetes.io/region: us-central1 - failure-domain.beta.kubernetes.io/zone: us-central1-b
Note: A special case of match against an empty list of labels will always match a CostTableEntry with no Labels.
func (*CostTableEntry) MemoryCostMicroCents ¶
func (e *CostTableEntry) MemoryCostMicroCents(membytes float64, duration time.Duration) int64
MemoryCostMicroCents returns the cost of the provided memory in bytes over a given duration in millionths of a cent.
type Coster ¶
Coster is used to calculate and emit metrics for services and components running in a kubernetes cluster.
type Mapper ¶
type Mapper struct {
Entries []Mapping
}
Mapper is a used to manage a set of mappings from source fields in a generic interface{} to a destination.
type Mapping ¶
Mapping models how to map a destination field from a source field within a kubernetes resource. The source is typically a jsonPath expression.
type PodFilters ¶
type PodFilters []PodFilter
PodFilters augments a slice of PodFilter functions with additional collection related functionality.
type PricingStrategy ¶
type PricingStrategy interface {
Calculate(t CostTable, duration time.Duration, pods []*core_v1.Pod, nodes []*core_v1.Node) []CostItem
}
PricingStrategy generates CostItems given the pods and nodes running in a cluster.
type PricingStrategyFunc ¶
type PricingStrategyFunc func(table CostTable, duration time.Duration, pods []*core_v1.Pod, nodes []*core_v1.Node) []CostItem
PricingStrategyFunc is an interface wrapper to convert a function into valid PricingStrategy.
func (PricingStrategyFunc) Calculate ¶
func (f PricingStrategyFunc) Calculate(table CostTable, duration time.Duration, pods []*core_v1.Pod, nodes []*core_v1.Node) []CostItem
Calculate returns CostItems given a pricing table of node costs, the duration we're costing out, and the pods as well as nodes running in a cluster.
type PubsubCostExporter ¶
type PubsubCostExporter struct {
// contains filtered or unexported fields
}
PubsubCostExporter emits data to pubsub.
func NewPubsubCostExporter ¶
func NewPubsubCostExporter(ctx context.Context, topic string, project string) (*PubsubCostExporter, error)
NewPubsubCostExporter creates a new PubsubCostExporter, instantiating an internal client against google cloud APIs.
func (*PubsubCostExporter) ExportCost ¶
func (pe *PubsubCostExporter) ExportCost(cd CostData)
ExportCost emits the CostItem to the PubsubCostExporter's configured pubsub topic.
type ResourceCostKind ¶
type ResourceCostKind string
ResourceCostKind is used to indidicate what resource a cost was derived from.
type StatsCostExporter ¶
type StatsCostExporter struct {
// contains filtered or unexported fields
}
StatsCostExporter emits metrics to a stats system.
func NewStatsCostExporter ¶
func NewStatsCostExporter(mapper *Mapper) *StatsCostExporter
NewStatsCostExporter returns a new StatsCostExporter.
func (*StatsCostExporter) ExportCost ¶
func (sce *StatsCostExporter) ExportCost(cd CostData)
ExportCost emits cost data to the stats system.