You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EC2实例停止重启后无法获取公网IP的Terraform配置问题

问题描述

使用Terraform创建带有3个额外私有网络接口的EC2实例,首次执行plan/apply操作后,实例正常创建并获取到公网IP。但停止该EC2实例再重启后,AWS不再为其分配公网IP;再次执行Terraform plan/apply时,EC2实例会被替换销毁。已开启子网自动分配公网IPv4地址,Terraform版本为v1.6.6,AWS Provider版本为~> 4.0。

相关配置

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.0"
    }
  }
}

resource "aws_instance" "monitoring" {
  ami                    = var.ami_id   
  instance_type          = var.instance_type
  subnet_id              = var.subnet_id
  associate_public_ip_address = true
  key_name               = var.key
  # iam_instance_profile   = var.aws_iam_instance_profile
  # security_groups = [aws_security_group.common.id]

  tags = merge(
    local.common_tags,
    {
      "Name" = "${var.monitoring}"
    }
  )

  lifecycle {
    # ignore_changes = [network_interface, associate_public_ip_address, vpc_security_group_ids, security_groups]
    prevent_destroy = false
  }
}

resource "aws_network_interface" "private1" {
  subnet_id       = var.subnet_id
  # security_groups = [aws_security_group.common.id]
  attachment {
    instance     = aws_instance.monitoring.id
    device_index = 1
  }

  tags = merge(
    local.common_tags,
    {
      "Name" = "${var.monitoring}"
    }
  )
}

# 另外两个私有网卡配置类似,省略...

Terraform Plan输出

Terraform will perform the following actions:

  # aws_instance.monitoring must be replaced
