When I run terraform apply first complete infrastructure get provisioned, which is followed by Ansible playbook to configure the server. Next time without updating or changing anything if I do terraform plan it will show force replacement for SG, Instance, Route53 rule and it’ll create new instance and which will not have any configuration applied. I’m locked at single time terraform apply. How to avoid this force replacement or what is the best practice on to maintain terraform in long-run.
If you look at the plan output it should indicate why it is wanting to destroy & recreate or update resources. Some types of changes can only be done by removing and recreating, but they should be clearly marked.
If you can post the plan we might be able to advise more…
Hi, Thanks for quick response.
Terraform will perform the following actions:
# module.security_route53_rule.aws_route53_record.default will be updated in-place
~ resource "aws_route53_record" "default" {
id = "XXXXXXXX"
name = "XXXXXXXXXXXXXX"
~ records = [
- "XXXXXXXXXXXXXX",
] -> (known after apply)
# (4 unchanged attributes hidden)
}
# module.security_service_instance.aws_instance.ec2_instance must be replaced
-/+ resource "aws_instance" "ec2_instance" {
~ arn = "arn:aws:ec2:XXXXXXXXXXXXXXX" -> (known after apply)
~ associate_public_ip_address = false -> (known after apply)
~ availability_zone = "us-XXXX" -> (known after apply)
~ cpu_core_count = 1 -> (known after apply)
~ cpu_threads_per_core = 1 -> (known after apply)
- disable_api_termination = false -> null
- ebs_optimized = false -> null
- hibernation = false -> null
+ host_id = (known after apply)
~ id = "XXXXXXXXXX" -> (known after apply)
~ instance_state = "running" -> (known after apply)
~ ipv6_address_count = 0 -> (known after apply)
~ ipv6_addresses = [] -> (known after apply)
- monitoring = false -> null
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
~ primary_network_interface_id = "eni-XXXXXXXXXXXXXXXXXXX" -> (known after apply)
~ private_dns = "XXXXXXXXXXXXXXXXXX" -> (known after apply)
~ private_ip = "XXXXXXXXXXXXXXXXX" -> (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
~ secondary_private_ips = [] -> (known after apply)
~ security_groups = [ # forces replacement
+ "sg-09911bcdf96471f08",
]
tags = {
"Environment" = "test"
"Name" = "test-creation"
"Purpose" = "XXXXXXXXXXXXXX"
}
~ tenancy = "default" -> (known after apply)
~ vpc_security_group_ids = [
- "XXXXXXXXXXXXXXXXXx",
] -> (known after apply)
# (7 unchanged attributes hidden)
- credit_specification {
- cpu_credits = "standard" -> null
}
+ ebs_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ snapshot_id = (known after apply)
+ tags = (known after apply)
+ throughput = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
- ebs_block_device {
- delete_on_termination = true -> null
- device_name = "/dev/sdf" -> null
- encrypted = false -> null
- iops = 100 -> null
- snapshot_id = "XXXXXXXXXXXXXXX" -> null
- tags = {} -> null
- throughput = 0 -> null
- volume_id = "XXXXXXXXXXXXXX" -> null
- volume_size = 10 -> null
- volume_type = "gp2" -> null
}
~ enclave_options {
~ enabled = false -> (known after apply)
}
+ ephemeral_block_device {
+ device_name = (known after apply)
+ no_device = (known after apply)
+ virtual_name = (known after apply)
}
~ metadata_options {
~ http_endpoint = "enabled" -> (known after apply)
~ http_put_response_hop_limit = 1 -> (known after apply)
~ http_tokens = "optional" -> (known after apply)
}
+ network_interface {
+ delete_on_termination = (known after apply)
+ device_index = (known after apply)
+ network_interface_id = (known after apply)
}
~ root_block_device {
~ delete_on_termination = true -> (known after apply)
~ device_name = "/dev/xvda" -> (known after apply)
~ encrypted = false -> (known after apply)
~ iops = 100 -> (known after apply)
+ kms_key_id = (known after apply)
~ tags = {} -> (known after apply)
~ throughput = 0 -> (known after apply)
~ volume_id = "vol-XXXXXXXXXXXX" -> (known after apply)
~ volume_size = 8 -> (known after apply)
~ volume_type = "gp2" -> (known after apply)
}
}
# module.security_target_attachment.aws_lb_target_group_attachment.moterum-ec2-TGA must be replaced
-/+ resource "aws_lb_target_group_attachment" "moterum-ec2-TGA" {
~ id = "arn:aws:elasticloadbalancing:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" -> (known after apply)
~ target_id = "i-XXXXXXXXXXXXXXXX" -> (known after apply) # forces replacement
# (2 unchanged attributes hidden)
}
Plan: 2 to add, 1 to change, 2 to destroy.
Hi @Ruman1996 ,
would you kindly format your tf plan output properly enclosing it in triple back-ticks? It helps reading it.
\\ Terraform will perform the following actions:
module.security_route53_rule.aws_route53_record.default will be updated in-place
~ resource “aws_route53_record” “default” {
id = “XXXXXXXX”
name = “XXXXXXXXXXXXXX”
~ records = [
- “XXXXXXXXXXXXXX”,
] → (known after apply)
# (4 unchanged attributes hidden)
}
module.security_service_instance.aws_instance.ec2_instance must be replaced
-/+ resource “aws_instance” “ec2_instance” {
~ arn = “arn:aws:ec2:XXXXXXXXXXXXXXX” → (known after apply)
~ associate_public_ip_address = false → (known after apply)
~ availability_zone = “us-XXXX” → (known after apply)
~ cpu_core_count = 1 → (known after apply)
~ cpu_threads_per_core = 1 → (known after apply)
- disable_api_termination = false → null
- ebs_optimized = false → null
- hibernation = false → null
+ host_id = (known after apply)
~ id = “XXXXXXXXXX” → (known after apply)
~ instance_state = “running” → (known after apply)
~ ipv6_address_count = 0 → (known after apply)
~ ipv6_addresses = → (known after apply)
- monitoring = false → null
+ outpost_arn = (known after apply)
+ password_data = (known after apply)
+ placement_group = (known after apply)
~ primary_network_interface_id = “eni-XXXXXXXXXXXXXXXXXXX” → (known after apply)
~ private_dns = “XXXXXXXXXXXXXXXXXX” → (known after apply)
~ private_ip = “XXXXXXXXXXXXXXXXX” → (known after apply)
+ public_dns = (known after apply)
+ public_ip = (known after apply)
~ secondary_private_ips = → (known after apply)
~ security_groups = [ # forces replacement
+ “sg-09911bcdf96471f08”,
]
tags = {
“Environment” = “test”
“Name” = “test-creation”
“Purpose” = “XXXXXXXXXXXXXX”
}
~ tenancy = “default” → (known after apply)
~ vpc_security_group_ids = [
- “XXXXXXXXXXXXXXXXXx”,
] → (known after apply)
# (7 unchanged attributes hidden)
- credit_specification {
- cpu_credits = "standard" -> null
}
+ ebs_block_device {
+ delete_on_termination = (known after apply)
+ device_name = (known after apply)
+ encrypted = (known after apply)
+ iops = (known after apply)
+ kms_key_id = (known after apply)
+ snapshot_id = (known after apply)
+ tags = (known after apply)
+ throughput = (known after apply)
+ volume_id = (known after apply)
+ volume_size = (known after apply)
+ volume_type = (known after apply)
}
- ebs_block_device {
- delete_on_termination = true -> null
- device_name = "/dev/sdf" -> null
- encrypted = false -> null
- iops = 100 -> null
- snapshot_id = "XXXXXXXXXXXXXXX" -> null
- tags = {} -> null
- throughput = 0 -> null
- volume_id = "XXXXXXXXXXXXXX" -> null
- volume_size = 10 -> null
- volume_type = "gp2" -> null
}
~ enclave_options {
~ enabled = false -> (known after apply)
}
+ ephemeral_block_device {
+ device_name = (known after apply)
+ no_device = (known after apply)
+ virtual_name = (known after apply)
}
~ metadata_options {
~ http_endpoint = "enabled" -> (known after apply)
~ http_put_response_hop_limit = 1 -> (known after apply)
~ http_tokens = "optional" -> (known after apply)
}
+ network_interface {
+ delete_on_termination = (known after apply)
+ device_index = (known after apply)
+ network_interface_id = (known after apply)
}
~ root_block_device {
~ delete_on_termination = true -> (known after apply)
~ device_name = "/dev/xvda" -> (known after apply)
~ encrypted = false -> (known after apply)
~ iops = 100 -> (known after apply)
+ kms_key_id = (known after apply)
~ tags = {} -> (known after apply)
~ throughput = 0 -> (known after apply)
~ volume_id = "vol-XXXXXXXXXXXX" -> (known after apply)
~ volume_size = 8 -> (known after apply)
~ volume_type = "gp2" -> (known after apply)
}
}
module.security_target_attachment.aws_lb_target_group_attachment.moterum-ec2-TGA must be replaced
-/+ resource “aws_lb_target_group_attachment” “moterum-ec2-TGA” {
~ id = “arn:aws:elasticloadbalancing:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX” → (known after apply)
~ target_id = “i-XXXXXXXXXXXXXXXX” → (known after apply) # forces replacement
# (2 unchanged attributes hidden)
}
Plan: 2 to add, 1 to change, 2 to destroy.
\\
Hi @Ruman1996,
I think this is a quirk of the AWS provider due to how it needs to deal with the differences between EC2 classic security groups and VPC security groups. You’ve put a VPC security group id in the security_groups
argument, but that argument is for EC2 classic security groups and so when reading the data back from the EC2 API during the refresh step the value essentially “migrates” to the vpc_security_group_ids
argument, causing a difference compared to your configuration.
I think you can avoid the problem by changing security_groups =
to vpc_security_groups =
in your aws_instance
resource configuration, which should then cause the configuration to still match after the data has been normalized by the remote API and AWS provider.
@apparentlymart ,
Thank you very much, it worked now it shows no changes required in second apply.
It was my base module for many ec2 instances and finding this issue was very important.
Thanks again