-
Notifications
You must be signed in to change notification settings - Fork 0
/
spot_maker.rb
161 lines (138 loc) · 4.73 KB
/
spot_maker.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
require 'dotenv'
Dotenv.load
require 'aws-sdk'
require 'date'
require 'byebug'
require 'securerandom'
class SpotMaker
begin
def initialize
# this decides how many instances need to run and/or start. this is the denominator
# of the ratio -> backlog / wip
JOBS_RATIO_DENOMINATOR = 10
IAM_FLEET_ROLE = 'arn:aws:iam::828660616807:role/render-man_fleet_request'
poll
end
def poll
loop do
sleep 5
run_program(adjusted_birth_ratio) if birth_ratio_acheived? || true
end
end
def birth_ratio_acheived?
birth_ratio >= JOBS_RATIO_DENOMINATOR
end
def birth_ratio
counts = [backlog_address, wip_address].map do |board|
sqs.get_queue_attributes(
queue_url: board,
attribute_names: ['ApproximateNumberOfMessages']
).attributes['ApproximateNumberOfMessages'].to_f
end
backlog = counts.first
wip = counts.last
wip = wip == 0.0 ? 1.0 : wip # guards against irrational values
backlog / wip
end
def adjusted_birth_ratio
adjusted_ratio = (birth_ratio / JOBS_RATIO_DENOMINATOR).floor
adjusted_ratio == 0 ? 1 : adjusted_ratio
end
def run_program(desired_instance_count)
ec2.request_spot_fleet(
dry_run: true,
spot_fleet_request_config: slave_fleet_params(desired_instance_count)
)
poll
end
def slave_fleet_params(instance_count)
bp = best_price # only want the method to run once
price = bp[:spot_price]
availability_zone = bp[:availability_zone]
{
client_token: "RenderSlave-#{SecureRandom.hex}",
spot_price: price, # '0.12',
target_capacity: instance_count,
terminate_instances_with_expiration: true,
iam_fleet_role: IAM_FLEET_ROLE,
launch_specifications: slave_fleet_launch_specifications(availability_zone)
}
end
def best_price(image = slave_image)
best_match = spot_prices.each.map(&:spot_price_history).
flatten.map do |sph|
{
spot_price: sph.spot_price,
availability_zone: sph.availability_zone,
instance_type: sph.instance_type
}
end.min_by { |sp| sp[:price] }
# make sure this markup in max spot price is wanted
best_match[:spot_price] = (best_match[:spot_price].to_f +
(best_match[:spot_price].to_f*0.15)).round(3).to_s
best_match
end
def spot_prices
@spot_prices = []
if @spot_prices.empty?
availability_zones.each do |az|
@spot_prices << ec2.describe_spot_price_history(
spot_price_history_params(az)
)
end
end
@spot_prices # this is a hash with the availability zone, instance type and recommended bid
end
# not caching for the same reason as slave_fleet_params (allows config via aws console via ami tags)
def slave_fleet_launch_specifications(availability_zone)
slave_image_tag_filter('instance_types').map do |inst_type|
{
image_id: slave_image.image_id,
key_name: 'RenderSlave',
instance_type: inst_type,
monitoring: { enabled: true },
placement: { availability_zone: availability_zone }
}
end
end
def slave_image_tag_filter(tag_name)
slave_image.tags.find { |t| t.key.include?(tag_name) }.value.split(',')
end
def slave_image
@slave_image ||= ec2.describe_images(
filters: [{ name: 'tag:Name', values: ['RenderSlave'] }]
).images.first
end
def availability_zones
@az ||= ec2.describe_availability_zones.
availability_zones.map(&:zone_name)
end
def spot_price_history_params(availability_zone)
{
start_time: (Time.now + 36000).iso8601.to_s,
instance_types: slave_image_tag_filter('instance_types'),#s.select { |t| t.key.eql? }.first.value.split(','),
product_descriptions: slave_image_tag_filter('product_descriptions'),#slave_image.tags.select{ |t| t.key.eql? 'product_descriptions' }.first.value.split(','),
availability_zone: availability_zone
}
end
def creds
@creds ||= Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'])
end
def ec2
@ec2 ||= Aws::EC2::Client.new(region: 'us-west-2', credentials: creds)
end
def sqs
@sqs ||= Aws::SQS::Client.new(credentials: creds)
end
def backlog_address
'https://sqs.us-west-2.amazonaws.com/088617881078/backlog_smashanalytics_sqs'
end
def wip_address
'https://sqs.us-west-2.amazonaws.com/088617881078/wip_smashanalytics_sqs'
end
rescue => e
puts e
kill_everything
end
end
SpotMaker.new