-/+ resource "aws_instance" "monitoring" {
      ~ arn                                  = "arn:aws:ec2:eu-west-1:335094912020:instance/i-04dbad35419e5403d" -> (known after apply)
      ~ associate_public_ip_address          = false -> true # forces replacement
      ~ availability_zone                    = "eu-west-1a" -> (known after apply)
      ~ cpu_core_count                       = 2 -> (known after apply)
      ~ cpu_threads_per_core                 = 2 -> (known after apply)
      ~ disable_api_stop                     = false -> (known after apply)
      ~ disable_api_termination              = false -> (known after apply)
      ~ ebs_optimized                        = false -> (known after apply)
      - hibernation                          = false -> null
      + host_id                              = (known after apply)
      + host_resource_group_arn              = (known after apply)
      + iam_instance_profile                 = (known after apply)
      ~ id                                   = "i-04dbad35419e5403d" -> (known after apply)
      ~ instance_initiated_shutdown_behavior = "stop" -> (known after apply)
      ~ instance_state                       = "running" -> (known after apply)
      ~ ipv6_address_count                   = 0 -> (known after apply)
      ~ ipv6_addresses                       = [] -> (known after apply)
      ~ monitoring                           = false -> (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      ~ placement_partition_number           = 0 -> (known after apply)
      ~ primary_network_interface_id         = "eni-067ddc45b42b3bc58" -> (known after apply)
      ~ private_dns                          = "ip-10-1-4-200.eu-west-1.compute.internal" -> (known after apply)
      ~ private_ip                           = "10.1.4.200" -> (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      ~ secondary_private_ips                = [] -> (known after apply)
      ~ security_groups                      = [] -> (known after apply)
        tags                                 = {
            "Name"      = "advertisement-monitoring"
            "Owner"     = "devops-team"
            "Project"   = "advertising"
            "Role"      = "monitoring"
            "Terraform" = "true"
        }
      ~ tenancy                              = "default" -> (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      ~ vpc_security_group_ids               = [
          - "sg-058c8236c532c5a43",
        ] -> (known after apply)
        # (8 unchanged attributes hidden)

      - capacity_reservation_specification {
          - capacity_reservation_preference = "open" -> null
        }

      - cpu_options {
          - core_count       = 2 -> null
          - threads_per_core = 2 -> null
        }

      - credit_specification {
          - cpu_credits = "unlimited" -> null
        }

      - enclave_options {
          - enabled = false -> null
        }

      - maintenance_options {
          - auto_recovery = "default" -> null
        }

      - metadata_options {
          - http_endpoint               = "enabled" -> null
          - http_put_response_hop_limit = 2 -> null
          - http_tokens                 = "required" -> null
          - instance_metadata_tags      = "disabled" -> null
        }

      - private_dns_name_options {
          - enable_resource_name_dns_a_record    = false -> null
          - enable_resource_name_dns_aaaa_record = false -> null
          - hostname_type                        = "ip-name" -> null
        }

      - root_block_device {
          - delete_on_termination = true -> null
          - device_name           = "/dev/xvda" -> null
          - encrypted             = false -> null
          - iops                  = 3000 -> null
          - tags                  = {} -> null
          - throughput            = 125 -> null
          - volume_id             = "vol-05544aeda21e9408c" -> null
          - volume_size           = 8 -> null
          - volume_type           = "gp3" -> null
        }
    }

  # aws_network_interface.private1 will be updated in-place
  ~ resource "aws_network_interface" "private1" {
        id                        = "eni-0f66a33626a3a3f66"
        tags                      = {
            "Name"      = "advertisement-monitoring"
            "Owner"     = "devops-team"
            "Project"   = "advertising"
            "Role"      = "monitoring"
            "Terraform" = "true"
        }
        # (22 unchanged attributes hidden)

      - attachment {
          - attachment_id = "eni-attach-0740bf5f5dfab0d38" -> null
          - device_index  = 1 -> null
          - instance      = "i-04dbad35419e5403d" -> null
        }
      + attachment {
          + attachment_id = (known after apply)
          + device_index  = 1
          + instance      = (known after apply)
        }
    }
原因分析

从Plan输出可以明确看到,associate_public_ip_address属性的实际值从false变为配置中的true,而该属性是Terraform AWS Provider中的强制替换属性,因此触发了EC2实例的销毁重建。

导致状态不一致的核心原因是:

  1. 当EC2实例附加了额外弹性网卡(ENI)时,停止并重启实例后,AWS会自动将associate_public_ip_address的实际状态重置为false,即使子网已开启自动分配公网IP
  2. Terraform同步资源状态时,检测到实际值与配置值不符,进而触发替换逻辑
解决方案

方案1:忽略associate_public_ip_address的状态变化

修改aws_instance资源的lifecycle块,添加ignore_changes规则,让Terraform忽略该属性的差异,避免触发实例替换:

resource "aws_instance" "monitoring" {
  ami                    = var.ami_id   
  instance_type          = var.instance_type
  subnet_id              = var.subnet_id
  associate_public_ip_address = true
  key_name               = var.key

  tags = merge(
    local.common_tags,
    {
      "Name" = "${var.monitoring}"
    }
  )

  lifecycle {
    ignore_changes = [associate_public_ip_address]
    prevent_destroy = false
  }
}

此方案适用于接受临时公网IP的场景,重启后若需要公网IP,可手动在AWS控制台为实例分配,或依赖子网的自动分配规则。

方案2:绑定弹性公网IP(EIP)

如果需要固定公网IP,建议创建并绑定弹性公网IP,彻底避免依赖实例的临时公网IP分配机制:

# 创建弹性公网IP
resource "aws_eip" "monitoring" {
  vpc = true
  tags = merge(local.common_tags, {
    "Name" = "${var.monitoring}-eip"
  })
}

# 将EIP绑定到EC2实例
resource "aws_eip_association" "monitoring" {
  instance_id   = aws_instance.monitoring.id
  allocation_id = aws_eip.monitoring.id
}

弹性公网IP在实例停止重启后会自动重新绑定,确保公网IP不变。

方案3:调整网卡配置方式

避免在独立的aws_network_interface资源中定义attachment块,而是在aws_instance资源内部通过network_interface块统一管理所有网卡,让Terraform更好地维护资源关联状态:

resource "aws_instance" "monitoring" {
  ami                    = var.ami_id   
  instance_type          = var.instance_type
  subnet_id              = var.subnet_id
  associate_public_ip_address = true
  key_name               = var.key

  # 主网卡(device_index=0)
  network_interface {
    device_index = 0
    subnet_id    = var.subnet_id
    # security_groups = [aws_security_group.common.id]
  }

  # 额外私有网卡1
  network_interface {
    device_index = 1
    subnet_id    = var.subnet_id
    # security_groups = [aws_security_group.common.id]
  }

  # 额外私有网卡2
  network_interface {
    device_index = 2
    subnet_id    = var.subnet_id
    # security_groups = [aws_security_group.common.id]
  }

  # 额外私有网卡3
  network_interface {
    device_index = 3
    subnet_id    = var.subnet_id
    # security_groups = [aws_security_group.common.id]
  }

  tags = merge(
    local.common_tags,
    {
      "Name" = "${var.monitoring}"
    }
  )
}

此方式能让Terraform更精准地控制网卡与实例的绑定关系,减少状态不一致的概率。


内容的提问来源于stack exchange,提问作者Steve Angelo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 13:47:32