Delay in creation of launch config in AWS - terraform

Using Terraform, I have the following launch config and autoscale group resources defined:
resource "aws_launch_configuration" "lc_name" {
name = "lc_name"
image_id = "ami-035d01348bb6e6070"
instance_type = "m3.large"
security_groups = ["sg-61a0b51b"]
}
####################
# Autoscaling group
####################
resource "aws_autoscaling_group" "as_group_name" {
name = "as_group_name"
launch_configuration = "lc_name"
vpc_zone_identifier = ["subnet-be1088f7","subnet-fa8d6fa1"]
min_size = "1"
max_size = "1"
desired_capacity = "1"
load_balancers = ["${aws_elb.elb_name.name}"]
health_check_type = "EC2"
}
When I run terraform apply, I get:
Error: Error applying plan:
1 error(s) occurred:
aws_autoscaling_group.as_group_name: 1 error(s) occurred:
aws_autoscaling_group.as_group_name: Error creating AutoScaling Group: ValidationError: Launch configuration name not found - A launch configuration with the name: lc_name does not exist
status code: 400, request id: b09191d3-a47c-11e8-8198-198283743bc9
Terraform does not automatically rollback in the face of errors.
Instead, your Terraform state file has been partially updated with
any resources that successfully completed. Please address the error
above and apply again to incrementally change your infrastructure.
If I run apply again, all goes well, strongly implying that there is a delay in the create autoscale group code recognizing a new launch configuration. Is there a way to adjust to this delay?
Update:
Per suggestion, I added a dependency:
resource "aws_launch_configuration" "myLaunchConfig" {
name = "myLaunchConfig"
image_id = "ami-01c068891b0d9411a"
instance_type = "m3.large"
security_groups = ["sg-61a0b51b"]
}
resource "aws_autoscaling_group" "myAutoScalingGroup" {
name = "myAutoScalingGroup"
launch_configuration = "myLaunchConfig"
depends_on = ["myLaunchConfig"]
vpc_zone_identifier = ["subnet-be1088f7","subnet-fa8d6fa1"]
min_size = "1"
max_size = "1"
desired_capacity = "1"
load_balancers = ["${aws_elb.myLoadBalancer.name}"]
health_check_type = "EC2"
}
Still getting an error for the same reason, though it looks a bit different:
Error: aws_autoscaling_group.myAutoScalingGroup: resource depends on non-existent resource 'myLaunchConfig'

As far as Terraform can tell there is no relationship between your autoscaling group and your launch configuration so it is going to try to create these in parallel, leading you to the observed race condition that corrects itself on the next apply.
With Terraform you have two different ways of ordering a dependency chain between resources.
You can use the explicit depends_on syntax to force a resource to wait until another resource is created before it, in turn, is created.
In your case this would be something like:
resource "aws_launch_configuration" "lc_name" {
name = "lc_name"
image_id = "ami-035d01348bb6e6070"
instance_type = "m3.large"
security_groups = ["sg-61a0b51b"]
}
####################
# Autoscaling group
####################
resource "aws_autoscaling_group" "as_group_name" {
name = "as_group_name"
launch_configuration = "lc_name"
vpc_zone_identifier = ["subnet-be1088f7", "subnet-fa8d6fa1"]
min_size = "1"
max_size = "1"
desired_capacity = "1"
load_balancers = ["${aws_elb.elb_name.name}"]
health_check_type = "EC2"
depends_on = ["aws_launch_configuration.lc_name"]
}
Or, and this is generally preferable where possible, if you interpolate a value from one resource then it will automatically wait until that resource is created before creating the second resource.
In your case you would then use something like this:
resource "aws_launch_configuration" "lc_name" {
name = "lc_name"
image_id = "ami-035d01348bb6e6070"
instance_type = "m3.large"
security_groups = ["sg-61a0b51b"]
}
####################
# Autoscaling group
####################
resource "aws_autoscaling_group" "as_group_name" {
name = "as_group_name"
launch_configuration = "${aws_launch_configuration.lc_name.name}"
vpc_zone_identifier = ["subnet-be1088f7", "subnet-fa8d6fa1"]
min_size = "1"
max_size = "1"
desired_capacity = "1"
load_balancers = ["${aws_elb.elb_name.name}"]
health_check_type = "EC2"
}
If you are ever unsure as to the order of things that Terraform will operate on then you might want to take a look at the terraform graph command.

