Skip to content

Commit

Permalink
feat: Add support for spot instances
Browse files Browse the repository at this point in the history
  • Loading branch information
AndreZiviani committed Mar 18, 2023
1 parent a7e9a0f commit 17e19a7
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 32 deletions.
45 changes: 38 additions & 7 deletions exporter/ondemand.go → exporter/ec2.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func (m *Metrics) GetInstances(ctx context.Context) {

m.getInstances(ctx)
m.GetOnDemandPricing(ctx)
m.GetSpotPricing(ctx)
}

func (m *Metrics) getInstances(ctx context.Context) {
Expand All @@ -47,10 +48,11 @@ func (m *Metrics) getInstances(ctx context.Context) {
}
for _, instance := range instances.InstanceTypes {
m.Instances[string(instance.InstanceType)] = &Instance{
Memory: aws.ToInt64(instance.MemoryInfo.SizeInMiB),
VCpu: aws.ToInt32(instance.VCpuInfo.DefaultVCpus),
Kind: "ec2",
Type: string(instance.InstanceType),
Memory: aws.ToInt64(instance.MemoryInfo.SizeInMiB),
VCpu: aws.ToInt32(instance.VCpuInfo.DefaultVCpus),
Type: string(instance.InstanceType),
OnDemandCost: &Ec2Cost{},
SpotCost: make(map[string]*Ec2Cost, 0),
}
}
}
Expand Down Expand Up @@ -133,11 +135,40 @@ func (m *Metrics) GetOnDemandPricing(ctx context.Context) {

vcpu, memory := m.getNormalizedCost(value, tmp.Product.Attributes["instanceType"])

m.Instances[tmp.Product.Attributes["instanceType"]].Cost = value
m.Instances[tmp.Product.Attributes["instanceType"]].VCpuCost = vcpu
m.Instances[tmp.Product.Attributes["instanceType"]].MemoryCost = memory
m.Instances[tmp.Product.Attributes["instanceType"]].OnDemandCost.Type = "ondemand"
m.Instances[tmp.Product.Attributes["instanceType"]].OnDemandCost.Total = value
m.Instances[tmp.Product.Attributes["instanceType"]].OnDemandCost.VCpu = vcpu
m.Instances[tmp.Product.Attributes["instanceType"]].OnDemandCost.Memory = memory

}

}
}

