- 文档/教程
【免费下载链接】90DaysOfDevOps
This repository started out as a learning in public project for myself and has now become a structured learning map for many in the community. We have 3 years under our belt covering all things DevOps, including Principles, Processes, Tooling and Use Cases surrounding this vast topic.
本篇指南是 90DaysOfDevOps 挑战系列第 57 天的核心内容,围绕 HashiCorp Terraform 展开:它解决的是"如何用声明式配置文件把云 API 变成可重复、可审查、可版本化的基础设施交付流程"这一 DevOps 核心问题。读完本文,你将掌握 Terraform 的 Write / Plan / Apply 核心工作流、HCL 配置语言的基本要素、它与 Vagrant 的定位差异,以及在本仓库 2022/Days/IaC 目录下可直接运行验证的 Docker、VirtualBox、Kubernetes、AWS 实战示例,为后续用 Terraform 部署虚拟机、容器与 Kubernetes 资源打下基础。
为什么需要基础设施即代码(IaC)
几乎所有云厂商和本地(on-prem)平台都提供了管理控制台(UI),允许我们通过点击界面创建资源;这些平台通常也提供 CLI 或 API,以编程方式创建同样的资源。但通过 API,我们获得的不仅是速度——更重要的是可重复性与一致性。
基础设施即代码(Infrastructure as Code,IaC)的核心价值,就是把那些 API 接入到我们的工作流中,以"期望状态(desired state)"的方式部署资源:代码描述了基础设施最终应该长什么样,工具负责把现实状态收敛到期望状态。这正是 Terraform 的定位——HashiCorp 官方对它的定义是:
"Terraform 是一个用于安全高效地构建、变更和版本化管理基础设施的工具。"
"Terraform 是一个开源的基础设施即代码软件工具,提供一致的 CLI 工作流来管理数百种云服务。Terraform 将云 API 编码为声明式配置文件。"
这种"声明式 + 版本化"的思路,让基础设施的变更可以像应用代码一样走评审、留痕、可回滚,是 DevOps 工程化的前提。
云特定工具 vs 云无关工具:为什么选择 Terraform
市面上有大量 IaC 工具,可以粗略分为两类:云平台自有的专用工具,与跨云通用的工具。原文档给出的对照如下:
| 云特定(Cloud Specific) | 云无关(Cloud Agnostic) |
|---|---|
| AWS CloudFormation | Terraform |
| Azure Resource Manager(ARM) | Pulumi |
| Google Cloud Deployment Manager |
表格并非穷尽列表(not exclusive or exhaustive),但它清楚地说明了选型逻辑:如果使用 AWS 专属的 CloudFormation,或 Azure 专属的 ARM,那么基础设施代码就被绑死在单一云厂商上;而 Terraform 这类云无关工具,可以让同一套配置理念与工作流横跨多个云平台。
这正是 90DaysOfDevOps 挑战使用 Terraform 的重要原因:我们希望在演示中,乃至在整体工程实践中,对所使用的云与平台保持无关性(agnostic)——无论是公有云、私有云还是本地环境,都能用同一套心智模型与工具链去管理。
Terraform 概览:一个以供给(provisioning)为核心的 CLI
Terraform 是一个以基础设施供给为目标的工具,本质上是一个 CLI,具备编排复杂基础设施环境的能力。通过它,我们可以定义存在于本地或远端(云上)的复杂基础设施需求;它不仅负责最初的建设(build),也负责这些资源整个生命周期内的维护与更新(maintain and update)——后续任何基础设施的变更,都应该通过 Terraform 完成,从而保证所有变更都被代码记录在案。
Terraform 的核心工作流被概括为三个阶段:Write → Plan → Apply,官方工作流示意如下:
Write:用 HCL 写声明式配置
Terraform 允许我们创建**声明式(declarative)**的配置文件来构建环境。这些文件使用HashiCorp Configuration Language(HCL)编写,通过块(block)、参数(argument)和表达式(expression)对资源进行简洁描述。
一个最小的 Terraform 模块只需要几行代码。仓库中的 Hello-world/main.tf 就是"最简单的 Terraform 模块"示例:
terraform { # 该模块目前仅在 Terraform 0.13.x 上测试;为了便于升级,最低版本设为 0.12.26, # 因为该版本起才支持带 source URL 的 required_providers,可与 0.13.x 代码向前兼容。 required_version = ">= 0.12.26" } # 最简单的 Terraform 模块:仅输出 "Hello, World!" output "hello_world" { value = "Hello, 90DaysOfDevOps from Terraform" }可以看到,这个模块没有任何资源,只有一个output块,用于输出一段文本。它演示了 HCL 最基础的形态:terraform {}块声明引擎版本约束,output {}块声明输出值。在真实项目中,resource块才是主体——例如声明一台虚拟机、一个容器或一个命名空间。
Plan:部署前先"演练"
Plan 阶段是 Terraform 区别于手写脚本的关键能力:在部署或变更任何东西之前,用 Terraform CLI 的特定命令检查上述配置文件到底会部署出什么。它会对当前状态与期望状态做对比,生成一份执行计划(plan),让我们在真正执行前确认资源将如何被创建、修改或删除。
原文档特别强调了一个工程原则:Terraform 是基础设施的"持续工具"。如果你想改变基础设施的某个方面,应该通过 Terraform 去改,这样所有变更都完整落在代码里,可追溯、可复用。
Apply:把配置落地到众多 Provider
一旦对计划满意,就可以把配置应用到 Terraform 所支持的众多 Provider(供应商)上。Provider是 Terraform 与具体平台(AWS、Azure、Docker、Kubernetes、VirtualBox 等)交互的插件层;Module(模块)则是被封装、可复用的配置集合——原文档将其类比为容器镜像:社区已经创建并公开分享了大量模块,你不必反复重造轮子,而是可以"以相同方式在任何地方部署某个特定基础设施资源"的最佳实践复用。
Terraform 的 Provider 与 Module 都托管在官方 Registry 中,可以通过terraform init拉取到本地。
Terraform vs Vagrant:定位截然不同的两个 HashiCorp 工具
在之前的挑战中,我们已经使用过另一个 HashiCorp 开源工具Vagrant。两者常被混淆,但定位完全不同:
- Vagrant:专注于管理**开发环境(development environments)**的工具。
- Terraform:用于**构建基础设施(building infrastructure)**的工具。
简单说:Vagrant 解决的是"开发者在本地拥有一致、可复现的开发环境";Terraform 解决的是"生产级基础设施的供给与全生命周期管理"。两者可以配合使用,但职责边界清晰。
Terraform 安装:跨平台的 CLI,arkade 一行搞定
Terraform 的安装非常简单,它本身就是跨平台的:官方为 Linux、macOS、Windows 提供多种下载与安装方式,包括包管理器、二进制包等。原文档中的安装界面截图如下:
90DaysOfDevOps 挑战推荐使用arkade来安装:arkade 是一个便于把所需工具、应用和 CLI 装到系统上的小工具。一条命令即可完成安装,若已安装则会自动更新到可用版本:
arkade get terraform安装完成后,即可开始接触 HCL 语法,并使用 Terraform 在各个平台上创建基础设施资源。
仓库实战:五个可直接运行的 IaC 示例
本仓库的 2022/Days/IaC 目录正是第 57 天之后的延伸实践,覆盖了"VM、容器与 Kubernetes"三个原文档提到的部署场景,还包含 AWS 与测试相关代码。以下示例全部来自仓库,可作为入门后的第一手练习材料。
1. Docker:拉起 nginx 容器(docker.tf)
terraform { required_providers { docker = { source = "kreuzwerker/docker" version = "2.16.0" } } } provider "docker" {} resource "docker_image" "nginx" { name = "nginx:latest" keep_locally = false } resource "docker_container" "nginx" { image = docker_image.nginx.latest name = "tutorial" ports { internal = 80 external = 8000 } }要点拆解:
required_providers显式声明使用kreuzwerker/dockerProvider 及固定版本2.16.0,保证可复现性;docker_image拉取nginx:latest镜像,keep_locally = false表示不保留在本地;docker_container创建名为tutorial的容器,并把容器内 80 端口映射到宿主 8000 端口;- 资源间通过
docker_image.nginx.latest引用建立依赖关系——这正是 HCL 表达式与隐式依赖的体现。
2. Docker Compose 式的 WordPress 全家桶(docker-wordpress.tf)
该示例用 Terraform 实现了类似 Docker Compose 的多容器编排:MySQL 数据库 + WordPress 应用,引入变量、卷、网络等更多要素:
variable wordpress_port { default = "8080" } resource "docker_volume" "db_data" { name = "db_data" } resource "docker_network" "wordpress_net" { name = "wordpress_net" } resource "docker_container" "db" { name = "db" image = "mysql:5.7" restart = "always" network_mode = "wordpress_net" env = [ "MYSQL_ROOT_PASSWORD=wordpress", "MYSQL_PASSWORD=wordpress", "MYSQL_USER=wordpress", "MYSQL_DATABASE=wordpress" ] mounts { type = "volume" target = "/var/lib/mysql" source = "db_data" } } resource "docker_container" "wordpress" { name = "wordpress" image = "wordpress:latest" restart = "always" network_mode = "wordpress_net" env = [ "WORDPRESS_DB_HOST=db:3306", "WORDPRESS_DB_USER=wordpress", "WORDPRESS_DB_NAME=wordpress", "WORDPRESS_DB_PASSWORD=wordpress" ] ports { internal = "80" external = "${var.wordpress_port}" } }该示例展示的关键 HCL 能力:
- 变量(variable):
wordpress_port带默认值8080,可用${var.wordpress_port}在资源中引用,让配置参数化、可复用; - 卷与网络:
docker_volume持久化数据库数据,docker_network让两个容器在同一自定义网络内互通; - 容器依赖:WordPress 通过
WORDPRESS_DB_HOST=db:3306访问名为db的 MySQL 容器,Terraform 会自动识别并处理创建顺序。
3. VirtualBox:本地 VM 集群(virtualbox.tf)
本地环境也能被 IaC 化。该示例使用terra-farm/virtualboxProvider 创建两台 Ubuntu 虚拟机:
terraform { required_providers { virtualbox = { source = "terra-farm/virtualbox" version = "0.2.2-alpha.1" } } } resource "virtualbox_vm" "node" { count = 2 name = format("node-%02d", count.index + 1) image = "https://app.vagrantup.com/ubuntu/boxes/bionic64/versions/20180903.0.0/providers/virtualbox.box" cpus = 2 memory = "512 mib" network_adapter { type = "hostonly" host_interface = "vboxnet1" } } output "IPAddr" { value = element(virtualbox_vm.node.*.network_adapter.0.ipv4_address, 1) } output "IPAddr_2" { value = element(virtualbox_vm.node.*.network_adapter.0.ipv4_address, 2) }这里出现了更多 HCL 进阶特性:
count元参数:count = 2一次创建两台相同规格的 VM;- 内置函数与表达式:
format("node-%02d", count.index + 1)生成node-01、node-02这样的有序名称; - 输出(output):
element(virtualbox_vm.node.*.network_adapter.0.ipv4_address, 1)取出第二台节点的 IPv4 地址,.*是列表展开(splat)表达式的典型用法。
4. Kubernetes:命名空间 + Deployment + Service(kubernetes.tf)
把基础设施即代码延伸到 Kubernetes 集群内部对象,是原文档提到的三大场景之一:
terraform { required_providers { kubernetes = { source = "hashicorp/kubernetes" version = ">= 2.0.0" } } } provider "kubernetes" { config_path = "~/.kube/config" } resource "kubernetes_namespace" "test" { metadata { name = "nginx" } } resource "kubernetes_deployment" "test" { metadata { name = "nginx" namespace = kubernetes_namespace.test.metadata.0.name } spec { replicas = 2 selector { match_labels = { app = "MyTestApp" } } template { metadata { labels = { app = "MyTestApp" } } spec { container { image = "nginx" name = "nginx-container" port { container_port = 80 } } } } } } resource "kubernetes_service" "test" { metadata { name = "nginx" namespace = kubernetes_namespace.test.metadata.0.name } spec { selector = { app = kubernetes_deployment.test.spec.0.template.0.metadata.0.labels.app } type = "NodePort" port { node_port = 30201 port = 80 target_port = 80 } } }该示例演示了:
- Provider 通过
config_path = "~/.kube/config"读取本地 kubeconfig 与集群交互; - 声明
kubernetes_namespace、kubernetes_deployment(2 副本 nginx)与kubernetes_service(NodePort 类型,节点端口 30201); - 资源引用链:Deployment 的
namespace引用 Namespace 的.metadata.0.name,Service 的 selector 引用 Deployment 模板中的 labels——Terraform 据此自动推导资源依赖与执行顺序。
5. Terratest:用 Go 测试 AWS 上的 Terraform 代码(Terratest/examples 与 test/terraform_test.go)
基础设施代码同样应该被测试。仓库中的 Terratest 示例展示了如何用 Go 编写针对 Terraform 的自动化测试:
- provider.tf:AWS Provider 的 access key、secret key 与 region 均来自变量;
- vars.tf:定义
AWS_ACCESS_KEY、AWS_SECRET_KEY、AWS_REGION(默认ap-south-1)与AMIS(region 到 AMI ID 的映射); - terraform.tfvars:以
键 = 值形式填充实际变量,其中密钥位置为占位符; - instance.tf:创建
t2.micro实例,并通过user_data在启动时用 busybox 起一个返回 "Hello, World!" 的 8080 端口 Web 服务; - output.tf:输出实例公网 IP;
- versions.tf:约束 Terraform 版本
>= 0.12.26。
对应的测试 terraform_test.go 完整覆盖了"init → apply → 验证 → destroy"闭环:
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{ /* 指向 Terraform 代码所在路径 */ TerraformDir: "../examples", }) /* 测试结束时运行 terraform destroy 清理已创建的资源 */ defer terraform.Destroy(t, terraformOptions) /* 运行 terraform init 与 terraform apply,出错即失败 */ terraform.InitAndApply(t, terraformOptions) /* 用 terraform output 获取实例 IP */ publicIp := terraform.Output(t, terraformOptions, "public_ip") /* 对实例发起 HTTP 请求,确认返回 200 OK 且内容为 "Hello, World!" */ url := fmt.Sprintf("http://%s:8080", publicIp) http_helper.HttpGetWithRetry(t, url, nil, 200, "Hello, World!", 30, 5*time.Second)这个测试把 Terraform 的 CLI 生命周期(init、apply、output、destroy)与 HTTP 断言串成一条完整的验证链,是从"手动跑 terraform apply"迈向"CI 中自动验证基础设施"的典型范式。
后续与学习资源
第 57 天是 Terraform 的起点:接下来我们将深入 HCL 语法细节,并开始用 Terraform 在多种不同平台上创建基础设施资源(虚拟机、容器、Kubernetes 对象),对应的实践代码就存放在 2022/Days/IaC 目录中,可逐目录对照学习。
关于更深入的学习材料,原文档提供了一批被社区反复验证过的公开资源,覆盖从"什么是 IaC 与工具差异"到"Terraform 从入门到专家"的完整路径,包括入门教程、15 分钟速览、完整课程、HashiCorp Terraform Associate 认证备考课程、KodeKloud 的带实验的实战指南、Terraform 示例项目合集以及 Awesome Terraform 精选清单等。如果你有额外的优质资源,也可以像原文档倡导的那样通过 Pull Request 贡献到 Contributors.md 维护的资源列表中。
下一站,继续前往 Day 58,进一步探索 HCL 与 Terraform 的实际部署应用。
- 文档/教程
【免费下载链接】90DaysOfDevOps
This repository started out as a learning in public project for myself and has now become a structured learning map for many in the community. We have 3 years under our belt covering all things DevOps, including Principles, Processes, Tooling and Use Cases surrounding this vast topic.
相关推荐
90DaysOfDevOps Day 57:Terraform 入门——用声明式 IaC 构建、变更与版本化基础设施
90DaysOfDevOps Day 57:Terraform 入门——用声明式 IaC 构建、变更与版本化基础设施 本文是 90DaysOfDevOps 学习
文档/教程90DaysOfDevOps Day 57:Terraform 入门——用声明式 IaC 安全高效地编排多云基础设施
90DaysOfDevOps Day 57:Terraform 入门——用声明式 IaC 安全高效地编排多云基础设施 本篇指南以 90DaysOfDevOps
文档/教程yajl-objc vs 原生JSON库:为什么它是Objective-C项目的最佳选择?
yajl objc vs 原生JSON库:为什么它是Objective C项目的最佳选择? 在Objective C开发中,JSON处理是日常任务的重要组成部分
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考