文章背景图

Terraform 学习笔记:从基础概念到 Docker 发布实战

2026-09-24
0
-
- 分钟

Terraform 学习笔记:从基础概念到 Docker 发布实战

本文系统整理 Terraform 的核心概念、日常工作流、状态管理、模块化、多环境管理,以及 Docker 和 AWS 示例。示例用于学习与复习;执行任何 apply 或 destroy 前,请先核对账号、环境、计划范围和费用影响。

目录

  1. Terraform 的本质与工作原理
  2. 基本工作流程与命令
  3. 文件分工与配置块
  4. 变量、tfvars 与保存的执行计划
  5. State、资源地址与配置漂移
  6. count、for_each 与条件过滤
  7. Module、依赖与多环境管理
  8. moved 与生命周期保护
  9. Data Source、模板与 JSON
  10. Docker 容器管理实战
  11. 镜像构建、升级与回退
  12. 实战踩坑与排障
  13. 复习清单与实验目录
  14. 补充:本地 AWS EC2 示例

1. Terraform 的本质与工作原理

Terraform 用配置描述目标状态,通过 Provider 对比实际资源与受管理资源的记录,生成并执行变更计划。

例如:希望有一个 Nginx 容器、监听指定端口、加入指定网络,并限制内存。你描述这些要求,Terraform 根据依赖安排创建或修改操作。

配置:希望资源是什么样
             +
State:配置地址与实际资源的对应记录
             +
Provider 查询到的实际资源信息
             ↓
plan:计算新增、修改、替换、删除
             ↓
apply:通过 Provider 执行变更
             ↓
更新 State,并验证实际效果

三个关键点:

  • Terraform 是声明式工具:主要描述目标,执行顺序由依赖关系决定。
  • Provider 是 Terraform 与 Docker、AWS、文件系统等平台交互的插件。
  • Terraform CLI 不会一直在后台修复资源;通常在运行命令时检查和执行变更。

学习的核心习惯:修改并保存配置 → 看懂 plan → 执行 apply → 验证实际效果。

2. 基本工作流程与命令

命令作用需要记住的边界
terraform init初始化工作目录、Backend、Provider 和模块首次使用或依赖等配置变化后执行
terraform fmt统一 HCL 格式格式正确不等于逻辑正确
terraform validate检查配置语法和内部一致性不保证云端权限、配额和实际部署都成功
terraform plan预览变更不执行计划中的资源变更,但可能读取平台数据
terraform apply生成并执行计划,或执行已保存计划可能创建、修改、替换或删除资源
terraform output读取状态中保存的输出值输出存在不等于服务当前可访问
terraform console交互式检查表达式可用于检查变量、集合和计算结果
terraform state list列出当前状态中的资源地址注意当前目录和 Workspace
terraform state show 地址查看某个资源的状态记录需要再结合平台侧检查
terraform destroy计划并删除当前状态管理的资源应检查删除范围后确认

日常操作模板:

terraform init
terraform fmt
terraform validate
terraform plan
terraform apply
terraform output
terraform plan

最后一次 plan 用来检查是否仍有预期外差异。-no-color 可去掉终端颜色,便于阅读或保存输出。

常见计划标记:

标记含义
+创建
~原地修改
-删除
-/+先删除,再创建,即替换
+/-先创建,再删除,即另一种替换顺序
known after apply执行后才能确定的值
No changes本次计划没有检测到需要执行的变更

3. 文件分工与配置块

3.1 文件名是组织约定

文件常见用途
main.tf资源、模块调用等主要配置
variables.tf输入变量、类型、默认值和校验
outputs.tf / output.tf输出定义,两种文件名都可以
terraform.tfvars当前根模块的具体变量值
*.tfvars其他变量文件,普通文件名需要通过 -var-file 指定
.terraform.lock.hclProvider 版本选择及校验信息,通常应提交 Git
terraform.tfstate本地 State,可能包含敏感数据,不应提交 Git
.terraform/本地工作数据、下载的 Provider 和模块等
*.tfplan保存的二进制执行计划,可能包含敏感数据