func (m *Metrics) GetSpotPricing(ctx context.Context) {
config := m.awsconfig

ec2Svc := ec2.NewFromConfig(config)

pag := ec2.NewDescribeSpotPriceHistoryPaginator(
ec2Svc,
&ec2.DescribeSpotPriceHistoryInput{
StartTime: aws.Time(time.Now()),
ProductDescriptions: []string{"Linux/UNIX"},
})

for pag.HasMorePages() {
history, err := pag.NextPage(ctx)
if err != nil {
panic(err.Error())
}

for _, price := range history.SpotPriceHistory {
value, _ := strconv.ParseFloat(*price.SpotPrice, 64)

vcpu, memory := m.getNormalizedCost(value, string(price.InstanceType))

m.Instances[string(price.InstanceType)].SpotCost[aws.ToString(price.AvailabilityZone)] = &Ec2Cost{Type: "spot", Total: value, VCpu: vcpu, Memory: memory}
}
}
}
10 changes: 7 additions & 3 deletions exporter/fargate.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (m *Metrics) GetFargatePricing(ctx context.Context) {

pricingSvc := pricing.NewFromConfig(config)

m.Instances["fargate"] = &Instance{Type: "fargate", Kind: "fargate"}
m.Instances["fargate"] = &Instance{Type: "fargate"}

pag := pricing.NewGetProductsPaginator(
pricingSvc,
Expand Down Expand Up @@ -62,10 +62,14 @@ func (m *Metrics) GetFargatePricing(ctx context.Context) {
value, _ := strconv.ParseFloat(tmp.Terms.OnDemand[skuOnDemand].PriceDimensions[skuOnDemandPerHour].PricePerUnit["USD"], 64)

description := tmp.Terms.OnDemand[skuOnDemand].PriceDimensions[skuOnDemandPerHour].Description

if m.Instances["fargate"].OnDemandCost == nil {
m.Instances["fargate"].OnDemandCost = &Ec2Cost{Type: "fargate"}
}
if strings.Contains(description, "AWS Fargate - vCPU - ") {
m.Instances["fargate"].VCpuCost = value
m.Instances["fargate"].OnDemandCost.VCpu = value
} else if strings.Contains(description, "AWS Fargate - Memory - ") {
m.Instances["fargate"].MemoryCost = value
m.Instances["fargate"].OnDemandCost.Memory = value
}
}
}
Expand Down
24 changes: 16 additions & 8 deletions exporter/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func (m *Metrics) podCreated(obj interface{}) {

resources := m.mergeResources(pod.Spec.Containers)
if m.Nodes[pod.Spec.NodeName] != nil {
if m.Nodes[pod.Spec.NodeName].Instance.Kind == "fargate" {
if m.Nodes[pod.Spec.NodeName].Instance.Type == "fargate" {
// fargate allocates more resources than requested and charges accordingly
// the allocation size is exposed as an annotation
// https://docs.aws.amazon.com/eks/latest/userguide/fargate-pod-configuration.html
Expand Down Expand Up @@ -220,11 +220,19 @@ func (m *Metrics) nodeCreated(obj interface{}) {
}

if _, ok := node.ObjectMeta.Labels["node.kubernetes.io/instance-type"]; ok {
// EC2
tmp.Instance = m.Instances[node.ObjectMeta.Labels["node.kubernetes.io/instance-type"]]
} else if _, ok := node.Labels["eks.amazonaws.com/compute-type"]; ok {
if node.Labels["eks.amazonaws.com/compute-type"] == "fargate" {
tmp.Instance = m.Instances["fargate"]

if _, ok := node.ObjectMeta.Labels["karpenter.sh/capacity-type"]; ok && node.ObjectMeta.Labels["karpenter.sh/capacity-type"] == "spot" {
// Node managed by Karpenter and is Spot
tmp.Cost = tmp.Instance.SpotCost[tmp.AZ]
} else {
tmp.Cost = tmp.Instance.OnDemandCost
}
} else if _, ok := node.Labels["eks.amazonaws.com/compute-type"]; ok && node.Labels["eks.amazonaws.com/compute-type"] == "fargate" {
// Fargate
tmp.Instance = m.Instances["fargate"]
tmp.Cost = tmp.Instance.OnDemandCost
}

m.nodesMtx.Lock()
Expand Down Expand Up @@ -290,12 +298,12 @@ func (m *Metrics) updatePodCost(pod *Pod) {
}

// convert bytes to GB
pod.MemoryCost = float64(pod.Usage.Memory.Value()) / 1024 / 1024 / 1024 * pod.Node.Instance.MemoryCost
pod.MemoryRequestsCost = float64(pod.Resources.Memory.Value()) / 1024 / 1024 / 1024 * pod.Node.Instance.MemoryCost
pod.MemoryCost = float64(pod.Usage.Memory.Value()) / 1024 / 1024 / 1024 * pod.Node.Cost.Memory
pod.MemoryRequestsCost = float64(pod.Resources.Memory.Value()) / 1024 / 1024 / 1024 * pod.Node.Cost.Memory

//convert millicore to core
pod.VCpuCost = float64(pod.Usage.Cpu.MilliValue()) / 1000 * pod.Node.Instance.VCpuCost
pod.VCpuRequestsCost = float64(pod.Resources.Cpu.MilliValue()) / 1000 * pod.Node.Instance.VCpuCost
pod.VCpuCost = float64(pod.Usage.Cpu.MilliValue()) / 1000 * pod.Node.Cost.VCpu
pod.VCpuRequestsCost = float64(pod.Resources.Cpu.MilliValue()) / 1000 * pod.Node.Cost.VCpu

pod.Cost = max(pod.MemoryCost, pod.MemoryRequestsCost) + max(pod.VCpuCost, pod.VCpuRequestsCost)
}
Expand Down
14 changes: 7 additions & 7 deletions exporter/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,15 @@ func (m *Metrics) Collect(ch chan<- prometheus.Metric) {
m.podsMtx.Lock()
m.GetUsageCost()

podLabels := []string{"pod", "namespace", "node", "kind", "type"}
podLabels := []string{"pod", "namespace", "node", "type", "lifecycle"}
if len(m.addPodLabels) > 0 {
for _, v := range m.addPodLabels {
podLabels = append(podLabels, sanitizeLabel(v))
}
}

for _, pod := range m.Pods {
podLabelValues := []string{pod.Name, pod.Namespace, pod.Node.Name, pod.Node.Instance.Kind, pod.Node.Instance.Type}
podLabelValues := []string{pod.Name, pod.Namespace, pod.Node.Name, pod.Node.Instance.Type, pod.Node.Cost.Type}
for _, l := range m.addPodLabels {
podLabelValues = append(podLabelValues, pod.Labels[l])
}
Expand Down Expand Up @@ -137,15 +137,15 @@ func (m *Metrics) Collect(ch chan<- prometheus.Metric) {
}
m.podsMtx.Unlock()

nodeLabels := []string{"node", "region", "az", "kind", "type"}
nodeLabels := []string{"node", "region", "az", "type", "lifecycle"}
if len(m.addNodeLabels) > 0 {
for _, v := range m.addNodeLabels {
nodeLabels = append(nodeLabels, sanitizeLabel(v))
}
}

for _, node := range m.Nodes {
nodeLabelValues := []string{node.Name, node.Region, node.AZ, node.Instance.Type, node.Instance.Kind}
nodeLabelValues := []string{node.Name, node.Region, node.AZ, node.Instance.Type, node.Cost.Type}
for _, l := range m.addNodeLabels {
nodeLabelValues = append(nodeLabelValues, node.Labels[l])
}
Expand All @@ -157,7 +157,7 @@ func (m *Metrics) Collect(ch chan<- prometheus.Metric) {
nodeLabels, nil,
),
prometheus.GaugeValue,
node.Instance.Cost,
node.Cost.Total,
nodeLabelValues...,
)

Expand All @@ -168,7 +168,7 @@ func (m *Metrics) Collect(ch chan<- prometheus.Metric) {
nodeLabels, nil,
),
prometheus.GaugeValue,
node.Instance.VCpuCost,
node.Cost.VCpu,
nodeLabelValues...,
)

Expand All @@ -179,7 +179,7 @@ func (m *Metrics) Collect(ch chan<- prometheus.Metric) {
nodeLabels, nil,
),
prometheus.GaugeValue,
node.Instance.MemoryCost,
node.Cost.Memory,
nodeLabelValues...,
)
}
Expand Down
21 changes: 14 additions & 7 deletions exporter/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,20 @@ type Metrics struct {
addNodeLabels []string
}

type Ec2Cost struct {
Type string
Total float64
VCpu float64
Memory float64
}

type Instance struct {
Kind string
Type string
VCpu int32
Memory int64
Cost float64
VCpuCost float64
MemoryCost float64
//Kind string
Type string
VCpu int32
Memory int64
OnDemandCost *Ec2Cost
SpotCost map[string]*Ec2Cost
}

type Pod struct {
Expand All @@ -62,6 +68,7 @@ type Node struct {
AZ string
Region string
Instance *Instance
Cost *Ec2Cost
}

type PodResources struct {
Expand Down

0 comments on commit 17e19a7

Please sign in to comment.