跳到主要内容

Import 导入现有资源

在实际工作中,经常会遇到需要将已有基础设施纳入 Terraform 管理的情况。Terraform 提供了导入功能,可以将手动创建或通过其他方式创建的资源导入到 Terraform 状态中。

概述

为什么需要导入

以下场景需要使用导入功能:

  • 迁移到 Terraform:组织决定采用 IaC,需要将现有资源纳入管理
  • 紧急手动操作:在紧急情况下手动创建了资源,事后需要纳入管理
  • 合并项目:将多个 Terraform 项目或手动管理的资源整合
  • 遗留系统:接手他人创建的基础设施

导入的工作原理

导入操作的核心是将现有资源与 Terraform 配置关联:

  1. 在配置文件中定义资源块
  2. 运行导入命令,将资源 ID 映射到状态文件
  3. Terraform 记录资源的当前状态
  4. 后续可以通过 Terraform 管理该资源

导入方式

Terraform 提供两种导入方式:

方式命令行导入配置块导入
Terraform 版本所有版本1.5+
导入方式命令行配置文件
自动生成配置
适用场景单个资源批量导入

命令行导入

基本语法

terraform import [选项] <资源地址> <资源ID>
  • 资源地址:Terraform 配置中的资源标识,如 aws_instance.web
  • 资源ID:云平台分配的资源标识符

导入 AWS EC2 实例

# 步骤 1:在配置中定义资源
# main.tf
resource "aws_instance" "web" {
# 初始可以为空,导入后再补充
}

# 步骤 2:导入资源
terraform import aws_instance.web i-0123456789abcdef0

# 步骤 3:查看状态
terraform state show aws_instance.web

# 步骤 4:根据状态补充配置
# 然后编辑 main.tf 添加完整配置

常见资源的导入 ID 格式

不同资源类型有不同的 ID 格式:

# AWS 资源
terraform import aws_vpc.main vpc-0123456789abcdef0
terraform import aws_subnet.public subnet-0123456789abcdef0
terraform import aws_security_group.web sg-0123456789abcdef0
terraform import aws_s3_bucket.assets my-bucket-name
terraform import aws_db_instance.main mydb

# 阿里云资源
terraform import alicloud_instance.web i-0123456789abcdef0
terraform import alicloud_vpc.main vpc-0123456789abcdef0

# Azure 资源
terraform import azurerm_resource_group.main /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup

# Google Cloud 资源
terraform import google_compute_instance.web projects/myproject/zones/us-central1-a/instances/web

导入模块中的资源

# 模块中的资源地址格式
terraform import module.vpc.aws_vpc.main vpc-0123456789abcdef0
terraform import module.web_server.aws_instance.main i-0123456789abcdef0

导入使用 count 的资源

# 配置中使用 count
resource "aws_instance" "web" {
count = 3
}

# 导入特定索引的资源
terraform import 'aws_instance.web[0]' i-aaa
terraform import 'aws_instance.web[1]' i-bbb
terraform import 'aws_instance.web[2]' i-ccc

导入使用 for_each 的资源

# 配置中使用 for_each
resource "aws_instance" "web" {
for_each = toset(["web1", "web2"])
}

# 导入特定键的资源
terraform import 'aws_instance.web["web1"]' i-aaa
terraform import 'aws_instance.web["web2"]' i-bbb

配置块导入(Terraform 1.5+)

配置块导入允许在配置文件中声明导入,更加可追踪和可版本控制。

基本语法

import {
to = <资源地址>
id = "<资源ID>"
}

示例:导入 S3 存储桶

# import.tf
import {
to = aws_s3_bucket.assets
id = "my-assets-bucket"
}

# main.tf
resource "aws_s3_bucket" "assets" {
bucket = "my-assets-bucket"
}

自动生成配置

使用 terraform plan -generate-config-out 可以自动生成资源配置:

# 步骤 1:创建导入块(不需要资源块)
import {
to = aws_instance.web
id = "i-0123456789abcdef0"
}

# 步骤 2:生成配置
terraform plan -generate-config-out=generated.tf

# 步骤 3:审查生成的配置
# 文件 generated.tf 包含资源的完整配置

# 步骤 4:调整配置并应用
terraform apply

生成的配置示例:

# generated.tf
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
key_name = "my-key"
vpc_security_group_ids = ["sg-0123456789abcdef0"]
subnet_id = "subnet-0123456789abcdef0"

tags = {
Name = "WebServer"
}
}

批量导入

对于大量资源,可以使用循环和数据源:

# 使用数据源发现资源
data "aws_instances" "existing" {
filter {
name = "tag:ManagedBy"
values = ["Manual"]
}
}

# 为每个实例创建导入块(需要 Terraform 1.7+)
import {
for_each = toset(data.aws_instances.existing.ids)
to = aws_instance.imported[each.value]
id = each.value
}

resource "aws_instance" "imported" {
for_each = toset(data.aws_instances.existing.ids)

# 配置...
}

导入工作流程

标准导入流程

以导入一个手动创建的 EC2 实例为例:

# 步骤 1:获取资源信息
aws ec2 describe-instances --instance-ids i-0123456789abcdef0

# 步骤 2:创建配置文件
cat > main.tf << 'EOF'
resource "aws_instance" "imported_web" {
# 先留空或填写已知属性
}
EOF

# 步骤 3:初始化
terraform init

# 步骤 4:导入资源
terraform import aws_instance.imported_web i-0123456789abcdef0

# 步骤 5:查看导入的状态
terraform state show aws_instance.imported_web

# 步骤 6:根据状态完善配置
# 编辑 main.tf,添加所有必要属性

# 步骤 7:验证配置
terraform plan
# 确保输出显示 "No changes"

# 如果显示有变更,需要调整配置直到无变更

使用生成配置的流程(推荐)

# 步骤 1:创建导入配置
cat > import.tf << 'EOF'
import {
to = aws_instance.web
id = "i-0123456789abcdef0"
}
EOF

# 步骤 2:生成资源配置
terraform plan -generate-config-out=main.tf

# 步骤 3:审查并调整配置
# 编辑 main.tf,移除不需要的属性,调整敏感信息

# 步骤 4:应用导入
terraform apply

复杂导入场景

导入 VPC 及其依赖资源

VPC 通常包含多个关联资源,需要逐个导入:

# 导入顺序很重要

# 1. 导入 VPC
import {
to = aws_vpc.main
id = "vpc-0123456789abcdef0"
}

# 2. 导入互联网网关
import {
to = aws_internet_gateway.main
id = "igw-0123456789abcdef0"
}

# 3. 导入子网
import {
to = aws_subnet.public_a
id = "subnet-aaa"
}

import {
to = aws_subnet.public_b
id = "subnet-bbb"
}

# 4. 导入路由表
import {
to = aws_route_table.public
id = "rtb-0123456789abcdef0"
}

# 5. 导入路由表关联
import {
to = aws_route_table_association.public_a
id = "subnet-aaa/rtb-0123456789abcdef0"
}

对应的资源配置:

# VPC
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true

tags = {
Name = "MainVPC"
}
}

# 互联网网关
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id

tags = {
Name = "MainIGW"
}
}

# 子网
resource "aws_subnet" "public_a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
map_public_ip_on_launch = true

tags = {
Name = "PublicSubnetA"
}
}

# 路由表
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id

route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}

tags = {
Name = "PublicRouteTable"
}
}

# 路由表关联
resource "aws_route_table_association" "public_a" {
subnet_id = aws_subnet.public_a.id
route_table_id = aws_route_table.public.id
}

导入 RDS 数据库

RDS 有多个关联资源,需要分别导入:

# 导入数据库子网组
terraform import aws_db_subnet_group.main my-db-subnet-group

# 导入数据库参数组
terraform import aws_db_parameter_group.main my-db-params

# 导入数据库实例
terraform import aws_db_instance.main mydb

# 导入安全组
terraform import aws_security_group.database sg-0123456789abcdef0

导入带有敏感信息的资源

导入后需要处理敏感信息:

# 导入的数据库实例
import {
to = aws_db_instance.main
id = "mydb"
}

resource "aws_db_instance" "main" {
identifier = "mydb"
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.micro"

# 敏感信息使用变量
username = var.db_username
password = var.db_password

# 其他配置...
}

# 在 terraform.tfvars 或环境变量中设置敏感信息
variable "db_username" {
sensitive = true
}

variable "db_password" {
sensitive = true
}

处理导入后的差异

导入后运行 terraform plan 可能显示差异,需要处理:

常见差异原因

  1. 默认值差异:Provider 设置的默认值与实际资源不同
  2. 只读属性:某些属性无法在配置中设置
  3. 计算属性:资源创建后由系统生成的值

解决方法

# 方法 1:忽略特定属性的变更
resource "aws_instance" "web" {
# ...

lifecycle {
ignore_changes = [
ami, # AMI 可能更新
user_data, # 用户数据变更
root_block_device[0].volume_id, # 系统生成的 ID
]
}
}