同一个模块目录里的 .tf 文件会一起读取,main.tf 没有特殊优先级。

新增 release.tf 后,再执行 plan 会同时考虑该目录内其他 .tf 文件。子目录不会自动并入当前模块,需要通过模块调用等方式使用。

3.2 八种常见配置块

作用典型引用
terraformTerraform 版本、Provider 要求、Backend 等
provider平台连接配置,如区域、Docker 地址
resource创建和管理资源random_string.demo.result
data读取已有对象或信息data.local_file.team.content
variable定义外部输入var.environment
locals定义内部计算值local.resource_name
output暴露模块结果根模块用 terraform output 查看
module调用可复用配置module.name.name

本地随机字符串实验中的关系:

variable "string_length" {
  type    = number
  default = 12
}

resource "random_string" "demo" {
  length  = var.string_length
  special = false
}

output "result" {
  value = random_string.demo.result
}

以上是核心片段;完整实验还包含 Provider 要求、其他变量和批量资源。

4. 变量、tfvars 与保存的执行计划

4.1 定义参数与填写参数

# variables.tf:定义允许输入什么
variable "environment" {
  type    = string
  default = "dev"
}
# terraform.tfvars:填写本次使用的值
environment = "prod"

variables.tf 相当于参数接口;terraform.tfvars 给接口传值。locals 则适合内部计算:

locals {
  resource_name = "${var.environment}-${random_string.demo.result}"
}

4.2 已练习的变量优先级

在此前练习涉及的输入方式中:

命令行 -var / -var-file > terraform.tfvars > variable 的 default

这是所学输入方式的简化比较,不是 Terraform 所有输入来源的完整列表。同一命令中,重复设置同一个变量时,后面的 -var / -var-file 会覆盖前面的。

terraform plan -var="string_length=20"

这个值用于本次命令,不会写回 .tf.tfvars 文件。

普通命名的变量文件要显式指定:

terraform plan -var-file="experiment.tfvars"

4.3 类型和校验

学过的类型包括 stringnumberboolset(string)map(object(...))object(...)

当前随机字符串实验要求长度为 8~32 的整数:

validation {
  condition = (
    var.string_length >= 8 &&
    var.string_length <= 32 &&
    floor(var.string_length) == var.string_length
  )
  error_message = "字符串长度必须是 8 到 32 之间的整数。"
}

4.4 普通 plan 不会被下一次 apply 自动复用

terraform plan -var="string_length=20"
terraform apply

第二条命令重新计算计划,不会自动继承第一条临时传入的 20。

需要执行同一份计划时:

terraform plan -var="string_length=20" -out="lesson.tfplan"
terraform show -no-color lesson.tfplan
terraform apply lesson.tfplan

保存的计划包含计划时使用的变量值。执行已保存计划通常不会再询问 yes,所以应先检查计划。计划保存后再修改配置,并不会自动改变该计划中的内容。

5. State、资源地址与配置漂移

5.1 区分三种信息

信息表达什么
配置希望管理成什么样
State哪个配置地址对应哪个实际对象,以及记录的属性
实际资源Docker、文件系统、云平台中真实存在的对象

例如 docker_container.nginx 是 Terraform 的资源地址;terraform-learning-nginx 是 Docker 容器名称。两者用途不同。

terraform state list
terraform state show docker_container.nginx

5.2 配置漂移

在 Terraform 外部修改受管理对象,可能产生漂移,例如手动修改生成文件,或修改容器参数。Terraform 能否发现、如何处理,取决于 Provider 读取哪些属性,以及配置是否管理相应字段。

Terraform CLI 不会因你保存了文件就立即执行变更,也不会持续巡检。对容器运行状态的持续管理来自 Docker 重启策略或 Kubernetes 等运行系统。

5.3 状态保护

  • 不要把删除 State 当成清理实际资源;实际资源可能仍然存在。
  • 不随意手工编辑 State。
  • State 和保存的计划可能包含敏感信息。
  • 团队协作时需要考虑远程状态、访问控制及状态锁;这些是后续进阶主题,本文不声称已完成相关实验。

