Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added SpotInstance Percentage Multiplier with dynamic tags #175

Merged
merged 1 commit into from
Dec 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions START.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ module "autospotting" {
autospotting_min_on_demand_percentage = "50.0"
autospotting_regions_enabled = "eu*,us*"
on_demand_price_multiplier = "1.0"
spot_price_buffer_percentage = "10.0"
bidding_policy = "normal"
lambda_zipname = "./lambda.zip"
lambda_runtime = "python2.7"
Expand Down Expand Up @@ -224,6 +226,17 @@ Usage of ./autospotting:
or if you want to set your bid price to be higher than the on demand
price to reduce the chances that your spot instances will be terminated.
(default 1)
-spot_price_buffer_percentage float
Percentage Value of the bid above the current spot price. A spot
bid would be placed at a value
current_spot_price * [1 + (spot_price_buffer_percentage/100.0)] .
The main benefit is that it protects the group from running spot instances
that got significantly more expensive than when they were initially launched,
but still somewhat less than the on-demand price. (default 10.0)
-bidding_policy string
Policy choice for spot bid. If set to 'normal', we bid at the
on-demand price. If set to 'aggressive', we bid at a multiple of
the spot price. (default "normal")
-regions="": Regions where it should be activated (comma or whitespace separated
list, also supports globs), by default it runs on all regions.
Example: ./autospotting -regions 'eu-*,us-east-1'
Expand Down
Binary file added autospotting
Binary file not shown.
20 changes: 18 additions & 2 deletions autospotting.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,16 @@ func run() {
"min_on_demand_number=%d "+
"min_on_demand_percentage=%.1f "+
"allowed_instance_types=%v "+
"on_demand_price_multiplier=%.2f",
"on_demand_price_multiplier=%.2f"+
"spot_price_buffer_percentage=%.3f"+
"bidding_policy=%s",
conf.Regions,
conf.MinOnDemandNumber,
conf.MinOnDemandPercentage,
conf.AllowedInstanceTypes,
conf.OnDemandPriceMultiplier)
conf.OnDemandPriceMultiplier,
conf.SpotPriceBufferPercentage,
conf.BiddingPolicy)

autospotting.Run(conf.Config)
log.Println("Execution completed, nothing left to do")
Expand Down Expand Up @@ -124,6 +128,18 @@ func (c *cfgData) parseCommandLineFlags() {
"\tset your bid price to be higher than the on demand price to reduce the chances that your\n"+
"\tspot instances will be terminated.")

flag.Float64Var(&c.SpotPriceBufferPercentage, "spot_price_buffer_percentage", 10,
"Percentage Value of the bid above the current spot price. A spot bid would be placed at a value :\n"+
"\tcurrent_spot_price * [1 + (spot_price_buffer_percentage/100.0)]. The main benefit is that\n"+
"\tit protects the group from running spot instances that got significantly more expensive than\n"+
"\twhen they were initially launched, but still somewhat less than the on-demand price. Can be\n"+
"\tenforced using the tag: "+autospotting.SpotPriceBufferPercentageTag+". If the bid exceeds\n"+
"\tthe on-demand price, we place a bid at on-demand price itself.")

flag.StringVar(&c.BiddingPolicy, "bidding_policy", "normal",
"Policy choice for spot bid. If set to 'normal', we bid at the on-demand price. If set to 'aggressive',\n"+
"\twe bid at a percentage value above the spot price. ")

v := flag.Bool("version", false, "Print version number and exit.")