# 方法 2:显式设置默认值
resource "aws_instance" "web" {
# 显式设置所有属性,避免使用默认值
associate_public_ip_address = true
source_dest_check = true

# ...
}

# 方法 3:使用 keepers 触发更新
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"

# 当 user_data 变化时触发更新
user_data = "..."
}

批量导入策略

使用脚本批量导入

#!/bin/bash
# import-vpc.sh

VPC_ID="vpc-0123456789abcdef0"

# 导入 VPC
terraform import aws_vpc.main $VPC_ID

# 获取子网列表
SUBNETS=$(aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=$VPC_ID" \
--query 'Subnets[*].SubnetId' \
--output text)

# 导入每个子网
for SUBNET in $SUBNETS; do
NAME=$(aws ec2 describe-subnets \
--subnet-ids $SUBNET \
--query 'Subnets[0].Tags[?Key==`Name`].Value' \
--output text)

terraform import "aws_subnet.$NAME" $SUBNET
done

echo "Import completed!"

使用工具辅助

terraformer:自动从云平台生成 Terraform 配置

# 安装
brew install terraformer

# 导入 AWS 资源
terraformer import aws --resources=vpc,subnet,security_group --regions=us-east-1

# 生成的文件结构
# generated/aws/vpc/vpc.tf
# generated/aws/subnet/subnet.tf
# generated/aws/security_group/security_group.tf

terraforming:导入 AWS 资源

# 安装
gem install terraforming

# 导出 S3 存储桶配置
terraforming s3 > s3.tf

# 导出 EC2 实例配置
terraforming ec2 > ec2.tf

导入后的验证

验证步骤

# 1. 检查状态
terraform state list

# 2. 查看特定资源状态
terraform state show aws_instance.web

# 3. 运行计划,确保无意外变更
terraform plan

# 4. 验证资源属性
terraform console
> aws_instance.web.public_ip
> aws_instance.web.tags

创建验证输出

# 导入后添加输出用于验证
output "imported_instance_id" {
value = aws_instance.web.id
}

output "imported_instance_ip" {
value = aws_instance.web.public_ip
}

output "imported_vpc_id" {
value = aws_vpc.main.id
}

导入故障排除

常见错误

1. 资源不存在

Error: Cannot import non-existent remote object

解决方法:确认资源 ID 正确,资源在正确的区域/账户。

2. 资源已被管理

Error: Resource already managed

解决方法:资源已存在于状态中,检查状态:

terraform state list
terraform state rm aws_instance.web # 如果需要重新导入

3. 配置不匹配

Error: Provider produced inconsistent result after apply

解决方法:检查配置是否正确,可能需要调整配置或使用 ignore_changes

4. 权限不足

Error: error reading ... AccessDenied

解决方法:确认凭证有足够的权限访问资源。

调试导入问题

# 启用详细日志
TF_LOG=DEBUG terraform import aws_instance.web i-xxx

# 查看完整状态
terraform state pull > state.json

# 手动检查资源
aws ec2 describe-instances --instance-ids i-xxx

最佳实践

1. 导入前备份

# 备份当前状态
terraform state pull > backup.tfstate

# 如果导入失败,可以恢复
terraform state push backup.tfstate

2. 分批导入

对于大型基础设施,分批导入可以降低风险:

# 第一批:网络资源
import {
to = aws_vpc.main
id = "vpc-xxx"
}

# 第二批:计算资源(确认第一批无问题后添加)
import {
to = aws_instance.web
id = "i-xxx"
}

3. 使用模块组织

# 将导入的资源组织到模块中
module "imported_vpc" {
source = "./modules/vpc"

vpc_id = "vpc-xxx"
}

# 在模块内处理导入

4. 添加文档注释

# 导入的 VPC
# 原始创建者:operations team
# 导入日期:2024-01-15
# 原因:迁移到 Terraform 管理
import {
to = aws_vpc.main
id = "vpc-0123456789abcdef0"
}

5. 清理导入配置

导入成功后,可以保留或删除导入块:

# 选项 1:保留导入块用于文档记录
import {
to = aws_s3_bucket.assets
id = "my-assets-bucket"
}

# 选项 2:删除导入块,只保留资源块
resource "aws_s3_bucket" "assets" {
bucket = "my-assets-bucket"
}

下一步

了解了导入功能后,可以继续学习 最佳实践,掌握生产环境中使用 Terraform 的高级技巧。