6. count、for_each 与条件过滤

6.1 count:按数量创建

当前 JSON 文件实验使用:

count = var.json_config.enabled ? 1 : 0

启用时,资源地址带索引,例如 local_file.json_info[0]。从 1 改为 0,会计划删除已有受管理实例。

6.2 for_each:按键创建

随机字符串实验按环境名创建实例:

resource "random_string" "per_env" {
  for_each = var.environments
  length   = var.string_length
  special  = false
}

例如地址为 random_string.per_env["dev"]。模块实验则使用环境名到配置对象的映射:

environments = {
  dev = {
    length  = 10
    enabled = true
  }
  prod = {
    length  = 12
    enabled = true
  }
}

6.3 过滤集合

locals {
  enabled_environments = {
    for name, config in var.environments :
    name => config
    if config.enabled
  }
}

已有环境从集合中被过滤掉,会导致对应实例进入删除计划。enabled = false 在这个实现中代表不再保留该实例,不是暂停管理。

6.4 for 表达式与 for_each

for_each 创建多个资源或模块实例;for 表达式把一个集合转换成另一个值。例如汇总输出:

output "names" {
  value = {
    for env, instance in module.environment_names :
    env => instance.name
  }
}

7. Module、依赖与多环境管理

7.1 Module:输入、内部资源、输出

本地 naming 模块接收 prefixlength,创建随机后缀,再输出完整名称。

module "environment_names" {
  source   = "./modules/naming"
  for_each = local.enabled_environments
  prefix   = each.key
  length   = each.value.length
}

同一模块可以调用多次,每个实例有自己的输入与受管理资源。根模块的 terraform.tfvars 不会自动给子模块注入变量,需要像上面一样显式传入。

7.2 引用建立依赖

image = docker_image.nginx.image_id

这里容器依赖镜像资源的输出,Terraform 会据此安排顺序。它并不按 .tf 文件的上下顺序逐行执行。显式 depends_on 用于无法通过普通引用表达的依赖,不必给每个资源都加。

7.3 Workspace 与独立目录

方式作用边界
CLI Workspace同一配置使用不同 State不自动选择环境变量值,也不等于账号或权限隔离
独立环境目录每个根模块维护自己的参数和状态位置可复用同一模块;配置远程 Backend 时仍需确保状态地址不同

Workspace 常用命令:

terraform workspace list
terraform workspace show
terraform workspace new dev
terraform workspace select dev

切换 Workspace 后,仍然要检查当前变量和资源命名;不同 State 可以意外指向同一个外部对象,因此不能只靠 Workspace 名称判断隔离是否完整。

本地环境目录实验:

03-environments/
├─ modules/naming/
└─ envs/
   ├─ dev/
   └─ prod/

分别在 devprod 中执行命令,两个根模块都通过 ../../modules/naming 复用命名模块。

8. moved 与生命周期保护

8.1 moved:告诉 Terraform 地址变了

改资源或模块的配置名称,可能使 Terraform 把旧地址视为删除、新地址视为新增。moved 声明地址之间的迁移关系。

当前模块实验包含连续迁移记录:

moved {
  from = module.dev_name
  to   = module.names["dev"]
}

moved {
  from = module.names
  to   = module.environment_names
}

这两条描述从最初单独调用到批量模块,再到模块改名的历史。moved 处理的是地址迁移,不会阻止因实际属性变化导致的资源替换。迁移后应检查计划中的创建、销毁数量。

单个资源增加 count 时,本地也练过:

moved {
  from = local_file.json_info
  to   = local_file.json_info[0]
}

8.2 prevent_destroy

lifecycle {
  prevent_destroy = true
}

它可在相关配置仍保留时,阻止 Terraform 执行删除或需要删除的替换计划;无法阻止外部手动删除。把整个资源块及保护配置从代码移除后,不能继续依赖这条保护。该片段属于此前学习内容,不代表当前每个实验资源都启用了它。

