为什么需要 import?
现实场景
很多团队在引入 Terraform 前,已经有大量通过控制台或 CLI 手工创建的云资源:几年前搭建的 VPC、手工配置的 RDS 实例、运行中的 EC2 集群……这些资源不能随便删除重建,但又希望纳入 Terraform 管理,享受版本控制、变更审计和代码化的好处。
terraform import 解决的正是这个问题:把已存在的云资源的状态信息写入 Terraform State 文件,让 Terraform「认领」这个资源,此后的所有变更通过 Terraform 进行管理。
很多初学者误解 terraform import 的作用。传统的 terraform import 命令只更新 state 文件,不会自动生成对应的 .tf 配置代码。你还需要手动编写(或用 -generate-config-out 生成)对应的资源块。如果 state 中有记录但 .tf 文件中没有对应资源块,下次 terraform apply 会尝试删除该资源。
terraform import 命令(传统方式)
基本用法
传统的 terraform import 是命令式操作:指定资源类型+名称,以及云资源的唯一 ID。
# 命令格式:terraform import <资源地址> <云资源ID>
# 资源地址格式:资源类型.资源名称
# 导入 EC2 实例
terraform import aws_instance.web i-0123456789abcdef0
# 导入 S3 Bucket(Bucket 名称即为 ID)
terraform import aws_s3_bucket.assets my-existing-bucket-name
# 导入安全组
terraform import aws_security_group.app sg-0123456789
# 导入 RDS 实例
terraform import aws_db_instance.production mydb-instance-identifier
# 导入 VPC
terraform import aws_vpc.main vpc-0123456789abcdef0
# 导入子网
terraform import aws_subnet.private subnet-0123456789abcdef0
# 导入 IAM 角色
terraform import aws_iam_role.app MyExistingRoleName
# 导入模块内的资源(用点分隔路径)
terraform import module.vpc.aws_vpc.main vpc-0123456789
如何找到云资源 ID?
不同资源类型使用不同的 ID 格式。查找方法:
aws ec2 describe-instances --filters "Name=tag:Name,Values=my-server" --query 'Reservations[0].Instances[0].InstanceId'subnet_id/route_table_id。# 导入后,用 terraform show 查看 state 中的属性
(这是了解资源有哪些属性的最快方法,然后据此编写 .tf 文件)
terraform state show aws_instance.web
# 输出示例:
# # aws_instance.web:
# resource "aws_instance" "web" {
# ami = "ami-0c55b159cbfafe1f0"
# arn = "arn:aws:ec2:us-east-1:..."
# instance_type = "t3.medium"
# tags = {
# "Name" = "production-web-server"
# }
# ...
# }
# 导入后执行 plan,检查是否有 drift(state 和实际配置的差异)
terraform plan -no-color
传统 import 的工作流
import 块(Terraform 1.5+,推荐方式)
声明式 import
Terraform 1.5 引入了 import 块,让 import 操作变成声明式的——写在 .tf 文件中,受版本控制管理,而不是一条一条地执行命令。
# import.tf — 声明式导入(Terraform 1.5+)
# 导入 VPC
import {
to = aws_vpc.main
id = "vpc-0123456789abcdef0"
}
# 导入子网
import {
to = aws_subnet.private_a
id = "subnet-0a1b2c3d4e5f"
}
# 导入安全组
import {
to = aws_security_group.app
id = "sg-0123456789"
}
# 导入模块内的资源
import {
to = module.vpc.aws_vpc.this
id = "vpc-0123456789abcdef0"
}
-generate-config-out:自动生成配置文件
Terraform 1.5 配合 import 块提供了 -generate-config-out 参数,可以自动生成资源的 .tf 配置代码,大大减少手工编写的工作量。
# 第一步:写 import.tf(声明要导入哪些资源)
cat > import.tf <<'EOF'
import {
to = aws_instance.web
id = "i-0123456789abcdef0"
}
EOF
# 第二步:自动生成 .tf 配置文件
# -generate-config-out 会根据 state 内容生成完整的资源块
terraform plan -generate-config-out=generated_resources.tf
# 第三步:查看自动生成的配置
cat generated_resources.tf
# 第四步:检查并精简(去除只读属性,保留必要字段)
# 自动生成的代码包含所有属性(含只读属性),需要手动清理
# 第五步:执行 apply 完成导入
terraform apply
自动生成的 .tf 文件包含资源的所有属性,包括计算属性(如 arn、id)。这些只读属性不应该出现在配置中(Terraform 会自动填充)。导入后需要:1) 删除只读计算属性;2) 用变量或 local 替换硬编码值;3) 提取共用配置到模块。
批量导入大量资源
# 使用 for_each 批量导入(Terraform 1.7+)
# 假设要导入 3 个 S3 Bucket
locals {
existing_buckets = {
"logs" = "company-logs-bucket"
"assets" = "company-assets-bucket"
"backup" = "company-backup-bucket"
}
}
# 声明式批量导入
import {
for_each = local.existing_buckets
to = aws_s3_bucket.buckets[each.key]
id = each.value
}
# 对应的资源声明
resource "aws_s3_bucket" "buckets" {
for_each = local.existing_buckets
bucket = each.value
}
Terraformer:逆向工程整个账号
Terraformer 是什么?
Terraformer 是 Google 开源的工具,可以扫描整个 AWS/GCP/Azure 账号,自动生成对应的 Terraform 代码和 state 文件。适合需要一次性将大量资源纳入 Terraform 管理的场景。
# 安装 Terraformer(macOS)
brew install terraformer
# 或者下载二进制
curl -LO "https://github.com/GoogleCloudPlatform/terraformer/releases/latest/download/terraformer-aws-darwin-amd64"
chmod +x terraformer-aws-darwin-amd64
# 从 AWS 账号生成 Terraform 代码
# --resources:要导入的资源类型
# --regions:AWS 区域
# --profile:AWS CLI profile
terraformer import aws \
--resources=vpc,subnet,sg,igw,nat,ec2_instance,s3,rds \
--regions=us-east-1 \
--profile=my-aws-profile
# 生成的文件结构:
# generated/aws/
# ├── vpc/
# │ ├── main.tf ← 资源定义
# │ ├── outputs.tf ← 输出值
# │ └── terraform.tfstate
# ├── ec2_instance/
# └── ...
# 也可以只导入特定标签的资源
terraformer import aws \
--resources=ec2_instance \
--filter="aws_instance=tags.Environment=production" \
--regions=us-east-1
Terraformer 自动生成的代码质量参差不齐:大量硬编码 ID、没有变量参数化、不遵循模块化最佳实践。通常需要大量人工重构。建议把它的输出作为参考,而不是直接使用。对于大规模迁移,推荐先用 Terraformer 理解资源间的依赖关系,再手写干净的 Terraform 代码。
moved 块:安全地重构资源地址
重命名的痛点
在重构 Terraform 代码时,经常需要重命名资源(如把 aws_instance.server 改为 aws_instance.web_server),或者把根模块中的资源移入子模块。
如果直接改名而不做任何处理,Terraform 会认为旧资源被删除、新资源需要创建,触发销毁并重建——这对生产资源来说是灾难性的。moved 块解决了这个问题。
# moved.tf — 声明资源移动(Terraform 1.1+)
# 场景 1:重命名资源(不触发重建)
moved {
from = aws_instance.server # 旧地址
to = aws_instance.web_server # 新地址
}
# 场景 2:将根模块资源移入子模块
moved {
from = aws_s3_bucket.logs # 以前在根模块
to = module.storage.aws_s3_bucket.logs # 现在在 storage 模块
}
# 场景 3:重命名 for_each 中的 key
moved {
from = aws_s3_bucket.buckets["old-key"]
to = aws_s3_bucket.buckets["new-key"]
}
# 场景 4:将 count 资源转为 for_each
moved {
from = aws_subnet.private[0]
to = aws_subnet.private["us-east-1a"]
}
moved 块在 apply 后其历史记录可以删除,但建议保留一段时间(至少保留到下一个 release),以便其他团队成员了解变更历史。也可以把 moved 块保留在代码中作为重构文档。
配置漂移(Drift)的检测与处理
什么是配置漂移?
配置漂移(Configuration Drift)是指云资源的实际状态与 Terraform state 文件中记录的状态出现了不一致。通常的原因是有人在 Terraform 之外直接修改了云资源(例如在控制台手工修改了安全组规则、修改了实例类型等)。
# 检测漂移(只刷新 state,不修改资源)
terraform plan -refresh-only
# 输出示例(当发现安全组被手工修改时):
# ~ update in-place
# aws_security_group.app
# ~ ingress = [
# - {from_port=80, to_port=80, cidr=[0.0.0.0/0]},
# + {from_port=8080, to_port=8080, cidr=[0.0.0.0/0]},
# ]
# 选项 1:将漂移同步到 state(接受手工修改)
terraform apply -refresh-only
# 选项 2:下次 plan/apply 时 Terraform 会将资源改回 .tf 定义的状态
(这是默认行为:代码定义的状态优先)
# 手动强制刷新 state(更新单个资源的 state)
terraform refresh # 已废弃,等同于 apply -refresh-only
定期漂移检测(调度任务)
# .github/workflows/drift-detection.yml
name: Terraform Drift Detection
on:
schedule:
# 每天 UTC 00:00 检测一次漂移
- cron: '0 0 * * *'
jobs:
drift-check:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
issues: write # 创建 GitHub Issue 报告漂移
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/TerraformReadRole
aws-region: us-east-1
- uses: hashicorp/setup-terraform@v3
- name: Terraform Init
run: terraform init
working-directory: terraform/environments/prod
- name: Check for Drift
id: drift
run: |
terraform plan -refresh-only -detailed-exitcode -out=drift.tfplan 2>&1 | tee plan_output.txt
echo "exit_code=$?" >> $GITHUB_OUTPUT
working-directory: terraform/environments/prod
continue-on-error: true
# exit code 2 = changes detected(发现漂移)
- name: Create Issue on Drift
if: steps.drift.outputs.exit_code == '2'
uses: actions/github-script@v7
with:
script: |
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: '⚠️ Terraform 配置漂移检测:生产环境',
body: '检测到生产环境配置与 Terraform State 不一致,请立即处理。\n\n详见 Actions 运行日志。',
labels: ['drift', 'infrastructure']
})
State 文件管理
常用 state 子命令
# 查看 state 中的所有资源
terraform state list
# 查看单个资源的详细属性
terraform state show aws_instance.web
# 从 state 中移除资源(不销毁!只是让 Terraform 不再管理它)
# 用于:不需要 Terraform 管理的资源,或需要手动移除再重新导入
terraform state rm aws_instance.old_server
# 在 state 中重命名资源地址(等同于 moved 块,但是命令式操作)
terraform state mv aws_instance.server aws_instance.web_server
# 将资源从一个 state 文件迁移到另一个(多 state 拆分时使用)
terraform state mv \
-state=source.tfstate \
-state-out=target.tfstate \
aws_vpc.main aws_vpc.main
# 拉取远程 state 的内容到本地(调试用)
terraform state pull > current.tfstate
# 强制推送本地 state 到远程(危险!谨慎使用)
terraform state push emergency-fix.tfstate
terraform state rm 会让 Terraform 忘记某个资源,但不会删除云上的实际资源。terraform state push 会直接覆盖远程 state,如果推送了错误的文件可能导致灾难性后果。在执行任何 state 操作前,务必先用 terraform state pull > backup.tfstate 备份当前 state。
本章小结
- terraform import 只更新 state,不生成 .tf 代码:导入后必须手动编写(或用 -generate-config-out 自动生成)对应的资源配置,否则下次 apply 会删除资源。
- 声明式 import 块(v1.5+):将 import 操作写入 .tf 文件,受版本控制管理;配合 -generate-config-out 可自动生成配置代码;v1.7+ 支持 for_each 批量导入。
- Terraformer:逆向扫描整个云账号生成代码,适合大规模初始迁移,但生成代码质量需人工重构。
- moved 块(v1.1+):重命名资源、将资源移入模块、转换 count 为 for_each,所有这些操作都不触发销毁重建。
- 配置漂移检测:
terraform plan -refresh-only检测手工修改导致的漂移;建议在 CI 中设置定期漂移检测任务,发现漂移时自动创建 Issue。 - state 管理:
state list/show/rm/mv是常用的诊断和重构工具;所有 state 操作前务必先备份。