flag.Parse()
Expand Down
12 changes: 12 additions & 0 deletions cloudformation/stacks/AutoSpotting/template.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@
"Description": "Multiplier for the on-demand price. This is useful for volume discounts or if you want to set your bid price to be higher than the on demand price to reduce the chances that your spot instances will be terminated.",
"Type": "Number"
},
"SpotPricePercentageBuffer": {
"Default": "10.0",
"Description": "Percentage Value of the bid above the current spot price. A spot bid would be placed at a value = current_spot_price * [1 + (spot_price_buffer_percentage/100.0)]. The main benefit is that it protects the group from running spot instances that got significantly more expensive than when they were initially launched, but still somewhat less than the on-demand price.",
"Type": "Number"
},
"BiddingPolicy": {
"Default": "normal",
"Description": "Policy choice for spot bid. If set to 'normal', we bid at the on-demand price. If set to 'aggressive', we bid at a multiple of the spot price.",
"Type": "String"
},
"Regions": {
"Default": "",
"Description": "Space separated list of regions where it should run (supports globs), in case you may want to limit it to a smaller set of regions. If unset it will run against all available regions. Example: 'us-east-1 eu-*'",
Expand Down Expand Up @@ -88,6 +98,8 @@
"MIN_ON_DEMAND_NUMBER": { "Ref": "MinOnDemandNumber" },
"MIN_ON_DEMAND_PERCENTAGE": { "Ref": "MinOnDemandPercentage" },
"ON_DEMAND_PRICE_MULTIPLIER": { "Ref": "OnDemandPriceMultiplier" },
"SPOT_PRICE_BUFFER_PERCENTAGE": { "Ref": "SpotPricePercentageBuffer"},
"BIDDING_POLICY": { "Ref": "BiddingPolicy"},
"REGIONS": { "Ref": "Regions" },
"ALLOWED_INSTANCE_TYPES": { "Ref": "AllowedInstanceTypes" }
}
Expand Down
120 changes: 110 additions & 10 deletions core/autoscaling.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ package autospotting

import (
"errors"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/ec2"
"math"
"strconv"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/autoscaling"
"github.com/aws/aws-sdk-go/service/ec2"
)

const (
Expand All @@ -23,9 +22,24 @@ const (
// absolute number.
OnDemandNumberLong = "autospotting_on_demand_number"

// BiddingPolicyTag stores the bidding policy for the spot instance
BiddingPolicyTag = "autospotting_bidding_policy"

// SpotPriceBufferPercentageTag stores percentage value above the
// current spot price to place the bid
SpotPriceBufferPercentageTag = "spot_price_buffer_percentage"

// DefaultMinOnDemandValue stores the default on-demand capacity to be kept
// running in a group managed by autospotting.
DefaultMinOnDemandValue = 0

// DefaultSpotPriceBufferPercentage stores the default percentage value
// above the current spot price to place a bid
DefaultSpotPriceBufferPercentage = 10.0

// DefaultBiddingPolicy stores the default bidding policy for
// the spot bid on a per-group level
DefaultBiddingPolicy = "normal"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think those new variables (except SpotPriceBufferPercentageTag) don't need to be exported.
It's most likely true for other older one as well, would need to be corrected. (Opened https://github.com/cristim/autospotting/issues/186)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I leave it as is in this pull request?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe it's fine like this, I'll pass on those later on

)

type autoScalingGroup struct {
Expand Down Expand Up @@ -60,6 +74,21 @@ func (a *autoScalingGroup) loadPercentageOnDemand(tagValue *string) (int64, bool
return DefaultMinOnDemandValue, false
}

func (a *autoScalingGroup) loadSpotPriceBufferPercentage(tagValue *string) (float64, bool) {
spotPriceBufferPercentage, err := strconv.ParseFloat(*tagValue, 64)

if err != nil {
logger.Printf("Error with ParseFloat: %s\n", err.Error())
return DefaultSpotPriceBufferPercentage, false
} else if spotPriceBufferPercentage <= 0 {
logger.Printf("Ignoring out of range value : %f\n", spotPriceBufferPercentage)
return DefaultSpotPriceBufferPercentage, false
}

logger.Printf("Loaded SpotPriceBufferPercentage value to %f from tag %s\n", spotPriceBufferPercentage, SpotPriceBufferPercentageTag)
return spotPriceBufferPercentage, true
}

func (a *autoScalingGroup) loadNumberOnDemand(tagValue *string) (int64, bool) {
onDemand, err := strconv.Atoi(*tagValue)
if err != nil {
Expand Down Expand Up @@ -88,18 +117,72 @@ func (a *autoScalingGroup) loadConfOnDemand() bool {
return done
}
}
} else {
debug.Println("Couldn't find tag", tagKey)
}
debug.Println("Couldn't find tag", tagKey)
}
return false
}

func (a *autoScalingGroup) loadBiddingPolicy(tagValue *string) (string, bool) {
biddingPolicy := *tagValue
if biddingPolicy != "aggressive" {
return DefaultBiddingPolicy, false
}

logger.Printf("Loaded BiddingPolicy value with %s from tag %s\n", biddingPolicy, BiddingPolicyTag)
return biddingPolicy, true
}

