澄清Terraform之外的更改
我不完全了解Terraform如何处理外部变化。让我们举一个例子:
resource "aws_instance" "ec2-test" {
ami = "ami-0d71ea30463e0ff8d"
instance_type = "t2.micro"
}
1:安全组修改
默认安全组已由另一个人手动替换。 Terraform检测更改:
❯ terraform plan --refresh-only
aws_instance.ec2-test: Refreshing state... [id=i-5297abcc6001ce9a8]
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the last "terraform apply" which may have affected this plan:
# aws_instance.ec2-test has changed
~ resource "aws_instance" "ec2-test" {
id = "i-5297abcc6001ce9a8"
~ security_groups = [
- "default",
+ "test",
]
tags = {}
~ vpc_security_group_ids = [
+ "sg-8231be9a95a4b1886",
- "sg-f2fc3af19c4adefe0",
]
# (28 unchanged attributes hidden)
# (7 unchanged blocks hidden)
}
无需更改:
❯ terraform plan
aws_instance.ec2-test: Refreshing state... [id=i-5297abcc6001ce9a8]
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
似乎正常,因为我们没有在resource> resource> resource> block中设置
security_groups
参数(所需状态与当前状态对齐)。
2:IAM实例配置文件添加了
IAM角色,已手动连接到实例上。 Terraform还检测到了变化:
❯ terraform plan --refresh-only
aws_instance.ec2-test: Refreshing state... [id=i-5297abcc6001ce9a8]
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the last "terraform apply" which may have affected this plan:
# aws_instance.ec2-test has changed
~ resource "aws_instance" "ec2-test" {
+ iam_instance_profile = "test"
id = "i-5297abcc6001ce9a8"
tags = {}
# (30 unchanged attributes hidden)
# (7 unchanged blocks hidden)
}
This is a refresh-only plan, so Terraform will not take any actions to undo these. If you were expecting these changes then you can apply this plan to record the updated values in the Terraform state without
changing any remote objects.
但是,Terraform还计划恢复变化:
❯ terraform plan
aws_instance.ec2-test: Refreshing state... [id=i-5297abcc6001ce9a8]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# aws_instance.ec2-test will be updated in-place
~ resource "aws_instance" "ec2-test" {
- iam_instance_profile = "test" -> null
id = "i-5297abcc6001ce9a8"
tags = {}
# (30 unchanged attributes hidden)
# (7 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
我试图弄清楚为什么这两个变化不会产生相同的效果。本文重点介绍了差异,具体取决于参数默认值: https://nedinthecloud.com/2021/12/23/terraform-apply-papply-when-when-external-change-happens/
但是security> security_groups
和iam_instance_profile 参数似乎相似(没有默认值的可选),那么为什么Terraform以不同的方式处理这两种情况?
(用Terraform v1.2.2,Hashicorp/AWS 4.21.0测试)
I don't fully understand how Terraform handles external changes. Let's take an example:
resource "aws_instance" "ec2-test" {
ami = "ami-0d71ea30463e0ff8d"
instance_type = "t2.micro"
}
1: security group modification
The default security group has been manually replaced by another one. Terraform detects the change:
❯ terraform plan --refresh-only
aws_instance.ec2-test: Refreshing state... [id=i-5297abcc6001ce9a8]
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the last "terraform apply" which may have affected this plan:
# aws_instance.ec2-test has changed
~ resource "aws_instance" "ec2-test" {
id = "i-5297abcc6001ce9a8"
~ security_groups = [
- "default",
+ "test",
]
tags = {}
~ vpc_security_group_ids = [
+ "sg-8231be9a95a4b1886",
- "sg-f2fc3af19c4adefe0",
]
# (28 unchanged attributes hidden)
# (7 unchanged blocks hidden)
}
No change planned:
❯ terraform plan
aws_instance.ec2-test: Refreshing state... [id=i-5297abcc6001ce9a8]
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
It seems normal as we did not set the security_groups
argument in the resource
block (the desired state is aligned with the current state).
2: IAM instance profile added
An IAM role has been manually attached to the instance. Terraform also detects the change:
❯ terraform plan --refresh-only
aws_instance.ec2-test: Refreshing state... [id=i-5297abcc6001ce9a8]
Note: Objects have changed outside of Terraform
Terraform detected the following changes made outside of Terraform since the last "terraform apply" which may have affected this plan:
# aws_instance.ec2-test has changed
~ resource "aws_instance" "ec2-test" {
+ iam_instance_profile = "test"
id = "i-5297abcc6001ce9a8"
tags = {}
# (30 unchanged attributes hidden)
# (7 unchanged blocks hidden)
}
This is a refresh-only plan, so Terraform will not take any actions to undo these. If you were expecting these changes then you can apply this plan to record the updated values in the Terraform state without
changing any remote objects.
However, Terraform also plans to revert the change:
❯ terraform plan
aws_instance.ec2-test: Refreshing state... [id=i-5297abcc6001ce9a8]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# aws_instance.ec2-test will be updated in-place
~ resource "aws_instance" "ec2-test" {
- iam_instance_profile = "test" -> null
id = "i-5297abcc6001ce9a8"
tags = {}
# (30 unchanged attributes hidden)
# (7 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
I tried to figure out why these two changes don't produce the same effect. This article highlights differences depending on the argument default values: https://nedinthecloud.com/2021/12/23/terraform-apply-when-external-change-happens/
But the security_groups
and iam_instance_profile
arguments seems similar (optional with no default value), so why Terraform is handling these two cases differently?
(tested with Terraform v1.2.2, hashicorp/aws 4.21.0)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
不幸的是,对这些情况的处理很大程度上取决于提供者开发人员做出的决策,因为提供商有责任决定如何调和配置与先前状态之间的任何差异。 (“先前的状态”是Terraform所说的状态,该状态是通过运行“刷新”步骤与远程系统同步的)。
Terraform Core采用您在配置中定义的值(如果尚不设置任何可选参数,Terraform Core使用
null
来表示该值)以及来自先前状态的值并将两个发送给提供者实施计划步骤。然后,只要每个属性的计划新值与输入一致,提供商就可以执行所需的任何逻辑。 “一致”表示以下条件之一是正确的:计划的值等于配置中设置的值。
这是遵循的最直接情况,但是有多种原因导致提供商可能不这样做,我将稍后讨论。
计划的值等于存储在先前状态的值。
这代表了情况,即先前状态的值在功能上等效于配置中的值,但并不完全相等,例如远程系统将特定的字符串视为案例不敏感的,而两个值仅在情况下有所不同。
提供商在其架构中指示这是一个可以由远程系统决定的值,例如远程系统在应用步骤中生成的对象ID,配置中的相应值为
> null
表示根本不设置参数。在这种情况下,提供商可以选择所需的任何值,因为配置对属性没有任何说明,因此远程系统对值是什么。
从您所描述的内容来看,在您的第一个示例中听起来像是提供商使用的方法3,而在第二个示例中,提供商使用的方法编号1。
由于我不是此提供商的开发人员,所以我不能确定为什么开发人员为什么做出了他们在这里做出的决定,但是提供者开发人员可以选择选项的一个常见原因是,对于可能由多种不同的资源类型设置特定价值的情况,在这种情况下,提供商可能被设计为治疗不存在的论点配置为含义“保持远程系统已经拥有的任何内容”,而配置中的非null参数意味着“设置远程系统使用此给定值”。
对于
iam_instance_profile
,似乎提供商认为null
是该参数的有效配置值,并使用它来表示根本没有关联的实例配置文件的EC2实例。对于vpc_security_groups
和security_groups
,将参数设置为配置中的null
“保持远程系统拥有的任何内容”,因此Terraform只是承认更改,但并没有提议撤消它。根据我对EC2的了解,我可以猜测,这里的原因可能是基础EC2 API有两种不同的方法来设置安全组:您可以使用按名称指定安全组的旧式EC2经典样式(<<代码> security_groups 提供商中的参数)或通过ID指定它的新EC2-VPC样式(
vpc_security_group_ids
提供者中的论点)。无论您选择的两个中的哪个,远程系统大概会自动填充另一个,因此如果没有提供商中的特殊例外,则任何配置都无法收敛,除非您设置两个security> security_groups
和vpc_security_group_ids
,将它们设置为两个参考相同的安全组。为了避免这种情况,我认为提供商只允许您剩下的两者中的一个自动跟踪远程系统,远程系统具有副作用,提供商无法自动“修复”在Terraform之外进行的更改,除非您将至少设置为其中一个因此,提供商可以看到正确的价值应该是什么。Terraform通过重置以匹配配置来调和远程系统的变化的能力是“最佳努力”机制,因为在许多情况下,需求与其他要求发生冲突,因此提供者开发人员必须逐案决定。要优先考虑什么。尽管Terraform确实会尽力告诉您Terraform之外的更改并提议在可能的情况下修复它们,但保持Terraform配置和远程系统同步的唯一某种方法是防止任何人在Terraform之外进行更改,例如使用AWS中的IAM政策。
The handling of these situations unfortunately depends a lot on decisions made by the provider developer, since it's the provider's responsibility to decide how to reconcile any differences between the configuration and the prior state. (The "prior state" is what Terraform calls the state that results from running the "refresh" steps to synchronize with the remote system).
Terraform Core takes the values you've defined in the configuration (if any optional arguments are unset, Terraform Core uses
null
to represent that) and the values from the prior state and sends both of them to the provider to implement the planning step. The provider can then do whatever logic it wants as long as the planned new value for each attribute is consistent with the input. "Consistent" means that one of the following conditions is true:The planned value is equal to the value set in the configuration.
This is the most straightforward situation to follow, but there are various reasons why a provider might not do this, which I'll discuss later.
The planned value is equal to the value stored in the prior state.
This represents situations where the value in the prior state is functionally equivalent to the value in the configuration but not exactly equal, such as if the remote system treats a particular string as case insensitive and the two values differ only in case.
The provider indicated in its schema that this is a value that can be decided by the remote system, such as an object ID that's generated by the remote system during the apply step, and the corresponding value in the configuration was
null
to represent the argument not being set at all.In this case the provider gets to choose whichever value it wants, because the configuration says nothing about the attribute and thus the remote system has authority on what the value is.
From what you've described, it sounds like in your first example the provider used approach number 3, while in the second example the provider used approach number 1.
Since I am not the developer of this provider I cannot say for certain why the developers made the decisions they did here, but one common reason why a provider developer might choose option three is for situations where a particular value can potentially be set by multiple different resource types, in which case the provider might be designed to treat an absent argument in the configuration as meaning "keep whatever the remote system already has", whereas a non-null argument in the configuration would mean "set the remote system to use this given value".
For
iam_instance_profile
it seems like the provider considersnull
to be a valid configuration value for that argument and uses it to represent the EC2 instance having no associated instance profile at all. Forvpc_security_groups
andsecurity_groups
though, leaving the argument set tonull
in the configuration (or omitting it, which is equivalent) the provider treats that as "keep whatever the remote system has", and so Terraform just acknowledges the change but doesn't propose to undo it.Based on my knowledge about EC2, I can guess that the reason here is probably that the underlying EC2 API has two different ways to set security groups: you can either use the legacy EC2-Classic style of specifying a security group by name (the
security_groups
argument in the provider), or the new EC2-VPC style of specifying it by ID (thevpc_security_group_ids
argument in the provider). Whichever of the two you choose, the remote system will presumably populate the other one automatically and therefore without this special exception in the provider it would be impossible for any configuration to converge unless you set bothsecurity_groups
andvpc_security_group_ids
and set them to both refer to the same security groups. To avoid that, I think the provider just lets whichever one of the two you left unset automatically track the remote system, which has the side-effect the provider cannot automatically "fix" changes made outside of Terraform unless you set at least one of them so the provider can see what the correct value ought to be.Terraform's ability to reconcile changes in the remote system by resetting back to match the configuration is a "best effort" mechanism because in many cases that requirement comes into conflict with other requirements, and provider developers must therefore decide on a case-by-case basis what to prioritize. Although Terraform does try its best to tell you about changes outside of Terraform and to propose fixing them where possible, the only certain way to keep your Terraform configuration and your remote system synchronized is to prevent anyone from making changes outside of Terraform, for example using IAM policies in AWS.