Cannot get public IP address of spot instance with Terraform - amazon-ec2

I’m spinning up a spot instance as you can see in below config and then trying to get the IP address from the spot. It seems to work fine with a regular ec2 instance (ie. that is not spot instance).
The error that I get is:
aws_route53_record.staging: Resource
‘aws_spot_instance_request.app-ec2’ does not have attribute
‘public_ip’ for variable ‘aws_spot_instance_request.app-ec2.public_ip’
Here is the config that I’m using:
resource "aws_spot_instance_request" "app-ec2" {
ami = "ami-1c999999"
spot_price = "0.008"
instance_type = "t2.small"
tags {
Name = "${var.app_name}"
}
key_name = "mykeypair"
associate_public_ip_address = true
vpc_security_group_ids = ["sg-99999999"]
subnet_id = "subnet-99999999"
iam_instance_profile = "myInstanceRole"
user_data = <<-EOF
#!/bin/bash
echo ECS_CLUSTER=APP-STAGING >> /etc/ecs/ecs.config
EOF
}
resource "aws_route53_record" "staging" {
zone_id = "XXXXXXXX"
name = "staging.myapp.com"
type = "A"
ttl = "300"
records = ["${aws_spot_instance_request.app-ec2.public_ip}"]
The spot request is fulfilled on the AWS Console as per below:
Any help will be greatly appreciated!

So I've been trying to figure this out since last night and kept seeing the spot instance request being fulfilled via the AWS Console. Likewise, I could see the public IP for the spot and this was misleading me.
It turns out I was missing 1 line (argument) in my script:
wait_for_fulfillment = true
By default, it is set to false, and therefore when I tried to set the public_ip address it simply did not exist at that time.
Now Terraform will wait for the Spot Request to be fulfilled. According to the documentation, it will throw an error if the timeout of 10m is reached.

I tried the code snippet you provided with Terraform version 0.12.10 and got the same error. I checked the terraform.tfstate file and saw that the fields were not populated yet (for example private_ip, public_ip, and public_dns were set to null). I checked the "Spot Requests" section in the AWS Console and saw the following Status: price-too-low: Your Spot request price of 0.0075 is lower than the minimum required Spot request fulfillment price of 0.008. The request state was still open so this is why all the variables in the state file were set to null.

Related

Unable to Launch EC2 Instances Asynchronously via Terraform

I am willing to launch two instances via Terraform. First one will generate some certificate files, push to S3 bucket. The second instance will pull those certificates from particular S3 bucket. Both operations will be handled by user data. The problem here is pull commands (aws cli) in user data of second instance are not working. (It is working when I try from shell) I think the issue is about terraform is launching both instances synchronously so that second instance is getting launched before first instance pushes the certificates to S3.
I also tried to handle this by adding "depends_on" to my code but it did not work. I am looking for a way to launch the instances asynchronously. Like second instance will be launched after 30 seconds then first instance is launched. Here I am pasting the related part of the code.
data "template_file" "first_executor" {
template = file("some_path/first_executor.sh")
}
resource "aws_instance" "first_instance" {
ami = data.aws_ami.amazon-linux-2.id
instance_type = "t2.micro"
user_data = data.template_file.first_executor.rendered
network_interface {
device_index = 0
network_interface_id = aws_network_interface.first_instance-network-interface.id
}
}
###
data "template_file" "second_executor" {
template = file("some_path/second_executor.sh")
}
resource "aws_instance" "second_instance" {
depends_on = [aws_instance.first_instance]
ami = data.aws_ami.amazon-linux-2.id
instance_type = "t2.micro"
user_data = data.template_file.second_executor.rendered
network_interface {
device_index = 0
network_interface_id = aws_network_interface.second-network-interface.id
}
}
Answer is no. "depends_on" in Terraform means it will wait for a resource to be available. This means, your second EC2 will be created as soon as first EC2 is triggered.
Terraform will not wait till your first EC2 is in "running" state or if user data is executed.
I would suggest go with depdens_on and then, in your second EC2 user data script, add some logic to have a loop which will look up S3 and will wait and repeat till the resources are found.

Terraform version 0.9.6 support spot fleet tags

Using Terraform 0.9.6 I get this error when I try to create spot fleet using the code below, but when I use
v0.11.7, it works. Terraform doc is here. My question is how can I determine if this is due to lack of v0.9.6 features or if I'm using the syntax incorrectly. How can I find this out?
I need to make the sport fleet resource tags work with v0.9.6.
Error:
* aws_spot_fleet_request.cheap_compute: launch_specification.0: invalid or unknown key: tags
Code:
# Request a Spot fleet
resource "aws_spot_fleet_request" "cheap_compute" {
iam_fleet_role = "arn:aws:iam::xxxxxxxxxxxxx:role/aws-service-role/spotfleet.amazonaws.com/AWSServiceRoleForEC2SpotFleet"
spot_price = "0.03"
allocation_strategy = "diversified"
target_capacity = 2
valid_until = "2018-07-21T20:44:20Z"
launch_specification {
instance_type = "t2.micro"
ami = "ami-1853ac65"
spot_price = "0.777"
availability_zone = "us-east-1a"
key_name = "${var.key_name}"
tags {
Name = "spot-fleet-example"}
}
}
Support for tags in aws_spot_fleet_request was added in the terraform-provider-aws in 1.2.0. This was almost 5 months after the release of Terraform 0.9.6. At that time of 0.9.6 providers were shipped with Terraform. They later moved to shipping independently as part of 0.10.0 a few months later. You'll need a newer version to get tag support.

When provisioning with Terraform, how does code obtain a reference to machine IDs (e.g. database machine address)

Let's say I'm using Terraform to provision two machines inside AWS:
An EC2 Machine running NodeJS
An RDS instance
How does the NodeJS code obtain the address of the RDS instance?
You've got a couple of options here. The simplest one is to create a CNAME record in Route53 for the database and then always point to that CNAME in your application.
A basic example would look something like this:
resource "aws_db_instance" "mydb" {
allocated_storage = 10
engine = "mysql"
engine_version = "5.6.17"
instance_class = "db.t2.micro"
name = "mydb"
username = "foo"
password = "bar"
db_subnet_group_name = "my_database_subnet_group"
parameter_group_name = "default.mysql5.6"
}
resource "aws_route53_record" "database" {
zone_id = "${aws_route53_zone.primary.zone_id}"
name = "database.example.com"
type = "CNAME"
ttl = "300"
records = ["${aws_db_instance.default.endpoint}"]
}
Alternative options include taking the endpoint output from the aws_db_instance and passing that into a user data script when creating the instance or passing it to Consul and using Consul Template to control the config that your application uses.
You may try Sparrowform - a lightweight provision tool for Terraform based instances, it's capable to make an inventory of Terraform resources and provision related hosts, passing all the necessary data:
$ terrafrom apply # bootstrap infrastructure
$ cat sparrowfile # this scenario
# fetches DB address from terraform cache
# and populate configuration file
# at server with node js code:
#!/usr/bin/env perl6
use Sparrowform;
$ sparrowfrom --ssh_private_key=~/.ssh/aws.pem --ssh_user=ec2 # run provision tool
my $rdb-adress;
for tf-resources() -> $r {
my $r-id = $r[0]; # resource id
if ( $r-id 'aws_db_instance.mydb') {
my $r-data = $r[1];
$rdb-address = $r-data<address>;
last;
}
}
# For instance, we can
# Install configuration file
# Next chunk of code will be applied to
# The server with node-js code:
template-create '/path/to/config/app.conf', %(
source => ( slurp 'app.conf.tmpl' ),
variables => %(
rdb-address => $rdb-address
),
);
# sparrowform --ssh_private_key=~/.ssh/aws.pem --ssh_user=ec2 # run provisioning
PS. disclosure - I am the tool author

How to get order username and provisionDate for all SoftLayer machines using Ruby?

Using Ruby I'm making a call like:
client = SoftLayer::Client.new(:username => user, :api_key => api_key, :timeout => 999999)
client['Account'].object_mask("mask[id, hostname, fullyQualifiedDomainName, provisionDate, datacenter[name], billingItem[recurringFee, associatedChildren[recurringFee], orderItem[description, order[userRecord[username], id]]], tagReferences[tagId, tag[name]], primaryIpAddress, primaryBackendIpAddress]").getHardware
But only some machines return a provisionDate and only some return orderItem information. How can I consistently get this information for each machine? What would cause one machine to return this data and another machine to not?
Example output:
{"fullyQualifiedDomainName"=>"<removed_by_me>",
"hostname"=>"<removed_by_me>",
"id"=>167719,
"provisionDate"=>"",
"primaryBackendIpAddress"=>"<removed_by_me>",
"primaryIpAddress"=>"<removed_by_me>",
"billingItem"=>
{"recurringFee"=>"506.78",
"associatedChildren"=>
[<removed_by_me>]},
"datacenter"=>{"name"=>"dal09"},
"tagReferences"=>
[{"tagId"=>139415, "tag"=>{"name"=>"<removed_by_me>"}},
{"tagId"=>139417, "tag"=>{"name"=>"<removed_by_me>"}},
{"tagId"=>140549, "tag"=>{"name"=>"<removed_by_me>"}}]}
To be clear, most machines return this data so I'm trying to understand why some do not.
Please see the following provisioning steps, below is a little flow to consider:
1. Order a Server
Result:
* An orderId is assigned to the server
* The createDate has a new value
* activeTransaction value is = Null
* provisionDate value is = Null
2. The order is approved
Result:
* activeTransaction value is <> Null
* provisionDate value = Null
3. Server is already provisioned
Result:
* activeTransaction value is = Null
* provisionDate value has a New value
* billingItem property has a new value
To see if your machines have still ”activeTransaction”, please execute:
https://[username]:[apikey]#api.softlayer.com/rest/v3/SoftLayer_Hardware_Server/[server_id]/getActiveTransaction
Method: GET
Now, after reviewing your example response, this server had some problems when completing the provisioning; for that reason this step was completed manually but the provisionDate was not set for any reason(please open a ticket if you want that the provisionDate can be set) . This is a special case. I can see that another server has a similar behavior. But the other servers that don’t have provisionDate, have still ”activeTransaction<>null” (it means that these server are not provisioned yet).
EDIT:
Other property can help you to know that your machine has been already provisioned although other kind of transaction is being executed, is “hardwareStatus”, it should have “ACTIVE” value.
https://[username]:[apikey]#api.softlayer.com/rest/v3/SoftLayer_Account/getHardware?objectMask=mask[id, hostname, fullyQualifiedDomainName, provisionDate,hardwareStatus]
Method: GET
The response should be something like this:
{
"fullyQualifiedDomainName": "myhostname.softlayer.com"
"hostname": " myhostname"
"id": 1234567
"provisionDate": "2015-06-29T00:21:39-05:00"
"hardwareStatus": {
"id": 5
"status": "ACTIVE"
}

Why is EC2 instance status 'terminated' when I try to create it with IO1 volume type?

Here's a block of code that is used to create an EC2 instance:
def create_instance(connection, name, instance_type, security_groups, ami, key, placement, cluster, optimized_ebs):
#NEW BLOCK BEGIN
dev_sda1 = boto.ec2.blockdevicemapping.EBSBlockDeviceType()
dev_sda1.size = 23 # size in Gigabytes
dev_sda1.volume_type = 'io1'
dev_sda1.iops = 44
bdm = boto.ec2.blockdevicemapping.BlockDeviceMapping()
bdm['/dev/sda1'] = dev_sda1
#NEW BLOCK END
res = connection.run_instances(
ami,
key_name=key,
instance_type=instance_type,
security_groups=security_groups,
placement=placement,
ebs_optimized=optimized_ebs,
block_device_map=bdm)
inst = res.instances[0]
time.sleep(30)
inst.update()
connection.create_tags([inst.id], {'Name': '%s-%s' % (cluster, name),'Cluster': cluster})
Before the #NEW BLOCK code block was added it all worked. After create_instance was called, I would check the state of the new instance and it would be 'running'.
I added the block to create the instance with the volume of type 'IO1' instead of the default (following the accepted answer here). I do not get any exceptions or other errors here but when I check the instance state later I get 'terminated'. What am I doing wrong?
There may be other issues but one problem I see is the value you are providing for iops in the block device mapping. That value must be between 100-4000 (see API docs).

Resources