Related

Terrafrom AWS EC2 with no change in the code, trying to destroy and create instance

I used below terrafrom code to create AWS EC2 instance,
resource "aws_instance" "example" {
ami = var.ami-id
instance_type = var.ec2_type
key_name = var.keyname
subnet_id = "subnet-05a63e5c1a6bcb7ac"
security_groups = ["sg-082d39ed218fc0f2e"]
# root disk
root_block_device {
volume_size = "10"
volume_type = "gp3"
encrypted = true
delete_on_termination = true
}
tags = {
Name = var.instance_name
Environment = "dev"
}
metadata_options {
http_endpoint = "enabled"
http_put_response_hop_limit = 1
http_tokens = "required"
}
}
after 5 minutes with no change in the code when I try to run terraform plan. It shows something changed outside of Terraform, its trying destroy and re-create the Ec2 instance. Why is this happening?
How to prevent this?
aws_instance.example: Refreshing state... [id=i-0aa279957d1287100]
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the last "terraform apply":
# aws_instance.example has been changed
~ resource "aws_instance" "example" {
id = "i-0aa279957d1287100"
~ security_groups = [
- "sg-082d39ed218fc0f2e",
]
tags = {
"Environment" = "dev"
"Name" = "ec2linux"
}
# (26 unchanged attributes hidden)
~ root_block_device {
+ tags = {}
# (9 unchanged attributes hidden)
}
# (4 unchanged blocks hidden)
}
Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these
changes.
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
adding image:
You must use vpc_security_group_ids instead of security_groups
resource "aws_instance" "example" {
ami = var.ami-id
instance_type = var.ec2_type
key_name = var.keyname
subnet_id = "subnet-05a63e5c1a6bcb7ac"
vpc_security_group_ids = ["sg-082d39ed218fc0f2e"]
# root disk
root_block_device {
volume_size = "10"
volume_type = "gp3"
encrypted = true
delete_on_termination = true
}
tags = {
Name = var.instance_name
Environment = "dev"
}
metadata_options {
http_endpoint = "enabled"
http_put_response_hop_limit = 1
http_tokens = "required"
}
}

Argument or block definition required Terraform - Error

I have autoscaling group and I need to create an application load balancer for accessing the application this is my two code for autoscaling and application load balancer but I get this issue
autoscaling group
resource "aws_launch_configuration" "OS-Type"{
name_prefix = "OS-Type"
image_id = "ami-0996d3051b72b5b2c"
instance_type = "t2.micro"
lifecycle {
create_before_destroy = true
}
}
resource "aws_autoscaling_group" "Dynamic-IN"{
name = "Dynamic-EC2-instance"
min_size = 1
max_size = 4
desired_capacity = 2
health_check_type = "ELB"
launch_configuration = aws_launch_configuration.OS-Type.name
vpc_zone_identifier = [aws_subnet.P-AV1.id, aws_subnet.P-AV2.id]
target_group_arns="aws_lb.App-lb.name"
lifecycle {
create_before_destroy = true
}
}
Application load balancer
resource "aws_lb_target_group" "Target-group"{
name = "Target-group"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.main.id
}
resource "aws_lb" "App-lb"{
name = "Application-load-balancer"
load_balancer_type = "application"
subnets = [aws_subnet.P-AV1.id , aws_subnet.P-AV2.id]
internal = false
}
resource "aws_autoscaling_attachment" "TG-attach" {
autoscaling_group_name = aws_autoscaling_group.Dynamic-IN.id
alb_target_group_arn = aws_lb_target_group.Target-group.arn
}
I get this error
Error: Argument or block definition required
on autoscalling-group.tf line 20, in resource "aws_autoscaling_group" "Dynamic-IN":
20: target_group.arns="aws_lb.App-lb.name"
An argument or block definition is required here. To set an argument, use the
equals sign "=" to introduce the argument value.
I have tried
I have tried aws_lb.App-lb.arns for the target group alos but not working in the both ways
Yes like you suspect there should not quotes there:
target_group_arns="aws_lb.App-lb.name"
and that 'target_group_arns' is a set not a single item: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/autoscaling_group#target_group_arns
target_group_arns (Optional) A set of aws_alb_target_group ARNs, for use with Application or Network Load Balancing.
Your code should probably be something like:
resource "aws_autoscaling_group" "Dynamic-IN" {
name = "Dynamic-EC2-instance"
min_size = 1
max_size = 4
desired_capacity = 2
health_check_type = "ELB"
launch_configuration = aws_launch_configuration.OS-Type.name
vpc_zone_identifier = [ aws_subnet.P-AV1.id, aws_subnet.P-AV2.id ]
target_group_arns = [ aws_lb_target_group.Target-group.arn ]
lifecycle {
create_before_destroy = true
}
}

