Skip to content

Commit fe1090b

Browse files
DavidGOrtegarestyled-io[bot]restyled-commits
authored
Spot instances (#23)
* Spot instances * spot_price * unused vars * Restyle Spot instances (#24) * Restyled by jq * Restyled by prettier-json * Restyled by whitespace Co-authored-by: Restyled.io <commits@restyled.io> * fix azure non spot * no examples * aws can not have negative price as azure * docs Co-authored-by: restyled-io[bot] <32688539+restyled-io[bot]@users.noreply.github.com> Co-authored-by: Restyled.io <commits@restyled.io>
1 parent 462f0cb commit fe1090b

File tree

7 files changed

+197
-122
lines changed

7 files changed

+197
-122
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.DS_Store
22
examples/.terraform
3+
examples
34
bin
45

56
# Local .terraform directories

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ terraform apply --auto-approve
141141
| `cloud` | `aws` `azure` | | Sets cloud vendor. |
142142
| `region` | `us-west` `us-east` `eu-west` `eu-north` | `us-west` | Sets the collocation region. AWS or Azure regions are also accepted. |
143143
| `image` | | `iterative-cml` in AWS `Canonical:UbuntuServer:18.04-LTS:latest` in Azure | Sets the image to be used. On AWS the provider does a search in the cloud provider by image name not by id, taking the lastest version in case there are many with the same name. Defaults to [iterative-cml image](#iterative-cml-image). On Azure uses the form Publisher:Offer:SKU:Version |
144+
| `spot`| boolean | false | If true launch a spot instance |
145+
| `spot_price`| float with 5 decimals at most | -1 | Sets the max price that you are willing to pay by the hour. If not specified it takes current spot bidding pricing |
144146
| `name` | | iterative\_{UID} | Sets the instance name and related resources based on that name. In Azure groups everything under a resource group with that name. |
145147
| `instance_hdd_size` | | 10 | Sets the instance hard disk size in gb |
146148
| `instance_type` | `m`, `l`, `xl` | `m` | Sets thee instance computing size. You can also specify vendor specific machines in AWS i.e. `t2.micro`. [See equivalences](#Supported-vendors) table below. |
@@ -260,6 +262,8 @@ terraform destroy --auto-approve
260262
| `region` | `us-west` `us-east` `eu-west` `eu-north` | `us-west` | Sets the collocation region. AWS or Azure regions are also accepted. |
261263
| `image` | | `iterative-cml` in AWS `Canonical:UbuntuServer:18.04-LTS:latest` in Azure | Sets the image to be used. On AWS the provider does a search in the cloud provider by image name not by id, taking the lastest version in case there are many with the same name. Defaults to [iterative-cml image](#iterative-cml-image). On Azure uses the form Publisher:Offer:SKU:Version |
262264
| `name` | | iterative\_{UID} | Sets the instance name and related resources based on that name. In Azure groups everything under a resource group with that name. |
265+
| `spot`| boolean | false | If true launch a spot instance |
266+
| `spot_price`| float with 5 decimals at most | -1 | Sets the max price that you are willing to pay by the hour. If not specified it takes current spot bidding pricing |
263267
| `instance_hdd_size` | | 10 | Sets the instance hard disk size in gb |
264268
| `instance_type` | `m`, `l`, `xl` | `m` | Sets thee instance computing size. You can also specify vendor specific machines in AWS i.e. `t2.micro`. [See equivalences](#Supported-vendors) table below. |
265269
| `instance_gpu` | ``, `testla`, `k80` | `` | Sets the desired GPU if the `instance_type` is one of our types. |

cml/ami.json

Lines changed: 41 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,44 @@
11
{
2-
"variables" : {
3-
"instance_type" : "{{env `AWS_INSTANCE_TYPE`}}"
4-
},
5-
"builders" : [
6-
{
7-
"type" : "amazon-ebs",
8-
"assume_role": {
9-
"role_arn": "arn:aws:iam::260760892802:role/dvc-cml-packer",
10-
"session_name": "cml-packer-session"
11-
},
12-
"region" : "us-west-1",
13-
"ami_name" : "iterative-cml",
14-
"ami_description" : "CML (Continous Machine Learning). Ubuntu 18.04",
15-
"ami_groups": ["all"],
16-
"force_deregister": "true",
17-
"force_delete_snapshot": "true",
18-
"ssh_username" : "ubuntu",
19-
"instance_type" : "g2.2xlarge",
20-
"source_ami_filter": {
21-
"filters": {
22-
"virtualization-type": "hvm",
23-
"name": "ubuntu/images/*ubuntu-*-18.04-amd64-server-*",
24-
"root-device-type": "ebs"
25-
},
26-
"owners": ["099720109477"],
27-
"most_recent": true
28-
},
29-
"run_tags" : {
30-
"Author" : "iterative"
31-
}
32-
}
33-
],
34-
"provisioners" : [
35-
{
36-
"type" : "shell",
37-
"environment_vars": ["DEBIAN_FRONTEND=noninteractive"],
38-
"script" : "./setup.sh"
2+
"builders": [
3+
{
4+
"type": "amazon-ebs",
5+
"assume_role": {
6+
"role_arn": "arn:aws:iam::260760892802:role/dvc-cml-packer",
7+
"session_name": "cml-packer-session"
8+
},
9+
"region": "us-west-1",
10+
"ami_name": "iterative-cml",
11+
"ami_description": "CML (Continous Machine Learning). Ubuntu 18.04",
12+
"ami_groups": ["all"],
13+
"force_deregister": "true",
14+
"force_delete_snapshot": "true",
15+
"ssh_username": "ubuntu",
16+
"instance_type": "g2.2xlarge",
17+
"source_ami_filter": {
18+
"filters": {
19+
"virtualization-type": "hvm",
20+
"name": "ubuntu/images/*ubuntu-*-18.04-amd64-server-*",
21+
"root-device-type": "ebs"
3922
},
40-
{
41-
"type": "shell",
42-
"inline": [
43-
"sudo shutdown -r now",
44-
"sleep 60"
45-
],
46-
"start_retry_timeout": "10m",
47-
"expect_disconnect": true
48-
}
49-
]
23+
"owners": ["099720109477"],
24+
"most_recent": true
25+
},
26+
"run_tags": {
27+
"Author": "iterative"
28+
}
29+
}
30+
],
31+
"provisioners": [
32+
{
33+
"type": "shell",
34+
"environment_vars": ["DEBIAN_FRONTEND=noninteractive"],
35+
"script": "./setup.sh"
36+
},
37+
{
38+
"type": "shell",
39+
"inline": ["sudo shutdown -r now", "sleep 60"],
40+
"start_retry_timeout": "10m",
41+
"expect_disconnect": true
42+
}
43+
]
5044
}

iterative/aws/provider.go

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"errors"
66
"sort"
7+
"strconv"
78
"time"
89

910
"github.com/aws/aws-sdk-go/aws"
@@ -23,6 +24,8 @@ func ResourceMachineCreate(ctx context.Context, d *schema.ResourceData, m interf
2324
ami := d.Get("image").(string)
2425
keyPublic := d.Get("ssh_public").(string)
2526
securityGroup := d.Get("aws_security_group").(string)
27+
spot := d.Get("spot").(bool)
28+
spotPrice := d.Get("spot_price").(float64)
2629
if ami == "" {
2730
ami = "iterative-cml"
2831
}
@@ -139,35 +142,76 @@ func ResourceMachineCreate(ctx context.Context, d *schema.ResourceData, m interf
139142
return errors.New("no subnets found")
140143
}
141144

142-
//launch instance
143-
runResult, err := svc.RunInstancesWithContext(ctx, &ec2.RunInstancesInput{
144-
UserData: aws.String(userData),
145-
ImageId: aws.String(instanceAmi),
146-
KeyName: aws.String(pairName),
147-
InstanceType: aws.String(instanceType),
148-
MinCount: aws.Int64(1),
149-
MaxCount: aws.Int64(1),
150-
SecurityGroupIds: []*string{aws.String(sgID)},
151-
SubnetId: aws.String(*subDesc.Subnets[0].SubnetId),
152-
BlockDeviceMappings: []*ec2.BlockDeviceMapping{
153-
{
154-
//VirtualName: aws.String("Root"),
155-
DeviceName: aws.String("/dev/sda1"),
156-
Ebs: &ec2.EbsBlockDevice{
157-
DeleteOnTermination: aws.Bool(true),
158-
Encrypted: aws.Bool(false),
159-
//Iops: aws.Int64(0),
160-
VolumeSize: aws.Int64(int64(hddSize)),
161-
VolumeType: aws.String("gp2"),
162-
},
145+
blockDeviceMappings := []*ec2.BlockDeviceMapping{
146+
{
147+
DeviceName: aws.String("/dev/sda1"),
148+
Ebs: &ec2.EbsBlockDevice{
149+
DeleteOnTermination: aws.Bool(true),
150+
Encrypted: aws.Bool(false),
151+
VolumeSize: aws.Int64(int64(hddSize)),
152+
VolumeType: aws.String("gp2"),
163153
},
164154
},
165-
})
166-
if err != nil {
167-
return err
168155
}
169156

170-
instanceID := *runResult.Instances[0].InstanceId
157+
//launch instance
158+
var instanceID string
159+
if spot {
160+
requestSpotInstancesInput := &ec2.RequestSpotInstancesInput{
161+
LaunchSpecification: &ec2.RequestSpotLaunchSpecification{
162+
UserData: aws.String(userData),
163+
ImageId: aws.String(instanceAmi),
164+
KeyName: aws.String(pairName),
165+
InstanceType: aws.String(instanceType),
166+
SecurityGroupIds: []*string{aws.String(sgID)},
167+
SubnetId: aws.String(*subDesc.Subnets[0].SubnetId),
168+
BlockDeviceMappings: blockDeviceMappings,
169+
},
170+
InstanceCount: aws.Int64(1),
171+
}
172+
173+
if spotPrice >= 0 {
174+
requestSpotInstancesInput.SpotPrice = aws.String(strconv.FormatFloat(spotPrice, 'f', 5, 64))
175+
}
176+
177+
spotInstanceRequest, err := svc.RequestSpotInstancesWithContext(ctx, requestSpotInstancesInput)
178+
if err != nil {
179+
return err
180+
}
181+
182+
spotInstanceRequestID := *spotInstanceRequest.SpotInstanceRequests[0].SpotInstanceRequestId
183+
err = svc.WaitUntilSpotInstanceRequestFulfilled(&ec2.DescribeSpotInstanceRequestsInput{
184+
SpotInstanceRequestIds: []*string{aws.String(spotInstanceRequestID)},
185+
})
186+
if err != nil {
187+
return err
188+
}
189+
resolvedSpotInstance, err := svc.DescribeSpotInstanceRequests(&ec2.DescribeSpotInstanceRequestsInput{
190+
SpotInstanceRequestIds: []*string{aws.String(spotInstanceRequestID)},
191+
})
192+
if err != nil {
193+
return err
194+
}
195+
196+
instanceID = *resolvedSpotInstance.SpotInstanceRequests[0].InstanceId
197+
} else {
198+
runResult, err := svc.RunInstancesWithContext(ctx, &ec2.RunInstancesInput{
199+
UserData: aws.String(userData),
200+
ImageId: aws.String(instanceAmi),
201+
KeyName: aws.String(pairName),
202+
InstanceType: aws.String(instanceType),
203+
MinCount: aws.Int64(1),
204+
MaxCount: aws.Int64(1),
205+
SecurityGroupIds: []*string{aws.String(sgID)},
206+
SubnetId: aws.String(*subDesc.Subnets[0].SubnetId),
207+
BlockDeviceMappings: blockDeviceMappings,
208+
})
209+
if err != nil {
210+
return err
211+
}
212+
213+
instanceID = *runResult.Instances[0].InstanceId
214+
}
171215

172216
// Add name to the created instance
173217
_, err = svc.CreateTags(&ec2.CreateTagsInput{

iterative/azure/provider.go

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ func ResourceMachineCreate(ctx context.Context, d *schema.ResourceData, m interf
2727
instanceType := getInstanceType(d.Get("instance_type").(string), d.Get("instance_gpu").(string))
2828
keyPublic := d.Get("ssh_public").(string)
2929
hddSize := int32(d.Get("instance_hdd_size").(int))
30+
spot := d.Get("spot").(bool)
31+
spotPrice := d.Get("spot_price").(float64)
3032

3133
image := d.Get("image").(string)
3234
if image == "" {
@@ -181,60 +183,70 @@ func ResourceMachineCreate(ctx context.Context, d *schema.ResourceData, m interf
181183
}
182184

183185
vmClient, _ := getVMClient(subscriptionID)
184-
futureVM, err := vmClient.CreateOrUpdate(
185-
ctx,
186-
gpName,
187-
vmName,
188-
compute.VirtualMachine{
189-
Location: to.StringPtr(region),
190-
VirtualMachineProperties: &compute.VirtualMachineProperties{
191-
HardwareProfile: &compute.HardwareProfile{
192-
VMSize: compute.VirtualMachineSizeTypes(instanceType),
186+
vmSettings := compute.VirtualMachine{
187+
Location: to.StringPtr(region),
188+
VirtualMachineProperties: &compute.VirtualMachineProperties{
189+
HardwareProfile: &compute.HardwareProfile{
190+
VMSize: compute.VirtualMachineSizeTypes(instanceType),
191+
},
192+
StorageProfile: &compute.StorageProfile{
193+
ImageReference: &compute.ImageReference{
194+
Publisher: to.StringPtr(publisher),
195+
Offer: to.StringPtr(offer),
196+
Sku: to.StringPtr(sku),
197+
Version: to.StringPtr(version),
193198
},
194-
StorageProfile: &compute.StorageProfile{
195-
ImageReference: &compute.ImageReference{
196-
Publisher: to.StringPtr(publisher),
197-
Offer: to.StringPtr(offer),
198-
Sku: to.StringPtr(sku),
199-
Version: to.StringPtr(version),
200-
},
201-
OsDisk: &compute.OSDisk{
202-
Name: to.StringPtr(fmt.Sprintf(vmName + "-hdd")),
203-
Caching: compute.CachingTypesReadWrite,
204-
CreateOption: compute.DiskCreateOptionTypesFromImage,
205-
DiskSizeGB: to.Int32Ptr(hddSize),
206-
ManagedDisk: &compute.ManagedDiskParameters{
207-
StorageAccountType: compute.StorageAccountTypesStandardLRS,
208-
},
199+
OsDisk: &compute.OSDisk{
200+
Name: to.StringPtr(fmt.Sprintf(vmName + "-hdd")),
201+
Caching: compute.CachingTypesReadWrite,
202+
CreateOption: compute.DiskCreateOptionTypesFromImage,
203+
DiskSizeGB: to.Int32Ptr(hddSize),
204+
ManagedDisk: &compute.ManagedDiskParameters{
205+
StorageAccountType: compute.StorageAccountTypesStandardLRS,
209206
},
210207
},
211-
OsProfile: &compute.OSProfile{
212-
CustomData: to.StringPtr(customData),
213-
ComputerName: to.StringPtr("iterative"),
214-
AdminUsername: to.StringPtr(username),
215-
LinuxConfiguration: &compute.LinuxConfiguration{
216-
SSH: &compute.SSHConfiguration{
217-
PublicKeys: &[]compute.SSHPublicKey{
218-
{
219-
Path: to.StringPtr(fmt.Sprintf("/home/%s/.ssh/authorized_keys", username)),
220-
KeyData: to.StringPtr(keyPublic),
221-
},
208+
},
209+
OsProfile: &compute.OSProfile{
210+
CustomData: to.StringPtr(customData),
211+
ComputerName: to.StringPtr("iterative"),
212+
AdminUsername: to.StringPtr(username),
213+
LinuxConfiguration: &compute.LinuxConfiguration{
214+
SSH: &compute.SSHConfiguration{
215+
PublicKeys: &[]compute.SSHPublicKey{
216+
{
217+
Path: to.StringPtr(fmt.Sprintf("/home/%s/.ssh/authorized_keys", username)),
218+
KeyData: to.StringPtr(keyPublic),
222219
},
223220
},
224221
},
225222
},
226-
NetworkProfile: &compute.NetworkProfile{
227-
NetworkInterfaces: &[]compute.NetworkInterfaceReference{
228-
{
229-
ID: nic.ID,
230-
NetworkInterfaceReferenceProperties: &compute.NetworkInterfaceReferenceProperties{
231-
Primary: to.BoolPtr(true),
232-
},
223+
},
224+
NetworkProfile: &compute.NetworkProfile{
225+
NetworkInterfaces: &[]compute.NetworkInterfaceReference{
226+
{
227+
ID: nic.ID,
228+
NetworkInterfaceReferenceProperties: &compute.NetworkInterfaceReferenceProperties{
229+
Primary: to.BoolPtr(true),
233230
},
234231
},
235232
},
236233
},
237234
},
235+
}
236+
237+
if spot {
238+
vmSettings.EvictionPolicy = compute.Delete
239+
vmSettings.Priority = compute.Spot
240+
vmSettings.BillingProfile = &compute.BillingProfile{
241+
MaxPrice: to.Float64Ptr(spotPrice),
242+
}
243+
}
244+
245+
futureVM, err := vmClient.CreateOrUpdate(
246+
ctx,
247+
gpName,
248+
vmName,
249+
vmSettings,
238250
)
239251
if err != nil {
240252
return err
@@ -261,11 +273,7 @@ func ResourceMachineDelete(ctx context.Context, d *schema.ResourceData, m interf
261273
if err != nil {
262274
return err
263275
}
264-
_, err = groupsClient.Delete(context.Background(), d.Id())
265-
if err != nil {
266-
return err
267-
}
268-
276+
groupsClient.Delete(context.Background(), d.Id())
269277
return err
270278
}
271279

0 commit comments

Comments
 (0)