terraform-provider-aws: DNS record not updated when public IP changes due to instance type change

This issue was originally opened by @wayneworkman as hashicorp/terraform#19578. It was migrated here as a result of the provider split. The original body of the issue is below.


Terraform Version

Terraform v0.11.10

  • provider.aws v1.46.0
  • provider.template v1.0.0

I have an ec2 instance that gets assigned a public IP, and a route53 resource that creates a CNAME record pointing at that ec2 instance’s dns name.

I updated the ec2 instance from a t3.nano to t3.micro. This resulted in the instance stopping, changing it’s type, and then starting up. The instance’s public IP changed due to the stop/start, but the route53 record was not updated with the new DNS name.

Expected Behavior

Terraform should have updated the dns record.

Actual Behavior

Terraform did not update the dns record.

How to reproduce

Run this, which makes an instance with a public IP and a DNS record for it.

resource "aws_instance" "bastion" {
  ami           = "${var.amis["debian9"]}"
  instance_type = "t2.micro"
  subnet_id = "${aws_subnet.public-subnet.id}"
  vpc_security_group_ids = ["${aws_security_group.sg-ssh.id}"]
  associate_public_ip_address = true
  key_name = "${aws_key_pair.ssh-key.key_name}"
}

resource "aws_route53_record" "bastion-dns-record" {
  zone_id = "${var.zone_id}"
  name    = "fogbastion.${var.zone_name}"
  type    = "CNAME"
  ttl     = "300"
  records = ["${aws_instance.bastion.public_dns}"]
}

Then change the instance type to something else, and apply.

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 23
  • Comments: 18 (4 by maintainers)

Most upvoted comments

The “workaround” is to terraform apply (resize) -> wait for it… -> terraform apply (change dns record).

I think I have a better one…

WORKAROUND: Use a “middle-man” data source.

So, instead of, say…

resource "aws_instance" "example" {
  (...)
}

resource "aws_route53_record" "example" {
 (...)
  records = [aws_instance.example.public_dns]
}

…use this:

resource "aws_instance" "example" {
  (...)
}

data "aws_instance" "example" {
  instance_id = aws_instance.example.id
}

resource "aws_route53_record" "example" {
 (...)
  records = [data.aws_instance.example.public_dns]
}

It seems the underlying “data” management code doesn’t try to be as “clever” as others and forces a full “recalculation” of its contents only after the upstream resource (the aws instance, in this case) is fully updated.

This way, since the aws_instance.example is changed, the associated datasource gets “recalculated” and the final dependent resource (a DNS record in this case) ends up with the proper value instead of the old one.

Use of aws_eip seems to work well as a workaround for this issue.

Thank you for the workaround @jklusis! I was experiencing the same issue, and your solution worked for me 😄

Hey! I’m also facing this issue in a similar case when changing the instance state via resource.

Attempted to use the data solution, but, unfortunately, it did not work out by itself in my case, probably due to me not changing the aws_instance itself. Found out a hack that, if the data attribute depends on a resource that will be directly changed (in my case – instance state), it will wait for it to be provisioned before reading the values.

Terraform:

resource "aws_instance" "this" {
  ami           = ...
  instance_type = ...

  associate_public_ip_address = true

...

  lifecycle {
    ignore_changes = [associate_public_ip_address]
  }
}

data "aws_instance" "this" {
  instance_id = aws_instance.this.id

  depends_on = [aws_ec2_instance_state.instance] # This is the addition that forces the data to force-refresh
}

resource "aws_ec2_instance_state" "instance" {
  instance_id = aws_instance.this.id
  state       = var.ec2_target_state
}

resource "aws_route53_record" "base" {
  count = var.ec2_target_state == "running" ? 1 : 0

  zone_id = var.dns_zone_id
  name    = <redacted>
  type    = "A"
  ttl     = 300
  records = [data.aws_instance.this.public_ip]
}

Plan:

  # data.aws_instance.this will be read during apply
  # (depends on a resource or a module with changes pending)
 <= data "aws_instance" "this" {
...
      + instance_id                 = <redacted>
...
      + private_dns                 = (known after apply)
      + private_dns_name_options    = (known after apply)
      + private_ip                  = (known after apply)
      + public_dns                  = (known after apply)
      + public_ip                   = (known after apply)
...
    }

...

 # aws_ec2_instance_state.instance will be updated in-place
  ~ resource "aws_ec2_instance_state" "instance" {
        id          = "i-1234567890"
      ~ state       = "stopped" -> "running"
        # (2 unchanged attributes hidden)
    }

  # aws_route53_record.base[0] will be created
  + resource "aws_route53_record" "base" {
      + allow_overwrite = (known after apply)
      + fqdn            = (known after apply)
      + id              = (known after apply)
      + name            = "<redacted>"
      + records         = (known after apply) # This is what we like to see
      + ttl             = 300
      + type            = "A"
      + zone_id         = "<redacted>"
    }

Initially also tried depending directly within aws_route53_record but unfortunately it did not work. And, adding the dependency within aws_instance creates a circular dependency.

Agreed that ideally this would be resolved within Terraform itself.