Commit 528613d4 authored by Bryant Biggs's avatar Bryant Biggs Committed by GitHub

feat: add support for spot instances via spot instance requests (#236)

parent 75effc3f
...@@ -52,6 +52,33 @@ module "ec2_instance" { ...@@ -52,6 +52,33 @@ module "ec2_instance" {
} }
``` ```
### Spot EC2 Instance
```hcl
module "ec2_instance" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "~> 3.0"
name = "spot-instance"
create_spot_instance = true
spot_price = "0.60"
spot_type = "persistent"
ami = "ami-ebd02392"
instance_type = "t2.micro"
key_name = "user1"
monitoring = true
vpc_security_group_ids = ["sg-12345678"]
subnet_id = "subnet-eddcdzz4"
tags = {
Terraform = "true"
Environment = "dev"
}
}
```
## Examples ## Examples
- [Complete EC2 instance](https://github.com/terraform-aws-modules/terraform-aws-ec2-instance/tree/master/examples/complete) - [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" { ...@@ -105,10 +132,27 @@ data "aws_ami" "encrypted-ami" {
} }
``` ```
## Conditional creation
The following combinations are supported to conditionally create resources:
- Disable resource creation (no resources created):
```hcl
create = false
```
- Create spot instance:
```hcl
create_spot_instance = true
```
## Notes ## Notes
- `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. - `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.
- 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). - 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).
- 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`
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK --> <!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## Requirements ## Requirements
...@@ -133,6 +177,7 @@ No modules. ...@@ -133,6 +177,7 @@ No modules.
| Name | Type | | Name | Type |
|------|------| |------|------|
| [aws_instance.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource | | [aws_instance.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance) | resource |
| [aws_spot_instance_request.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/spot_instance_request) | resource |
## Inputs ## Inputs
...@@ -146,6 +191,7 @@ No modules. ...@@ -146,6 +191,7 @@ No modules.
| <a name="input_cpu_credits"></a> [cpu\_credits](#input\_cpu\_credits) | The credit option for CPU usage (unlimited or standard) | `string` | `null` | no | | <a name="input_cpu_credits"></a> [cpu\_credits](#input\_cpu\_credits) | The credit option for CPU usage (unlimited or standard) | `string` | `null` | no |
| <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 | | <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 |
| <a name="input_create"></a> [create](#input\_create) | Whether to create an instance | `bool` | `true` | no | | <a name="input_create"></a> [create](#input\_create) | Whether to create an instance | `bool` | `true` | no |
| <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 |
| <a name="input_disable_api_termination"></a> [disable\_api\_termination](#input\_disable\_api\_termination) | If true, enables EC2 Instance Termination Protection | `bool` | `null` | no | | <a name="input_disable_api_termination"></a> [disable\_api\_termination](#input\_disable\_api\_termination) | If true, enables EC2 Instance Termination Protection | `bool` | `null` | no |
| <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 | | <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 |
| <a name="input_ebs_optimized"></a> [ebs\_optimized](#input\_ebs\_optimized) | If true, the launched EC2 instance will be EBS-optimized | `bool` | `null` | no | | <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. ...@@ -171,6 +217,14 @@ No modules.
| <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 | | <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 |
| <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 | | <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 |
| <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 | | <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 |
| <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 |
| <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 |
| <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 |
| <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 |
| <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 |
| <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 |
| <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 |
| <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 |
| <a name="input_subnet_id"></a> [subnet\_id](#input\_subnet\_id) | The VPC Subnet ID to launch in | `string` | `null` | no | | <a name="input_subnet_id"></a> [subnet\_id](#input\_subnet\_id) | The VPC Subnet ID to launch in | `string` | `null` | no |
| <a name="input_tags"></a> [tags](#input\_tags) | A mapping of tags to assign to the resource | `map(string)` | `{}` | no | | <a name="input_tags"></a> [tags](#input\_tags) | A mapping of tags to assign to the resource | `map(string)` | `{}` | no |
| <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 | | <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. ...@@ -194,6 +248,9 @@ No modules.
| <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 | | <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 |
| <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 | | <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 |
| <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 | | <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 |
| <a name="output_spot_bid_status"></a> [spot\_bid\_status](#output\_spot\_bid\_status) | The current bid status of the Spot Instance Request |
| <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 |
| <a name="output_spot_request_state"></a> [spot\_request\_state](#output\_spot\_request\_state) | The current request state of the Spot Instance Request |
| <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 | | <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 |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> <!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
......
...@@ -37,6 +37,7 @@ Note that this example may create resources which can cost money. Run `terraform ...@@ -37,6 +37,7 @@ Note that this example may create resources which can cost money. Run `terraform
| <a name="module_ec2_metadata_options"></a> [ec2\_metadata\_options](#module\_ec2\_metadata\_options) | ../../ | | | <a name="module_ec2_metadata_options"></a> [ec2\_metadata\_options](#module\_ec2\_metadata\_options) | ../../ | |
| <a name="module_ec2_multiple"></a> [ec2\_multiple](#module\_ec2\_multiple) | ../../ | | | <a name="module_ec2_multiple"></a> [ec2\_multiple](#module\_ec2\_multiple) | ../../ | |
| <a name="module_ec2_network_interface"></a> [ec2\_network\_interface](#module\_ec2\_network\_interface) | ../../ | | | <a name="module_ec2_network_interface"></a> [ec2\_network\_interface](#module\_ec2\_network\_interface) | ../../ | |
| <a name="module_ec2_spot_instance"></a> [ec2\_spot\_instance](#module\_ec2\_spot\_instance) | ../../ | |
| <a name="module_ec2_t2_unlimited"></a> [ec2\_t2\_unlimited](#module\_ec2\_t2\_unlimited) | ../../ | | | <a name="module_ec2_t2_unlimited"></a> [ec2\_t2\_unlimited](#module\_ec2\_t2\_unlimited) | ../../ | |
| <a name="module_ec2_t3_unlimited"></a> [ec2\_t3\_unlimited](#module\_ec2\_t3\_unlimited) | ../../ | | | <a name="module_ec2_t3_unlimited"></a> [ec2\_t3\_unlimited](#module\_ec2\_t3\_unlimited) | ../../ | |
| <a name="module_security_group"></a> [security\_group](#module\_security\_group) | terraform-aws-modules/security-group/aws | ~> 4.0 | | <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. ...@@ -69,6 +70,15 @@ No inputs.
| <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 | | <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 |
| <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 | | <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 |
| <a name="output_ec2_multiple"></a> [ec2\_multiple](#output\_ec2\_multiple) | The full output of the `ec2_module` module | | <a name="output_ec2_multiple"></a> [ec2\_multiple](#output\_ec2\_multiple) | The full output of the `ec2_module` module |
| <a name="output_ec2_spot_instance_arn"></a> [ec2\_spot\_instance\_arn](#output\_ec2\_spot\_instance\_arn) | The ARN of the instance |
| <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 |
| <a name="output_ec2_spot_instance_id"></a> [ec2\_spot\_instance\_id](#output\_ec2\_spot\_instance\_id) | The ID of the instance |
| <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` |
| <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 |
| <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 |
| <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 |
| <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 |
| <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 |
| <a name="output_ec2_t2_unlimited_arn"></a> [ec2\_t2\_unlimited\_arn](#output\_ec2\_t2\_unlimited\_arn) | The ARN of the instance | | <a name="output_ec2_t2_unlimited_arn"></a> [ec2\_t2\_unlimited\_arn](#output\_ec2\_t2\_unlimited\_arn) | The ARN of the instance |
| <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 | | <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 |
| <a name="output_ec2_t2_unlimited_id"></a> [ec2\_t2\_unlimited\_id](#output\_ec2\_t2\_unlimited\_id) | The ID of the instance | | <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. ...@@ -87,4 +97,7 @@ No inputs.
| <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 | | <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 |
| <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 | | <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 |
| <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 | | <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 |
| <a name="output_spot_bid_status"></a> [spot\_bid\_status](#output\_spot\_bid\_status) | The current bid status of the Spot Instance Request |
| <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 |
| <a name="output_spot_request_state"></a> [spot\_request\_state](#output\_spot\_request\_state) | The current request state of the Spot Instance Request |
<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK --> <!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
...@@ -264,3 +264,64 @@ module "ec2_multiple" { ...@@ -264,3 +264,64 @@ module "ec2_multiple" {
tags = local.tags tags = local.tags
} }
################################################################################
# EC2 Module - spot instance request
################################################################################
module "ec2_spot_instance" {
source = "../../"
name = "${local.name}-spot-instance"
create_spot_instance = true
ami = data.aws_ami.amazon_linux.id
instance_type = "c4.4xlarge"
availability_zone = element(module.vpc.azs, 0)
subnet_id = element(module.vpc.private_subnets, 0)
vpc_security_group_ids = [module.security_group.security_group_id]
placement_group = aws_placement_group.web.id
associate_public_ip_address = true
# Spot request specific attributes
spot_price = "0.60"
spot_wait_for_fulfillment = true
spot_type = "persistent"
spot_instance_interruption_behavior = "terminate"
# End spot request specific attributes
user_data_base64 = base64encode(local.user_data)
cpu_core_count = 2 # default 4
cpu_threads_per_core = 1 # default 2
capacity_reservation_specification = {
capacity_reservation_preference = "open"
}
enable_volume_tags = false
root_block_device = [
{
encrypted = true
volume_type = "gp3"
throughput = 200
volume_size = 50
tags = {
Name = "my-root-block"
}
},
]
ebs_block_device = [
{
device_name = "/dev/sdf"
volume_type = "gp3"
volume_size = 5
throughput = 200
encrypted = true
# kms_key_id = aws_kms_key.this.arn # you must grant the AWSServiceRoleForEC2Spot service-linked role access to any custom KMS keys
}
]
tags = local.tags
}
...@@ -141,3 +141,64 @@ output "ec2_multiple" { ...@@ -141,3 +141,64 @@ output "ec2_multiple" {
description = "The full output of the `ec2_module` module" description = "The full output of the `ec2_module` module"
value = module.ec2_multiple value = module.ec2_multiple
} }
# EC2 Spot Instance
output "ec2_spot_instance_id" {
description = "The ID of the instance"
value = module.ec2_spot_instance.id
}
output "ec2_spot_instance_arn" {
description = "The ARN of the instance"
value = module.ec2_spot_instance.arn
}
output "ec2_spot_instance_capacity_reservation_specification" {
description = "Capacity reservation specification of the instance"
value = module.ec2_spot_instance.capacity_reservation_specification
}
output "ec2_spot_instance_instance_state" {
description = "The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped`"
value = module.ec2_spot_instance.instance_state
}
output "ec2_spot_instance_primary_network_interface_id" {
description = "The ID of the instance's primary network interface"
value = module.ec2_spot_instance.primary_network_interface_id
}
output "ec2_spot_instance_private_dns" {
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"
value = module.ec2_spot_instance.private_dns
}
output "ec2_spot_instance_public_dns" {
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"
value = module.ec2_spot_instance.public_dns
}
output "ec2_spot_instance_public_ip" {
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"
value = module.ec2_spot_instance.public_ip
}
output "ec2_spot_instance_tags_all" {
description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block"
value = module.ec2_spot_instance.tags_all
}
output "spot_bid_status" {
description = "The current bid status of the Spot Instance Request"
value = module.ec2_spot_instance.spot_bid_status
}
output "spot_request_state" {
description = "The current request state of the Spot Instance Request"
value = module.ec2_spot_instance.spot_request_state
}
output "spot_instance_id" {
description = "The Instance ID (if any) that is currently fulfilling the Spot Instance request"
value = module.ec2_spot_instance.spot_instance_id
}
...@@ -3,7 +3,7 @@ locals { ...@@ -3,7 +3,7 @@ locals {
} }
resource "aws_instance" "this" { resource "aws_instance" "this" {
count = var.create ? 1 : 0 count = var.create && !var.create_spot_instance ? 1 : 0
ami = var.ami ami = var.ami
instance_type = var.instance_type instance_type = var.instance_type
...@@ -133,3 +133,145 @@ resource "aws_instance" "this" { ...@@ -133,3 +133,145 @@ resource "aws_instance" "this" {
tags = merge({ "Name" = var.name }, var.tags) tags = merge({ "Name" = var.name }, var.tags)
volume_tags = var.enable_volume_tags ? merge({ "Name" = var.name }, var.volume_tags) : null volume_tags = var.enable_volume_tags ? merge({ "Name" = var.name }, var.volume_tags) : null
} }
resource "aws_spot_instance_request" "this" {
count = var.create && var.create_spot_instance ? 1 : 0
ami = var.ami
instance_type = var.instance_type
cpu_core_count = var.cpu_core_count
cpu_threads_per_core = var.cpu_threads_per_core
user_data = var.user_data
user_data_base64 = var.user_data_base64
hibernation = var.hibernation
availability_zone = var.availability_zone
subnet_id = var.subnet_id
vpc_security_group_ids = var.vpc_security_group_ids
key_name = var.key_name
monitoring = var.monitoring
get_password_data = var.get_password_data
iam_instance_profile = var.iam_instance_profile
associate_public_ip_address = var.associate_public_ip_address
private_ip = var.private_ip
secondary_private_ips = var.secondary_private_ips
ipv6_address_count = var.ipv6_address_count
ipv6_addresses = var.ipv6_addresses
ebs_optimized = var.ebs_optimized
# Spot request specific attributes
spot_price = var.spot_price
wait_for_fulfillment = var.spot_wait_for_fulfillment
spot_type = var.spot_type
launch_group = var.spot_launch_group
block_duration_minutes = var.spot_block_duration_minutes
instance_interruption_behavior = var.spot_instance_interruption_behavior
valid_until = var.spot_valid_until
valid_from = var.spot_valid_from
# End spot request specific attributes
dynamic "capacity_reservation_specification" {
for_each = var.capacity_reservation_specification != null ? [var.capacity_reservation_specification] : []
content {
capacity_reservation_preference = lookup(capacity_reservation_specification.value, "capacity_reservation_preference", null)
dynamic "capacity_reservation_target" {
for_each = lookup(capacity_reservation_specification.value, "capacity_reservation_target", [])
content {
capacity_reservation_id = lookup(capacity_reservation_target.value, "capacity_reservation_id", null)
}
}
}
}
dynamic "root_block_device" {
for_each = var.root_block_device
content {
delete_on_termination = lookup(root_block_device.value, "delete_on_termination", null)
encrypted = lookup(root_block_device.value, "encrypted", null)
iops = lookup(root_block_device.value, "iops", null)
kms_key_id = lookup(root_block_device.value, "kms_key_id", null)
volume_size = lookup(root_block_device.value, "volume_size", null)
volume_type = lookup(root_block_device.value, "volume_type", null)
throughput = lookup(root_block_device.value, "throughput", null)
tags = lookup(root_block_device.value, "tags", null)
}
}
dynamic "ebs_block_device" {
for_each = var.ebs_block_device
content {
delete_on_termination = lookup(ebs_block_device.value, "delete_on_termination", null)
device_name = ebs_block_device.value.device_name
encrypted = lookup(ebs_block_device.value, "encrypted", null)
iops = lookup(ebs_block_device.value, "iops", null)
kms_key_id = lookup(ebs_block_device.value, "kms_key_id", null)
snapshot_id = lookup(ebs_block_device.value, "snapshot_id", null)
volume_size = lookup(ebs_block_device.value, "volume_size", null)
volume_type = lookup(ebs_block_device.value, "volume_type", null)
throughput = lookup(ebs_block_device.value, "throughput", null)
}
}
dynamic "ephemeral_block_device" {
for_each = var.ephemeral_block_device
content {
device_name = ephemeral_block_device.value.device_name
no_device = lookup(ephemeral_block_device.value, "no_device", null)
virtual_name = lookup(ephemeral_block_device.value, "virtual_name", null)
}
}
dynamic "metadata_options" {
for_each = var.metadata_options != null ? [var.metadata_options] : []
content {
http_endpoint = lookup(metadata_options.value, "http_endpoint", "enabled")
http_tokens = lookup(metadata_options.value, "http_tokens", "optional")
http_put_response_hop_limit = lookup(metadata_options.value, "http_put_response_hop_limit", "1")
}
}
dynamic "network_interface" {
for_each = var.network_interface
content {
device_index = network_interface.value.device_index
network_interface_id = lookup(network_interface.value, "network_interface_id", null)
delete_on_termination = lookup(network_interface.value, "delete_on_termination", false)
}
}
dynamic "launch_template" {
for_each = var.launch_template != null ? [var.launch_template] : []
content {
id = lookup(var.launch_template, "id", null)
name = lookup(var.launch_template, "name", null)
version = lookup(var.launch_template, "version", null)
}
}
enclave_options {
enabled = var.enclave_options_enabled
}
source_dest_check = length(var.network_interface) > 0 ? null : var.source_dest_check
disable_api_termination = var.disable_api_termination
instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior
placement_group = var.placement_group
tenancy = var.tenancy
host_id = var.host_id
credit_specification {
cpu_credits = local.is_t_instance_type ? var.cpu_credits : null
}
timeouts {
create = lookup(var.timeouts, "create", null)
delete = lookup(var.timeouts, "delete", null)
}
tags = merge({ "Name" = var.name }, var.tags)
volume_tags = var.enable_volume_tags ? merge({ "Name" = var.name }, var.volume_tags) : null
}
output "id" { output "id" {
description = "The ID of the instance" description = "The ID of the instance"
value = element(concat(aws_instance.this.*.id, [""]), 0) value = element(concat(aws_instance.this.*.id, aws_spot_instance_request.this.*.id, [""]), 0)
} }
output "arn" { output "arn" {
description = "The ARN of the instance" description = "The ARN of the instance"
value = element(concat(aws_instance.this.*.arn, [""]), 0) value = element(concat(aws_instance.this.*.arn, aws_spot_instance_request.this.*.arn, [""]), 0)
} }
output "capacity_reservation_specification" { output "capacity_reservation_specification" {
description = "Capacity reservation specification of the instance" description = "Capacity reservation specification of the instance"
value = element(concat(aws_instance.this.*.capacity_reservation_specification, [""]), 0) value = element(concat(aws_instance.this.*.capacity_reservation_specification, aws_spot_instance_request.this.*.capacity_reservation_specification, [""]), 0)
} }
output "instance_state" { output "instance_state" {
description = "The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped`" description = "The state of the instance. One of: `pending`, `running`, `shutting-down`, `terminated`, `stopping`, `stopped`"
value = element(concat(aws_instance.this.*.instance_state, [""]), 0) value = element(concat(aws_instance.this.*.instance_state, aws_spot_instance_request.this.*.instance_state, [""]), 0)
} }
output "outpost_arn" { output "outpost_arn" {
description = "The ARN of the Outpost the instance is assigned to" description = "The ARN of the Outpost the instance is assigned to"
value = element(concat(aws_instance.this.*.outpost_arn, [""]), 0) value = element(concat(aws_instance.this.*.outpost_arn, aws_spot_instance_request.this.*.outpost_arn, [""]), 0)
} }
output "password_data" { output "password_data" {
description = "Base-64 encoded encrypted password data for the instance. Useful for getting the administrator password for instances running Microsoft Windows. This attribute is only exported if `get_password_data` is true" description = "Base-64 encoded encrypted password data for the instance. Useful for getting the administrator password for instances running Microsoft Windows. This attribute is only exported if `get_password_data` is true"
value = element(concat(aws_instance.this.*.password_data, [""]), 0) value = element(concat(aws_instance.this.*.password_data, aws_spot_instance_request.this.*.password_data, [""]), 0)
} }
output "primary_network_interface_id" { output "primary_network_interface_id" {
description = "The ID of the instance's primary network interface" description = "The ID of the instance's primary network interface"
value = element(concat(aws_instance.this.*.primary_network_interface_id, [""]), 0) value = element(concat(aws_instance.this.*.primary_network_interface_id, aws_spot_instance_request.this.*.primary_network_interface_id, [""]), 0)
} }
output "private_dns" { output "private_dns" {
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" 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"
value = element(concat(aws_instance.this.*.private_dns, [""]), 0) value = element(concat(aws_instance.this.*.private_dns, aws_spot_instance_request.this.*.private_dns, [""]), 0)
} }
output "public_dns" { output "public_dns" {
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" 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"
value = element(concat(aws_instance.this.*.public_dns, [""]), 0) value = element(concat(aws_instance.this.*.public_dns, aws_spot_instance_request.this.*.public_dns, [""]), 0)
} }
output "public_ip" { output "public_ip" {
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" 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"
value = element(concat(aws_instance.this.*.public_ip, [""]), 0) value = element(concat(aws_instance.this.*.public_ip, aws_spot_instance_request.this.*.public_ip, [""]), 0)
} }
output "tags_all" { output "tags_all" {
description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block" description = "A map of tags assigned to the resource, including those inherited from the provider default_tags configuration block"
value = element(concat(aws_instance.this.*.tags_all, [""]), 0) value = element(concat(aws_instance.this.*.tags_all, aws_spot_instance_request.this.*.tags_all, [""]), 0)
}
output "spot_bid_status" {
description = "The current bid status of the Spot Instance Request"
value = element(concat(aws_spot_instance_request.this.*.spot_bid_status, [""]), 0)
}
output "spot_request_state" {
description = "The current request state of the Spot Instance Request"
value = element(concat(aws_spot_instance_request.this.*.spot_request_state, [""]), 0)
}
output "spot_instance_id" {
description = "The Instance ID (if any) that is currently fulfilling the Spot Instance request"
value = element(concat(aws_spot_instance_request.this.*.spot_instance_id, [""]), 0)
} }
...@@ -243,3 +243,58 @@ variable "cpu_threads_per_core" { ...@@ -243,3 +243,58 @@ variable "cpu_threads_per_core" {
type = number type = number
default = null default = null
} }
# Spot instance request
variable "create_spot_instance" {
description = "Depicts if the instance is a spot instance"
type = bool
default = false
}
variable "spot_price" {
description = "The maximum price to request on the spot market. Defaults to on-demand price"
type = string
default = null
}
variable "spot_wait_for_fulfillment" {
description = "If set, Terraform will wait for the Spot Request to be fulfilled, and will throw an error if the timeout of 10m is reached"
type = bool
default = null
}
variable "spot_type" {
description = "If set to one-time, after the instance is terminated, the spot request will be closed. Default `persistent`"
type = string
default = null
}
variable "spot_launch_group" {
description = "A launch group is a group of spot instances that launch together and terminate together. If left empty instances are launched and terminated individually"
type = string
default = null
}
variable "spot_block_duration_minutes" {
description = "The required duration for the Spot instances, in minutes. This value must be a multiple of 60 (60, 120, 180, 240, 300, or 360)"
type = number
default = null
}
variable "spot_instance_interruption_behavior" {
description = "Indicates Spot instance behavior when it is interrupted. Valid values are `terminate`, `stop`, or `hibernate`"
type = string
default = null
}
variable "spot_valid_until" {
description = "The end date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ)"
type = string
default = null
}
variable "spot_valid_from" {
description = "The start date and time of the request, in UTC RFC3339 format(for example, YYYY-MM-DDTHH:MM:SSZ)"
type = string
default = null
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment