☰
90DaysOfDevOps 实战:用 Terraform 与变量在 VirtualBox 上批量创建虚拟机(Day 59)
2026/10/7 20:19:32 网站建设 项目流程
  • 文档/教程

【免费下载链接】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.

项目地址:https://gitcode.com/gh_mirrors/90/90DaysOfDevOps
点击查看免费下载

本篇技术指南是 90DaysOfDevOps 挑战中 Day 59 的完整实战记录:以 Terraform 的「期望状态配置」为核心,通过社区terra-farm/virtualboxProvider 在本地 VirtualBox 上批量创建并管理多台 Ubuntu 虚拟机,并深入讲解变量(Variables)与输出(Outputs)的多种定义方式、优先级规则和敏感信息处理。读完本文,你将掌握一套不依赖云端账号、在笔记本上即可复现的 Terraform 全流程(init → plan → apply → destroy),并能将其无缝迁移到 AWS、Azure 等公有云环境。

为什么用 Terraform 管理本地 VirtualBox

通常 Terraform 被用于公有云(AWS、Google Cloud、Microsoft Azure)以及 VMware、Hyper-V、Nutanix AHV 等虚拟化平台的资源编排。VirtualBox 属于工作站级虚拟化软件,正常情况下并不是 Terraform 的典型使用场景——正如原文档作者所言,本次演示发生在三万六千英尺的高空航班上,相比在云端开通资源,在笔记本电脑本地创建虚拟机要快得多。

但这并不妨碍我们用它来理解核心思想:编写一份描述目标状态的配置代码(HCL),交给 Provider 去执行,让基础设施按声明的方式被创建、修改与销毁。这个「声明式」过程与以往用 Vagrant 的体验不同——在本章节开头已经对比过 Vagrant 与 Terraform 的差异,简单来说:

  • Terraform以「期望状态」为驱动力,配置变更时对比状态文件,只做必要的增删改;
  • Vagrant更偏向开发环境的可重复供给(provisioning),面向单机工作流。

Terraform 的架构由两部分组成:代码(configuration)与状态(state),二者合称 Terraform Core;而真正与目标平台通信的是Provider(如 AWS Provider、Azure Provider,以及本次使用的 VirtualBox 社区 Provider),它们通过平台 API 执行创建、更新、删除操作。Terraform Registry 上有数百个 Provider,既能引用官方发布的(如hashicorp/aws),也能自行编写并本地使用或发布到 Registry。

前置准备

动手前,需要在本机准备好两样东西:

  1. VirtualBox:Provider 实际调用的虚拟化引擎,负责承载并运行虚拟机;
  2. Terraform CLI:解析 HCL 配置并驱动 Provider 的命令行工具。

接着创建项目目录:

mkdir virtualbox cd virtualbox

在目录内新建virtualbox.tf,所有资源定义都写在这个文件中。仓库中对应的完整源码位于 2022/Days/IaC/Virtualbox/virtualbox.tf,下文将逐段拆解。

编写 virtualbox.tf:Provider、资源与输出

virtualbox.tf由三个层次组成:声明 Provider 的terraform块、描述资源的resource块、以及展示结果的output块。

1. 声明 Provider

terraform { required_providers { virtualbox = { source = "terra-farm/virtualbox" version = "0.2.2-alpha.1" } } } # There are currently no configuration options for the provider itself.
  • source:Provider 在 Registry 上的发布地址,terra-farm/virtualbox是社区维护的 VirtualBox Provider;
  • version:锁定0.2.2-alpha.1版本。这是一个alpha 版本,说明 VirtualBox Provider 仍处于活跃迭代期,实际生产环境建议优先使用官方云平台 Provider;
  • 该 Provider 目前没有可供配置的 provider 级选项,所以无需写provider "virtualbox" {}块。

2. 定义资源:批量创建两台 VM

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" } }

关键参数说明:

