Terraform更新EC2 user_data后输出旧IP问题及解决咨询
当更新EC2实例的user_data时,实例会触发重建,但Terraform最终输出的是旧实例的公网IP,而非新实例的IP。
环境信息
- 通过GitHub Actions运行Terraform,使用
hashicorp/setup-terraform@v2动作 - Terraform状态存储在AWS S3中
- AWS凭证通过
aws-actions/configure-aws-credentials@v2动作,借助OIDC分配IAM角色
相关配置代码
resource "aws_instance" "ec2_instance" { ami = "ami-053b0d53c279acc90" # Ubuntu 20.04 AMI instance_type = "t2.micro" key_name = "aws_keys_pairs-tfa" iam_instance_profile = aws_iam_instance_profile.ec2_profile.name vpc_security_group_ids = [aws_security_group.ssh-sec-grp.id] tags = { Name = "Ubuntu-Docker-Instance=tfa" } connection { type = "ssh" host = self.public_ip user = "ubuntu" private_key = file("aws_keys_pairs.pem") timeout = "4m" } } output "ec2_public_ip" { description = "Public IP of the instance" value = aws_instance.ec2_instance.*.public_ip }
日志与输出示例
日志显示Provider已检测到新IP:
2023-08-23T05:23:37.274Z [WARN] Provider "provider["registry.terraform.io/hashicorp/aws"]" produced an unexpected new value for aws_instance.ec2_instance, but we are tolerating it because it is using the legacy plugin SDK.
The following problems may be the cause of any confusing errors from downstream operations:
- .public_ip: was cty.StringVal("54.152.128.106"), but now cty.StringVal("34.230.23.155")
- .public_dns: was cty.StringVal("ec2-54-152-128-106.compute-1.amazonaws.com"), but now cty.StringVal("ec2-34-230-23-155.compute-1.amazonaws.com")
aws_instance.ec2_instance: Modifications complete after 1m1s [id=i-06225b4f29db7d3dc]
data.aws_instance.new_instance: Reading...
data.aws_instance.new_instance: Read complete after 1s [id=i-06225b4f29db7d3dc]
但最终输出仍为旧IP:
ec2_public_ip = [ "54.158.213.194", ]
原因分析
- 输出语法问题:使用了旧的列表展开语法
aws_instance.ec2_instance.*.public_ip,这种语法在单个实例场景下可能无法正确同步最新的资源属性,Terraform状态更新后,该表达式可能仍引用旧的缓存值。 - 状态同步延迟:虽然日志显示Provider已识别到新IP,但输出表达式的写法导致Terraform没有正确拉取最新的实例状态。
解决方案
1. 修正输出表达式
将输出中的列表展开语法改为直接引用单个实例的public_ip属性:
output "ec2_public_ip" { description = "Public IP of the instance" value = aws_instance.ec2_instance.public_ip }
如果需要输出列表格式,可显式包裹为数组:
output "ec2_public_ip" { description = "Public IP of the instance" value = [aws_instance.ec2_instance.public_ip] }
2. 确保user_data变更触发正确重建
如果配置中包含user_data字段,默认情况下Terraform会在user_data变更时销毁并重建实例。若使用了lifecycle规则,需确保create_before_destroy不会导致状态混淆:
resource "aws_instance" "ec2_instance" { # ... 其他配置 ... user_data = base64encode(file("user_data.sh")) lifecycle { create_before_destroy = true # 可选,先创建新实例再销毁旧的 } }
3. 确保GitHub Actions中获取最新输出
在GitHub Actions workflow中,执行terraform apply后直接调用terraform output获取最新值,避免依赖缓存:
- name: Apply Terraform changes run: terraform apply -auto-approve - name: Get latest EC2 public IP run: terraform output ec2_public_ip
内容的提问来源于stack exchange,提问作者manu muraleedharan

