Skip to content

Commit

Permalink
Implemented Scheduled Capacity Metrics Producer
Browse files Browse the repository at this point in the history
  • Loading branch information
njtran committed Jan 21, 2021
1 parent ca82b4c commit 8373504
Show file tree
Hide file tree
Showing 8 changed files with 218 additions and 466 deletions.
63 changes: 63 additions & 0 deletions docs/examples/scheduled-capacity.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
apiVersion: autoscaling.karpenter.sh/v1alpha1
kind: MetricsProducer
metadata:
name: scheduling
spec:
scheduleSpec:
timezone: America/Los_Angeles
defaultReplicas: 1
behaviors:
# leaving hours/minutes empty == specifying 0
# leaving every other field empty == *
# single value (non-replicas) int fields need quotations
# Scale way down for the weekend
- replicas: 2
start:
weekdays: fri
hours: "17"
end:
weekdays: mon
hours: "9"
# Scale way up for higher traffic for weekdays during work hours
- replicas: 6
start:
weekdays: mon,tue,wed,thu,fri
hours: "9"
end:
weekdays: Mon,Tue,Wed,Thu,Fri
hours: "17"
# Scale a little down for lower traffic for weekdays, but not as much as on the weekends
- replicas: 4
start:
weekdays: Mon,Tue,Wed,Thu,Fri
hours: "17"
end:
weekdays: Mon,Tue,Wed,Thu,Fri
hours: "9"
---
apiVersion: autoscaling.karpenter.sh/v1alpha1
kind: HorizontalAutoscaler
metadata:
name: scheduled-autoscaler
spec:
scaleTargetRef:
apiVersion: autoscaling.karpenter.sh/v1alpha1
kind: ScalableNodeGroup
name: scheduled-nodegroup
minReplicas: 1
maxReplicas: 10
metrics:
- prometheus:
query: karpenter_scheduled_capacity_recommendation{name="scheduling"}
target:
type: AverageValue
value: 1
---
apiVersion: autoscaling.karpenter.sh/v1alpha1
kind: ScalableNodeGroup
metadata:
name: scheduled-nodegroup
spec:
replicas: 1
type: AWSEKSNodeGroup
id: arn:aws:eks:us-west-2:112358132134:nodegroup/fibonacci/demo/qwertyuiop
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ require (
github.com/prometheus/client_golang v1.8.0
github.com/prometheus/common v0.14.0
github.com/robfig/cron/v3 v3.0.0
github.com/willf/bitset v1.1.11
go.uber.org/multierr v1.6.0
go.uber.org/zap v1.16.0
k8s.io/api v0.19.3
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,6 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/willf/bitset v1.1.11 h1:N7Z7E9UvjW+sGsEl7k/SJrvY2reP1A07MrGuCjIOjRE=
github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down
4 changes: 1 addition & 3 deletions pkg/apis/autoscaling/v1alpha1/metricsproducer.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,13 @@ type PendingCapacitySpec struct {
NodeSelector map[string]string `json:"nodeSelector"`
}

type Timezone string

type ScheduleSpec struct {
// Behaviors may be layered to achieve complex scheduling autoscaling logic
Behaviors []ScheduledBehavior `json:"behaviors"`
// Defaults to UTC. Users will specify their schedules assuming this is their timezone
// ref: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
// +optional
Timezone *Timezone `json:"timezone,omitempty"`
Timezone *string `json:"timezone,omitempty"`
// A schedule defaults to this value when no behaviors are active
DefaultReplicas int32 `json:"defaultReplicas"`
}
Expand Down
6 changes: 4 additions & 2 deletions pkg/apis/autoscaling/v1alpha1/metricsproducer_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,15 @@ type QueueStatus struct {

type ScheduledCapacityStatus struct {
// The current recommendation - the metric the MetricsProducer is emitting
CurrentValue *int32 `json:"currentValue,omitempty"`
CurrentValue int32 `json:"currentValue,omitempty"`

// Not Currently Implemented
// The time in the future where CurrentValue will switch to NextValue
NextValueTime *apis.VolatileTime `json:"nextValueTime,omitempty"`

// Not Currently Implemented
// The next recommendation for the metric
NextValue *int32 `json:"nextValue,omitempty"`
NextValue int32 `json:"nextValue,omitempty"`
}

// We use knative's libraries for ConditionSets to manage status conditions.
Expand Down
25 changes: 25 additions & 0 deletions pkg/metrics/producers/scheduledcapacity/gauges.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package scheduledcapacity

import "github.com/awslabs/karpenter/pkg/metrics"

const (
Subsystem = "scheduled_capacity"
)

func init() {
metrics.RegisterNewGauge(Subsystem, "recommendation")
}
65 changes: 65 additions & 0 deletions pkg/metrics/producers/scheduledcapacity/producer.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ limitations under the License.
package scheduledcapacity

import (
"fmt"
"github.com/awslabs/karpenter/pkg/apis/autoscaling/v1alpha1"
"github.com/awslabs/karpenter/pkg/metrics"
"time"
)

// Producer implements the ScheduledCapacity metric
Expand All @@ -25,5 +28,67 @@ type Producer struct {

// Reconcile of the metrics
func (p *Producer) Reconcile() error {
var (
loc *time.Location
err error
currVal int32
)
matchFound := false
now := time.Now()

if p.Spec.Schedule.Timezone != nil {
loc, err = time.LoadLocation(*p.Spec.Schedule.Timezone)
if err != nil {
return fmt.Errorf("timezone was not a valid input")
}
} else {
// Set Default location to UTC if not specified
loc = time.UTC
}
now = now.In(loc)

F:
for _, behavior := range p.Spec.Schedule.Behaviors {
// Turn Strongly-typed into crontab
crontabStart := parseScheduleSpecIntoCrontab(behavior.Start)
crontabEnd := parseScheduleSpecIntoCrontab(behavior.End)

// use Cron library to find the next time start and end next match
startTime, err := nextTime(crontabStart, loc)
if err != nil {
return fmt.Errorf("start crontab could not be parsed: %w", err)
}
endTime, err := nextTime(crontabEnd, loc)
if err != nil {
return fmt.Errorf("end crontab could not be parsed: %w", err)
}

switch {
case endTime.After(startTime) && startTime.After(now):
continue
case !now.After(endTime):
// Since the way collisions are handled are by how they're ordered in the spec, stop on first match
currVal = behavior.Replicas
matchFound = true
break F
default:
continue
}
}
if !matchFound {
currVal = p.Spec.Schedule.DefaultReplicas
}

p.Status.ScheduledCapacity = &v1alpha1.ScheduledCapacityStatus{
CurrentValue: currVal,
}

p.record()
return nil
}

func (p *Producer) record() {
metrics.Gauges[Subsystem]["recommendation"].
WithLabelValues(p.Name, p.Namespace).
Set(float64(p.Status.ScheduledCapacity.CurrentValue))
}
Loading

0 comments on commit 8373504

Please sign in to comment.