参数值含义
count2元参数(meta-argument),将同一资源块实例化 2 份,生成node[0]、node[1]两个实例
nameformat("node-%02d", count.index + 1)利用format函数格式化名称:count.index从 0 开始,%02d保证两位补零,最终得到node-01、node-02
imageUbuntu bionic64 的 VirtualBox box 下载地址虚拟机模板镜像,Provider 会据此创建系统盘
cpus2每台虚拟机分配 2 个 vCPU
memory"512 mib"每台分配 512 MiB 内存(注意是带单位的字符串)
network_adapter.type"hostonly"使用 VirtualBox 仅主机(host-only)网络
network_adapter.host_interface"vboxnet1"绑定到宿主机已创建的vboxnet1虚拟网卡

3. 定义输出:读取每台 VM 的 IP

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) }
  • virtualbox_vm.node.*.network_adapter.0.ipv4_address是splat 表达式:把所有node实例的第一个网卡(下标 0)的 IPv4 地址收集成列表;
  • element(list, index)按索引取值,分别取出第 1 个与第 2 个元素的地址输出到终端。原文档有意从1而非0开始取,仅作演示,读者可根据实际实例数调整索引。

apply完成后,Terraform 会在终端打印这两个输出值,方便你直接 SSH 登录对应虚拟机。

三步创建:init → plan → apply

代码就绪后,按顺序执行三个核心命令。整个 CLI 流程与 Day 58(HCL 基础) 中 hello-world 示例一脉相承,仓库中的 2022/Days/IaC/Hello-world/main.tf 就是那个最小示例。

terraform init:初始化项目并下载 Provider

terraform init

init是任何 Terraform 项目的第一步:解析required_providers,从 Registry 下载terra-farm/virtualbox插件到本地,并在项目目录生成.terraform目录与.terraform.lock.hcl锁文件(记录 Provider 版本哈希,保证后续执行的一致性)。

terraform plan:预览执行计划

terraform plan

plan会读取配置与现有状态,输出一份「将要创建 / 变更 / 销毁什么资源」的执行计划,相当于真正的apply之前的安全预览,不会对基础设施产生任何实际改动。

terraform apply:落地创建

terraform apply

apply内置安全确认机制:先展示与plan相同的执行计划,并要求你输入yes才真正创建资源。下图展示了 2 台名为node[0]、node[1]的虚拟机创建完成、以及输出的内网 IP 地址:

完成后打开 VirtualBox 管理器,即可看到 2 台正在运行的虚拟机node-01、node-02。

横向扩容:修改 count 增加节点

Terraform 最核心的价值在于通过修改期望状态驱动基础设施变更。现在想把集群扩到 3 台,只需把count从2改成3:

resource "virtualbox_vm" "node" { count = 3 # ...其余参数保持不变 }

再次执行terraform apply,Terraform 会对比状态文件:已有的 2 台保持不变,只新增第 3 台(node-03)。这种「增量式、幂等化」的变更能力,正是声明式 IaC 相比手工操作的根本优势——同样的配置反复执行,得到的结果始终一致,全程无需人工干预。

terraform destroy:一键清理

实验结束后,用一条命令移除全部资源:

terraform destroy

同样带安全确认:预览即将销毁的资源清单(含虚拟机网卡等配置),输入yes后执行删除:

如果你希望在自动化脚本中跳过确认,可以在apply/destroy末尾追加--auto-approve。但建议仅在学习和测试环境使用——资源往往销毁得比创建时更快,生产环境请务必保留人工确认环节。

Day 58 把本次用到的 CLI 命令总结为四件套,这里一并列出:

命令作用
terraform init初始化项目目录、下载并锁定 Provider
terraform plan预览将要创建、变更的资源
terraform apply按代码部署资源
terraform destroy销毁项目创建的资源

对应的两个核心配置概念是:providers(Terraform 如何通过 API 与目标平台通信)与resources(我们想用代码部署什么)。

变量与输出:六种定义方式与优先级

Day 59 的另一个重头戏是变量系统。上一节的 hello-world 示例已经展示过output的用法,本节深入变量定义。

六种变量来源

Terraform 允许通过以下方式为变量赋值:

  1. 在terraform plan/terraform apply命令中手动交互输入;
  2. 在.tf文件内的variable块中定义(通常配合default);
  3. 使用系统环境变量,格式为TF_VAR_变量名;
  4. 在项目目录放置terraform.tfvars文件(作者的个人偏好,也是团队协作最常见的做法);
  5. 使用*.auto.tfvars文件(文件名以.auto.tfvars结尾,Terraform 启动时自动加载,无需显式指定);
  6. 执行命令时通过-var或-var-file参数显式传入。

优先级规则

上述来源的优先级从高到低(即-var/-var-file最高):

  1. 命令行-var与-var-file
  2. *.auto.tfvars文件
  3. terraform.tfvars文件
  4. TF_VAR_环境变量
  5. .tf文件内的default默认值
  6. 手动交互输入(仅当变量未被任何来源赋值时才触发)

也就是说,高优先级的来源会覆盖低优先级来源对同一变量的赋值,而default仅在没有任何来源提供值时生效。原文档中「从列表底部往上数,就是变量生效的顺序」描述的正是指向这一规则。

仓库中的真实变量示例

仓库 2022/Days/IaC/Terratest/examples/vars.tf 展示了生产风格的定义方式:

variable "AWS_ACCESS_KEY" { } variable "AWS_SECRET_KEY" { } variable "AWS_REGION" { default = "ap-south-1" } variable "AMIS" { type = map(string) default = { ap-south-1 = "ami-0860c9429baba6ad2" } }

这里可以看到:没有default的变量(如AWS_ACCESS_KEY)必须在 apply 前由外部来源提供值,否则 Terraform 会交互式询问;而带default的变量(如AWS_REGION)在未显式赋值时自动回退到默认值;AMIS则演示了map(string)这类复合类型的默认值写法。配套的 terraform.tfvars 文件即用于集中存放这类敏感或区域相关的赋值。

sensitive 变量:保护状态文件中的敏感信息

前面 Day 58 已提醒过:terraform.tfstate状态文件是 JSON 格式的「Terraform 眼中的世界」,其中会明文记录敏感数据。最佳实践是:

  • 将.tfstate文件加入.gitignore,避免提交到代码仓库;
  • 生产环境把状态存到远端共享位置(如 S3 桶、Terraform Cloud),以换取加密、协作与自动化能力,代价是增加一定的复杂度。

针对敏感数据,可以为变量开启sensitive标记(注意 HCL 赋值语法是=,原文档示例中的type:应写作type =):

variable "some_resource" { description = "something important" type = string sensitive = true }

标记后,该变量在plan/apply输出中会被脱敏隐藏,避免密码、密钥等直接暴露在终端日志中。

延伸:从本地 VirtualBox 到云端与容器

本次演示虽然跑在 VirtualBox 上,但掌握的原理完全可迁移:

  • 把resource块换成aws_instance(参考 Day 58 的 AWS 示例),配合hashicorp/awsProvider,即可在 AWS 上创建 EC2 并注入user_data启动脚本;
  • 仓库的 2022/Days/IaC/Docker/docker.tf 与 2022/Days/IaC/Kubernetes/kubernetes.tf 还提供了用 Terraform 管理 Docker 容器与 Kubernetes 资源的示例,可作为后续深入方向;
  • 若要为 Terraform 代码编写自动化测试,可参考仓库中的 Terratest 示例(测试用例),把「配置 → 应用 → 验证 → 清理」固化为可重复的测试流程。

小结

通过 Day 59 的完整演练,你掌握了:

  • 用virtualbox_vm资源 +count元参数批量创建虚拟机,并用format、splat 表达式、element组织名称与输出;
  • Terraform 的标准工作流:init下载 Provider、plan预览变更、apply落地资源、destroy一键清理;
  • 六种变量赋值方式及其优先级,以及用sensitive = true保护敏感信息;
  • 状态文件的安全风险与远程存储方案。

这套「代码定义期望状态 → Provider 执行 → 状态文件追踪」的心智模型,是后续在公有云、容器与 Kubernetes 场景下使用 Terraform 的通用基础。下一节(Day 60)将继续深入 Terraform 的进阶用法。

注:原文档末尾附有若干 Terraform 学习视频与资源清单,此处不再逐一罗列外部链接;仓库内 2022/Days/day59.md 保留了完整原文,可直接查阅。

  • 文档/教程

【免费下载链接】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.

项目地址:https://gitcode.com/gh_mirrors/90/90DaysOfDevOps
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询