9. Data Source、模板与 JSON

9.1 resource 与 data

data "local_file" "team" {
  filename = "${path.module}/operations-team.txt"
}

这里读取现有文件;resource "local_file" 则负责创建和管理输出文件。

9.2 templatefile

当前开发环境用模板生成说明文件:

content = templatefile(
  "${path.module}/environment-info.tftpl",
  {
    environment = var.environment
    team        = trimspace(data.local_file.team.content)
    name        = module.name.name
  }
)

模板内容:

Environment: ${environment}
Team: ${team}
Name: ${name}
Managed by: Terraform

9.3 jsonencode 与条件输出

content = jsonencode({
  environment = var.environment
  team        = trimspace(data.local_file.team.content)
  name        = module.name.name
  name_length = var.name_length
  managed     = true
})

jsonencode 生成 JSON,可以减少手工处理逗号、引号和转义的错误。

当资源可能不存在时,输出也需要处理禁用情况:

output "json_file_path" {
  value = var.json_config.enabled ? local_file.json_info[0].filename : null
}

常用表达式速查:

写法用途
path.module当前模块的目录位置
abspath(...)转换为绝对路径
trimspace(...)去掉字符串首尾空白
templatefile(...)渲染文本模板
jsonencode(...)把值编码为 JSON
condition ? a : b条件选择
alltrue(...)检查一组布尔值是否全部为真

10. Docker 容器管理实战

实验目录:terraform-learning/04-docker

当前代码配置 Windows Docker named pipe:

provider "docker" {
  host = "npipe:////./pipe/docker_engine"
}

运行前需要 Docker 引擎可用。这份连接配置针对 Windows;迁移到其他系统时需要相应调整。

10.1 已覆盖的资源与行为

配置作用
docker_image.nginx管理 Nginx 镜像
docker_container.nginx管理网页容器
ports把主机端口映射到容器 80 端口
volumes把主机网页目录只读挂载到容器
docker_network.learning创建网络,网页容器加入该网络
docker_volume.learning_data创建命名卷;当前文件没有把该卷挂载进网页容器
healthcheck检测容器内网页是否可访问
restart配置 Docker 的重启策略
内存与 CPU 参数限制容器可使用的资源

10.2 端口与目录挂载

ports {
  internal = 80
  external = var.http_port
  ip       = "127.0.0.1"
}

volumes {
  host_path      = abspath("${path.module}/html")
  container_path = "/usr/share/nginx/html"
  read_only      = true
}

当前 terraform.tfvars 设置 http_port = 8081,覆盖变量默认值 8080。只监听 127.0.0.1,访问入口是本机。

目录挂载会读取主机上已保存的文件。单纯修改挂载目录中的网页,一般不需要重新构建镜像;浏览器是否立即显示变化,还要考虑缓存。

10.3 健康检查与重启策略

restart = "unless-stopped"

healthcheck {
  test         = ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:80/ || exit 1"]
  interval     = "10s"
  timeout      = "3s"
  start_period = "10s"
  retries      = 3
}

这里的 127.0.0.1:80 位于容器内部。要区分:

  • Up:容器主进程正在运行。
  • healthy / unhealthy:健康检查的结果。
  • 重启策略:处理符合条件的容器退出。

普通 Docker 容器不会仅因为变成 unhealthy 就自动重启。 unless-stopped 会尊重主动停止行为;运行 docker stop 后,不应期待它马上自己启动。

10.4 内存与 CPU

memory      = 128
memory_swap = 128
cpu_period  = 100000
cpu_quota   = 50000

本实验实际验证的内存上限为 134217728 字节,即 128 MiB。memory_swap 是内存加 swap 的总上限;两者相等时不额外分配 swap 使用空间。

CPU 配额计算:50000 / 100000 = 0.5,即相当于半核的计算时间上限,不代表绑定半个核心,也不代表持续消耗半核。

CPU 配额用完通常表现为限速;内存超限可能触发 OOM。具体实际行为仍需结合运行时与应用检查。

