Skip to content

Commit 528613d

Browse files
authored
feat: add support for spot instances via spot instance requests (#236)
1 parent 75effc3 commit 528613d

File tree

7 files changed

+416
-12
lines changed

7 files changed

+416
-12
lines changed

README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,33 @@ module "ec2_instance" {
5252
}
5353
```
5454

55+
### Spot EC2 Instance
56+
57+
```hcl
58+
module "ec2_instance" {
59+
source = "terraform-aws-modules/ec2-instance/aws"
60+
version = "~> 3.0"
61+
62+
name = "spot-instance"
63+
64+
create_spot_instance = true
65+
spot_price = "0.60"
66+
spot_type = "persistent"
67+
68+
ami = "ami-ebd02392"
69+
instance_type = "t2.micro"
70+
key_name = "user1"
71+
monitoring = true
72+
vpc_security_group_ids = ["sg-12345678"]
73+
subnet_id = "subnet-eddcdzz4"
74+
75+
tags = {
76+
Terraform = "true"
77+
Environment = "dev"
78+
}
79+
}
80+
```
81+
5582
## Examples
5683

5784
- [Complete EC2 instance](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/complete)
@@ -105,10 +132,27 @@ data "aws_ami" "encrypted-ami" {
105132
}
106133
```
107134

135+
## Conditional creation
136+
137+
The following combinations are supported to conditionally create resources:
138+
139+
- Disable resource creation (no resources created):
140+
141+
```hcl
142+
create = false
143+
```
144+
145+
- Create spot instance:
146+
147+
```hcl
148+
create_spot_instance = true
149+
```
150+
108151
## Notes
109152

110153
- `network_interface` can't be specified together with `vpc_security_group_ids`, `associate_public_ip_address`, `subnet_id`. See [complete example](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/complete) for details.
111154
- Changes in `ebs_block_device` argument will be ignored. Use [aws_volume_attachment](https://www.terraform.io/docs/providers/aws/r/volume_attachment.html) resource to attach and detach volumes from AWS EC2 instances. See [this example](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/volume-attachment).
155+
- In regards to spot instances, you must grant the `AWSServiceRoleForEC2Spot` service-linked role access to any custom KMS keys, otherwise your spot request and instances will fail with `bad parameters`. You can see more details about why the request failed by using the awscli and `aws ec2 describe-spot-instance-requests`
112156

113157
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
114158
## Requirements
@@ -133,6 +177,7 @@ No modules.
133177
| Name | Type |
134178
|------|------|
135179
| [aws_instance.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource |
180+
| [aws_spot_instance_request.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/spot_instance_request) | resource |
136181

137182
## Inputs
138183

@@ -146,6 +191,7 @@ No modules.
146191
| <a name="input_cpu_credits"></a> [cpu\_credits](#input\_cpu\_credits) | The credit option for CPU usage (unlimited or standard) | `string` | `null` | no |
147192
| <a name="input_cpu_threads_per_core"></a> [cpu\_threads\_per\_core](#input\_cpu\_threads\_per\_core) | Sets the number of CPU threads per core for an instance (has no effect unless cpu\_core\_count is also set). | `number` | `null` | no |
148193
| <a name="input_create"></a> [create](#input\_create) | Whether to create an instance | `bool` | `true` | no |
194+
| <a name="input_create_spot_instance"></a> [create\_spot\_instance](#input\_create\_spot\_instance) | Depicts if the instance is a spot instance | `bool` | `false` | no |
149195
| <a name="input_disable_api_termination"></a> [disable\_api\_termination](#input\_disable\_api\_termination) | If true, enables EC2 Instance Termination Protection | `bool` | `null` | no |
150196
| <a name="input_ebs_block_device"></a> [ebs\_block\_device](#input\_ebs\_block\_device) | Additional EBS block devices to attach to the instance | `list(map(string))` | `[]` | no |
151197
| <a name="input_ebs_optimized"></a> [ebs\_optimized](#input\_ebs\_optimized) | If true, the launched EC2 instance will be EBS-optimized | `bool` | `null` | no |
@@ -171,6 +217,14 @@ No modules.
171217
| <a name="input_root_block_device"></a> [root\_block\_device](#input\_root\_block\_device) | Customize details about the root block device of the instance. See Block Devices below for details | `list(any)` | `[]` | no |
172218
| <a name="input_secondary_private_ips"></a> [secondary\_private\_ips](#input\_secondary\_private\_ips) | A list of secondary private IPv4 addresses to assign to the instance's primary network interface (eth0) in a VPC. Can only be assigned to the primary network interface (eth0) attached at instance creation, not a pre-existing network interface i.e. referenced in a `network_interface block` | `list(string)` | `null` | no |
173219
| <a name="input_source_dest_check"></a> [source\_dest\_check](#input\_source\_dest\_check) | Controls if traffic is routed to the instance when the destination address does not match the instance. Used for NAT or VPNs. | `bool` | `true` | no |
220+
| <a name="input_spot_block_duration_minutes"></a> [spot\_block\_duration\_minutes](#input\_spot\_block\_duration\_minutes) | The required duration for the Spot instances, in minutes. This value must be a multiple of 60 (60, 120, 180, 240, 300, or 360) | `number` | `null` | no |
221+
| <a name="input_spot_instance_interruption_behavior"></a> [spot\_instance\_interruption\_behavior](#input\_spot\_instance\_interruption\_behavior) | Indicates Spot instance behavior when it is interrupted. Valid values are `terminate`, `stop`, or `hibernate` | `string` | `null` | no |
222+
| <a name="input_spot_launch_group"></a> [spot\_launch\_group](#input\_spot\_launch\_group) | A launch group is a group of spot instances that launch together and terminate together. If left empty instances are launched and terminated individually | `string` | `null` | no |
223+
| <a name="input_spot_price"></a> [spot\_price](#input\_spot\_price) | The maximum price to request on the spot market. Defaults to on-demand price | `string` | `null` | no |
224+
| <a name="input_spot_type"></a> [spot\_type](#input\_spot\_type) | If set to one-time, after the instance is terminated, the spot request will be closed. Default `persistent` | `string` | `null` | no |
225+
| <a name="input_spot_valid_from"></a> [spot\_valid\_from](#input\_spot\_valid\_from) | The start date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ) | `string` | `null` | no |
226+
| <a name="input_spot_valid_until"></a> [spot\_valid\_until](#input\_spot\_valid\_until) | The end date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ) | `string` | `null` | no |
227+
| <a name="input_spot_wait_for_fulfillment"></a> [spot\_wait\_for\_fulfillment](#input\_spot\_wait\_for\_fulfillment) | If set, Terraform will wait for the Spot Request to be fulfilled, and will throw an error if the timeout of 10m is reached | `bool` | `null` | no |
174228
| <a name="input_subnet_id"></a> [subnet\_id](#input\_subnet\_id) | The VPC Subnet ID to launch in | `string` | `null` | no |
175229
| <a name="input_tags"></a> [tags](#input\_tags) | A mapping of tags to assign to the resource | `map(string)` | `{}` | no |
176230
| <a name="input_tenancy"></a> [tenancy](#input\_tenancy) | The tenancy of the instance (if the instance is running in a VPC). Available values: default, dedicated, host. | `string` | `null` | no |
@@ -194,6 +248,9 @@ No modules.
194248
| <a name="output_private_dns"></a> [private\_dns](#output\_private\_dns) | The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC |
195249
| <a name="output_public_dns"></a> [public\_dns](#output\_public\_dns) | The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC |
196250
| <a name="output_public_ip"></a> [public\_ip](#output\_public\_ip) | The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws\_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached |
251+
| <a name="output_spot_bid_status"></a> [spot\_bid\_status](#output\_spot\_bid\_status) | The current bid status of the Spot Instance Request |
252+
| <a name="output_spot_instance_id"></a> [spot\_instance\_id](#output\_spot\_instance\_id) | The Instance ID (if any) that is currently fulfilling the Spot Instance request |
253+
| <a name="output_spot_request_state"></a> [spot\_request\_state](#output\_spot\_request\_state) | The current request state of the Spot Instance Request |
197254
| <a name="output_tags_all"></a> [tags\_all](#output\_tags\_all) | A map of tags assigned to the resource, including those inherited from the provider default\_tags configuration block |
198255
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
199256

examples/complete/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Note that this example may create resources which can cost money. Run `terraform
3737
| <a name="module_ec2_metadata_options"></a> [ec2\_metadata\_options](#module\_ec2\_metadata\_options) | ../../ | |
3838
| <a name="module_ec2_multiple"></a> [ec2\_multiple](#module\_ec2\_multiple) | ../../ | |
3939
| <a name="module_ec2_network_interface"></a> [ec2\_network\_interface](#module\_ec2\_network\_interface) | ../../ | |
40+
| <a name="module_ec2_spot_instance"></a> [ec2\_spot\_instance](#module\_ec2\_spot\_instance) | ../../ | |
4041
| <a name="module_ec2_t2_unlimited"></a> [ec2\_t2\_unlimited](#module\_ec2\_t2\_unlimited) | ../../ | |
4142
| <a name="module_ec2_t3_unlimited"></a> [ec2\_t3\_unlimited](#module\_ec2\_t3\_unlimited) | ../../ | |
4243
| <a name="module_security_group"></a> [security\_group](#module\_security\_group) | terraform-aws-modules/security-group/aws | ~> 4.0 |
@@ -69,6 +70,15 @@ No inputs.
6970
| <a name="output_ec2_complete_public_ip"></a> [ec2\_complete\_public\_ip](#output\_ec2\_complete\_public\_ip) | The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws\_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached |
7071
| <a name="output_ec2_complete_tags_all"></a> [ec2\_complete\_tags\_all](#output\_ec2\_complete\_tags\_all) | A map of tags assigned to the resource, including those inherited from the provider default\_tags configuration block |
7172
| <a name="output_ec2_multiple"></a> [ec2\_multiple](#output\_ec2\_multiple) | The full output of the `ec2_module` module |
73+
| <a name="output_ec2_spot_instance_arn"></a> [ec2\_spot\_instance\_arn](#output\_ec2\_spot\_instance\_arn) | The ARN of the instance |
74+
| <a name="output_ec2_spot_instance_capacity_reservation_specification"></a> [ec2\_spot\_instance\_capacity\_reservation\_specification](#output\_ec2\_spot\_instance\_capacity\_reservation\_specification) | Capacity reservation specification of the instance |
75+
| <a name="output_ec2_spot_instance_id"></a> [ec2\_spot\_instance\_id](#output\_ec2\_spot\_instance\_id) | The ID of the instance |
76+
| <a name="output_ec2_spot_instance_instance_state"></a> [ec2\_spot\_instance\_instance\_state](#output\_ec2\_spot\_instance\_instance\_state) | The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped` |
77+
| <a name="output_ec2_spot_instance_primary_network_interface_id"></a> [ec2\_spot\_instance\_primary\_network\_interface\_id](#output\_ec2\_spot\_instance\_primary\_network\_interface\_id) | The ID of the instance's primary network interface |
78+
| <a name="output_ec2_spot_instance_private_dns"></a> [ec2\_spot\_instance\_private\_dns](#output\_ec2\_spot\_instance\_private\_dns) | The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC |
79+
| <a name="output_ec2_spot_instance_public_dns"></a> [ec2\_spot\_instance\_public\_dns](#output\_ec2\_spot\_instance\_public\_dns) | The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC |
80+
| <a name="output_ec2_spot_instance_public_ip"></a> [ec2\_spot\_instance\_public\_ip](#output\_ec2\_spot\_instance\_public\_ip) | The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws\_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached |
81+
| <a name="output_ec2_spot_instance_tags_all"></a> [ec2\_spot\_instance\_tags\_all](#output\_ec2\_spot\_instance\_tags\_all) | A map of tags assigned to the resource, including those inherited from the provider default\_tags configuration block |
7282
| <a name="output_ec2_t2_unlimited_arn"></a> [ec2\_t2\_unlimited\_arn](#output\_ec2\_t2\_unlimited\_arn) | The ARN of the instance |
7383
| <a name="output_ec2_t2_unlimited_capacity_reservation_specification"></a> [ec2\_t2\_unlimited\_capacity\_reservation\_specification](#output\_ec2\_t2\_unlimited\_capacity\_reservation\_specification) | Capacity reservation specification of the instance |
7484
| <a name="output_ec2_t2_unlimited_id"></a> [ec2\_t2\_unlimited\_id](#output\_ec2\_t2\_unlimited\_id) | The ID of the instance |
@@ -87,4 +97,7 @@ No inputs.
8797
| <a name="output_ec2_t3_unlimited_public_dns"></a> [ec2\_t3\_unlimited\_public\_dns](#output\_ec2\_t3\_unlimited\_public\_dns) | The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC |
8898
| <a name="output_ec2_t3_unlimited_public_ip"></a> [ec2\_t3\_unlimited\_public\_ip](#output\_ec2\_t3\_unlimited\_public\_ip) | The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws\_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached |
8999
| <a name="output_ec2_t3_unlimited_tags_all"></a> [ec2\_t3\_unlimited\_tags\_all](#output\_ec2\_t3\_unlimited\_tags\_all) | A map of tags assigned to the resource, including those inherited from the provider default\_tags configuration block |
100+
| <a name="output_spot_bid_status"></a> [spot\_bid\_status](#output\_spot\_bid\_status) | The current bid status of the Spot Instance Request |
101+
| <a name="output_spot_instance_id"></a> [spot\_instance\_id](#output\_spot\_instance\_id) | The Instance ID (if any) that is currently fulfilling the Spot Instance request |
102+
| <a name="output_spot_request_state"></a> [spot\_request\_state](#output\_spot\_request\_state) | The current request state of the Spot Instance Request |
90103
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->

examples/complete/main.tf

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,64 @@ module "ec2_multiple" {
264264

265265
tags = local.tags
266266
}
267+
268+
################################################################################
269+
# EC2 Module - spot instance request
270+
################################################################################
271+
272+
module "ec2_spot_instance" {
273+
source = "../../"
274+
275+
name = "${local.name}-spot-instance"
276+
create_spot_instance = true
277+
278+
ami = data.aws_ami.amazon_linux.id
279+
instance_type = "c4.4xlarge"
280+
availability_zone = element(module.vpc.azs, 0)
281+
subnet_id = element(module.vpc.private_subnets, 0)
282+
vpc_security_group_ids = [module.security_group.security_group_id]
283+
placement_group = aws_placement_group.web.id
284+
associate_public_ip_address = true
285+
286+
# Spot request specific attributes
287+
spot_price = "0.60"
288+
spot_wait_for_fulfillment = true
289+
spot_type = "persistent"
290+
spot_instance_interruption_behavior = "terminate"
291+
# End spot request specific attributes
292+
293+
user_data_base64 = base64encode(local.user_data)
294+
295+
cpu_core_count = 2 # default 4
296+
cpu_threads_per_core = 1 # default 2
297+
298+
capacity_reservation_specification = {
299+
capacity_reservation_preference = "open"
300+
}
301+
302+
enable_volume_tags = false
303+
root_block_device = [
304+
{
305+
encrypted = true
306+
volume_type = "gp3"
307+
throughput = 200
308+
volume_size = 50
309+
tags = {
310+
Name = "my-root-block"
311+
}
312+
},
313+
]
314+
315+
ebs_block_device = [
316+
{
317+
device_name = "/dev/sdf"
318+
volume_type = "gp3"
319+
volume_size = 5
320+
throughput = 200
321+
encrypted = true
322+
# kms_key_id = aws_kms_key.this.arn # you must grant the AWSServiceRoleForEC2Spot service-linked role access to any custom KMS keys
323+
}
324+
]
325+
326+
tags = local.tags
327+
}

examples/complete/outputs.tf

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,64 @@ output "ec2_multiple" {
141141
description = "The full output of the `ec2_module` module"
142142
value = module.ec2_multiple
143143
}
144+
145+
# EC2 Spot Instance
146+
output "ec2_spot_instance_id" {
147+
description = "The ID of the instance"
148+
value = module.ec2_spot_instance.id
149+
}
150+
151+
output "ec2_spot_instance_arn" {
152+
description = "The ARN of the instance"
153+
value = module.ec2_spot_instance.arn
154+
}
155+
156+
output "ec2_spot_instance_capacity_reservation_specification" {
157+
description = "Capacity reservation specification of the instance"
158+
value = module.ec2_spot_instance.capacity_reservation_specification
159+
}
160+
161+
output "ec2_spot_instance_instance_state" {
162+
description = "The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped`"
163+
value = module.ec2_spot_instance.instance_state
164+
}
165+
166+
output "ec2_spot_instance_primary_network_interface_id" {
167+
description = "The ID of the instance's primary network interface"
168+
value = module.ec2_spot_instance.primary_network_interface_id
169+
}
170+
171+
output "ec2_spot_instance_private_dns" {
172+
description = "The private DNS name assigned to the instance. Can only be used inside the Amazon EC2, and only available if you've enabled DNS hostnames for your VPC"
173+
value = module.ec2_spot_instance.private_dns
174+
}
175+
176+
output "ec2_spot_instance_public_dns" {
177+
description = "The public DNS name assigned to the instance. For EC2-VPC, this is only available if you've enabled DNS hostnames for your VPC"
178+
value = module.ec2_spot_instance.public_dns
179+
}
180+
181+
output "ec2_spot_instance_public_ip" {
182+
description = "The public IP address assigned to the instance, if applicable. NOTE: If you are using an aws_eip with your instance, you should refer to the EIP's address directly and not use `public_ip` as this field will change after the EIP is attached"
183+
value = module.ec2_spot_instance.public_ip
184+
}
185+
186+
output "ec2_spot_instance_tags_all" {
187+
description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block"
188+
value = module.ec2_spot_instance.tags_all
189+
}
190+
191+
output "spot_bid_status" {
192+
description = "The current bid status of the Spot Instance Request"
193+
value = module.ec2_spot_instance.spot_bid_status
194+
}
195+
196+
output "spot_request_state" {
197+
description = "The current request state of the Spot Instance Request"
198+
value = module.ec2_spot_instance.spot_request_state
199+
}
200+
201+
output "spot_instance_id" {
202+
description = "The Instance ID (if any) that is currently fulfilling the Spot Instance request"
203+
value = module.ec2_spot_instance.spot_instance_id
204+
}

0 commit comments

Comments
 (0)