func (a *autoScalingGroup) loadConfSpot() bool {
tagValue := a.getTagValue(BiddingPolicyTag)
if tagValue == nil {
debug.Println("Couldn't find tag", BiddingPolicyTag)
return false
}
if newValue, done := a.loadBiddingPolicy(tagValue); done {
a.region.conf.BiddingPolicy = newValue
logger.Println("BiddingPolicy =", a.region.conf.BiddingPolicy)
return done
}
return false
}

func (a *autoScalingGroup) loadConfSpotPrice() bool {

tagValue := a.getTagValue(SpotPriceBufferPercentageTag)
if tagValue == nil {
return false
}

newValue, done := a.loadSpotPriceBufferPercentage(tagValue)
if !done {
debug.Println("Couldn't find tag", SpotPriceBufferPercentageTag)
return false
}

a.region.conf.SpotPriceBufferPercentage = newValue
return done
}

// Add configuration of other elements here: prices, whitelisting, etc
func (a *autoScalingGroup) loadConfigFromTags() bool {

if a.loadConfOnDemand() {
resOnDemandConf := a.loadConfOnDemand()

resSpotConf := a.loadConfSpot()

resSpotPriceConf := a.loadConfSpotPrice()

if resOnDemandConf {
logger.Println("Found and applied configuration for OnDemand value")
}
if resSpotConf {
logger.Println("Found and applied configuration for Spot Bid")
}
if resSpotPriceConf {
logger.Println("Found and applied configuration for Spot Price")
}
if resOnDemandConf || resSpotConf || resSpotPriceConf {
return true
}
return false
Expand Down Expand Up @@ -131,6 +214,10 @@ func (a *autoScalingGroup) loadDefaultConfig() bool {
done := false
a.minOnDemand = DefaultMinOnDemandValue

if a.region.conf.SpotPriceBufferPercentage <= 0 {
a.region.conf.SpotPriceBufferPercentage = DefaultSpotPriceBufferPercentage
}

if a.region.conf.MinOnDemandNumber != 0 {
a.minOnDemand, done = a.loadDefaultConfigNumber()
}
Expand Down Expand Up @@ -536,6 +623,20 @@ func (a *autoScalingGroup) getAllowedInstanceTypes(baseInstance *instance) []str

}

func (a *autoScalingGroup) getPricetoBid(
baseOnDemandPrice float64, currentSpotPrice float64) float64 {

logger.Println("BiddingPolicy: ", a.region.conf.BiddingPolicy)

if a.region.conf.BiddingPolicy == DefaultBiddingPolicy {
logger.Println("Launching spot instance with a bid =", baseOnDemandPrice)
return baseOnDemandPrice
}

logger.Println("Launching spot instance with a bid =", math.Min(baseOnDemandPrice, currentSpotPrice*(1.0+a.region.conf.SpotPriceBufferPercentage/100.0)))
return math.Min(baseOnDemandPrice, currentSpotPrice*(1.0+a.region.conf.SpotPriceBufferPercentage/100.0))
}

func (a *autoScalingGroup) launchCheapestSpotInstance(
azToLaunchIn *string) error {

Expand Down Expand Up @@ -569,12 +670,11 @@ func (a *autoScalingGroup) launchCheapestSpotInstance(
baseOnDemandPrice := baseInstance.price

currentSpotPrice := newInstance.pricing.spot[*azToLaunchIn]

logger.Println("Finished searching for best spot instance in ", *azToLaunchIn)
logger.Println("Replacing an on-demand", *baseInstance.InstanceType,
"instance having the ondemand price", baseOnDemandPrice)
logger.Println("Launching best compatible instance:", newInstanceType,
"with current spot price:", currentSpotPrice)
"with the current spot price:", currentSpotPrice)

lc := a.getLaunchConfiguration()

Expand All @@ -584,7 +684,7 @@ func (a *autoScalingGroup) launchCheapestSpotInstance(
*azToLaunchIn)

logger.Println("Bidding for spot instance for ", a.name)
return a.bidForSpotInstance(spotLS, baseOnDemandPrice)
return a.bidForSpotInstance(spotLS, a.getPricetoBid(baseOnDemandPrice, currentSpotPrice))
}

func (a *autoScalingGroup) loadSpotInstanceRequest(
Expand Down
Loading