验证命令:

docker inspect --format '{{.HostConfig.Memory}}' terraform-learning-nginx
docker inspect --format 'Period={{.HostConfig.CpuPeriod}} Quota={{.HostConfig.CpuQuota}}' terraform-learning-nginx
docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' terraform-learning-nginx
docker inspect --format '{{.State.Health.Status}}' terraform-learning-nginx
docker stats --no-stream terraform-learning-nginx

11. 镜像构建、升级与回退

11.1 目录挂载与镜像打包

方式页面来源修改网页后的流程
Bind mount 目录挂载主机目录中的文件保存文件,刷新并检查缓存
Dockerfile COPY构建时复制进镜像的文件构建新镜像,用新镜像创建容器

此前使用的 Dockerfile:

FROM nginx:stable-alpine
COPY html/ /usr/share/nginx/html/

构建命令在 04-docker 目录执行:

docker build -t terraform-learning-web:v1 .
# 修改并保存网页后,再构建另一个版本
docker build -t terraform-learning-web:v2 .

最后的 . 是构建上下文。.dockerignore 用于限制发送的文件,避免把 State 等无关文件包含进去。

只构建新镜像,不会自动更新已运行的旧容器。实验中的 v1v2 是人为约定的标签,需要保留对应旧镜像才能按该方式回退。

11.2 用 Terraform 选择版本

当前 release.tf 的核心配置:

variable "release_version" {
  type    = string
  default = "v1"
}

data "docker_image" "release" {
  name = "terraform-learning-web:${var.release_version}"
}

resource "docker_container" "release" {
  name  = "terraform-learning-release"
  image = data.docker_image.release.id

  ports {
    internal = 80
    external = 8084
    ip       = "127.0.0.1"
  }
}

数据源读取已存在的本地镜像,不负责构建。因为 release.tfmain.tf 在同一目录,运行整个目录的计划前,就应确保所选版本镜像存在。

升级时在 terraform.tfvars 中设置:

release_version = "v2"

保存后执行 plan → apply,检查发布容器的替换是否符合预期。回退时改回 v1 并重复同样流程。实际页面来自容器中的镜像内容,不能只看变量或输出地址判断发布成功。

这种单容器替换可能有短暂中断;应用镜像回退也不会自动回退数据库数据。

11.3 实验中的地址

端口实验用途管理方式
8081挂载 Windows 网页目录的 NginxTerraform
8082手动启动的镜像测试容器,曾用于 v1/v2 切换Docker 手动命令
8083手动启动的 v2 对比容器Docker 手动命令
8084通过 release_version 选择镜像的发布容器Terraform

这些是历史实验端口,不保证当前所有端口都在运行。当前文件中的 release_versionv1,这只表明配置选择;实际运行版本仍需检查。

12. 实战踩坑与排障

现象此前定位出的原因或判断方法处理思路
修改后页面没变文件未保存,或访问了使用旧镜像的另一个端口先核对磁盘内容、端口和挂载来源
Dockerfile 构建失败当时磁盘上的 Dockerfile 是 0 字节保存实际内容,再构建
新镜像构建成功,旧容器没更新构建与替换运行容器是两个步骤用新镜像创建或替换容器
手动停止容器后没有恢复健康检查不负责重启;unless-stopped 尊重主动停止区分进程状态、健康检查和重启策略
内存参数更新失败当时已有容器更新需要同时处理 swap 总上限检查 memorymemory_swap 的组合
CPU 配置写入但限制仍为 0历史实验中的 Docker Provider 3.9.0 更新路径未正确处理相应参数当时经 Terraform 替换实验容器后,验证配额生效
No changes,效果仍不符合预期Provider 记录与实际效果可能存在偏差用 Docker inspect、页面和日志交叉验证
改资源名出现删除与新增Terraform 地址发生变化检查是否需要 moved
关闭一个开关却出现删除count 变为 0 或 for_each 键被移除先看删除计划,理解开关的实现语义

