Terraform
导入 Terraform 配置
Terraform 支持将您现有的基础设施纳入其管理范围。通过将资源导入 Terraform,您可以使用通用的工作流程一致地管理您的基础设施。
使用 Terraform 创建新的基础设施时,通常使用以下工作流程
编写 Terraform 配置,定义您想要创建的基础设施。
查看 Terraform 计划,以确保配置将产生预期的基础设施。
应用配置,让 Terraform 创建您的基础设施。

Terraform 将有关您的基础设施的信息存储在状态文件中。要更新您的基础设施,您首先修改配置,然后使用 Terraform 计划和应用所需的更改。Terraform 使用状态文件中的数据来确定需要对您的基础设施进行哪些更改。
从 Terraform 1.5 开始,您可以使用配置将现有资源导入到您的状态文件中,并使用计划和应用工作流程。您仍然可以使用 terraform import 命令,但基于配置的导入更安全,适用于 CICD 管道,并允许您在修改状态之前预览导入操作。您还可以选择使用 Terraform 生成要导入的资源的初始配置。
使用配置导入资源涉及以下步骤
- 识别您将导入的现有基础设施。
- 为资源定义一个
import块。 - 运行
terraform plan以查看导入计划并选择性地为资源生成配置。 - 修剪生成的配置,仅保留所需的参数。
- 应用配置,将资源导入到您的 Terraform 状态文件中。
在本教程中,您将使用 Docker CLI 创建一个 Docker 容器。然后,您将声明一个用于现有 Docker 容器的 import 块,使用 Terraform 生成容器的配置,修改生成的配置,并使用计划和应用工作流程将容器纳入 Terraform 管理。
先决条件
您可以使用 Terraform 社区版或 HCP Terraform 完成本教程,工作流程相同。
HCP Terraform 是一个平台,您可以使用它来管理和执行 Terraform 项目。它包括远程状态和执行、结构化计划输出、工作空间资源摘要等功能。
选择 HCP Terraform 标签页以使用 HCP Terraform 完成本教程。
创建 Docker 容器
使用 Docker Hub 中最新的 NGINX 镜像创建一个名为 hashicorp-learn 的容器,并将该容器的 80 端口 (HTTP) 发布到您的本地主机系统的 8080 端口。您将在本教程中导入此容器。
$ docker run --name hashicorp-learn --detach --publish "0.0.0.0:8080:80" nginx:latest
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
afb6ec6fdc1c: Pull complete
dd3ac8106a0b: Pull complete
8de28bdda69b: Pull complete
a2c431ac2669: Pull complete
e070d03fd1b5: Pull complete
Digest: sha256:883874c218a6c71640579ae54e6952398757ec65702f4c8ba7675655156fcca6
Status: Downloaded newer image for nginx:latest
e7ba41fd94e51c501533241e4cffd307fbda81c5b402c372d989c4578518d2e5
使用 docker ps 验证容器是否正在运行。
$ docker ps --filter="name=hashicorp-learn"
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
e7ba41fd94e5 nginx:latest "/docker-entrypoint.…" About a minute ago Up 59 seconds 0.0.0.0:8080->80/tcp hashicorp-learn
在您的网络浏览器中访问地址 localhost:8080,以查看 NGINX 默认索引页面。
现在您拥有一个 Docker 镜像和容器,可以将其导入到您的项目中并使用 Terraform 进行管理。
初始化配置
现在,克隆本教程的 示例仓库。
$ git clone https://github.com/hashicorp-education/learn-terraform-import
切换到仓库目录。
$ cd learn-terraform-import
此目录将 Terraform 配置组织到三个文件中
terraform.tf配置 Terraform 和 provider 版本main.tf配置 Docker providerdocker.tf将包含您在本教程中编写的配置
初始化此配置。
$ terraform init
Initializing the backend...
##...
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
定义 import 块
基于配置的导入依赖于 import 块,该块有两个必需的参数
首先,运行以下命令以返回 hashicorp-learn 容器的完整 SHA256 容器 ID。
$ docker inspect --format="{{.ID}}" hashicorp-learn
现在,在您的编辑器中打开 docker.tf 并定义一个 import 块以指导导入操作。将 FULL_CONTAINER_ID 替换为来自 docker inspect 命令的容器 ID。
docker.tf
import {
id = "FULL_CONTAINER_ID"
to = docker_container.web
}
生成配置
在导入资源时,您必须将资源引入您的状态文件,并在您的配置中定义相应的 resource 块。虽然您可以手动定义资源,但配置驱动的导入可以为您生成配置作为起点。
生成的配置包含导入资源的所有可能参数,包括设置为默认值和没有值的参数。我们建议您修剪生成的配置,仅保留必需的参数和值与默认值不同的参数,以减小配置的大小。
使用 terraform plan 命令,并带有 -generate-config-out 标志,以生成您将要导入的容器的配置。Terraform 会构建一个计划,并将生成的容器配置输出到指定的文件。
$ terraform plan -generate-config-out=generated.tf
docker_container.web: Preparing import... [id=72d53edc26459adc666d60be2d57e6b8973238b6cedcc59fcb4e95639816b0bb]
docker_container.web: Refreshing state... [id=72d53edc26459adc666d60be2d57e6b8973238b6cedcc59fcb4e95639816b0bb]
Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# docker_container.web must be replaced
# (imported from "72d53edc26459adc666d60be2d57e6b8973238b6cedcc59fcb4e95639816b0bb")
# Warning: this will destroy the imported resource
-/+ resource "docker_container" "web" {
## ...
+ env = (known after apply) # forces replacement
## ...
}
Plan: 1 to import, 1 to add, 0 to change, 1 to destroy.
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Terraform has generated configuration and written it to generated.tf. Please
review the configuration and edit it as necessary before adding it to version
control.
Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.
Terraform 使用 docker 提供程序查找具有您在 import 块中指定的 ID 的 docker_container 资源。然后,它将资源定义保存到 generated.tf。
请注意,Terraform 计划在导入后替换容器,因为提供程序返回的 env 参数的默认值。您将在下一节中修复此问题,以确保 Terraform 导入容器而不会对其进行破坏性更改。
打开 generated.tf 以查看生成的配置。
generated.tf
# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.
# __generated__ by Terraform from "371e85d406a937b359d5cc3a49a423b736ec6e9367abe705dcdf6673ec1fb4c4"
resource "docker_container" "web" {
## ...
env = null
## ...
}
请注意,env 设置为 null。打开 Docker 容器资源模式,了解 env 参数。提供程序期望此参数类型为字符串集合。
使用方括号将 env 的值更改为空集合。
generated.tf
resource "docker_container" "web" {
## ...
env = []
## ...
}
容器资源模式需要 image 和 name 参数,因此您必须在配置中定义它们。
回想一下,您在启动 Docker 容器时指定了端口映射,因此您的配置也必须包含与容器当前配置匹配的 ports 块。
修剪生成的配置,仅包含您标识的必需参数。
generated.tf
## ...
resource "docker_container" "web" {
env = []
image = "..."
name = "hashicorp-learn"
ports {
external = 8080
internal = 80
ip = "0.0.0.0"
protocol = "tcp"
}
}
运行 terraform plan 以验证您的配置是否与容器的当前设置匹配。
$ terraform plan
docker_container.web: Preparing import... [id=72d53edc26459adc666d60be2d57e6b8973238b6cedcc59fcb4e95639816b0bb]
docker_container.web: Refreshing state... [id=72d53edc26459adc666d60be2d57e6b8973238b6cedcc59fcb4e95639816b0bb]
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:
# docker_container.web will be updated in-place
# (imported from "72d53edc26459adc666d60be2d57e6b8973238b6cedcc59fcb4e95639816b0bb")
~ resource "docker_container" "web" {
+ attach = false
## ...
+ container_read_refresh_timeout_milliseconds = 15000
## ...
+ logs = false
## ...
+ must_run = true
## ...
+ remove_volumes = true
## ...
+ start = true
## ...
+ wait = false
+ wait_timeout = 60
## ...
}
Plan: 1 to import, 0 to add, 1 to change, 0 to destroy.
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
请注意,Terraform 现在计划导入资源,然后进行更改以添加 attach、container_read_refresh_timeout_milliseconds、logs、must_run、remove_volumes、start、wait 和 wait_timeout 属性。这些是非破坏性更改。
Terraform 使用这些属性来创建 Docker 容器,但 Docker 不会存储它们。由于 Docker 不跟踪这些属性,Terraform 未将其包含在生成的配置中。当您应用配置时,Docker 提供程序将分配这些属性的默认值并将其保存在状态中,但它们不会影响正在运行的容器。
导入并更新资源
在导入资源时,我们建议限制任何破坏性更改,并使资源上的第一个操作为无操作。但是,Terraform 的配置驱动的导入允许您在同一操作中导入和修改资源,因此您可以使用 Terraform 工作流程来立即管理您的资源。
将 external 端口更改为 8081。这将是一次破坏性更改。
generated.tf
## ...
resource "docker_container" "web" {
env = []
name = "hashicorp-learn"
image = "sha256:c42efe0b54387756e68d167a437aef21451f63eebd9330bb555367d67128386c"
ports {
external = 8081
internal = 80
ip = "0.0.0.0"
protocol = "tcp"
}
}
现在,应用配置。回复 yes 以确认操作。
$ terraform apply
docker_container.web: Preparing import...
[id=76fc3728c7bb84137469bd3c15d3e77e57be88819e88ac6f2077da9a874cea89]
docker_container.web: Refreshing state...
[id=76fc3728c7bb84137469bd3c15d3e77e57be88819e88ac6f2077da9a874cea89]
Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
-/+ destroy and then create replacement
Terraform will perform the following actions:
# docker_container.web must be replaced
# (imported from "76fc3728c7bb84137469bd3c15d3e77e57be88819e88ac6f2077da9a874cea89")
# Warning: this will destroy the imported resource
-/+ resource "docker_container" "web" {
## ...
~ ports {
~ external = 8080 -> 8081 # forces replacement
internal = 80
ip = "0.0.0.0"
protocol = "tcp"
}
}
Plan: 1 to import, 1 to add, 0 to change, 1 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
docker_container.web: Importing... [id=76fc3728c7bb84137469bd3c15d3e77e57be88819e88ac6f2077da9a874cea89]
docker_container.web: Import complete [id=76fc3728c7bb84137469bd3c15d3e77e57be88819e88ac6f2077da9a874cea89]
docker_container.web: Destroying... [id=76fc3728c7bb84137469bd3c15d3e77e57be88819e88ac6f2077da9a874cea89]
docker_container.web: Destruction complete after 0s
docker_container.web: Creating...
docker_container.web: Creation complete after 0s [id=c30d6bc712cd95e41260a04f1a979ae9c91a054a8ba32f2bbabb09f0e7f95220]
Apply complete! Resources: 1 imported, 1 added, 0 changed, 1 destroyed.
Terraform 导入了资源,然后替换了容器,因为您更改了外部端口映射。
现在验证 Terraform 是否使用新的容器替换了旧容器,方法是运行 docker ps 或在您的网络浏览器中访问 localhost:8081。
$ docker ps --filter "name=hashicorp-learn"
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c30d6bc712cd c42efe0b5438 "/docker-entrypoint.…" 50 seconds ago Up 50 seconds 0.0.0.0:8081->80/tcp hashicorp-learn
通过运行 terraform show 来查看您的状态文件的内容。
$ terraform show
# docker_container.web:
resource "docker_container" "web" {
attach = false
command = [
"nginx",
"-g",
"daemon off;",
]
## ...
ports {
external = 8081
internal = 80
ip = "0.0.0.0"
protocol = "tcp"
}
}
现在您的配置文件、Terraform 状态和容器都已同步,您可以使用 Terraform 来管理 Docker 容器的设置和生命周期。
创建镜像资源
您可以在不使用 import 块的情况下,将一些资源纳入 Terraform 的管理。这通常适用于由单个唯一 ID 或标签定义的资源,例如 Docker 镜像。
在您的 generated.tf 文件中,docker_container.web 资源指定了用于创建容器的镜像的 SHA256 散列 ID。这就是 Docker 内部存储镜像 ID 的方式,因此导入操作直接将镜像 ID 加载到您的状态中。但是,通过标签或名称识别镜像将使您的配置更易于理解。
通过运行以下命令检索镜像的标签名称。
$ docker image inspect -f {{.RepoTags}} `docker inspect --format="{{.Image}}" hashicorp-learn`
[nginx:latest]
然后将以下配置添加到您的 docker.tf 文件,以将此镜像表示为资源。
docker.tf
resource "docker_image" "nginx" {
name = "nginx:latest"
}
运行 terraform apply 以在状态中创建一个镜像资源。请记住使用 yes 确认应用步骤。
$ terraform apply
docker_container.web: Refreshing state... [id=5cf0052f45a4c22e6b077ca476cd0f584f4e874b64c1a7c89e8a3ceb2e41bad6]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# docker_image.nginx will be created
+ resource "docker_image" "nginx" {
+ id = (known after apply)
+ image_id = (known after apply)
+ name = "nginx:latest"
+ repo_digest = (known after apply)
}
Plan: 1 to add, 0 to change, 0 to destroy.
##...
Apply complete! Resources: 1 added, 0 changed, 0 destroyed
现在 Terraform 创建了镜像的资源,您可以在容器的配置中引用它。将 docker_container.web 的 image 值更改为引用新的镜像资源。
generated.tf
resource "docker_container" "web" {
## ...
image = docker_image.nginx.image_id
## ...
}
由于 docker_image.nginx.latest 与您替换的硬编码镜像 ID 匹配,terraform apply 返回一个无操作。
$ terraform apply
docker_image.nginx: Refreshing state... [id=sha256:9e7e7b26c784556498f584508123ae46da82b4915e262975893be4c8ec8009a5nginx:latest]
docker_container.web: Refreshing state... [id=5cf0052f45a4c22e6b077ca476cd0f584f4e874b64c1a7c89e8a3ceb2e41bad6]
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.
现在 Terraform 管理 Docker 容器和镜像,您可以使用 Terraform 修改它们的配置。
销毁基础设施
您现在已将 Docker 容器及其使用的镜像导入到 Terraform 中。
通过运行 terraform destroy 销毁容器和镜像。回复 yes 以确认操作。
$ terraform destroy
docker_image.nginx: Refreshing state... [id=sha256:9beeba249f3ee158d3e495a6ac25c5667ae2de8a43ac2a8bfd2bf687a58c06c9nginx:latest]
docker_container.web: Refreshing state... [id=3fe1cb2e5326c31bac9250f6d09eade77945ee07ccea025d6424d91a89f98557]
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# docker_container.web will be destroyed
- resource "docker_container" "web" {
- attach = false -> null
## ...
}
Plan: 0 to add, 0 to change, 2 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
docker_container.web: Destroying... [id=3fe1cb2e5326c31bac9250f6d09eade77945ee07ccea025d6424d91a89f98557]
docker_container.web: Destruction complete after 1s
docker_image.nginx: Destroying... [id=sha256:9beeba249f3ee158d3e495a6ac25c5667ae2de8a43ac2a8bfd2bf687a58c06c9nginx:latest]
docker_image.nginx: Destruction complete after 0s
Destroy complete! Resources: 2 destroyed.
最后,运行 docker ps 以验证 Terraform 是否已销毁容器。
$ docker ps --filter "name=hashicorp-learn"
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
如果您在此教程中使用了 HCP Terraform,则在销毁资源后,请从您的 HCP Terraform 组织中删除 learn-terraform-import 工作区。
限制和其他注意事项
在使用配置将资源导入 Terraform 时,需要考虑以下几个重要事项
Terraform 导入使用提供程序报告的您的基础设施的当前状态。它无法确定
- 基础设施的健康状况。
- 基础设施的意图。
- 对 Terraform 不可控的基础设施所做的更改,例如 Docker 容器文件系统的状态。
导入涉及手动步骤,容易出错,尤其是在操作员不了解基础设施的目的和历史的情况下。我们建议在应用之前仔细查看计划输出,以避免破坏性更改。
导入操作会在应用期间操纵 Terraform 状态文件。您可能需要在导入新基础设施之前创建备份。
Terraform 导入不会检测或生成基础设施之间的关系。您可以在应用更改之前手动将关系添加到配置中。
Terraform 导入不会检测您可以跳过设置的默认属性。
并非所有提供程序和资源都支持 Terraform 导入。
将资源导入 Terraform 并不意味着 Terraform 可以销毁并重新创建它。例如,导入的基础设施可能依赖于其他未管理的基础设施或配置。
遵循基础设施即代码 (IaC) 最佳实践,例如 不可变基础设施,可以帮助防止这些问题中的许多问题。
下一步
在本教程中,您使用了配置驱动的导入将 Docker 容器置于 Terraform 管理之下。然后,您使用 Terraform 修改其配置,包括定义资源依赖项。您还回顾了导入工作流程的限制,以及配置驱动的导入相对于 terraform import 命令所提供的增强的安全性和可预测性。
查看以下资源,以了解如何使用 Terraform 安全可靠地管理您的基础设施
- 查看 Terraform 导入文档。
- 了解如何 将配置迁移到 HCP Terraform。
- 了解如何 使用模块创建可重用的配置。
- 了解如何 管理 Terraform 状态中的资源。