How re-attache ebs volume using terraform

I'm trying to keep AWS EBS volume as a persistent data-store, every week my AMI changes so I have to spin-up new VM in aws. At this time I'm expecting my volume to detach from the old VM and attach to a new VM without destroying the EBS volume and data.
resource "aws_instance" "my_instance" {
count = var.instance_count
ami = lookup(var.ami,var.aws_region)
instance_type = var.instance_type
key_name = aws_key_pair.terraform-demo.key_name
subnet_id = aws_subnet.main-public-1.id
// user_data = "${file("install_apache.sh")}"
tags = {
Name = "Terraform-${count.index + 1}"
Batch = "5AM"
}
}
variable "instances" {
type = map
default = {
"xx" = "sss-console"
"4xx" = "sss-upload-port"
"xxx" = "sss"
}
}
resource "aws_kms_key" "cmp_kms" {
description = "ssss-ebsencrypt"
tags = local.all_labels
}
resource "aws_ebs_volume" "volumes" {
count = var.instance_count
availability_zone = element(aws_instance.my_instance.*.availability_zone, count.index )
encrypted = true
kms_key_id = aws_kms_key.cmp_kms.arn
size = local.volume_size
type = local.volume_type
iops = local.volume_iops
// tags = merge(var.extra_labels, map("Name", "${var.cell}-${element(local.volume_name, count.index)}"))
lifecycle {
// prevent_destroy = true
ignore_changes = [kms_key_id, instance_id]
}
}
resource "aws_volume_attachment" "volumes-attachment" {
depends_on = [aws_instance.my_instance, aws_ebs_volume.volumes]
count = var.instance_count
device_name = "/dev/${element(local.volume_name, count.index)}"
volume_id = element(aws_ebs_volume.volumes.*.id, count.index)
instance_id = element(aws_instance.my_instance.*.id, count.index)
force_detach = true
}
ERROR on terraform apply
Error: Unsupported attribute
on instance.tf line 71, in resource "aws_ebs_volume" "volumes":
71: ignore_changes = [kms_key_id, instance_id]
This object has no argument, nested block, or exported attribute named
"instance_id".
earlier the same code use to work with terraform v0.11 but it's not working with v0.12. what is the replacement for this or how can we re-attach EBS to a different machine without destroying it?
As per terraform documentation, they do not expose any attribute named as instance_id for resource aws_ebs_volume.
For reference: https://www.terraform.io/docs/providers/aws/d/ebs_volume.html.
You can specify the instance_id at the time of volume attachment using resource
aws_volume_attachment.
You can refer the answer given in https://gitter.im/hashicorp-terraform/Lobby?at=5ab900eb2b9dfdbc3a237e36 for more information.

How to create multiple volumes and attach to each instance via Terraform