CPU 问题是特定实验和版本下的历史记录,不应推断所有 Provider 版本都会如此。相关历史源码:Docker Provider v3.9.0 容器实现

网页不可访问时,按顺序检查:

Docker 引擎可用吗?
    ↓
容器运行了吗?
    ↓
端口映射和访问地址正确吗?
    ↓
日志里有没有请求和错误?
    ↓
网页文件和挂载路径正确吗?
    ↓
Nginx 配置有效吗?
docker version
docker ps -a --filter name=terraform-learning-nginx
docker logs --tail 20 terraform-learning-nginx
docker exec terraform-learning-nginx cat /usr/share/nginx/html/index.html
docker exec terraform-learning-nginx nginx -t
docker inspect --format '{{.Config.Image}}' terraform-learning-release

docker exec 要求目标容器处于可执行命令的运行状态。排查后需要长期保留的资源配置,应更新到 Terraform 配置中。

13. 复习清单与实验目录

13.1 十个自测问题

  1. resourcedata 分别负责什么?
  2. variables.tfterraform.tfvars 有什么区别?
  3. plan -var=... 后直接 apply,变量会继承吗?
  4. 保存的 .tfplan 与普通 plan 有什么不同?
  5. 配置、State、实际资源分别表示什么?
  6. 删除一个 for_each 键,会对对应资源产生什么影响?
  7. Module 为什么可以被多次调用?引用如何建立依赖?
  8. Workspace 能自动选择参数或提供权限隔离吗?
  9. 改名后如何通过 moved 保持资源地址连续性?
  10. 为什么 apply 成功和 No changes 之后,还要验证实际服务?

13.2 本地实验位置

目录学习内容
terraform-learning/01-random随机字符串、变量、校验、locals、for_each、输出
terraform-learning/02-modules子模块、环境对象、过滤、模块批量调用、moved
terraform-learning/03-environments独立环境目录、Data Source、模板、JSON、条件资源
terraform-learning/04-docker镜像、容器、网络、挂载、健康检查、资源限制、发布回退

旧 README 中有早期练习的简化描述。例如第一课已从直接写 length = 8 演进为 length = var.string_length;Docker 项目也已增加 release.tf。复习和操作时以当前代码和本次计划为准。

建议按目录顺序复习,每一课都能够完成:解释输入 → 预测计划 → 检查实际结果 → 解释差异。

14. 补充:本地 AWS EC2 示例

另外发现已有目录:terraform-aws-ec2-demo。本节基于本地代码梳理,不表示此次创建或验证了 AWS 资源。

代码包含 VPC、公有子网、Internet Gateway、路由表及关联、安全组、EC2,以及读取 Amazon Linux 2023 AMI 的数据源。EC2 的启动脚本安装 Nginx。

默认参数:

aws_region      = "ap-southeast-1"
instance_type   = "t3.micro"
create_instance = false

这些资源使用 count = var.create_instance ? 1 : 0 控制。初次、空状态且开关为 false 时,不计划创建这些资源,但 AMI 数据查询仍需要 AWS 身份和 API 访问。如果状态中已经有资源,把开关设为 false 可能产生删除计划。

复习流程:

cd terraform-aws-ec2-demo
aws sts get-caller-identity
terraform init
terraform fmt -check
terraform validate
terraform plan

需要真正创建时,原示例采用保存计划后执行的方式:

terraform plan -var="create_instance=true" -out="demo.tfplan"
terraform show -no-color demo.tfplan
# 确认账号、区域及变更范围后再执行
terraform apply demo.tfplan

这会创建真实云资源,可能产生费用。清理应依据该目录的实际状态和销毁计划进行;本文没有执行这些命令,也没有检查云端现存资源。

参考资料

这些链接供继续查阅;本文主要归纳此前会话与本地实验内容,不作为所有最新版本行为的完整说明。

原创

Terraform 学习笔记:从基础概念到 Docker 发布实战

本文链接: Terraform 学习笔记:从基础概念到 Docker 发布实战

本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

评论交流

文章目录