这篇文章应该可以覆盖所有日常工作中常用的操作。一些大概率一次性的setup就没有包含在内了。
Basic concepts
Commandline operations
Terraform Commands
Plan and Apply 当前 root
terraform planFor a specific target:
terraform plan --target 'azurerm_role_assignment.fleetmanager_qa_role_assignments["/providers/Microsoft.Management/managementGroups/qa_services:Compute-Gallery-Image-Reader"]'List 当前 root 下的所有的 resources
terraform state list查看某一个 resources 的情况
terraform state show <resource_path>
# Example:
# terraform state show module.vmlifecycle.azurerm_storage_account.vmlifecycleNOTE
注意,
terraform state show的参数是 resource 的 path,而不是 resource 的 name。 而且只能有一个 resource 的 path。不支持多个 resources
查看所有的 resource 的详情
terraform showNOTE
注意跟
terraform state show的区别。terraform show是查看所有的 resources 的详情,而terraform state show是查看某一个 resource 的详情。 如果你不带任何参数运行 terraform show,它会显示当前目录中默认状态文件 (terraform.tfstate) 的所有资源的详细信息。所以其实查看的 terraform.tfstate
State Import
Terraform 在很多时候,对于一些 resource 的更新方式是 delete then recreate。尤其是涉及到更改 resource name 的时候。
这在很多情况下是不能允许的。所以 Terraform 提供了 terraform import 这个命令,来帮助我们把一个已经存在的 resource 导入到 Terraform 的状态文件中。
NOTE
凡是涉及到 resource name 的更改,这一步几乎是无法避免的。
而这个 import 怎么用,很大程度上取决于 provider 的定义。比如说,对于 Azure storage queue,我们需要查文档:
- https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_queue#import
- we should use the ‘resoruce id’ to import the resource
Example:
terraform import azurerm_storage_queue.queue1 https://example.queue.core.windows.net/queue1如果这个 queue 是通过 module 创建的,那么我们需要在用这个命令的时候,指定 module 的 path。
Example:
terraform import module.vmlifecycle.azurerm_storage_queue.vmlifecycle_old_test \
https://vmlifecycle.queue.core.windows.net/vmlifecycle-events-qa-test \Meta arguments
for_each
以 Azure Role Assignment 为例。需要绑定的 Roles 肯定是一个 list。然后我们通过扩展 role_defs 这个 list,来实现对每个 Role 的绑定。
locals {
role_defs = [
// Role definition name SoT: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
"Virtual Machine Contributor",
// Role definition name SoT: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles
"Compute Gallery Image Reader",
// Role definition name SoT: https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles/identity#managed-identity-operator
"Managed Identity Operator",
]
}
resource "azurerm_role_assignment" "my_role_assignment" {
for_each = toset(local.role_defs)
scope = "your-subscription-id"
role_definition_name = each.value
// This needs your managed identity already fetched with 'data' stanza, like the block below
principal_id = data.azurerm_user_assigned_identity.your_managed_identity.principal_id
}
// Assume you already have an managed identity
// data "azurerm_user_assigned_identity" "your_managed_identity" {
// provider = azurerm.your_provider_for_subscription
// name = "your_managed_identity_name"
// resource_group_name = "your_resource_group_for_managed_identity"
// }NOTE
for_eachdoes not work with arrays. So we usedtoset()to convert the list to a set.
You can use for_each to conditionally create resources, and it is a better option than using count when you just want to create either one or none.
Take ‘Azure resoruce group’ as an example, assume we only create the resource group when var.create_resource_group is true.
resource "azurerm_resource_group" "test" {
for_each = var.create_resource_group ? { "not-really-used-key" = "not-really-used-value" } : {}
name = "a-static-resource-group-name-you-want"
location = var.location
}This may look a bit ugly as it has “not-really-used-key” and “not-really-used-value” in the for_each argument.
So, you may want to add some ‘content’ in the ‘for_each’ set so that you can use the value in the map to create your resource group.
resource "azurerm_resource_group" "test" {
for_each = var.create_resource_group ? { "group_name" = "a-static-resource-group-name-you-want" } : {}
name = each.value
location = var.location
}However, when using for_each, make sure you don’t get lost in the ‘heirarchy’.
IMPORTANT
Need to understand
for_eachis for a ‘map’, and ‘each’ means ‘each kv pair’. Following one is incorrect:resource "azurerm_storage_queue" "test_queue_for_qa_only" { for_each = var.env == "qa" ? { name = "events-${var.env}-test" } : {} name = for_each.value.name storage_account_name = azurerm_storage_account.your_storage_account.name }Because:
- ‘for_each.value.name’ means:
"events-${var.env}-test".name, which does not existsFollowing is correct (althought the key string: ‘name’ may not looks like a good choice for you):
// This will work resource "azurerm_storage_queue" "test_queue_for_qa_only" { for_each = var.env == "qa" ? { name = "events-${var.env}-test" } : {} name = for_each.value storage_account_name = azurerm_storage_account.your_storage_account.name }Or you can create a map of objects:
// This will work resource "azurerm_storage_queue" "test_queue_for_qa_only" { for_each = var.env == "qa" ? { queue1 = {name = "events-${var.env}-test"} } : {} name = for_each.value.name storage_account_name = azurerm_storage_account.your_storage_account.name }
Related:
count
count 也是可以实现 “如果 env 是 qa,就创建某一个 resource,否则就不创建任何东西” 这样的功能的。但是搞出来的 resource 会是一个 array, 所以需要 [0] 来访问这个 resource。
resource "azurerm_storage_queue" "event_queue_test" {
count = var.env == "qa" ? 1 : 0
name = "events-${var.env}-test"
storage_account_name = azurerm_storage_account.your_storage_account.name
}这样创建出来的 resource 是 'azurerm_storage_queue.event_queue_test[0]'.
IMPORTANT
不稳定的身份标识 (Unstable Identity) - 这是
count的缺点 count 创建一个资源的列表(List),并通过数字索引(0, 1, 2, …)来标识每个实例。如果你依赖一个列表来计算 count 的值,并且从列表的中间移除了一个元素,那么所有后续元素的索引都会发生变化。毁灭性例子: 假设你有三个服务器,由一个列表定义:[“app-server”, “db-server”, “web-server”]。
app-server 是
[0]db-server 是[1]web-server 是[2]现在,你决定删除 db-server(索引 [1])。你的列表变为 [“app-server”, “web-server”],count 变为 2。 Terraform 会这样计算变更:[0]:app-server 保持不变。 [1]:之前是 db-server,现在变成了 web-server。Terraform 认为你需要销毁 db-server 并用 web-server 的配置重新创建它。 [2]:之前是 web-server,现在这个索引已经不存在了。Terraform 会销毁 web-server。 最终结果是:你只想删除一个数据库服务器,却导致了 Web 服务器被销毁和重建,引发了不必要的停机和风险。
总之,尽量还是不用了吧
Updated at 2025-12-05-Friday 实际上还是有个方法的,用
moved:moved { from = <旧地址> to = <新地址> }
lifecycle
lifecycle.precondition 是不能实现 “如果 env 是 qa,就创建某一个 resource,否则就不创建任何东西” 这样的功能的。这个需求还是需要靠 for_each 实现。
lifecycle.precondition 决定一个将被创建或更新的资源是否满足某些前置条件,如果不满足,则整个 Terraform 操作失败 (Should this operation proceed?)。
lifecycle.precondition 的真正用途
precondition 的核心功能是 断言(Assertion) 或 校验(Validation)。它允许你在 Terraform 对一个资源执行创建(Create)、更新(Update)或销毁(Destroy)操作之前,检查一个或多个条件。
它的目的是为了保证基础设施的一致性和合规性,防止不符合规范的资源被创建出来。
Built-in Functions
setproduct
这个function可以对两个 list 做笛卡尔积,相当于如果你需要针对两个 list 做嵌套循环,就可以用这个 function,直接产生一个新的 list,这个新的 list 的每个元素都是两个 list 的元素一一配对,所有的组合,构成的一个 list。
因为 terraform 是没有办法做 nested loop 的。所以如果你想要实现嵌套循环,就需要用这个 function。
详情可以参见:
toset
这个就是把一个 list 变成一个 set,这个 set 是 list 的子集,但是这个 set 的元素不会重复。然后这个 set 就可以用在 for_each 里面了。
IMPORTANT
When using
for_eachwith a set, the key and value are the same, both are the value of the element in the set. E.g. useeach.valueor useeach.keyis the same.
Top Level Blocks
Basic ones:
- resource, module, variable, output, locals, provider
moved
moved 块是 Terraform 1.1 引入的一个功能,用于在重构配置时保持现有资源的状态,避免资源被销毁后重新创建。
基本语法
moved {
from = <旧地址>
to = <新地址>
}
moved { from = <旧地址> to = <新地址>}我们代码中的例子
moved { from = module.fleetmanager_policy[0] to = module.fleetmanager_policy}原来是这样写的:
fleetmanager_policy" {
count = var.enable_policy ? 1 : 0
...常见使用场景和例子
- 重命名资源
# 把 aws_instance.old_name 重命名为 aws_instance.new_name
resource "aws_instance" "new_name" {
# ...配置...
}
moved {
from = aws_instance.old_name
to = aws_instance.new_name
}- 移除 count(索引变单实例)
# 之前: aws_s3_bucket.my_bucket[0]
# 现在: aws_s3_bucket.my_bucket
resource "aws_s3_bucket" "my_bucket" {
bucket = "my-bucket"
}
moved {
from = aws_s3_bucket.my_bucket[0]
to = aws_s3_bucket.my_bucket
}- 添加 for_each(单实例变 map)
moved {
from = module.fleetmanager_policy[0]
to = module.fleetmanager_policy
}