The below code works when ec2_instance_count = 1
However, when ec2_instance_count = 2 the following errors appears.
Error: Error running plan: 1 error(s) occurred:
* module.build_ec2_app.aws_ebs_volume.ebs_volume: 1 error(s) occurred:
* module.build_ec2_app.aws_ebs_volume.ebs_volume: Resource 'aws_instance.ec2' not found for variable 'aws_instance.ec2.availability_zone'
I believe it has something to do with the if statement var.ec2_create_volume == "true" ? 1 : 0
But even if I change "true" ? 1 : 0 to "true" ? 2 : 0 on both resources I still get an error when ec2_instance_count = 2
# Create EC2 instances
resource "aws_instance" "ec2" {
count = "${var.ec2_instance_count}"
ami = "${var.ec2_instance_ami_id}"
instance_type = "${var.ec2_instance_type}"
subnet_id = "${var.ec2_instance_subnet_id}"
key_name = "${var.ec2_instance_key_name}"
vpc_security_group_ids = ["${aws_security_group.create_security_group.id}"]
tags {
Name = "${var.ec2_app_name}0${count.index + 1}.${var.ec2_tl_domain_name}"
service = "${var.ec2_service}"
environment = "${var.ec2_environment}"
}
}
# Create EBS volume
resource "aws_ebs_volume" "ebs_volume" {
count = "${var.ec2_create_volume == "true" ? 1 : 0}"
availability_zone = "${aws_instance.ec2.availability_zone}"
size = "${var.ec2_ebs_volume_size}"
}
# Attach EBS Volume
resource "aws_volume_attachment" "volume_attachment" {
count = "${var.ec2_create_volume == "true" ? 1 : 0}"
device_name = "${var.ec2_device_name}"
volume_id = "${aws_ebs_volume.ebs_volume.id}"
instance_id = "${aws_instance.ec2.id}"
}
The problem here is that when the count for aws_instance.ec2 is increased to 2, the resource attribute export changes from a string to a list, and you are still referencing it as a string. You can see more information on that at the documentation here.
By updating the volume_attachment and ebs_volume resources to expect a list instead of a string and iterate correspondingly to the aws_instance resource, you can fix your problem:
# Create EBS volume
resource "aws_ebs_volume" "ebs_volume" {
count = "${var.ec2_create_volume == "true" ? 1 : 0}"
availability_zone = "${aws_instance.ec2.*.availability_zone[count.index]}"
size = "${var.ec2_ebs_volume_size}"
}
# Attach EBS Volume
resource "aws_volume_attachment" "volume_attachment" {
count = "${var.ec2_create_volume == "true" ? 1 : 0}"
device_name = "${var.ec2_device_name}"
volume_id = "${aws_ebs_volume.ebs_volume.*.id[count.index]}"
instance_id = "${aws_instance.ec2.*.id[count.index]}"
}
Note that this still works correctly even when count is 1. A recommendation here is to also sync your count of the aws_instance resource with the count of the other two so that you ensure they are the same.
# Create EBS volume
resource "aws_ebs_volume" "ebs_volume" {
count = "${var.ec2_instance_count}"
availability_zone = "${aws_instance.ec2.*.availability_zone[count.index]}"
size = "${var.ec2_ebs_volume_size}"
}
# Attach EBS Volume
resource "aws_volume_attachment" "volume_attachment" {
count = "${var.ec2_instance_count}"
device_name = "${var.ec2_device_name}"
volume_id = "${aws_ebs_volume.ebs_volume.*.id[count.index]}"
instance_id = "${aws_instance.ec2.*.id[count.index]}"
}
Locking the input var together ensures you will have the behavior you desire when you change the instance count and that undesired issues and side effects will not consequently arise.
The solution below works for multiple instances across multiple az. Here device_name is list of string so we need to pass as many names as the number of additional volumes and volume_count is the length of list of number additional_volume_size.
resource "aws_ebs_volume" "ebs_volume" {
count = var.instance_count * var.volume_count
availability_zone = aws_instance.ec2[floor(count.index/var.volume_count)].availability_zone
size = var.additional_volume_size[count.index%var.volume_count]
}
resource "aws_volume_attachment" "volume_attachement" {
count = var.instance_count * var.volume_count
volume_id = element(aws_ebs_volume.ebs_volume.*.id, count.index)
device_name = element(var.device_name, count.index)
instance_id = element(aws_instance.ec2.*.id, floor(count.index/var.volume_count))
force_detach = true
}

