英文:
How to keep the same public IP addresses for a set of AWS EC2 instances with Terraform
问题
我有一个Terraform配置,创建了4个EC2实例并为它们分配了静态的公共IP地址:
resource "aws_instance" "kit-prod-api" {
subnet_id = element(module.vpc.public_subnets, count.index)
ami = data.aws_ami.kit_prod_api_ami.id
instance_type = "c5.large"
associate_public_ip_address = true
vpc_security_group_ids = [module.vpc.default_security_group_id, aws_security_group.prod_ssh_security_group.id, aws_security_group.prod_lb_instances_security_group.id]
count = 4
lifecycle {
create_before_destroy = "true"
ignore_changes = [
ami,
instance_type,
]
}
# 这里还有标签和provisioner块
}
当我想要更新实例以使用新的AMI时,我逐个终止它们并运行terraform apply
来替换它们。这个方法有效,但它们会获得新的公共IP地址。我可以看到它们的旧IP地址显示在AWS控制台的EIP部分,但之前并没有。有没有办法修改这个脚本/工作流,以便将相同的IP地址与新实例关联起来?
英文:
I have a terraform config that creates 4 EC2 instances and gives them static public IP addresses:
resource "aws_instance" "kit-prod-api" {
subnet_id = element(module.vpc.public_subnets, count.index)
ami = data.aws_ami.kit_prod_api_ami.id
instance_type = "c5.large"
associate_public_ip_address = true
vpc_security_group_ids = [module.vpc.default_security_group_id, aws_security_group.prod_ssh_security_group.id, aws_security_group.prod_lb_instances_security_group.id]
count = 4
lifecycle {
create_before_destroy = "true"
ignore_changes = [
ami,
instance_type,
]
}
# there are tags and provisioner blocks here too
}
When I want to update the instances to use a new AMI, I terminate one at a time and run terraform apply
to replace them. This works, but they get new public IP addresses. I can see that their old IP addresses show up in the EIP section of the AWS console, but they weren't there beforehand. Is there a way to modify this script/workflow so that the same IP addresses get associated with the new instances?
答案1
得分: 1
EC2实例中的IP地址属于网络接口,而不属于EC2实例本身,但EC2 API允许在创建EC2实例的同时隐式创建网络接口,因此通常看起来像是网络接口属性(如IP地址)直接属于EC2实例,尽管这不是严格的真实情况。
您可以通过将网络接口声明为独立资源来使其存在于EC2实例之外:
resource "aws_network_interface" "example" {
count = 4
subnet_id = element(module.vpc.public_subnets, count.index)
}
resource "aws_instance" "example" {
count = length(aws_network_interface.example)
# ...
network_interface {
network_interface_id = aws_network_interface.example[count.index].id
}
}
上面的分离允许与网络接口关联的私有IPv4地址在关联的EC2实例被替换时仍然存在。但是,请注意,每个网络接口一次只能附加一个EC2实例,因此在这种情况下您将无法使用create_before_destroy
-- 这将导致两个实例尝试同时附加到同一个网络接口,因此您需要找到不同的策略以确保在替换所有实例的情况下保持连续性。
上面的操作只会保留私有IP地址。要将一致的公共IP地址与每个网络接口关联,您需要另外声明一个弹性IP地址,它允许您分配一个与任何特定网络接口或EC2实例的生存期无关的公共IP地址。弹性IP地址将与网络接口关联,因此它也可以在替换特定实例时继续存在:
#(这是上面示例的补充)
resource "aws_eip" "example" {
count = length(aws_network_interface.example)
network_interface = aws_network_interface.example[count.index].id
}
为了避免EC2实例已经开始启动后其公共IP地址发生更改,您还应该告诉Terraform EC2实例资源与弹性IP地址资源之间的隐含依赖关系,因为Terraform无法从引用中自动推断它:
resource "aws_instance" "example" {
# 其他内容不变,加上:
depends_on = [aws_eip.example]
}
请注意,当分配但未附加到运行的EC2实例时,弹性IP地址会有额外的成本。
有关这些资源类型的更多信息:
英文:
IP addresses in EC2 belong to network interfaces rather than to EC2 instances, but the EC2 API allows implicitly creating a network interface as a side-effect of creating an EC2 instance and so it often appears as though network interface attributes like IP addresses belong directly to EC2 instances, even though that isn't strictly true.
You can make a network interface outlive the EC2 instance it's attached to by declaring the network interface as a separate resource:
resource "aws_network_interface" "example" {
count = 4
subnet_id = element(module.vpc.public_subnets, count.index)
}
resource "aws_instance" "example" {
count = length(aws_network_interface.example)
# ...
network_interface {
network_interface_id = aws_network_interface.example[count.index].id
}
}
The separation above allows the private IPv4 address associated with the network interface to survive even if the associated EC2 instances are replaced. However, note that only one EC2 instance can be attached to each network interface at a particular time, so you won't be able to use create_before_destroy
in this case -- that would cause two instances to try to attach to the same network interface at the same time -- and so you'll need to find a different strategy to ensure continuity in situations where you're replacing all of the instances.
The above only causes the private IP address to be preserved. To associate a consistent public IP address with each network interface you'll need to additionally declare an Elastic IP address, which allows you to allocate a public IP address whose lifetime is independent of any particular network interface or EC2 instance. The Elastic IP address will be associated with the network interface so it can also survive any particular instance being replaced:
# (this is in addition to the example above)
resource "aws_eip" "example" {
count = length(aws_network_interface.example)
network_interface = aws_network_interface.example[count.index].id
}
To avoid the public IP address of the EC2 instance changing after it's already started booting, you should also tell Terraform about the hidden dependency between the EC2 instance resource and the elastic IP address resource, since Terraform cannot infer it automatically from references:
resource "aws_instance" "example" {
# everything as before, plus:
depends_on = [aws_eip.example]
}
Note that an Elastic IP address has additional cost when it is allocated but not attached to a running EC2 instance.
More information on these resource types:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论