Terraform resource recreation dynamic AWS RDS instance counts

I have a question relating to AWS RDS cluster and instance creation.
Environment
We recently experimented with:
Terraform v0.11.11
provider.aws v1.41.0
Background
Creating some AWS RDS databases. Our mission was that in some environment (e.g. staging) we may run fewer instances than in others (e.g. production.). With this in mind and not wanting to have totally different terraform files per environment we instead decided to specify the database resources just once and use a variable for the number of instances which is set in our staging.tf and production.tf files respectively for the number of instances.
Potentially one more "quirk" of our setup, is that the VPC in which the subnets exist is not defined in terraform, the VPC already existed via manual creation in the AWS console, so this is provided as a data provider and the subnets for the RDS are specific in terraform - but again this is dynamic in the sense that in some environments we might have 3 subnets (1 in each AZ), whereas in others perhaps we have only 2 subnets. Again to achieve this we used iteration as shown below:
Structure
|-/environments
-/staging
-staging.tf
-/production
-production.tf
|- /resources
- database.tf
Example Environment Variables File
dev.tf
terraform {
terraform {
backend "s3" {
bucket = "my-bucket-dev"
key = "terraform"
region = "eu-west-1"
encrypt = "true"
acl = "private"
dynamodb_table = "terraform-state-locking"
}
version = "~> 0.11.8"
}
provider "aws" {
access_key = "${var.access_key}"
secret_key = "${var.secret_key}"
region = "${var.region}"
version = "~> 1.33"
allowed_account_ids = ["XXX"]
}
module "main" {
source = "../../resources"
vpc_name = "test"
test_db_name = "terraform-test-db-dev"
test_db_instance_count = 1
test_db_backup_retention_period = 7
test_db_backup_window = "00:57-01:27"
test_db_maintenance_window = "tue:04:40-tue:05:10"
test_db_subnet_count = 2
test_db_subnet_cidr_blocks = ["10.2.4.0/24", "10.2.5.0/24"]
}
We came to this module based structure for environment isolation mainly due to these discussions:
https://github.com/hashicorp/terraform/issues/18632#issuecomment-412247266
https://github.com/hashicorp/terraform/issues/13700
https://www.terraform.io/docs/state/workspaces.html#when-to-use-multiple-workspaces
Our Issue
Initial resource creation works fine, our subnets are created, the database cluster starts up.
Our issues start the next time we subsequently run a terraform plan or terraform apply (with no changes to the files), at which point we see interesting things like:
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
module.main.aws_rds_cluster.test_db (new resource required)
id: "terraform-test-db-dev" => (forces new resource)
availability_zones.#: "3" => "1" (forces new resource)
availability_zones.1924028850: "eu-west-1b" => "" (forces new resource)
availability_zones.3953592328: "eu-west-1a" => "eu-west-1a"
availability_zones.94988580: "eu-west-1c" => "" (forces new resource)
and
module.main.aws_rds_cluster_instance.test_db (new resource required)
id: "terraform-test-db-dev" => (forces new resource)
cluster_identifier: "terraform-test-db-dev" => "${aws_rds_cluster.test_db.id}" (forces new resource)
Something about the way we are approaching this appears to be causing terraform to believe that the resource has changed to such an extent that it must destroy the existing resource and create a brand new one.
Config
variable "aws_availability_zones" {
description = "Run the EC2 Instances in these Availability Zones"
type = "list"
default = ["eu-west-1a", "eu-west-1b", "eu-west-1c"]
}
variable "test_db_name" {
description = "Name of the RDS instance, must be unique per region and is provided by the module config"
}
variable "test_db_subnet_count" {
description = "Number of subnets to create, is provided by the module config"
}
resource "aws_security_group" "test_db_service" {
name = "${var.test_db_service_user_name}"
vpc_id = "${data.aws_vpc.vpc.id}"
}
resource "aws_security_group" "test_db" {
name = "${var.test_db_name}"
vpc_id = "${data.aws_vpc.vpc.id}"
}
resource "aws_security_group_rule" "test_db_ingress_app_server" {
security_group_id = "${aws_security_group.test_db.id}"
...
source_security_group_id = "${aws_security_group.test_db_service.id}"
}
variable "test_db_subnet_cidr_blocks" {
description = "Cidr block allocated to the subnets"
type = "list"
}
resource "aws_subnet" "test_db" {
count = "${var.test_db_subnet_count}"
vpc_id = "${data.aws_vpc.vpc.id}"
cidr_block = "${element(var.test_db_subnet_cidr_blocks, count.index)}"
availability_zone = "${element(var.aws_availability_zones, count.index)}"
}
resource "aws_db_subnet_group" "test_db" {
name = "${var.test_db_name}"
subnet_ids = ["${aws_subnet.test_db.*.id}"]
}
variable "test_db_backup_retention_period" {
description = "Number of days to keep the backup, is provided by the module config"
}
variable "test_db_backup_window" {
description = "Window during which the backup is done, is provided by the module config"
}
variable "test_db_maintenance_window" {
description = "Window during which the maintenance is done, is provided by the module config"
}
data "aws_secretsmanager_secret" "test_db_master_password" {
name = "terraform/db/test-db/root-password"
}
data "aws_secretsmanager_secret_version" "test_db_master_password" {
secret_id = "${data.aws_secretsmanager_secret.test_db_master_password.id}"
}
data "aws_iam_role" "rds-monitoring-role" {
name = "rds-monitoring-role"
}
resource "aws_rds_cluster" "test_db" {
cluster_identifier = "${var.test_db_name}"
engine = "aurora-mysql"
engine_version = "5.7.12"
# can only request to deploy in AZ's where there is a subnet in the subnet group.
availability_zones = "${slice(var.aws_availability_zones, 0, var.test_db_instance_count)}"
database_name = "${var.test_db_schema_name}"
master_username = "root"
master_password = "${data.aws_secretsmanager_secret_version.test_db_master_password.secret_string}"
preferred_backup_window = "${var.test_db_backup_window}"
preferred_maintenance_window = "${var.test_db_maintenance_window}"
backup_retention_period = "${var.test_db_backup_retention_period}"
db_subnet_group_name = "${aws_db_subnet_group.test_db.name}"
storage_encrypted = true
kms_key_id = "${data.aws_kms_key.kms_rds_key.arn}"
deletion_protection = true
enabled_cloudwatch_logs_exports = ["audit", "error", "general", "slowquery"]
vpc_security_group_ids = ["${aws_security_group.test_db.id}"]
final_snapshot_identifier = "test-db-final-snapshot"
}
variable "test_db_instance_count" {
description = "Number of instances to create, is provided by the module config"
}
resource "aws_rds_cluster_instance" "test_db" {
count = "${var.test_db_instance_count}"
identifier = "${var.test_db_name}"
cluster_identifier = "${aws_rds_cluster.test_db.id}"
availability_zone = "${element(var.aws_availability_zones, count.index)}"
instance_class = "db.t2.small"
db_subnet_group_name = "${aws_db_subnet_group.test_db.name}"
monitoring_interval = 60
engine = "aurora-mysql"
engine_version = "5.7.12"
monitoring_role_arn = "${data.aws_iam_role.rds-monitoring-role.arn}"
tags {
Name = "test_db-${count.index}"
}
}
My question is, is there a way to achieve this so that terraform would not try to recreate the resource (e.g. ensure that the availability zones of the cluster and ID of the instance do not change each time we run terraform.
Turns out that simply by just removing the explicit availability zones definitions from the aws_rds_cluster and aws_rds_cluster_instance then this issue goes away and everything so far appears to work as expected. See also https://github.com/terraform-providers/terraform-provider-aws/issues/7307#issuecomment-457441633

Resources