Terraform-秘籍-全-
Terraform 秘籍(全)
原文:
annas-archive.org/md5/92a325b2a9b288c724c56d71a7b39589译者:飞龙
前言
基础设施即代码,通常称为 IaC,是一种 DevOps 文化的支柱实践。IaC 涉及用代码编写你期望的架构配置。除了其他优点外,IaC 还允许基础设施部署的自动化,减少或消除手动干预的需求,从而降低配置错误的风险,并且通过模块化和可扩展的代码创建模板并标准化基础设施。
在所有 DevOps 工具中,有许多支持基础设施即代码(IaC)。其中之一是 Terraform,由 HashiCorp 开发,今天非常流行,因为它不仅是开源和跨平台的,还具有以下优点:
-
它允许你预览将应用到基础设施的更改。
-
它支持操作的并行化,并考虑到依赖关系的管理。
-
它拥有众多的提供者。
在这本关于 Terraform 的书中,我们将首先讨论 Terraform 在本地开发中的安装,Terraform 配置的编写,以及动态构建多个环境。
然后,我们将学习如何使用 Terraform 命令行界面 (CLI) 以及如何共享和使用 Terraform 模块。
一旦理解了 Terraform 中的配置编写和命令使用,我们将讨论 Terraform 在构建基础设施时的实际应用,特别是在领先的云服务提供商 Azure 上的应用。
最后,我们将通过探讨 Terraform 的高级使用方法来结束本书内容,包括 Terraform 测试,将 Terraform 集成到 持续集成/持续部署 (CI/CD) 流水线中,以及使用 Terraform Cloud,它是 Terraform 为团队和公司提供的协作平台。
本书将通过若干最佳实践食谱,指导你如何编写 Terraform 配置和命令,并涵盖 Terraform 与其他工具的集成食谱,如 Terragrunt、kitchen-terraform 和 Azure Pipelines。
本书中描述的大多数 Terraform 配置都基于 Azure 提供者作为示例,但你可以将这些实践应用于所有其他 Terraform 提供者。
在编写这本以食谱格式呈现的书籍时,我希望分享我通过与客户和公司多年的合作,积累的实际 Terraform 场景经验。
第一章:本书适用对象
本书面向那些已经了解 IaC 和 DevOps 文化基础,并且对 Terraform 有一定基础知识的读者。本书不要求具备开发或操作系统方面的经验。
本书内容概览
第一章,设置 Terraform 环境,详细介绍了手动安装 Terraform、使用脚本安装或通过 Docker 容器安装的不同方式,同时还详细介绍了 Terraform 迁移配置过程。
第二章,编写 Terraform 配置,涉及为提供程序编写 Terraform 配置,变量、输出、外部数据、内置函数、条件表达式、文件操作以及使用 Terraform 执行本地程序。
第三章,使用 Terraform 构建动态环境,讨论了通过使用循环、映射和集合深入编写 Terraform 配置,构建动态环境。
第四章,使用 Terraform CLI,解释了如何使用 Terraform 的 CLI 来清理代码、销毁 Terraform 提供的资源、使用工作空间、导入现有资源、生成依赖关系图以及调试 Terraform 执行。
第五章,使用模块共享 Terraform 配置,涵盖了 Terraform 模块的创建、使用和共享。还展示了模块测试实践以及实现 Terraform 模块的 CI/CD 流水线。
第六章,使用 Terraform 配置 Azure 基础设施,展示了如何在云服务提供商 Azure 的实际场景中使用 Terraform。涵盖了身份验证、远程后端、ARM 模板、Azure CLI 执行以及为现有基础设施生成 Terraform 配置等主题。
第七章,深入探索 Terraform,讨论了与 Terraform 更深入相关的主题,如执行 Terraform 配置测试、零停机时间部署、使用 Terragrunt 的 Terraform 包装器,以及实现 CI/CD 流水线来执行 Terraform 配置。
第八章,使用 Terraform Cloud 改善协作,解释了如何在团队中使用 Terraform Cloud 运行 Terraform,通过私有注册表共享 Terraform 模块、使用远程后端存储 Terraform 状态、远程运行 Terraform 以及使用 Sentinel 编写合规性测试。
为了充分利用本书
以下是本书的软件/硬件先决条件列表:
| 书中涉及的软件/硬件 | 操作系统要求 |
|---|---|
| Terraform 版本 >= 12.0 | 任何操作系统 |
| Terraform Cloud | 无 |
| Azure | 任何操作系统 |
| PowerShell 脚本 | Windows/Linux |
| Shell 脚本 | Linux |
| Azure CLI | 任何操作系统 |
| Azure DevOps | 无 |
| GitHub | 无 |
| Git | 任何操作系统 |
| Ruby 版本 2.6 | 任何操作系统 |
| Docker | 任何操作系统 |
如果您使用的是本书的电子版,我们建议您自己输入代码,或者通过 GitHub 仓库访问代码(下节会提供链接)。这样可以帮助您避免复制粘贴代码时可能出现的错误。
下载示例代码文件
您可以从您的www.packt.com帐户下载本书的示例代码文件。如果您从其他地方购买了本书,可以访问www.packtpub.com/support,并注册以将文件直接发送到您的邮箱。
您可以通过以下步骤下载代码文件:
-
在www.packt.com登录或注册。
-
选择“Support”标签。
-
点击“Code Downloads”。
-
在“Search”框中输入书名并按照屏幕上的指示操作。
一旦文件下载完成,请确保使用最新版本的工具解压或提取文件夹:
-
Windows 的 WinRAR/7-Zip
-
Mac 的 Zipeg/iZip/UnRarX
-
Linux 的 7-Zip/PeaZip
本书的代码包也托管在 GitHub 上,地址为github.com/PacktPublishing/Terraform-Cookbook。如果代码有更新,它将在现有的 GitHub 仓库中更新。
我们还提供来自我们丰富书籍和视频目录的其他代码包,您可以在github.com/PacktPublishing/查看。快来看看吧!
Code in Action
本书的《Code in Action》视频可以在bit.ly/3h7cNME观看。
下载彩色图像
我们还提供了一份包含本书中截图/图示彩色图像的 PDF 文件。您可以在此下载:static.packt-cdn.com/downloads/9781800207554_ColorImages.pdf。
使用的约定
本书中使用了多种文本约定。
CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名。这里有一个例子:“请注意,current_version属性包含最新的 Terraform 版本,它是一个值。”
代码块的格式如下:
TERRAFORM_VERSION=$(curl -s https://checkpoint-api.hashicorp.com/v1/check/terraform | jq -r .current_version)
....
当我们希望将您的注意力集中在代码块的特定部分时,相关的行或项目将以粗体显示:
FROMgolang:latest
ENVTERRAFORM_VERSION=0.12.29
RUNapt-get update && apt-get install unzip \
&& curl -Os
任何命令行输入或输出的格式如下:
docker exec tfapp terraform init
docker exec tfapp terraform plan
docker exec tfapp terraform apply --auto-approve
粗体:表示一个新术语、一个重要词汇或您在屏幕上看到的词。例如,菜单或对话框中的词语在文本中将这样显示。这里有一个例子:“通过点击扩展的安装按钮来执行此操作。”
警告或重要提示以这样的形式出现。
提示和技巧以这样的形式出现。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何部分有疑问,请在邮件主题中提及书名,并通过customercare@packtpub.com与我们联系。
勘误:虽然我们已经尽力确保内容的准确性,但错误还是有可能发生。如果你在本书中发现错误,我们将不胜感激,若你能报告给我们。请访问 www.packtpub.com/support/errata,选择你的书籍,点击勘误提交表单链接,并填写相关细节。
盗版:如果你在互联网上发现任何我们作品的非法复制版本,我们将不胜感激,如果你能提供该内容的地址或网站名称。请通过copyright@packt.com与我们联系,并附上相关链接。
如果你有兴趣成为一名作者:如果你在某个领域有专业知识,并且有兴趣写书或为书籍做贡献,请访问 authors.packtpub.com。
评审
请留下评论。在阅读并使用本书后,何不在你购买书籍的网站上留下评论呢?潜在读者可以参考你的公正意见来做出购买决策,我们在 Packt 可以了解你对我们产品的看法,我们的作者也能看到你对他们书籍的反馈。谢谢!
如需了解更多关于 Packt 的信息,请访问 packt.com。
设置 Terraform 环境
在开始编写 Terraform 配置文件之前,必须先安装并配置本地开发环境。这个开发环境将允许在开发过程中编写和验证 Terraform 配置文件。
在本章的教程中,我们将学习如何在 Windows 机器上手动下载和安装 Terraform,以及如何使用脚本在 Windows 和 Linux 上安装它。我们还将学习如何在 Docker 容器中使用 Terraform,最后学习如何将 Terraform 配置从 0.11 版本迁移到 0.13 版本。
本章将涵盖以下教程:
-
手动下载和安装 Terraform
-
在 Linux 上使用脚本安装 Terraform
-
在 Windows 上使用脚本安装 Terraform
-
在 Docker 容器中执行 Terraform
-
在 VS Code 中编写 Terraform 配置
-
将 Terraform 配置迁移到 Terraform 0.13
我们开始吧!
第二章:技术要求
本章不要求你具备任何特定的技术知识。我们主要使用图形用户界面和简单的 Linux 和 Windows 脚本。然而,推荐了解 Docker,这样你可以完成 在 Docker 容器中执行 Terraform 的教程。
最后,对于 IDE,我们将使用免费的 Visual Studio Code,下载地址为 code.visualstudio.com/。
本章的源代码可在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP01 找到。
查看以下视频,了解代码的实际操作:bit.ly/3h9noXz
手动下载和安装 Terraform
在此教程中,我们将学习如何在 Windows 操作系统下的本地机器上下载和安装 Terraform。
准备工作
完成此教程的唯一前提是你正在使用 Windows 操作系统。
如何做…
执行以下步骤:
-
打开 Windows 文件资源管理器。选择一个位置并创建一个名为
Terraform的文件夹。我们将使用该文件夹来存储 Terraform 二进制文件;例如,C:/Terraform。 -
启动网页浏览器,访问
www.terraform.io/downloads.html。 -
向下滚动页面,直到找到适用于 Windows 的软件包:

-
点击 64 位链接,下载适用于 Windows 64 位操作系统的 Terraform ZIP 包。该包将被下载到本地。
-
将下载的 ZIP 文件内容解压到我们在 第 1 步 中创建的
Terraform文件夹中:

安装 Terraform 的最后一步是通过添加 Terraform 二进制文件夹的路径来配置 Path 环境变量。
为了完成此任务,请按照以下步骤操作:
- 在文件资源管理器中,右键点击“此电脑”菜单,选择属性:

- 点击高级系统设置链接,并点击新打开窗口中的环境变量按钮:

- 当提供环境列表时,选择用户变量或系统变量(选择此选项将环境变量应用于所有工作站用户),然后选择 Path 变量。接着,点击编辑按钮:

- 在路径列表中,添加我们创建的文件夹,也就是
C:\Terraform\:

最后,通过点击每个窗口底部的 OK 按钮来验证所有打开的窗口。
工作原理…
下载并安装 Terraform 很简单,将 Terraform 二进制文件的路径添加到PATH环境变量中,可以让我们从任何终端位置执行 Terraform 命令行。
完成所有这些步骤后,我们可以通过打开命令行终端或 PowerShell 并执行以下命令来检查 Terraform 是否正常工作:
terraform --help
执行上述命令的结果如下所示:

这样做后,Terraform 命令列表将显示在终端中。
在 Linux 上使用脚本安装 Terraform
在这个教程中,我们将学习如何通过脚本在 Linux 机器上安装 Terraform。
准备工作
要完成这个教程,唯一的先决条件是你正在运行 Linux 操作系统,并且已经安装了unzip工具。gpg、curl和shasum工具必须已安装,它们通常在所有 Linux 发行版中默认安装。
操作步骤…
执行以下步骤:
- 打开命令行终端并执行以下脚本:
TERRAFORM_VERSION="0.12.29"
curl -Os https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip \
&& curl -Os https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_SHA256SUMS \
&& curl https://keybase.io/hashicorp/pgp_keys.asc | gpg --import \
&& curl -Os https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_SHA256SUMS.sig \
&& gpg --verify terraform_${TERRAFORM_VERSION}_SHA256SUMS.sig terraform_${TERRAFORM_VERSION}_SHA256SUMS \
&& shasum -a 256 -c terraform_${TERRAFORM_VERSION}_SHA256SUMS 2>&1 | grep "${TERRAFORM_VERSION}_linux_amd64.zip:\sOK" \
&& unzip -o terraform_${TERRAFORM_VERSION}_linux_amd64.zip -d /usr/local/bin
此脚本的源代码也可以在本书的 GitHub 仓库中找到:github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP01/install_terraform_linux.sh
- 执行此脚本后,我们可以通过执行以下命令来检查 Terraform 的安装情况:
terraform --version
前面的命令会显示已安装的 Terraform 版本,这意味着我们可以检查 Terraform 是否正确安装,以及所需的版本是否安装。
工作原理…
在此脚本中,第一行填入了TERRAFORM_VERSION变量,指定我们要安装的 Terraform 版本。这里提到这个变量,是因为我们不想在脚本中反复写出我们正在使用的 Terraform 版本。
在这个教程中,我们使用的是 0.12.29 版本的 Terraform,但我们可以自由修改此版本。
使用curl工具,脚本下载包含 Terraform 二进制文件的 ZIP 文件。然后,脚本检查打包文件的安全完整性,这叫做shasum。
在最后一行,脚本将解压下载的包到本地目录/usr/local/bin,该目录已经在PATH环境变量中默认提到。
你可以通过执行以下命令来检查你安装的 Terraform 版本是否与脚本中提到的版本一致:
terraform --version
该命令显示已安装的 Terraform 版本,如下图所示:

如我们所见,这里我们安装的 Terraform 版本是 0.12.29。
还有更多内容…
在这个 Terraform 安装脚本中,我们已经指定了要安装的 Terraform 版本号。
如果你想安装最新版本而不需要知道版本号,也可以通过以下 API 动态获取最新版本号:checkpoint-api.hashicorp.com/v1/check/terraform。它会获取有关当前 Terraform 版本的信息。
以下截图显示了我们当前的版本:

请注意,current_version属性包含最新的 Terraform 版本,它是一个值。
使用这个 API,我们可以完美地修改安装脚本的第一行,使用以下代码:
TERRAFORM_VERSION=$(curl -s https://checkpoint-api.hashicorp.com/v1/check/terraform | jq -r .current_version)
....
这段完整的安装 Terraform 脚本可在github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP01/install_terraform_linux_v2.sh查看。
这段代码使用curl命令来获取 API 返回的数据,并解析其内容以提取current_version属性的值,使用的是 jq 工具(可在stedolan.github.io/jq/获取)。
此外,terraform --version命令会显示是否已安装最新版本。如果你安装的是旧版本,命令会显示一条消息,提示最新版本:

这里,我们可以看到我们安装的版本是 0.12.28,而最新版本是 0.12.29(在撰写本食谱时)。
最后,HashiCorp 宣布,Terraform 的二进制文件将很快在 Linux 包管理器中提供。欲了解更多信息,请查看以下文章:www.hashicorp.com/blog/announcing-the-hashicorp-linux-repository
另见
如需了解更多关于验证下载包的信息,可以查阅 HashiCorp 文档:www.hashicorp.com/security.html。
在 Windows 上使用脚本安装 Terraform
在本教程中,我们将学习如何使用 Chocolatey 软件包管理器在 Windows 机器上安装 Terraform。
准备就绪
为完成此步骤,您需要使用 Windows 操作系统并安装 Chocolatey(chocolatey.org/),这是一个 Windows 软件包管理器。
如果您尚未安装 Chocolatey,您可以按照以下步骤轻松安装它:
- 以管理员模式打开 PowerShell 终端,如下图所示:

- 然后,在终端中执行以下脚本:
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
完整的 Chocolatey 安装文档可在 chocolatey.org/install 上找到。
如何操作……
执行以下步骤:
-
以管理员模式打开 PowerShell 命令行终端。
-
执行以下命令:
choco install -y terraform
以下截图显示了执行该命令的结果:

-y 选项是可选的。它允许我们自动接受许可证协议。
它是如何工作的……
当 Chocolatey 安装 Terraform 包时,它会执行包源代码中的脚本,源代码可在 github.com/jamestoyer/chocolatey-packages/tree/master/terraform 上找到。
然后,通过执行 github.com/jamestoyer/chocolatey-packages/blob/master/terraform/tools/chocolateyInstall.ps1 中提供的脚本,Chocolatey 将 Terraform ZIP 文件下载到 Chocolatey 包的二进制目录中,该目录已包含在 PATH 环境变量中。
还有更多内容……
在升级 Terraform 时,您可以通过执行 choco upgrade -y terraform 命令直接通过 Chocolatey 升级。
默认情况下,choco install 命令会安装提到的包的最新版本。也可以通过在命令中添加 --version 选项来指定特定版本,例如,在我们的案例中,它将给出如下结果:
choco install -y terraform --version "0.12.28"
在本示例中,我们指定要安装版本 0.12.28 的 Terraform,而不是最新版本。
请注意,Chocolatey 提供的 Terraform 包可能与 Terraform 最新的官方版本存在时间差,而在 Linux 脚本中,如 在 Linux 上安装 Terraform 教程中,您可以指定刚发布的最新版本。
另请参阅
若要了解 Chocolatey 提供的所有命令,我建议阅读以下文档:chocolatey.org/docs/commands-reference#commands
在 Docker 容器中执行 Terraform
在本章的前面几篇配方中,我们讨论了如何在本地安装 Terraform,可以通过手动安装或脚本安装,具体取决于本地操作系统。
在这个配方中,我们将学习如何在 Docker 容器中运行 Terraform,这将使我们能够享受以下好处:
-
无需在本地安装 Terraform。
-
我们可以拥有一个与本地操作系统独立的 Terraform 运行环境。
-
我们可以使用不同版本的 Terraform 来测试我们的 Terraform 配置。
让我们开始吧!
准备就绪
要完成这个配方,你需要了解 Docker 及其命令,以及如何编写 Dockerfiles。请阅读文档以获取更多信息:docs.docker.com/get-started/overview/
在我们的本地计算机上,我们使用名为 Docker Desktop for Windows 的工具安装了 Docker。
关于其他操作系统的 Docker 安装指南,请阅读 Docker 安装文档:docs.docker.com/get-docker/。
我们已经编写了一个 Terraform 配置文件,关于它的细节这里不做介绍。它将在我们的 Docker 容器中执行。
你还需要相应的 Terraform 命令,init、plan 和 apply,这些命令在本配方中不会详细解释。
如何操作…
执行以下步骤:
- 在包含 Terraform 配置的文件夹根目录下,我们需要创建一个包含以下代码的 Dockerfile:
FROMgolang:latest
ENVTERRAFORM_VERSION=0.13.0
RUNapt-get update && apt-get install unzip \
&& curl -Os https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_amd64.zip \
&& curl -Os https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_SHA256SUMS \
&& curl https://keybase.io/hashicorp/pgp_keys.asc | gpg --import \ && curl -Os https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_SHA256SUMS.sig \
&& gpg --verify terraform_${TERRAFORM_VERSION}_SHA256SUMS.sig terraform_${TERRAFORM_VERSION}_SHA256SUMS \
&& shasum -a 256 c terraform_${TERRAFORM_VERSION}_SHA256SUMS 2>&1 | grep "${TERRAFORM_VERSION}_linux_amd64.zip:\sOK" \
&& unzip -o terraform_${TERRAFORM_VERSION}_linux_amd64.zip -d /usr/bin
RUNmkdir /tfcode
COPY. /tfcode
WORKDIR/tfcode
这段源代码也可以在github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP01/terraform-docker/Dockerfile中找到。
- 接下来,我们需要通过在终端中执行
docker build命令来创建一个新的 Docker 镜像:
docker build -t terraform-code:v1.0 .
- 然后,我们需要实例化这个镜像的新容器。为此,我们将执行
docker run命令:
docker run -it -d --name tfapp terraform-code:v1.0 /bin/bash
- 现在,我们可以通过以下命令在容器中执行 Terraform 命令:
docker exec tfapp terraform init
docker exec tfapp terraform plan
docker exec tfapp terraform apply --auto-approve
以下截图显示了执行这些命令(terraform plan)后的一部分输出:

它是如何工作的…
在第 1 步中,我们编写了 Docker 镜像的组成,在 Dockerfile 中进行定义。我们这样做如下:
-
我们使用 Golang 基础镜像。
-
我们通过初始化
TERRAFORM_VERSION变量来指定要安装的 Terraform 版本。 -
我们编写了与在 Linux 上安装 Terraform 配方中相同的 Terraform 安装脚本。
-
我们将 Terraform 配置从本地文件复制到镜像中的新文件夹。
-
我们指定我们的工作空间将是我们的新文件夹。
然后,在第 2 步和第 3 步中,我们创建了一个带有v1.0标签的 Docker terraform-code 镜像。这个标签用于对我们的 Terraform 配置进行版本控制。接着,我们创建了一个名为tfapp的该镜像实例,并使用 bash 工具运行。
最后,在 步骤 4 中,我们在 tfapp 实例中执行 Terraform 命令,操作在我们的容器工作区内进行。
还有更多…
在本教程中,我们学习了如何编写、构建并使用包含 Terraform 二进制文件的 Docker 镜像。通过这种方式,可以将其他工具,如 Terragrunt,集成到镜像中,这些工具也用于开发 Terraform 配置文件。
如果只使用 Terraform,您可以使用 HashiCorp 提供的官方镜像。该镜像是公开的,并且可以在 Docker Hub 上找到,网址为hub.docker.com/r/hashicorp/terraform/。
另见
-
完整的 Docker 命令文档请参见
docs.docker.com/engine/reference/run/。 -
要了解 Docker 的介绍,请参考书籍 Learning DevOps,该书可以在
www.packtpub.com/cloud-networking/learning-devops获取。
在 VS Code 中编写 Terraform 配置
编写 Terraform 配置文件并不需要特殊的代码编辑器。然而,流行的代码编辑器已经适应并提供了插件,简化了编写此类文件的过程。
在本教程中,我们将重点介绍 Visual Studio Code,它具有以下优点:
-
它是跨平台的,这意味着可以在 Windows、Linux 和 macOS 上安装。
-
这是免费的。
-
它拥有大量扩展,涵盖了开发人员日常工作的所有需求。
在这个教程中,我们将学习如何配置 Visual Studio Code,以便编写 Terraform 配置文件。我们还将看到这种方式编写代码的速度有多快。
准备就绪
对于本教程,您需要在本地机器上安装 Visual Studio Code。您可以通过访问code.visualstudio.com/来安装它。
如何操作…
要在 Visual Studio Code 中使用 Terraform,我们需要安装相应的扩展并进行配置。
要安装此扩展,请执行以下步骤:
- 打开 Visual Studio Code,点击扩展标签。此标签可以在编辑器左侧的侧边栏中找到,如下图所示:

-
然后,我们使用
Terraform关键字搜索扩展。 -
安装列表中的第一个扩展,名为 Terraform。它是由 HashiCorp 发布的。通过点击扩展的安装按钮进行安装:

- 重新加载 Visual Studio Code 以应用扩展。
它是如何工作的…
在本教程的第一部分,我们为 Visual Studio Code 安装了 Terraform 扩展。
在搜索 Terraform 扩展时,可能会出现多个选项,但我认为我们选择的这个是最强大的之一。
安装完成后,该扩展提供了许多编辑 Terraform 配置的功能,例如自动补全、配置验证、tflint 语法、代码格式化、官方文档链接以及模块浏览器。
该组件使 Visual Studio Code 在使用 Terraform 0.12 时表现更好——至少在扩展功能方面是这样的。
还有更多…
安装并配置扩展后,我们可以在 main.tf 文件中编写 Terraform 配置。在这里,我们可以使用一些非常有用的功能来开发 Terraform 配置,其中一些功能如下:
- 语法高亮:

- 资源和属性的自动补全:

- 实时代码验证:

- 显示引用次数的功能,并提供一个链接查看这些引用:

另请参见
-
若要查看该扩展的完整功能列表,请参考以下文档:
marketplace.visualstudio.com/items?itemName=HashiCorp.terraform,以及其更新日志:marketplace.visualstudio.com/items/HashiCorp.terraform/changelog. -
所有与 Terraform 相关的 Visual Studio Code 扩展可以在以下网址找到:
marketplace.visualstudio.com/search?term=terraform&target=VSCode&category=All%20categories&sortBy=Relevance。 -
要了解 HashiCorp 为此扩展提供的支持,请访问:
www.hashicorp.com/blog/supporting-the-hashicorp-terraform-extension-for-visual-studio-code/。
将您的 Terraform 配置迁移到 Terraform 0.13
Terraform 0.12 版本于 2019 年 5 月正式发布,带来了许多新特性,也有一些变化;而在 2020 年夏季,新的 Terraform 版本 0.13 发布,提供了新功能并进行了一些变化。
在我们升级代码之前,必须考虑这些变更。
在本教程中,我们将讨论如何验证我们的 Terraform 配置是否与版本 0.12 兼容。之后,我们将学习如何将 Terraform 配置从版本 0.11 迁移到版本 0.12,再迁移到版本 0.13。
准备工作
在将代码从版本 0.11 迁移到最新版本(当前为 0.13)之前,您需要确保代码可以与 Terraform 0.11 的最新版本兼容,即 0.11.14。您可以从releases.hashicorp.com/terraform/0.11.14/下载此版本。
需要知道的是,如果你的 Terraform 配置是版本 0.11,无法直接迁移到 0.13。你必须先升级到 0.12,然后再迁移到 0.13。
此外,在进行任何迁移之前,强烈建议阅读 HashiCorp 提供的升级文档(这里是 0.12 版本的文档,www.terraform.io/upgrade-guides/0-12.html,这是 0.13 版本的文档,github.com/hashicorp/terraform/blob/master/website/upgrade-guides/0-13.html.markdown)。这是因为在升级过程中,正如我们在本食谱中所学到的,许多元素会自动迁移,但其他的则需要手动迁移。
最后,HashiCorp 还建议,在执行迁移过程之前,将代码提交到源代码管理器(例如 Git),以便能够查看迁移所带来的代码变化。
用于本食谱的代码源(版本 0.11)可在此处获取:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP01/tf011
如何操作…
将 Terraform 配置从版本 0.11 升级到 0.13 分为两步,首先必须将代码迁移到 0.12,然后再迁移到 0.13。
要将我们的 Terraform 配置从版本 0.11 迁移到版本 0.12,请执行以下步骤:
在将此配置迁移到版本 0.12 之前,我们必须验证它是否与该版本兼容。为此,请按照以下步骤操作:
- 使用 Terraform 0.11.14,在终端中执行以下命令:
terraform 0.12checklist
以下截图显示了执行前述命令后的输出:

如我们所见,我们的 Terraform 配置与 Terraform 版本 0.12 兼容。现在,我们可以进行迁移。
-
接下来,我们需要手动安装 Terraform 0.12 的最新版本,如手动下载并安装 Terraform食谱中所述。根据我们的操作系统,也可以通过脚本来安装,如在 Linux 上安装 Terraform和在 Windows 上使用脚本安装 Terraform食谱中所示。
-
在包含我们代码的文件夹中,我们执行
init命令:
terraform init
- 然后,我们执行以下命令:
terraform 0.12upgrade
- 最后,在提示输入值时,我们通过回答
yes来确认迁移,如下图所示:

然后,按照以下步骤将我们的 Terraform 配置从 0.12 版本迁移到 0.13 版本:
-
下载并安装最新版本的 Terraform 0.13。
-
与 0.12 完全一样,运行
terraform 0.13upgrade命令将配置升级到 0.13:

它是如何工作的…
在第 1 步中,我们验证了我们的 Terraform 配置与 Terraform 0.12 中包含的语言演进(HCL 2)兼容。
然后,我们在本地安装了 Terraform 0.12,并通过执行terraform init命令开始迁移过程,该命令用于下载我们代码中将要调用的不同提供程序。
我们使用terraform 0.12upgrade命令将 Terraform 配置迁移到 0.12 版本,该命令直接升级 Terraform 配置。
最后,若要将 Terraform 配置升级到 0.13 版本,我们安装了 Terraform 0.13 二进制文件并执行了terraform 0.13upgrade命令。
还有更多内容…
请注意,迁移过程仅更改当前的 Terraform 配置。如果我们的 Terraform 配置调用了模块,则需要事先迁移模块的代码。
另请参见
有关 Terraform 迁移到 0.12 版本的程序的更多信息,请参考技术文档:www.terraform.io/upgrade-guides/0-12.html。
若想了解 Terraform 的演进及此新大版本中所做的更改,请查看以下文档和相关文章:
最后,以下是 Terraform 的官方仓库:github.com/hashicorp/terraform-guides/tree/master/infrastructure-as-code/terraform-0-12-examples。其中包含多个 Terraform 0.12 的代码示例,我们将在本书中逐一讲解。
关于 Terraform 0.13,升级指南可以在这里找到 – github.com/hashicorp/terraform/blob/master/website/upgrade-guides/0-13.html.markdown,变更日志可以在这里找到 – github.com/hashicorp/terraform/blob/master/CHANGELOG.md。
编写 Terraform 配置
当你开始编写 Terraform 配置时,你会很快注意到 Terraform 提供的语言非常丰富,可以进行大量操作。
在本章的配方中,你将学习如何有效地使用 Terraform 语言,并将其应用于现实生活中的商业场景。我们将讨论如何指定要使用的提供者版本,以及如何通过变量和输出使代码更加动态。然后,我们将使用这些概念用 Terraform 配置多个环境。之后,我们将考虑如何使用函数和条件。
我们还将学习如何使用数据块、其他 Terraform 状态文件和外部资源从外部系统中检索数据。最后,我们将介绍如何使用 Terraform 进行本地操作,例如运行本地可执行文件和操作本地文件。
在本章中,我们将介绍以下配方:
-
配置 Terraform 和要使用的提供者版本
-
操作变量
-
为自定义函数使用本地变量
-
使用输出暴露 Terraform 配置的数据
-
在多个环境中配置基础设施
-
使用数据源获取外部数据
-
使用其他状态文件中的外部资源
-
使用 Terraform 查询外部数据
-
调用 Terraform 内置函数
-
编写条件表达式
-
使用 Terraform 操作本地文件
-
使用 Terraform 执行本地程序
-
使用 Terraform 生成密码
让我们开始吧!
第三章:技术要求
本章需要你在计算机上安装 Terraform 二进制文件。本章的源代码可以在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP02找到。
查看以下视频,查看代码的实际运行:bit.ly/3hcZNVR
配置 Terraform 和要使用的提供者版本
Terraform 的默认行为是,当执行terraform init命令时,使用的 Terraform 二进制版本(我们称之为命令行界面(CLI),如这里解释的:www.terraform.io/docs/glossary.html#cli)是安装在本地工作站上的版本。此外,该命令还会下载代码中使用的最新版本的提供者。
然而,出于兼容性考虑,始终建议避免意外情况,因此你可以在 Terraform 配置文件中指定将要使用的 Terraform 二进制版本。以下是一些示例:
-
使用 HCL 2 编写的 Terraform 配置文件必须表明它必须使用 Terraform 版本大于或等于 0.12 进行执行。
-
包含新功能(如模块中的
count和for_each)的 Terraform 配置必须表明它必须使用 Terraform 版本大于或等于 0.13 进行执行。
有关 HCL 语法的更多详细信息,请阅读文档:www.terraform.io/docs/configuration/syntax.html。
同理,为了兼容性,我们可能希望指定要使用的提供程序版本。
在这个教程中,我们将学习如何指定 Terraform 版本以及提供程序版本。
准备工作
要开始这个教程,我们将编写一个包含以下代码的基础 Terraform 配置文件:
variable "resource_group_name" {
default = "rg_test"
}
resource "azurerm_resource_group" "rg" {
name = var.resource_group_name
location = "West Europe"
}
resource "azurerm_public_ip" "pip" {
name = "bookip"
location = "West Europe"
resource_group_name = azurerm_resource_group.rg.name
public_ip_address_allocation = "Dynamic"
domain_name_label = "bookdevops"
}
这个示例代码提供了 Azure 中的资源(一个资源组和一个公共 IP 地址)。有关更多详细信息,请阅读以下关于 Terraform AzureRM 提供程序的文档:www.terraform.io/docs/providers/azurerm/index.html
此外,这段代码包含了自 Terraform 0.12 以来对 HCL 2.0 语言所做的改进。有关这些 HCL 增强的更多详细信息,请访问 www.slideshare.net/mitchp/terraform-012-deep-dive-hcl-20-for-infrastructure-as-code-remote-plan-apply-125837028。
最后,当在此代码中执行terraform plan命令时,我们会收到以下警告信息:

这意味着,目前这个 Terraform 配置仍然与提供程序的最新版本兼容,但在未来的版本中,该属性将发生更改,因此这段代码将不再有效。
现在,让我们讨论一下我们需要遵循的步骤,以便做出以下合规性:
-
只有在本地计算机上安装了至少 Terraform 0.13 版本时,才能执行此配置。
-
即使
azurerm提供程序发生破坏性更改,我们当前的配置也可以执行。
关于 Terraform 0.13 提供的新功能,请在此查看变更日志 – github.com/hashicorp/terraform/blob/master/CHANGELOG.md,并在此查看升级指南 – github.com/hashicorp/terraform/blob/master/website/upgrade-guides/0-13.html.markdown。
接下来我们将查看这个内容。
如何操作……
要指定在本地工作站上安装的 Terraform 版本,请执行以下操作:
- 在 Terraform 配置中,添加以下块:
terraform {
required_version = ">= 0.13"
}
- 要指定要使用的提供程序源和版本,我们需要在同一个
terraform配置块中添加required_provider块:
terraform {
...
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "2.10.0"
}
}
}
它是如何工作的……
当执行terraform init命令时,Terraform 会检查执行 Terraform 配置文件的已安装 Terraform 二进制文件版本是否与terraform块中required_version属性指定的版本相对应。
如果匹配,它不会抛出错误,因为它大于版本 0.13\。否则,它将抛出错误:

关于提供程序版本的指定,执行 terraform init 命令时,如果没有指定版本,Terraform 将下载提供程序的最新版本,否则将下载指定版本,如下图所示。
以下截图显示了提供程序插件从指定的 source 被下载,而我们并未指定所需的版本(截至撰写时,提供程序的最新版本为 2.20.0):

如我们所见,已下载最新版本的 azurerm 提供程序(2.20.0)。
此外,以下截图显示了在我们指定所需版本(2.10.0)时,azurerm 提供程序插件正在被下载:

如我们所见,已下载指定版本的 azurerm 提供程序(2.10.0)。
有关 required_version 块和提供程序版本的更多细节,请访问 www.terraform.io/docs/configuration/terraform.html#specifying-required-provider-versions。
在这个 required_version 块中,我们还添加了 source 属性,它是在 Terraform 0.13 版本中引入的,相关文档可以在这里找到:github.com/hashicorp/terraform/blob/master/website/upgrade-guides/0-13.html.markdown#explicit-provider-source-locations
还有更多…
在本食谱中,我们学习了如何以多种方式下载 azurerm 提供程序。我们在这里做的适用于你可能想要下载的所有提供程序。
还需要提到的是,使用的 Terraform 二进制版本在 Terraform 状态文件中指定。这是为了确保没有人使用较低版本的 Terraform 二进制文件应用此 Terraform 配置,从而确保 Terraform 状态文件的格式与正确版本的 Terraform 二进制文件一致。
另见
-
关于 Terraform 块属性的更多信息,请访问
www.terraform.io/docs/configuration/terraform.html。 -
关于提供程序属性的更多信息,请访问
www.terraform.io/docs/configuration/providers.html。 -
关于 Terraform 二进制版本管理的更多信息,请参考
www.terraform.io/docs/extend/best-practices/versioning.html。 -
azurerm提供程序的升级指南(至版本 2.0)可以通过以下链接访问:www.terraform.io/docs/providers/azurerm/guides/2.0-upgrade-guide.html。
操作变量
当你编写一个所有属性都硬编码在代码中的 Terraform 配置文件时,你常常会遇到需要重复编写代码以便重用的问题。
在本教程中,我们将学习如何通过使用变量使 Terraform 配置更加动态。
准备工作
首先,我们将处理main.tf文件,该文件包含一个基本的 Terraform 配置:
resource "azurerm_resource_group" "rg" {
name = "My-RG"
location = "West Europe"
}
如我们所见,name和location属性的值在代码中是以静态方式写入的。
让我们学习如何使用变量使它们动态化。
如何操作……
执行以下步骤:
- 在同一个
main.tf文件中,添加以下变量声明:
variable "resource_group_name" {
description ="The name of the resource group"
}
variable "location" {
description ="The name of the Azure location"
default ="West Europe"
}
- 然后,修改本教程开始时的 Terraform 配置,使其引用我们的新变量,如下所示:
resource "azurerm_resource_group" "rg" {
name = var.resource_group_name
location = var.location
}
- 最后,在包含
main.tf文件的相同文件夹中,创建一个名为terraform.tfvars的新文件,并添加以下内容:
resource_group_name = "My-RG"
location = "westeurope"
工作原理……
在第 1 步中,我们编写了两个变量的声明,其中包含以下元素:
-
变量名称:此名称必须在该 Terraform 配置中唯一,并且必须足够明确,以便所有代码贡献者都能理解。
-
该变量表示的内容描述:此描述是可选的,但建议提供,因为它可以通过 CLI 显示,并且可以集成到自动生成的文档中。
-
默认值:这是可选的。不设置默认值会使其变为必填项。
然后,在第 2 步中,我们修改了 Terraform 配置,使用这两个变量。我们通过var.<变量名>语法完成了这一操作。
最后,在第 3 步中,我们在terraform.tfvars文件中为这些变量赋值,该文件由 Terraform 原生使用。
执行此 Terraform 配置后的结果显示在以下截图中:

还有更多……
在terraform.tfvars文件中设置变量的值是可选的,因为我们已经为该变量设置了默认值。
除了这个terraform.tfvars文件,还可以通过terraform plan和terraform apply命令的-var选项为变量设置值,如下命令所示:
terraform plan -var "location=westus"
因此,使用此命令时,我们代码中声明的location变量将具有westus的值,而不是westeurope。
此外,随着 Terraform 2020 年 8 月发布的 0.13 版本,我们现在可以为变量创建自定义验证规则,这使我们能够在执行terraform plan时验证值。
在我们的示例中,我们可以在 validation 块中使用如下代码完成 location 变量的验证规则:
variable "location" {
description ="The name of the Azure location"
default ="West Europe"
validation { # TF 0.13
condition = can(index(["westeurope","westus"], var.location) >= 0)
error_message = "The location must be westeurope or westus."
}
}
在前述配置中,规则检查了 location 变量的值是否为 westeurope 或 westus。
如果我们将 location 变量设置为 westus2 等其他值,则下面的截图显示了 terraform plan 命令的执行:

有关变量自定义规则验证的更多信息,请阅读文档:www.terraform.io/docs/configuration/variables.html#custom-validation-rules。
最后,还有另一种为变量设置值的方法,即设置名为 TF_VAR_<变量名> 的环境变量。在我们的案例中,我们可以创建一个名为 TF_VAR_location 的环境变量,并设置其值为 westus,然后以经典方式执行 terraform plan 命令。
请注意,使用 -var 选项或 TF_VAR_<变量名> 环境变量并不会将这些变量的值硬编码到 Terraform 配置中。它们使我们能够将变量的值传递给工具。但是要注意,如果在参数中提供的初始值与执行代码时提供的其他值不符,则可能会产生后果,应仔细检查计划的输出。
另请参阅
在这个示例中,我们查看了变量的基本使用。当我们学习如何管理环境时,在本章后面的在多个环境中管理基础设施一节中,我们将探讨其更高级的用法。
有关变量的更多信息,请参阅此处的文档:www.terraform.io/docs/configuration/variables.html
使用本地变量进行自定义函数
在前一个示例中,我们学习了如何使用变量来动态配置我们的 Terraform。有时,当涉及到变量的组合使用时,这种使用可能会有点繁琐。
在这个示例中,我们将学习如何实现本地变量并将它们用作自定义函数。
准备工作
首先,我们将使用以下 Terraform 配置:
variable "application_name" {
description = "The name of application"
}
variable "environment_name" {
description = "The name of environment"
}
variable "country_code" {
description = "The country code (FR-US-...)"
}
resource "azurerm_resource_group" "rg" {
name = "XXXX" # VARIABLE TO USE
location = "West Europe"
}
resource "azurerm_public_ip" "pip" {
name = "XXXX" # VARIABLE TO USE
location = "West Europe"
resource_group_name = azurerm_resource_group.rg.name
allocation_method = "Dynamic"
domain_name_label = "mydomain"
}
本示例的目标是始终渲染 Azure 资源的名称。我们必须为它们提供以下命名规则:
CodeAzureResource - Name Application - Environment name - Country Code
如何操作…
执行以下步骤:
- 在包含我们的 Terraform 配置的
main.tf文件中,我们将添加一个名为resource_name的本地变量,以及以下代码:
locals {
resource_name = "${var.application_name}-${var.environment_name}-${var.country_code}"
}
- 然后,我们在资源中使用这个本地变量,代码如下:
resource "azurerm_resource_group" "rg" {
name = "RG-${local.resource_name}"
location = "West Europe"
}
resource "azurerm_public_ip" "pip" {
name = "IP-${local.resource_name}"
location = "West Europe"
resource_group_name = azurerm_resource_group.rg.name
public_ip_address_allocation = "Dynamic"
domain_name_label = "mydomain"
}
如何运作…
在第 1 步中,我们创建了一个名为 resource_name 的变量,它是局部于我们 Terraform 配置的。这使我们能够创建多个 Terraform 变量的组合(我们将在本章的使用输出暴露 Terraform 配置数据这一部分看到结果)。
然后,在第 2 步中,我们使用了带有 local.<局部变量名称> 表达式的局部变量。此外,在 name 属性中,我们将其用作变量和静态文本的拼接,这就是为什么我们使用了 "${}" 语法。
执行此 Terraform 配置的结果如下:

在之前的截图中,我们可以看到执行 terraform plan 命令后的输出,其中包含我们通过 locals 变量计算得出的资源组的 name。
另见
有关局部变量的更多信息,请查看以下文档:www.terraform.io/docs/configuration/locals.html
使用输出暴露 Terraform 配置数据
在使用诸如 Terraform 之类的基础设施即代码工具时,通常需要在代码执行后从已配置的资源中检索输出值。
这些输出值的一个用途是,它们可以在执行后由其他程序使用。这通常发生在 Terraform 配置的执行集成到 CI/CD 流水线时。
例如,我们可以在 CI/CD 流水线中使用这些输出值,该流水线使用 Terraform 创建 Azure App Service,并将应用程序部署到该 Azure App Service 中。在这个例子中,我们可以将 App Service(Web 应用类型)的名称作为 Terraform 配置的输出。对于通过模块传递信息,这些输出值也非常有用,我们将在第五章《使用模块共享 Terraform 配置》中详细了解。
在本食谱中,我们将学习如何在 Terraform 配置中实现输出值。
准备工作
接下来,我们将添加一些我们已经在现有的 main.tf 文件中的 Terraform 配置。
以下是现有代码的摘录,该代码在 Azure 中提供一个 App Service:
...
resource "azurerm_app_service" "app" {
name = "${var.app_name}-${var.environment}"
location = azurerm_resource_group.rg-app.location
resource_group_name = azurerm_resource_group.rg-app.name
app_service_plan_id = azurerm_app_service_plan.plan-app.id
}
...
如何操作…
为确保我们有一个输出值,我们只需将以下代码添加到此 main.tf 文件中:
output "webapp_name" {
description = "output Name of the webapp"
value = azurerm_app_service.app.name
}
它是如何工作的…
Terraform 的 output 块通过一个名称 webapp_name 和一个值 azurerm_app_service.app.name 来定义。这些引用了在同一个 Terraform 配置中提供的 Azure App Service 的名称。可选地,我们可以添加一个 description 来描述输出返回的内容,这对于自动生成文档或在使用模块时非常有用。
当然,可以在同一个 Terraform 配置中定义多个输出。
输出存储在 Terraform 状态文件中,并在执行 terraform apply 命令时显示,如下图所示:

在这里,我们看到执行结束时显示的两个输出值。
还有更多内容…
有两种方法可以检索输出的值以便利用它们,如下所示:
-
通过使用 Terraform CLI 中的
terraform output命令,我们将在第四章的 以 JSON 导出输出 章节中看到,使用 Terraform CLI -
通过使用
terraform_remote_state数据源对象,我们将在本章稍后的 使用其他状态文件中的外部资源 章节中讨论。
另见
Terraform 输出的文档可在 www.terraform.io/docs/configuration/outputs.html 上查看。
在多个环境中配置基础设施
就像我们将应用程序部署到多个环境(开发、测试、QA 和生产环境)一样,我们也需要在这些不同的环境中配置基础设施。
一个常见的问题是如何编写一个可维护、可扩展的 Terraform 配置,使我们能够为多个环境配置基础设施。
要回答这个问题,重要的是要知道,有多种方法可以组织 Terraform 配置拓扑,这些方法能够支持此类资源的配置。
在这个教程中,我们将查看两种 Terraform 配置结构拓扑,它们可以帮助我们将 Azure 基础设施部署到多个环境中。
准备工作
要完全理解这个教程,你需要对变量的概念有深入的了解,正如本章中操作变量的教程所讨论的那样。
我们将要编写的 Terraform 配置的目标是为单一环境部署 Azure 应用服务。其代码分布在以下文件中:

在前面的图中,我们可以看到以下内容:
-
main.tf文件包含要配置的资源的 Terraform 配置。 -
variables.tf文件包含变量的声明。 -
terraform.tfvars文件包含变量的值。
这个基础示例的 Terraform 源代码可以在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP02/myApp/simple-env 上查看。
这个教程中重要的不是代码的内容,而是文件夹结构和需要执行的 Terraform 命令。
如何实现…
按照以下步骤来实现第一个 Terraform 配置文件夹拓扑:
-
在一个空文件夹中,为每个环境创建一个独立的目录:一个用于开发环境,一个用于测试环境,一个用于 QA,另一个用于生产环境。
-
将 Terraform 基础配置文件相同地复制到这些目录中。
-
然后,在这些目录中的每个目录中,修改
terraform.tfvars文件的值,填写特定于环境的信息。以下是每个terraform.tfvars文件的摘录:
resource_group_name = "RG-App"
service_plan_name = "Plan-App"
environment = "DEV" #name of the environment to change
- 最后,为了配置每个环境,在这些目录中的每个目录下,执行经典的 Terraform 执行工作流,通过运行
terraform init、terraform plan和terraform apply命令。
按照以下步骤实施 Terraform 配置文件的第二种拓扑结构:
-
在包含我们基本 Terraform 配置的文件夹中,创建三个子目录:
dev、test和production。 -
然后,在这些子目录中,只需复制
terraform.tfvars基础文件,在其中修改目标环境的正确变量值。以下是每个terraform.tfvars文件的摘录:
resource_group_name = "RG-App"
service_plan_name = "Plan-App"
environment = "DEV" #name of the environment to change
- 最后,为了配置这些环境,进入 Terraform 配置的根文件夹并执行以下命令:
terraform init
terraform plan -var-file="<environment folder>/terraform.tfvars"
terraform apply -var-file="<environment folder>/terraform.tfvars"
它是如何工作的……
在第一种拓扑中,我们为每个环境重复相同的 Terraform 配置,只需更改每个文件夹中 terraform.tfvars 文件的变量值。
通过这样做,我们得到了以下文件夹结构:

然后,执行基本的 Terraform 命令。此结构可用于基础设施在各个环境中没有相同资源的情况。因为将所有 Terraform 配置复制到每个环境文件夹中,可以轻松地为一个环境添加或删除资源,而不会影响其他环境。
然而,这是重复的代码,这意味着这些代码必须多次维护(我们必须修改所有环境的基础设施,改变 Terraform 配置等)。
在第二种拓扑中,我们将 Terraform 配置保留在所有环境的公共基础中,每个环境只有一个 terraform.tfvars 文件。通过这种方式,我们得到了以下文件夹结构:

关于 Terraform 配置的执行,我们在 plan 和 apply 命令中添加了 -var-file 选项。如果所有环境的基础设施相同,仅配置不同,可以使用此结构。
这种拓扑的优点是,我们只有一份公共的 Terraform 资源代码(在 main.tf 和 variables.tf 文件中),并且只有一个 terraform.tfvars 文件需要填写,因此在代码演变或新增环境时,我们只需做少量更改。
另一方面,对 Terraform main.tf 代码所做的更改将应用于所有环境,在这种情况下,需要更多的测试和验证。
另见
-
还有其他解决方案来处理 Terraform 配置文件夹结构拓扑,正如我们在第五章中讨论的,使用模块共享 Terraform 配置。
-
关于
plan和apply命令的-var-file选项的文档可以在www.terraform.io/docs/commands/plan.html找到。 -
一篇解释 Terraform 配置最佳实践的文章可以在
www.terraform-best-practices.com/code-structure找到。 -
以下博客文章解释了生产环境中 Terraform 配置的文件夹结构:
www.hashicorp.com/blog/structuring-hashicorp-terraform-configuration-for-production
使用数据源获取外部数据
当使用 Terraform 配置基础设施时,有时需要获取已有资源的信息。实际上,在将资源部署到某个基础设施时,我们常常需要将自己置于现有基础设施中,或者将其与已经配置的其他资源链接起来。
在这个食谱中,我们将学习如何在我们的 Terraform 配置中获取基础设施中已经存在的资源信息。
准备工作
对于这个食谱,我们将使用一个现有的 Terraform 配置,它在 Azure 云中提供一个 Azure 应用服务。这个源代码可以在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP02/data找到。
这段代码是不完整的,因为在这个项目中,我们需要将应用服务存储在一个现有的服务计划中。这个服务计划是我们将为整个应用服务使用的。
如何操作……
执行以下步骤:
- 在包含 Terraform 配置的文件中,添加以下数据块:
data "azurerm_app_service_plan" "myplan" {
name = "app-service-plan"
resource_group_name = "rg-service_plan"
}
在属性部分中,指定要使用的服务计划的名称和资源组。
- 然后,完成现有应用服务配置,如下所示:
resource "azurerm_app_service" "app" {
name = "${var.app_name}-${var.environement}"
location = azurerm_resource_group.rg-app.location
resource_group_name = azurerm_resource_group.rg-app.name
app_service_plan_id = data.azurerm_app_service_plan.myplan.id
}
它是如何工作的……
在步骤 1中,添加一个数据块来查询现有资源。在此数据块中,我们指定了现有服务计划的资源组和名称。
在步骤 2中,我们使用了通过在步骤 1中添加的数据块获取的服务计划 ID。
执行此 Terraform 配置的结果可以在以下截图中看到:

正如我们所看到的,我们得到了通过data块获取的服务计划 ID。
还有更多内容……
使用数据块的有趣之处在于,当我们在 Terraform 配置上执行terraform destroy命令时,Terraform 不会对数据块调用的资源执行销毁操作。
此外,推荐使用数据块,而不是在代码中写明的 ID,因为 ID 可能会变化,而数据块能够动态地恢复信息。
最后,数据块在执行terraform plan命令时也会被调用,因此在执行terraform plan和terraform apply命令之前,您的外部资源必须已经存在。
如果此外部资源尚未存在,我们会在terraform plan命令中遇到以下错误:

你需要知道在你的 Terraform 配置中使用哪些提供程序,因为并不是所有的提供程序都实现了数据块。
另见
要了解有关数据块的更多信息,请查看以下文档:www.terraform.io/docs/configuration/data-sources.html
从其他状态文件中使用外部资源
在前一个食谱中,我们看到可以使用数据块检索基础设施中已存在资源的信息。
在本食谱中,我们将学习也可以从其他 Terraform 状态文件中检索外部信息。
准备工作
对于本食谱,我们将像前一个食谱一样,使用一个 Terraform 配置来配置一个 Azure App Service,该服务必须是已经配置好的 Service Plan 的一部分。
与前一个食谱不同,我们将不使用单独的数据源;相反,我们将从用于配置 Service Plan 的现有 Terraform 状态文件中读取输出。
作为前提条件,在用于配置 Service Plan 的 Terraform 配置中,我们必须有一个输出值(请参见本章中的使用输出暴露 Terraform 配置的数据一节),该输出返回 Service Plan 的标识符,如下代码所示:
resource "azurerm_app_service_plan" "plan-app" {
name = "MyServicePlan"
location = "westeurope"
resource_group_name = "myrg"
sku {
tier = "Standard"
size = "S1"
}
}
output "service_plan_id" {
description = "output Id of the service plan"
value = azurerm_app_service_plan.plan-app.id
}
此外,我们使用了 Azure Storage 的远程后端版本(有关更多信息,请参见第六章中的保护 Azure 远程后端中的状态文件一节,以及使用 Terraform 配置 Azure 基础设施的食谱)来存储 Service Plan 的 Terraform 状态文件。
如何执行...
执行以下步骤:
- 在提供 Azure App Service 的 Terraform 配置中,添加并配置
terraform_remote_state块,如下所示:
data "terraform_remote_state" "service_plan_tfstate" {
backend = "azurerm"
config = {
resource_group_name = "rg_tfstate"
storage_account_name = "storstate"
container_name = "tfbackends"
key = "serviceplan.tfstate"
}
}
- 然后,在 Azure App Service 的 Terraform 配置中,使用 Service Plan 的创建输出,如下所示:
resource "azurerm_app_service" "app" {
name = "${var.app_name}-${var.environement}"
location = azurerm_resource_group.rg-app.location
resource_group_name = azurerm_resource_group.rg-app.name
app_service_plan_id = data.terraform_remote_state.service_plan_tfstate.service_plan_id
}
它是如何工作的...
在步骤 1中,我们添加了terraform_remote_state块,它允许我们检索另一个 Terraform 状态文件中存在的输出。在其块中,我们指定了远程后端信息,这是给定 Terraform 状态存储的位置(在本食谱中,我们使用了 Azure Storage)。
在步骤 2中,我们使用了 Terraform 状态文件中输出返回的 ID。
执行此代码的结果与我们在 使用数据块调用外部资源 方案中看到的完全相同。
还有更多…
当将部署复杂基础设施的 Terraform 配置进行分离时,这个技巧非常实用。
将 Terraform 配置分离是一个良好的实践,因为它可以更好地控制和维护 Terraform 配置。它还允许我们单独配置每个部分,而不会影响其他基础设施。
要知道何时使用数据块或 terraform_remote_state 块,必须记住以下建议:
-
data块用于以下情况:-
当外部资源未通过 Terraform 配置进行配置(它是手动构建的或通过脚本构建的)
-
当提供我们 Terraform 配置资源的用户无法访问另一个远程后端时
-
-
terraform_remote_state块用于以下情况:-
外部资源未通过 Terraform 配置进行配置
-
当提供我们 Terraform 配置资源的用户拥有对另一个远程后端的读取权限时
-
当外部 Terraform 状态文件包含我们在 Terraform 配置中所需的属性输出时
-
另请参见
terraform_remote_state 块的文档可以在 www.terraform.io/docs/providers/terraform/d/remote_state.html 找到。
使用 Terraform 查询外部数据
在前面两种方案中,我们学习了可以使用 data 块或 terraform_remote_state 块来获取外部数据。然而,也有一些场景,data 块在提供程序中不存在,或者 terraform_remote_state 无法使用,例如当我们需要与外部 API 进行处理,或者需要使用本地工具并处理其输出时。
为了满足这一需求,Terraform 中有一个 external 资源,它允许你调用外部程序并获取其输出数据,以便在 Terraform 配置中使用。
使用 external 提供程序会有一些前提条件,这些前提条件可能不明显(例如,在此案例中,我们期望特定版本的 PowerShell),或者可能只能通过 README 文件或文档进行传达。此外,Terraform 通常设计为跨平台(操作系统/架构)工作,但这实际上将配置限制为可以(并且确实可以)运行 PowerShell 的特定平台——大概仅限于 Windows。这些要求适用于 CI 和本地环境。
在这个方案中,我们将学习如何调用外部程序并获取其输出,以便我们可以重用它。
准备工作
对于这个方案,我们将使用一个现有的 Terraform 配置,它允许我们在 Azure 中配置一个资源组。
在这里,我们希望资源组位于不同的 Azure 区域(位置),具体取决于环境(开发环境或生产环境)。
本配方的源代码可在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP02/external获取。
如何操作…
执行以下步骤:
- 在包含
main.tf文件的目录中,创建一个 PowerShell 脚本GetLocation.ps1,内容如下:
# Read the JSON payload from stdin
$jsonpayload = [Console]::In.ReadLine()
# Convert JSON to a string
$json = ConvertFrom-Json $jsonpayload
$environment = $json.environment
if($environment -eq "Production"){
$location="westeurope"
}else{
$location="westus"
}
# Write output to stdout
Write-Output "{ ""location"" : ""$location""}"
- 在
main.tf文件中,添加external块,内容如下:
data "external" "getlocation" {
program = ["Powershell.exe", "./GetLocation.ps1"]
query = {
environment = "${var.environment_name}"
}
}
- 然后,修改资源组的代码,使其位置更加动态,如下所示:
resource "azurerm_resource_group" "rg" {
name = "RG-${local.resource_name}"
location = data.external.getlocation.result.location
}
- 可选地,你可以添加一个
output值,其配置如下:
output "locationname" {
value = data.external.getlocation.result.location
}
它是如何工作的…
在第 1 步中,我们编写了 PowerShell 脚本GetLocation.ps1,该脚本将在本地由 Terraform 调用。此脚本接受environment作为输入参数,格式为 JSON。然后,PowerShell 脚本根据此输入环境做出条件判断,并返回正确的 Azure 区域作为输出,以便我们在 Terraform 配置中使用它。
然后,在第 2 步中,我们使用了 Terraform 的external资源,它调用这个 PowerShell 脚本并将environment_name变量的内容作为参数提供给它。
最后,在第 3 步中,我们将external块的返回值用于资源组的location属性。
以下截图显示了执行terraform plan命令时,environment_name变量设置为Dev的输出:

如你所见,资源组的区域位置是westus。
以下截图显示了执行terraform plan命令时,environment_name变量设置为Production的输出:

如你所见,资源组的区域位置是westeurope。
正如我们在操作变量一节中看到的那样,在这个示例中,我们使用了terraform plan命令的-var选项,这允许我们在执行命令时为变量分配一个值。
可选地,我们还可以添加一个 Terraform output,以暴露此值。在执行 Terraform 时,这个值可以显示出来。它还可以在 Terraform 配置中的其他地方使用。
以下截图显示了运行terraform apply命令后的输出:

如我们所见,terraform output命令显示了正确的locationname值。
还有更多…
在这个配方中,我们使用了 PowerShell 脚本,但该脚本同样适用于本地机器上安装的所有其他脚本语言和工具。
这个external资源包含了关于协议、参数格式及其输出的详细信息。我建议你阅读它的文档以了解更多:www.terraform.io/docs/providers/external/data_source.html
另见
以下是一些关于如何使用external Terraform 资源的示例文章:
-
dzone.com/articles/lets-play-with-terraform-external-provider -
thegrayzone.co.uk/blog/2017/03/external-terraform-provider-powershell/
调用 Terraform 内置函数
在使用 Terraform 进行基础设施配置或处理资源时,有时需要使用变换或组合 Terraform 配置中提供的元素。
为此,Terraform 自带的语言(HCL2)包含了一些内置函数,可以在任何 Terraform 配置中使用。
在这个食谱中,我们将讨论如何使用内置函数对代码应用变换。
准备就绪
为了完成这个食谱,我们将从零开始配置 Terraform,用于在 Azure 中配置一个资源组。这个资源组将按照以下命名约定进行命名:
RG-<APP NAME>-<ENVIRONMENT>
这个名称应该完全是大写的。
本食谱的源代码可以在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP02/fct找到。
如何做到……
执行以下步骤:
-
在一个新的本地文件夹中,创建一个名为
main.tf的文件。 -
在这个
main.tf文件中,写入以下代码:
variable "app_name" {
description = "Name of application"
}
variable "environement" {
description = "Environement Name"
}
- 最后,在这个
main.tf文件中,写入以下 Terraform 配置:
resource "azurerm_resource_group" "rg-app" {
name = upper(format("RG-%s-%s",var.app-name,var.environement))
location = "westeurope"
}
它是如何工作的……
在第 3 步中,我们使用 Terraform 的format函数定义了资源的属性名称,该函数允许我们格式化文本。在这个函数中,我们使用了%s动词来表示它是一个字符字符串,稍后将被应用程序名称和环境名称按顺序替换。
此外,为了使其中的所有内容大写,我们将format函数封装在upper函数中,该函数将把所有内容转换为大写。
执行这些 Terraform 命令后的结果可以在以下截图中看到:

因此,得益于这些函数,我们可以控制 Terraform 配置中将使用的属性。这也使得我们能够自动应用变换,而不需要对使用 Terraform 配置的用户施加约束。
另见
Terraform 中有许多预定义的函数。完整列表可在 www.terraform.io/docs/configuration/functions.html 查找(请参见左侧菜单)。
编写条件表达式
在编写 Terraform 配置时,我们可能需要通过集成各种条件来使代码更加动态。在本食谱中,我们将讨论一个相等条件操作的示例。
准备工作
对于本食谱,我们将使用之前食谱中编写的 Terraform 配置,其代码可在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP02/fct 获取。
我们将通过向资源组的名称添加条件来完成此代码。此条件如下:如果环境的名称等于 Production,则资源组的名称将为 RG-<APP NAME>;否则,资源组的名称将为 RG-<APP NAME>-<ENVIRONMENT NAME>。
如何执行…
在 main.tf 文件的 Terraform 配置中,修改资源组的代码,如下所示:
resource "azurerm_resource_group" "rg-app" {
name = var.environment == "Production" ? upper(format("RG-%s",var.app-name)) : upper(format("RG-%s-%s",var.app-name,var.environment))
location = "westeurope"
}
它是如何工作的…
在这里,我们添加了以下条件:
condition ? true assert : false assert
如果 environment 变量等于 production,执行 Terraform 命令时该代码的结果可在以下屏幕截图中看到:

如果 environment 变量不等于 production,我们将得到以下输出:

参见
关于 Terraform 各种条件的文档可以在 www.terraform.io/docs/configuration/expressions.html#conditional-expressions 查找。
使用 Terraform 操作本地文件
由于其云服务提供商的基础设施即代码功能,Terraform 非常受欢迎。但它也有许多提供者,可以让我们操作本地系统。
在 使用 Terraform 查询外部数据 食谱中,我们讨论了由 Terraform 执行的本地脚本,以获取外部数据源的数据。
在这个食谱中,我们将研究另一种涉及使用 Terraform 创建和归档本地文件的本地操作。
准备工作
对于本食谱,我们不需要任何前提条件或基础代码——我们将从头开始编写代码。
本食谱的源代码可在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP02/files 获取。
如何执行…
执行以下步骤:
- 在名为
files的新文件夹中,创建一个main.tf文件。将以下代码写入其中:
resource "local_file" "myfile" {
content = "This is my text"
filename = "../mytextfile.txt"
}
- 在命令行终端中,导航到
files目录并执行 Terraform 的工作流命令,命令如下:
terraform init
terraform plan -out="app.tfplan"
terraform apply "app.tfplan"
- 在一个新的
archive文件夹中,创建一个main.tf文件,并在其中写入以下 Terraform 配置:
data "archive_file" "backup" {
type = "zip"
source_file = "../mytextfile.txt"
output_path = "${path.module}/archives/backup.zip"
}
- 然后,使用命令行终端,导航到
archive目录并执行以下 Terraform 命令:
terraform init
terraform plan
它是如何工作的…
在第 1 步中,我们编写了一段使用local提供者和local_file资源的 Terraform 配置。这个资源会创建一个名为mytextfile.txt的文件,并将This is my text添加到文件中。
然后,在第 2 步中,我们执行了 Terraform 代码。通过这样做,我们在本地磁盘上获取了mytextfile.txt文件。
执行terraform plan命令时的结果可以在以下截图中看到:

执行terraform apply后,mytextfile.txt文件在本地文件系统中变得可用。
在本教程的第二部分,第 3 步中,我们编写了一段使用archive提供者和archive_file资源的 Terraform 配置,创建了一个包含我们在第 1 步和第 2 步中创建的文件的 ZIP 文件。
在执行了terraform apply后,ZIP 归档文件backup.zip在本地文件系统中的archives文件夹中变得可用。
还有更多内容…
正如我们所看到的,本教程第二部分中使用的archive_file资源属于data块类型(我们在本章节的使用数据源获取外部数据教程中学习过),因此它是基于在执行terraform plan命令之前已经存在的元素。
在我们的例子中,必须已经存在要包含在归档中的文件,且该文件已经在本地磁盘上。
另见
-
有关
local_file资源的文档可以在www.terraform.io/docs/providers/local/r/file.html找到。 -
有关
archive_file资源的文档可以在www.terraform.io/docs/providers/archive/d/archive_file.html找到。
使用 Terraform 执行本地程序
正如我们在之前的文件操作教程中看到的,除了基础设施的配置外,Terraform 还允许你运行位于本地工作站上的程序或脚本,该工作站已经安装了 Terraform。
在本教程中,我们将学习如何在 Terraform 配置中执行本地程序。
准备工作
在本教程中,我们将完成之前教程中使用的 Terraform 配置,该配置用于在本地机器上写入文件。我们的目标是通过 Terraform 执行一个 PowerShell 命令,读取并显示我们通过 Terraform 写入的文件内容。
当然,我们必须在 Windows 操作系统上运行这个 Terraform 脚本。
此食谱的源代码可在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP02/files_local_exec 获取。
如何操作…
执行以下步骤:
- 在
main.tf文件中,该文件位于上一个食谱源代码的files目录中,使用以下代码完成 Terraform 配置:
resource "null_resource" "readcontentfile" {
provisioner "local-exec" {
command = "Get-Content -Path ../mytextfile.txt"
interpreter = ["PowerShell", "-Command"]
}
}
- 然后,在命令行终端中执行 Terraform 工作流命令,如下所示:
terraform init
terraform plan -out="app.tfplan"
terraform apply "app.tfplan"
它是如何工作的…
在这个食谱中,我们使用了 null_resource,这是一个 null 提供程序资源。此资源不允许我们创建资源,而是运行本地程序。
在这个资源中,我们有一个 provisioner 块,它是 local-exec 类型,在我们的本地机器上操作。然后,在这个块中,我们指定要执行的命令,即 PowerShell 的 -Content 命令。通过这个,我们告诉 Terraform 使用 PowerShell 解释器来执行该命令。
执行相应的 Terraform 命令时,我们得到以下结果:

如您所见,我们在文件中写入的文本 This is my text(在 local_file 资源中)会显示在 Terraform 运行时的输出中。
还有更多…
在这个食谱中,我们看到了一个简单的 local-exec 命令在 Terraform 中的执行。也可以通过示例 Terraform 配置执行存储在脚本文件中的多个命令(如 Bash、PowerShell 等),如下所示:
resource "null_resource" "readcontentfile" {
provisioner "local-exec" {
command = "myscript.ps1"
interpreter = ["PowerShell", "-Command"]
}
}
local-exec 配置器对本地系统的期望可能并不显而易见。通常,提供商和 Terraform 本身会通过跨平台构建来减轻这一点,这样实施通常在任何支持的平台(macOS/Linux/Windows)上都能正常工作。
此外,重要的是要知道,一旦执行了 local-exec 配置器,它确保 Terraform 状态文件无法通过 terraform apply 命令再次执行。
为了能够基于触发器元素执行 local-exec 命令,例如已修改的资源,有必要在 null_resource 中添加一个 trigger 对象,它将作为 local-exec 资源的触发元素。
以下示例代码使用基于 timestamp 的触发器,在 Terraform 的每次执行步骤中执行 local-exec 代码:
resource "null_resource" "readcontentfile" {
triggers = {
trigger = timestamp()
}
provisioner "local-exec" {
command = "Get-Content -Path ../mytextfile.txt"
interpreter = ["PowerShell", "-Command"]
}
}
在这个示例中,触发器是一个时间戳,每次运行 Terraform 时都会有不同的值。
我们将在 第六章 使用 Terraform 执行 Azure CLI 命令 的食谱中,深入了解 local-exec 的另一个具体用例,使用 Terraform 配置 Azure 基础设施。
另见
local-exec 配置器文档可以在 www.terraform.io/docs/provisioners/local-exec.html 查阅。
使用 Terraform 生成密码
在使用 Terraform 配置基础设施时,有些资源在其属性中需要密码,例如虚拟机和数据库。
为了确保更好的安全性,避免明文写入密码,您可以使用 Terraform 提供程序,它允许您生成密码。
在本食谱中,我们将讨论如何使用 Terraform 生成密码并将其分配给资源。
准备工作
在本食谱中,我们需要在 Azure 中配置一个虚拟机,该虚拟机将通过 Terraform 动态生成的管理员密码进行配置。
为此,我们将基于一个已经存在的 Terraform 配置,该配置用于在 Azure 中配置虚拟机。
本食谱的源代码可在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP02/password找到。
如何操作…
执行以下步骤:
- 在 VM 的 Terraform 配置文件中,添加以下代码:
resource "random_password" "password" {
length = 16
special = true
override_special = "_%@"
}
- 然后,在资源本身的代码中,修改密码属性,使用以下代码:
resource "azurerm_virtual_machine" "myterraformvm" {
name = "myVM"
location = "westeurope"
resource_group_name = azurerm_resource_group.myterraformgroup.name
network_interface_ids = [azurerm_network_interface.myterraformnic.id]
vm_size = "Standard_DS1_v2"
....
os_profile {
computer_name = "vmdemo"
admin_username = "admin"
admin_password = random_password.password.result
}
....
}
它的工作原理…
在第一步中,我们从random提供程序中添加了 Terraform 的random_password资源,它允许我们根据提供的属性生成字符串。这些字符串将是敏感的,意味着它们会被 Terraform 保护。
然后,在第二步中,我们使用了其结果(通过result属性)在虚拟机的密码属性中。
执行terraform plan命令时,代码的执行结果可以在以下截图中看到:

如我们所见,结果是sensitive value。
请注意,Terraform 中的敏感属性意味着在使用 Terraform 的plan和apply命令时,无法在控制台输出中显示该属性。
另一方面,它将以明文形式出现在 Terraform 状态文件中。
另请参见
-
要了解更多关于
random_password资源的信息,请阅读以下文档:www.terraform.io/docs/providers/random/r/password.html。 -
有关 Terraform 状态文件中敏感数据的文档,请参见
www.terraform.io/docs/state/sensitive-data.html。
使用 Terraform 构建动态环境
在上一章中,我们学习了如何使用 Terraform 的语言概念高效地配置基础设施。基础设施即代码(IaC)的一个优势是,它允许你比手动配置更快地大规模部署基础设施。
在编写 IaC(基础设施即代码)时,同样重要的是应用开发人员多年来已经掌握的开发和清晰代码原则。
这些原则之一是 不要重复自己(DRY),意味着不重复代码 (thevaluable.dev/dry-principle-cost-benefit-example/)。在本章中,我们将学习如何使用 Terraform 语言中的表达式,如 count、maps、collections 和 dynamic。我们将了解这些概念将帮助我们编写简单的 Terraform 配置,以便为基础设施提供多个资源,而不需要重复代码。
本章将涵盖以下内容:
-
使用 count 属性配置多个资源
-
使用键值对变量的映射表
-
遍历对象集合
-
使用动态表达式生成多个块
第四章:技术要求
本章没有任何技术先决条件。然而,建议已阅读上一章内容。
本章的源代码可在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP03 获取。
查看以下视频,查看代码的实际操作:bit.ly/2R5GSBN
使用 count 属性配置多个资源
在企业场景中,需要提供基础设施并考虑所谓的水平扩展性,即 N 个相同的资源,这些资源将减少单个资源(如计算实例)以及整个应用的负载。
我们将面临的挑战如下:
-
编写 Terraform 配置,无需为每个相同资源实例的配置重复代码
-
能够快速增加或减少这些资源的实例数量
在本食谱中,我们将看到 Terraform 如何使得快速配置 N 个资源实例成为可能,并且不会出现代码重复。
准备工作
首先,我们将使用一个 Terraform 配置,该配置允许我们配置一个 Azure App Service,它位于 main.tf 文件中,以下是其中的一个提取部分:
resource "azurerm_app_service" "app" {
name = "${var.app_name}-${var.environement}"
location = azurerm_resource_group.rg-app.location
resource_group_name = azurerm_resource_group.rg-app.name
app_service_plan_id = azurerm_app_service_plan.plan-app.id
}
本食谱的目的是应用并修改此 Terraform 配置,以配置 N 个 Azure App Service 实例,这些实例与在基础代码中已描述的实例相同,仅在名称上有细微差别,名称使用从 1 开始的递增索引号。
本配方的源代码可在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP03/count找到。
如何操作……
要创建多个相同的资源,请执行以下步骤:
- 在
variables.tf文件中,我们添加了以下变量:
variable "nb_webapp" {
description = "Number of App Service to create"
}
- 在
terraform.tfvars文件中,我们为这个新变量提供了以下值:
nb_webapp = 2
- 在
main.tf文件中,我们以以下方式修改了azurerm_app_service资源的代码:
resource "azurerm_app_service" "app" {
count = var.nb_webapp
name = "${var.app_name}-${var.environement}-${count.index+1}"
location = azurerm_resource_group.rg-app.location
resource_group_name = azurerm_resource_group.rg-app.name
app_service_plan_id = azurerm_app_service_plan.plan-app.id
}
- (可选:)在一个新的
outputs.tf文件中,我们添加了以下代码来输出值:
output "app_service_names"{
value = azurerm_app_service.app[*].name
}
它是如何工作的……
在第 1 步中,我们添加了一个nb_webapp变量,它将包含要写入的 Azure App Service 实例的数量,我们将在第 2 步中在terraform.tfvars文件中实例化该变量。
然后在第 3 步中,在azurerm_app_service资源中,我们添加了 Terraform 的count属性(该属性适用于所有资源和数据 Terraform 块),其值为之前创建的nb_webapp变量。
此外,在azurerm_app_service资源的name中,我们添加了一个后缀,表示当前count的索引,我们通过count.index + 1将索引递增 1(从 1 开始,而不是从 0 开始,以反映count索引从零开始的事实)。
最后,可选地,在第 4 步中,我们添加了一个输出,它将包含已配置的 App Service 实例的名称。
执行此配方的terraform plan命令时,nb_webapp变量等于2,我们可以看到已配置了两个 App Service 实例。
以下截图显示了此terraform plan命令的摘录,第一张图显示了第一个 App Service 的预览更改:

以下截图是terraform plan命令的延续,显示了第二个 App Service 实例的预览更改:

当更改应用时,输出将显示如下:

正如您在输出中看到的,我们有一个列表,其中包含生成的两个 App Service 实例的名称。
还有更多内容……
正如我们在上一章的操作变量配方中讨论的那样,我们还可以使用terraform plan和apply命令的-var选项,轻松增加或减少此资源的实例数量,而无需修改 Terraform 配置。
例如,在我们的案例中,我们可以使用以下plan和apply命令:
terraform plan -var "nb_webapp=5"
然而,使用这种选项,我们失去了基础设施即代码(IaC)的好处,即将所有内容写入代码中,从而拥有基础设施更改历史记录的优势。
此外,需要注意的是,降低nb_webapp值会将索引中的最后一部分资源移除,而无法移除位于索引中间的资源,这一点已经通过for_each表达式得到了改进,后者将在本章的遍历对象集合一节中讲解。
此外,感谢我们刚刚看到的count属性和我们在第二章编写 Terraform 配置一节中学习的条件表达式,我们可以通过动态方式使资源的配置变得可选,如下所示的代码片段所示:
resource "azurerm_application_insights" "appinsight-app" {
count = use_appinsight == true ? 1 : 0
....
}
在这段代码中,我们已经告诉 Terraform,如果use_appinsight变量为true,那么count属性为1,这将允许我们配置一个 Azure Application Insights 资源。在相反的情况下,当use_appinsight变量为false时,count属性为0,此时,Terraform 不会提供 Application Insight 资源实例。
这样,Terraform 配置可以以通用方式用于所有环境或所有应用程序,并根据变量动态和有条件地进行配置。
这种技术,也称为功能标志,已被广泛应用于开发领域,但在这里我们看到,我们也可以将其应用于基础设施即代码(IaC)。
正如我们在本节中看到的,count属性允许您快速配置多个在特性上相同的资源。
我们将在本章的遍历对象集合一节中学习如何配置多个相同性质但具有不同属性的资源。
另见
有关count属性的更多信息,请参阅文档:www.terraform.io/docs/configuration/resources.html#count-multiple-resource-instances-by-count。
使用键值变量表和映射
到目前为止,在本书中,我们学习了使用标准变量类型(字符串、数字或布尔值)的示例代码。然而,Terraform 语言还有其他类型的变量,如列表、映射、元组,甚至更复杂的对象变量。
这些变量类型中包括映射(maps),它们由一组键值元素表示,并广泛用于编写动态且可扩展的 Terraform 配置。
映射有多种用途,具体如下:
-
将 Terraform 资源中一个块的所有属性放入一个单一变量中
-
为了避免声明多个相同类型的变量,从而将这些变量的所有值放入一个
map类型的单一变量中 -
拥有一个键值参考表,用于在 Terraform 配置中使用的元素
在本节中,我们将看到一个使用映射变量来动态定义 Azure 资源所有标签的简单实用案例。
准备就绪
对于这个教程,我们从一个基本的 Terraform 配置开始,允许我们在 Azure 中创建一个资源组和一个应用服务实例。
这个教程的源代码可以在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP03/map上找到。
在这个教程中,我们将通过两个使用案例来演示如何使用映射,具体如下:
-
这个资源组的标签实现
-
应用服务的应用设置属性
如何做到这一点……
执行以下步骤:
- 在
variables.tf文件中,我们添加以下变量声明:
variable "tags" {
type = map(string)
description = "Tags"
default = {}
}
variable "app_settings" {
type = map(string)
description = "App settings of the App Service"
default = {}
}
- 然后,在
terraform.tfvars文件中,我们添加了以下代码:
tags = {
ENV = "DEV1"
CODE_PROJECT = "DEMO"
}
app_settings = {
KEY1 = "VAL1"
}
- 最后,我们修改
main.tf文件,加入以下代码:
resource "azurerm_resource_group" "rg-app" {
name = "${var.resource_group_name}-${var.environement}"
location = var.location
tags = var.tags
}
resource "azurerm_app_service" "app" {
name = "${var.app_name}-${var.environement}"
location = azurerm_resource_group.rg-app.location
resource_group_name = azurerm_resource_group.rg-app.name
app_service_plan_id = azurerm_app_service_plan.plan-app.id
site_config {
dotnet_framework_version = "v4.0"
}
app_settings = var.app_settings
}
它是如何工作的……
在步骤 1中,我们声明了两个变量,并指定了它们的类型,即map (string)。也就是说,它将由键值对类型的元素组成,其中值的格式为string。此外,考虑到这些变量可以省略,因此它们的值是可选的,我们为它们分配了一个空的默认值,即{},用于map。
然后,在步骤 2中,我们用资源的标签以及应用服务的app_settings定义了这两个变量的值。
最后,在步骤 3中,我们在 Terraform 配置中使用这些变量,以提供资源组和应用服务。
下图展示了在这个教程中执行terraform plan命令的示例:

我们可以从前面的截图中看到,app_settings和tags属性已经填充了map变量的值。
还有更多……
因此,映射使我们能够简化资源中对象块的使用,但要小心——不能将变量放入map中。map类型的变量因此应该被视为一个单独的变量块。
为了进一步了解,我们可以看到也可以合并映射;也就是说,要合并两个映射,我们可以使用 Terraform 的原生merge函数。
接下来的步骤展示了如何使用这个函数合并应用服务的应用设置属性:
- 在
variables.tf文件中,我们创建了一个custom_app_settings变量,它将包含用户提供的自定义应用设置:
variable "custom_app_settings" {
description = "Custom app settings"
type = map(string)
default = {}
}
- 在
terraform.tfvars文件中,我们用一个自定义映射实例化了这个变量:
custom_app_settings = {
CUSTOM_KEY1 = "CUSTOM_VAL1"
}
- 最后,在
main.tf文件中,我们使用本地变量定义了默认的应用设置,并在azurerm_app_service资源中,使用merge函数将默认应用设置与自定义应用设置合并:
locals {
default_app_settings = {
"DEFAULT_KEY1" = "DEFAULT_VAL1"
}
}
resource "azurerm_app_service" "app" {
name = "${var.app_name}-${var.environement}"
location = azurerm_resource_group.rg-app.location
resource_group_name = azurerm_resource_group.rg-app.name
app_service_plan_id = azurerm_app_service_plan.plan-app.id
site_config {
dotnet_framework_version = "v4.0"
scm_type = "LocalGit"
}
app_settings = merge(local.default_app_settings,var.custom_app_settings)
}
在之前的代码中,我们定义了 Azure App Service 的默认应用设置属性,用户可以在需要时通过添加自定义应用设置来丰富这些设置。
此外,也可以在代码中动态创建一个map对象,而无需使用变量。
为此,我们可以使用 Terraform 集成的{....}语法,它将键值对列表作为参数,正如以下代码所示:
app_settings = {"KEY1" = "VAL1", "KEY2" = "VAL2"}
在这个食谱中,我们研究了映射的使用。但如果我们想使用具有不同类型键值对的更复杂的映射,那么我们将使用对象变量,具体内容可以参考文档 www.terraform.io/docs/configuration/types.html#object-。
在下面的食谱中,我们将讨论如何对构成映射变量的键值对列表进行迭代。
另见
与 merge 函数相关的文档可以在 www.terraform.io/docs/configuration/functions/merge.html查看。
遍历对象集合
我们在本章的前几个食谱中已经看到了count属性的使用,它允许我们配置N个相同的资源,以及使用映射变量,它允许键值对象。
在这个食谱中,我们将讨论如何使用 Terraform 从 0.12 版本开始包含的循环功能来配置具有不同属性的N个相同类型的资源。
准备工作
要开始,我们从一个基本的 Terraform 配置开始,这个配置允许你在 Azure 中部署一个单一的 App Service。
基本的 Terraform 配置如下:
resource "azurerm_app_service" "app" {
name = "${var.app_name}-${var.environement}"
location = azurerm_resource_group.rg-app.location
resource_group_name = azurerm_resource_group.rg-app.name
app_service_plan_id = azurerm_app_service_plan.plan-app.id
}
这个食谱的源代码可以在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP03/list_map找到。
如何操作…
执行以下步骤:
- 在
variables.tf文件中,我们添加以下 Terraform 配置:
variable "web_apps" {
description = "List of App Service to create"
type = any
}
- 在
terraform.tfvars文件中,我们添加以下配置:
web_apps = {
webapp1 = {
"name" = "webappdemobook1"
"location" = "West Europe"
"dotnet_framework_version" = "v4.0"
"serverdatabase_name" = "server1"
},
webapp2 = {
"name" = "webapptestbook2"
"dotnet_framework_version" = "v4.0"
"serverdatabase_name" = "server2"
}
}
- 在
main.tf文件中,我们通过以下配置修改 App Service 的代码:
resource "azurerm_app_service" "app" {
for_each = var.web_apps
name = each.value["name"]
location = lookup(each.value, "location", "West Europe")
resource_group_name = azurerm_resource_group.rg-app.name
app_service_plan_id = azurerm_app_service_plan.plan-app.id
site_config {
dotnet_framework_version = each.value["dotnet_framework_version"]
}
connection_string {
name = "DataBase"
type = "SQLServer"
value = "Server=${each.value["serverdatabase_name"]};Integrated Security=SSPI"
}
}
- 最后,在
outputs.tf文件中,我们添加以下代码:
output "app_service_names" {
value = [for app in azurerm_app_service.app : app.name]
}
它是如何工作的…
在步骤 1中,我们声明了一个类型为any的新变量,也就是说,我们没有指定它的类型,因为它是复杂的。
值得注意的是,可以声明复杂的变量。虽然这会使代码更加冗长,但在验证时非常有帮助。
在步骤 2中,我们通过一个包含映射对象列表的变量实例化它,这些对象将成为每个 App Service 的属性。在这个列表中,我们有两个 App Service 实例,我们指定了框架名称、版本、Azure 区域位置以及应用程序数据库服务器名称等属性,这些属性将被用于 App Service。
在步骤 3中,在azurerm_app_service资源中,我们不再使用count属性,而是使用for_each表达式(这是 Terraform 0.12 版本中引入的),它允许我们对列表进行循环。
然后,对于azurerm_app_service资源的每个属性,我们可以使用简短表达式each.value["<property name>"]或 Terraform 中集成的lookup函数,该函数接受以下参数:
-
for_each表达式的当前元素为each.value,即列表中的这一行。 -
映射中属性的名称,在我们的示例中是
location。 -
然后是
lookup函数的第三个参数,它是可选的。它允许你指定在属性不存在于映射中时使用的值。我们使用的是 Azure 西欧地区,这是默认值。
最后,在步骤 4中,我们创建了一个输出,使用for表达式遍历已配置的资源列表,并将它们的名称作为输出导出。
这个输出的结果如下图所示:

在之前的截图中,我们可以看到输出的结果,它在控制台中显示了两个已配置的 Azure App Service 实例的名称。
还有更多……
在本例中,我们学习了 Terraform 语言中的表达式和函数,允许对资源集合进行配置。
我建议你仔细阅读关于for和for_each表达式的文章和文档。至于lookup和element函数,它们是可以使用的,但最好使用本地语法(例如var_name[42]和var_map["key"])来访问映射、列表或集合的元素。
很明显,在本例中,我们使用了简单的资源,如 Azure App Service,但这些方法也可以应用于更富属性的资源,如虚拟机。
另见
-
关于
for和for_each循环的文档可以在www.terraform.io/docs/configuration/resources.html找到,关于这些循环的文章可以在www.hashicorp.com/blog/hashicorp-terraform-0-12-preview-for-and-for-each/找到。 -
关于
lookup函数的文档可以在www.terraform.io/docs/configuration/functions/lookup.html找到。
使用动态表达式生成多个块
Terraform 资源由以下元素定义:
-
以
property name = value形式出现的属性,这在本书中已经看到过好几次 -
表示属性分组的块,例如
azurerm_app_service资源中的site_config块
根据 Terraform 资源的不同,一个区块可以在同一资源中出现一次,甚至多次,例如 azurerm_network_security_group 资源中的 security_rule 区块(例如,参见文档:www.terraform.io/docs/providers/azurerm/r/network_security_group.html)。
在 Terraform 0.12 之前,无法通过动态方式在同一资源中多次呈现这些区块,例如使用 list 类型的变量。
Terraform 0.12 的一个重要新特性是新的 dynamic 表达式,它允许我们在资源中循环处理区块。
在本食谱中,我们将看到如何使用动态表达式在 Azure 中配置一个 azurerm_network_security_group 资源,其中包含一组安全规则。
准备工作
要开始使用,我们不需要任何基础的 Terraform 配置。在本食谱中,我们将使用一个 Terraform 文件,允许我们创建一个 Azure 资源组,在其中我们将创建一个网络安全组。
本食谱的源代码可以在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP03/dynamics 找到。
如何做到这一点…
要使用动态表达式,请执行以下步骤:
- 在
variables.tf文件中,我们添加以下代码:
variable "ngs_rules" {
description = "List of NSG rules"
type = any
}
- 在
terraform.tfvars文件中,我们添加以下代码:
ngs_rules = [
{
name = "rule1"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "*"
destination_address_prefix = "*"
},
{
name = "rule"
priority = 110
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "*"
destination_address_prefix = "*"
}
]
- 在
main.tf文件中,我们使用以下代码添加网络安全组(Network Security Group):
resource "azurerm_network_security_group" "example" {
name = "acceptanceTestSecurityGroup1"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
dynamic "security_rule" {
for_each = var.ngs_rules
content {
name = security_rule.value["name"]
priority = security_rule.value["priority"]
direction = security_rule.value["direction"]
access = security_rule.value["access"]
protocol = security_rule.value["protocol"]
source_port_range = security_rule.value["source_port_range"]
destination_port_range = security_rule.value["destination_port_range"]
source_address_prefix = security_rule.value["source_address_prefix"]
destination_address_prefix = security_rule.value["destination_address_prefix"]
}
}
}
它是如何工作的…
在 步骤 1 中,我们创建了一个类型为 any 的 nsg_rules 变量,它将包含以映射格式表示的规则列表。
然后,在 步骤 2 中,我们实例化了这个 nsg_rules 变量,并传入规则列表及其属性。
最后,在 步骤 3 中,在 azurerm_network_security_group 资源中,我们添加了 dynamic 指令,这使我们能够生成 N 个 security_rule 区块。
在这个 dynamic Terraform 表达式中,我们使用了一个 for_each 循环(如本章前面的 循环遍历对象集合 食谱中所示),它将迭代 nsg_rules 变量的每一行,并将资源的每个属性映射到列表的映射中。
以下截图显示了 terraform plan 命令的执行:

我们可以在前面的输出中看到安全规则的列表。
还有更多内容…
如果你想有条件地呈现一个区块的存在,你也可以使用 dynamic 表达式中的条件,如下所示:
resource "azurerm_linux_virtual_machine" "virtual_machine" {
...
dynamic "boot_diagnostics" {
for_each = local.use_boot_diagnostics == true ? [1] : []
content {
storage_account_uri = "https://storageboot.blob.core.windows.net/"
}
}
}
在这个例子中,在 dynamic 表达式的 for_each 表达式中,我们有一个条件,如果本地值 use_boot_diagnostics 为 true,则返回一个包含一个元素的列表。否则,这个条件返回一个空列表,这样就不会在 azurerm_virtual_machine 资源中出现 boot_diagnostics 区块。
另见
-
关于动态表达式的文档可以在
www.terraform.io/docs/configuration/expressions.html#dynamic-blocks找到。 -
另一个关于动态表达式的 Terraform 指南示例可以在
github.com/hashicorp/terraform-guides/tree/master/infrastructure-as-code/terraform-0.12-examples/advanced-dynamic-blocks找到。
使用 Terraform CLI
Terraform 是一个基础设施即代码(IaC)工具,由两个相关的元素组成:用 HashiCorp 配置语言(HCL)编写的 Terraform 配置,描述了我们要提供的基础设施,以及 Terraform 客户端工具,它将分析并执行我们的 Terraform 配置。在前两章中,我们已经学习了如何使用变量、函数和 HCL 表达式来编写 Terraform 配置的多种方案。
本章将重点介绍 Terraform 的命令行和选项的使用。我们将讨论如何让代码格式规范、资源销毁和工作空间的使用。接着,我们将学习如何导入已存在的资源、标记功能,最后我们将看到如何生成依赖图并调试 Terraform 的执行。
本章将涵盖以下食谱:
-
保持 Terraform 配置的清洁
-
验证代码语法
-
销毁基础设施资源
-
使用工作空间来管理环境
-
导入现有资源
-
以 JSON 格式导出输出
-
标记资源
-
生成图形依赖关系
-
调试 Terraform 执行
第五章:技术要求
与前两章相反,本章提供的代码示例不是基础性的,因为我们将专注于 Terraform 命令行的执行。
本章为了提供 Terraform 配置示例,我们将在 Azure 云中管理资源;显然,这也适用于所有其他 Terraform 提供商。如果你想应用这些方案而且没有 Azure 账户,你可以在以下网站免费创建一个 Azure 账户:azure.microsoft.com/en-us/free/
本章中的代码示例可以在这里找到:
github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP04
查看以下视频,了解代码的实际操作:bit.ly/3m3oho7
保持 Terraform 配置的清洁
在任何有代码的应用中,代码的整洁和易读性对于所有参与维护和发展这段代码的贡献者(当前和未来)至关重要。
在基础设施即代码(IaC)和 Terraform 中,代码清晰非常重要,因为编写的代码作为文档是 IaC 的一个优势。
在本节中,我们将了解如何使用 Terraform 的命令行来正确格式化代码,同时还会看到一些自动化的技巧。
准备工作
为了开始,我们将从一个包含以下 Terraform 配置的 main.tf 文件开始:

如我们所见,这段代码并不太易读;它需要更好的格式化。
要通过 CLI 执行 Terraform 命令,我们使用命令行终端(CMD、PowerShell、Bash 等),执行文件夹将是包含本教程 Terraform 配置的文件夹。这适用于本章中的所有教程。
如何操作……
为了修复代码的缩进,按如下方式执行 terraform fmt 命令:
terraform fmt
它是如何工作的……
terraform fmt 命令使得以正确的缩进整理代码变得更加容易。
执行结束时,此命令会显示已修改的文件列表,如下图所示:

我们可以看到,执行 terraform fmt 命令修改了我们的 main.tf 文件。
然后,我们打开 main.tf 文件并阅读:

我们可以在上面的截图中看到,代码已经被很好地缩进,因此更易于阅读。
还有更多……
在本教程中,我们已经了解了以最基本的方式执行的 terraform fmt 命令,即不带任何附加选项的情况。
这个默认命令会缩进当前文件夹根目录下的 Terraform 文件代码。我们也可以递归执行此命令,也就是说,它还可以缩进当前文件夹下子文件夹中的代码。
为此,我们执行带有 -recursive 选项的 terraform fmt 命令,输出如以下截图所示:

我们看到命令也格式化了 sub 文件夹中的 main.tf 文件。
除了此命令的其他选项外,还有 -check 选项,可以添加此选项来预览将要缩进的文件,而不应用更改到文件中。
最后,我们也可以自动化执行此命令,因为除了像本教程中所示的那样手动在命令终端运行外,我们还可以将其自动化,以确保每次在 Git 中保存或提交文件时,与其他贡献者共享的代码始终保持正确的缩进。
因此,支持 Terraform 的集成开发环境(IDE)已经将此命令的执行与代码编写原生集成:
-
使用 Visual Studio Code 的 Terraform 扩展,我们可以让每个 Terraform 文件在保存时都使用
terraform fmt命令进行格式化。欲了解更多信息,请参阅相关文档:marketplace.visualstudio.com/items?itemName=HashiCorp.terraform。 -
在 IntelliJ 中,Save action 插件使得每次保存代码时都能自动格式化,而Terraform插件则在 IDE 中大范围集成了
terraform fmt命令。此外,使用这个 Terraform 插件,我们可以在每次代码提交时执行terraform fmt命令并整理代码,如下图所示:

有关 Save action 插件的更多信息,请参考plugins.jetbrains.com/plugin/7642-save-actions,有关 Terraform 插件的信息,请参考plugins.jetbrains.com/plugin/7808-hashicorp-terraform--hcl-language-support。
对于 Git 提交,可以通过使用 Git 的预提交钩子,自动执行每次提交前的terraform fmt命令:git-scm.com/book/en/v2/Customizing-Git-Git-Hooks。
要在 Terraform 中使用预提交,请参阅Gruntwork提供的钩子列表:github.com/gruntwork-io/pre-commit。
参见
完整的terraform fmt命令文档可以在此处找到:www.terraform.io/docs/commands/fmt.html。
验证代码语法
在编写 Terraform 配置时,重要的是能够在执行代码之前,甚至在将代码存档到 Git 仓库之前验证我们编写的代码语法。
在本教程中,我们将展示如何使用 Terraform 客户端工具检查 Terraform 配置的语法。
准备工作
对于此教程,我们将从以下 Terraform 配置开始,该配置写在main.tf文件中:

我们在前面的代码中注意到,environment变量的声明缺失。
如何操作…
要验证我们的 Terraform 配置语法,请执行以下步骤:
- 首先,通过运行以下命令初始化 Terraform 上下文:
terraform init
- 然后,通过执行
validate命令来验证代码:
terraform validate
它是如何工作的…
在步骤 1中,我们通过执行terraform init命令来初始化 Terraform 上下文。
然后,我们通过执行terraform validate命令来检查代码的有效性。
在执行此命令的末尾,我们得到以下输出:

我们看到 Terraform 配置中存在一个语法错误,提示我们调用了未声明的变量var.environment。
所以,我们修正代码并再次运行terraform validate命令,直到没有更多错误,如下截图所示:

输出结果显示 Terraform 配置有效。
还有更多…
这个验证命令在本地开发模式中非常有用,也可以用于代码集成中的持续集成(CI)流水线中,这样在terraform validate命令返回语法错误时,就不会执行terraform plan命令。
以下是 PowerShell 代码示例,显示了执行此命令后的返回代码:
> terraform validate
> $LASTEXITCODE
PowerShell 变量 $LASTEXITCODE,这是 PowerShell 的本地变量,如果没有错误,它将返回 0,如果有错误则返回 1。
也可以通过向此命令添加 -json 选项,以 JSON 格式获取该命令的输出,如下图所示:

然后,JSON 结果可以使用第三方工具如 jq 进行解析,并在工作流中使用。
然而需要注意的是,这个命令仅允许验证配置的语法,例如正确使用函数、变量和对象类型,而不是验证 Terraform 配置执行的结果。
如果 Terraform 配置中包含 backend 块,那么在验证该配置时,我们不需要连接到该状态文件。我们可以在 terraform init 命令中添加 -backend=false 选项。
最后,如果执行此 Terraform 配置时需要通过 -var 参数或 -var-file 选项传递变量,则无法使用此命令。相反,使用 terraform plan 命令,该命令在执行过程中会进行验证。
另请参见
terraform validate 命令的文档可以在这里查看: www.terraform.io/docs/commands/validate.html
销毁基础设施资源
正如我们在本书中多次提到的,基础设施即代码(IaC)允许快速配置基础设施。
IaC 的另一个优点是它可以快速构建并清理已配置的资源。
事实上,我们可能需要因不同原因清理基础设施。以下是一些示例:
-
我们销毁基础设施是为了根据新的规格重新构建它,使其更好。
-
我们提供的是按需调用的基础设施,这意味着它是为特定需求(例如测试新特性或新分支)而临时搭建的。并且该基础设施必须能够快速、自动地构建和销毁。
-
我们希望移除一个不再使用的基础设施,并同时停止为其付费。
在本示例中,我们将讨论如何销毁已通过 Terraform 配置的基础设施。
准备工作
要开始,我们将提供一个由 Azure App Service 组成的基础设施。
为此,我们使用的 Terraform 配置可以在这里找到: github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP04/sample-app
然后,为了配置它,我们执行以下基本的 Terraform 工作流命令:
> terraform init
> terraform plan -out="app.tfplan"
> terraform apply "app.tfplan"
在执行结束时,我们将在 Azure 中得到一个资源组、一个服务计划、一个应用服务和一个应用洞察资源。
本示例的目标是通过 Terraform 命令完全销毁此基础设施。
如何操作…
使用 Terraform 清理资源,请执行以下步骤:
- 首先,通过运行
init命令初始化 Terraform 上下文:
terraform init
- 然后,为了清理资源,我们执行以下命令:
terraform destroy
在执行的开始,此命令会显示所有将被销毁的资源,并要求确认删除资源。然后通过输入yes来确认验证。
它是如何工作的…
在步骤 2中,我们通过执行terraform destroy命令来销毁所有已配置的资源。
以下截图显示了该命令的提取输出:

在命令执行结束时,Terraform 报告资源已成功销毁。
还有更多内容…
在本食谱中,我们研究了如何销毁所有已描述并由 Terraform 配置提供的资源。
由于terraform destroy命令会删除 Terraform 状态文件中跟踪的所有资源,因此重要的是通过将 Terraform 配置拆分为多个状态文件来减少更改基础设施时发生错误的可能性。
如果您只需要销毁一个单独的资源,而不是销毁状态文件中跟踪的所有资源,可以将-target选项添加到terraform destroy命令中,允许您指定要删除的资源。以下是带有target选项的命令示例:
terraform destroy -target azurerm_application_insights.appinsight-app
在此示例中,仅销毁了 Application Insights 资源。更多详细信息,请在此查看相关文档:www.terraform.io/docs/commands/plan.html#resource-targeting
请注意,定位机制应仅在最后手段下使用。在理想情况下,配置应与状态文件保持同步(应用时不带任何额外的target标志)。执行目标应用或销毁操作的风险在于,其他贡献者可能会错过上下文,更重要的是,在更改配置后,进行进一步更改将变得更加困难。
此外,如果此 Terraform 配置中的terraform plan命令需要-var-file选项来指定或覆盖变量的值,则必须在terraform destroy命令中也添加相同的选项。
在大多数情况下,适用于terraform plan命令的所有选项也适用于terraform destroy命令。
另请参见
-
与
terraform destroy命令相关的文档可以在这里查看:www.terraform.io/docs/commands/destroy.html -
与资源定位相关的文档可以在这里查看:
www.terraform.io/docs/internals/resource-addressing.html
使用工作区来管理环境
在 Terraform 中,有 工作区 的概念,允许使用相同的 Terraform 配置来构建多个环境。
每个配置将写入不同的 Terraform 状态文件,因此会与其他配置隔离开。工作区可以用来创建多个基础设施环境。
本教程中,我们将研究在 Terraform 配置中使用 Terraform 工作区,并执行 Terraform 命令。
准备工作
本教程的目的是让一个应用程序为它的每个环境(dev 和 prod)创建一个资源组。
关于 Terraform 配置,没有必要的前提条件。我们将在步骤中看到它。
本教程的 Terraform 配置可以在此查看:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP04/workspaces
如何做...
要管理 Terraform 工作区,请执行以下步骤:
- 在新的
main.tf文件中,我们编写以下 Terraform 配置:
resource "azurerm_resource_group" "rg-app" {
name = "RG-APP-${terraform.workspace}"
location = "westeurope"
}
- 在命令终端中,我们进入包含此 Terraform 配置的文件夹,并执行以下命令:
terraform workspace new dev
- 要配置
dev环境,我们运行 Terraform 工作流的基本命令,具体如下:
> terraform init
> terraform plan -out="outdev.tfplan"
> terraform apply "outdev.tfplan"
- 然后,我们执行
workspace new命令,创建生产工作区,命名为prod:
terraform workspace new prod
- 要完成并配置
prod环境,我们执行 Terraform 工作流生产的基本命令,具体如下:
> terraform init
> terraform plan -out="outprod.tfplan"
> terraform apply "outprod.tfplan"
它是如何工作的…
在 步骤 1 中,我们在 Terraform 配置中提供了一个 Azure 资源组,它的名称由 RG-APP 前缀和动态后缀 terraform.workspace 组成,后缀将是我们即将创建的工作区的名称。
在 步骤 2 中,我们创建与 dev 环境对应的工作区,为此我们使用 terraform workspace new 命令,后跟工作区名称(在本例中为 dev)。
一旦创建,Terraform 会自动将其放入该工作区,如下图所示:

创建工作区后,我们只需执行 Terraform 工作流的基本命令,这些命令在 步骤 3 中执行。
请注意,这里我们已经在 terraform plan 命令中添加了 -out 选项,以将计划的结果保存在 outdev.tfplan 文件中。然后,为了应用这些更改,我们特别将此文件作为参数添加到 terraform apply 命令中。
然后,为了配置 prod 环境,我们完全按照 步骤 2 和 步骤 3 进行,但这次创建名为 prod 的工作区。
在执行完所有这些步骤后,我们可以在 Azure 门户中看到,我们有两个资源组,它们在后缀中包含了工作区的名称,如下图所示:

此外,我们还注意到创建了两个 Terraform 状态文件,每个工作空间一个,它们是自动创建的,如下图所示:

在这张截图中,我们可以看到两个terraform.tfstate文件,一个位于dev目录,另一个位于prod目录。
还有更多…
在任何 Terraform 配置执行中,都有一个默认的工作空间,名称为default。
可以通过执行以下命令查看我们代码中的工作空间列表:
terraform workspace list
以下截图展示了在我们的案例中执行此命令的过程:

我们可以清晰地看到我们的dev和prod工作空间,并且当前的工作空间是prod(在其名称前标有*)。
如果您想切换到另一个工作空间,请执行terraform workspace select命令,后面跟上要选择的工作空间名称;例如:
terraform workspace select dev
最后,您还可以通过执行terraform workspace delete命令删除一个工作空间,后面跟上要删除的工作空间名称;例如:
terraform workspace delete dev
在删除工作空间时要小心,确保它不会删除关联的资源。因此,为了删除工作空间,您必须先使用terraform destroy命令删除该工作空间提供的资源。否则,如果未执行此操作,您将无法使用 Terraform 管理这些资源,因为该工作空间的 Terraform 状态文件将被删除。
此外,默认情况下,不可能删除状态文件非空的工作空间。但是,我们可以通过在terraform workspace delete -force命令中添加-force选项,强制销毁该工作空间,相关文档请见:www.terraform.io/docs/commands/workspace/delete.html。
另见
-
工作空间的一般文档可以在这里找到:
www.terraform.io/docs/state/workspaces.html -
terraform workspace命令的 CLI 文档可以在这里找到:www.terraform.io/docs/commands/workspace/index.html -
阅读这篇博客文章,了解更多关于工作空间的完整使用方法:
www.colinsalmcorner.com/terraform-all-the-things-with-vsts/
导入现有资源
到目前为止,在本书中,我们已经看到了 Terraform 的正常使用方法,即编写 Terraform 配置并由 Terraform 执行和应用。此执行将配置或应用对基础设施的更改,并会反映在 Terraform 状态文件中。
然而,在某些场景中,可能需要将已经配置好的资源导入到 Terraform 状态文件中。这类场景的例子包括:
-
资源已经手动(或通过脚本)配置好,现在希望它们的配置出现在 Terraform 配置文件和状态文件中。
-
如果包含基础设施配置的 Terraform 状态文件已损坏或丢失,且需要重新生成,可以采取此步骤。
在本教程中,我们将讨论如何借助 Terraform 命令,将已经配置好的资源导入到 Terraform 状态文件中。
准备工作
对于这个教程,我们将使用我们已经编写的以下 Terraform 配置,以便为资源组进行配置:
resource "azurerm_resource_group" "rg-app" {
name = "RG-APP-IMPORT"
location = "westeurope"
}
这段代码也可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP04/import
同时,在 Azure 门户中,我们已手动创建了名为RG-APP-IMPORT的资源组,如相关文档中所述:docs.microsoft.com/en-us/azure/azure-resource-manager/management/manage-resource-groups-portal
以下截图显示了 Azure 中的该资源组:

此时,如果我们在此代码上运行 Terraform,terraform apply命令将尝试创建此资源组。它会失败并返回错误,提示资源组已经存在,无法创建,如下截图所示:

因此,有必要直接在 Terraform 状态文件中使用资源导入。
在这个教程中,我们将执行一个导入操作,将一个资源组导入到 Azure 中。但需要注意的是,每个 Terraform 提供者对import命令有不同的目标参数。
本教程的目标是将该资源组的配置导入到与我们的 Terraform 配置相对应的 Terraform 状态文件中。
如何操作…
执行以下步骤:
- 我们通过执行
init命令初始化 Terraform 环境:
terraform init
- 然后,我们执行以下
terraform import命令:
terraform import azurerm_resource_group.rg-app /subscriptions/8a7aace5-xxxxx-xxx/resourceGroups/RG-APP-IMPORT
它是如何工作的…
在步骤 1中,使用terraform init命令初始化 Terraform 环境。
然后,在步骤 2中,我们执行terraform import命令,将 Terraform 资源的引用作为第一个参数,Azure 中资源组的标识符作为第二个参数。
以下截图显示了执行此命令的输出结果:

我们可以看到资源确实已被导入到 Terraform 状态文件中。
现在,我们已经更新了 Terraform 配置、Terraform 状态文件以及 Azure 中的资源。
还有更多内容…
为了检查资源导入的执行情况,我们执行terraform plan命令,并且必须确保没有任何更改要求,如下截图所示:

如果你在 Azure 中预置资源,有一些相当有趣的工具可以从已创建的 Azure 资源生成 Terraform 配置和相应的 Terraform 状态文件。这个开源工具Az2Tf可以在github.com/andyt530/py-az2tf找到。或者,TerraCognita也可以在github.com/cycloidio/terracognita/blob/master/README.md找到。
另见
有关import命令的文档可以在这里找到:www.terraform.io/docs/commands/import.html
以 JSON 格式导出输出
我们在第三章的遍历对象集合食谱中讨论了 Terraform 输出的使用,这允许你在执行 Terraform 配置时拥有输出值。实际上,我们已经了解了如何在 Terraform 配置中声明输出,并且我们知道这些输出及其值会在执行terraform apply命令结束时显示出来。
这些输出的优势在于它们可以被程序检索,从而用于其他操作;例如,在 CI/CD 流水线中。
在本食谱中,我们将看到如何以 JSON 格式检索输出的值,以便它们可以在外部程序中使用。
准备工作
在本教程中,我们将仅使用我们在第三章中学习过的 Terraform 配置,相关源代码可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP03/list_map
在这段代码中,我们添加了另一个输出,它返回 App Service URL 的列表,如下所示:
output "app_service_urls" {
value = {for x in azurerm_app_service.app : x.name => x.default_site_hostname }
}
要利用输出的值,我们需要使用一个可以处理 JSON 的工具。为此,你可以根据你的脚本语言使用任何框架和库。在本食谱中,我们将使用jq,这是一款免费工具,可以让你在命令行中轻松操作 JSON。关于 jq 的文档可以在这里找到:stedolan.github.io/jq/
本教程的目的是使用 Terraform 配置预置两个 Azure 应用服务,并通过脚本对第一个应用服务的 URL 进行响应检查。
如何操作…
执行以下步骤以使用输出结果:
- 执行以下命令以运行 Terraform 工作流:
> terraform init
> terraform plan -out="app.tfplan"
> terraform apply "app.tfplan"
- 然后,运行
terraform output命令:
terraform output
- 最后,为了获取创建的应用服务的 URL,我们在命令终端执行以下命令:
urlwebapp1=$(terraform output -json | jq -r .app_service_urls.value.webappdemobook1) &&
curl -sL "%{http_code}" -I "$urlwebapp1/hostingstart.html"
它是如何工作的…
在 步骤 1 中,我们执行 Terraform 工作流的基本命令。执行 terraform apply 命令后,命令会显示输出结果。
然后,在 步骤 2 中,我们通过执行 terraform output 命令来更清晰地查看此 Terraform 配置的输出,如下图所示:

在上面的截图中,我们可以看到此命令返回了代码中声明的两个输出,它们如下:
-
app_service_names:返回提供的应用服务名称列表。 -
app_service_urls:返回已配置应用服务的 URL 列表。
最后,在 步骤 3 中,我们运行一个脚本来检查 webappdemobook1 应用服务的 URL。在此脚本的第一行,我们执行 terraform output -json 命令,这样输出结果会以 JSON 格式返回,正如下面的截图所示:

然后,使用这个 JSON 结果,我们通过获取 webappdemobook1 应用服务的 URL 来使用 jq 工具。返回的 URL 被存储在一个名为 urlwebapp1 的变量中。
然后,在此脚本的第二行,我们通过传递选项使用 curl 命令对这个 URL 进行操作,返回该 URL 的 HTTP 头信息。
执行此脚本的结果显示在以下截图中:

你可以看到检查结果为 OK,状态码为 200。
还有更多…
在这个教程中,我们学习了如何获取 Terraform 配置的所有输出。也可以通过执行 terraform output <输出名称> 命令来获取特定输出的值。
在我们的例子中,我们本可以执行 app_service_urls 命令来显示输出的值:
terraform output app_service_urls
以下截图展示了这个命令的执行情况:

然后,我们运行以下命令来检查 URL:
urlwebapp1=$(terraform output app_service_urls -json | jq -r .webappdemobook1) &&
curl -sL "%{http_code}" -I "$urlwebapp1/hostingstart.html"
我们在这个脚本中看到,使用的命令是 terraform output app_service_urls -json,比 $(terraform output -json | jq -r .app_service_urls.value.webappdemobook1) 更简洁。
另见
-
terraform output命令的文档可以在这里找到:www.terraform.io/docs/commands/output.html -
jq 的官网可以在这里找到:
stedolan.github.io/jq/
污点资源
之前,在 销毁基础设施资源 章节中,我们学习了如何销毁通过 Terraform 配置的资源。
然而,在某些情况下,你可能需要销毁特定的资源,以便立即重新创建它。此类情况的示例可能包括该资源已手动修改。
要销毁并重新创建一个资源,你可以执行 terraform destroy -target <resource> 命令,然后执行 apply 命令。然而,问题在于,在 destroy 和 apply 命令之间,Terraform 配置中可能会有其他变更被应用,而这些变更是我们不希望有的。
因此,在本教程中,我们将展示如何使用 Terraform 的 taint 概念来执行此操作。
正在准备中
为了应用本教程,我们首先提供了由资源组、服务计划、App Service 和应用洞察资源组成的基础设施。用于此提供的 Terraform 配置可以在此找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP04/sample-app。
本教程的目标是通过使用 Terraform 的 taint 命令,在一次操作中销毁并重新创建 App Service。
如何操作……
要应用 taint 操作,请执行以下步骤:
-
运行
terraform init命令来初始化上下文。 -
然后,执行
terraform taint命令将该资源标记为受污染:
terraform taint azurerm_app_service.app
- 最后,要重新创建 App Service,请执行以下命令:
terraform apply
它是如何工作的……
在第 1 步中,我们执行 terraform init 命令来初始化上下文。然后,在第 2 步中,我们执行 terraform taint 命令,将 azurerm_app_service.app 资源标记为受污染;即,标记为销毁并重新创建。
该命令不会影响资源本身,而只是将其标记为在 Terraform 状态文件中受污染。
以下截图显示了taint命令的结果:

最后,在第 3 步中,我们执行 terraform apply 命令,执行后可以看到 Terraform 将删除并重新创建 Azure App Service,如下图所示:

我们可以在前面的截图中看到,Terraform 销毁了 App Service 资源,并且重新创建了它。
还有更多……
要进一步查看,我们可以通过执行 terraform state show 命令,在终端显示该资源在 Terraform 状态文件中的标记状态,该命令将在命令终端显示状态文件的内容(文档链接:www.terraform.io/docs/commands/state/show.html),如下所示:
terraform state show azurerm_app_service.app
以下截图显示了此命令的结果:

我们可以看到资源 App Service 已被标记为 tainted。
我们已使用terraform state命令显示状态的内容,强烈建议不要手动读取和修改状态文件,详情请参阅此处:www.terraform.io/docs/state/index.html#inspection-and-modification
此外,为了取消应用terraform taint命令的污点标志,我们可以执行反向命令terraform untaint。此命令可以这样执行:
terraform untaint azurerm_app_service.app
然后,如果我们执行terraform plan命令,可以看到没有变化,如下截图所示:

我们在此截图中看到,untaint命令已取消了taint命令的效果,并且在执行plan命令期间,不会对基础设施应用任何更改。
另请参见
-
terraform taint命令的文档可在此处找到:www.terraform.io/docs/commands/taint.html -
terraform untaint命令的文档可在此处找到:www.terraform.io/docs/commands/untaint.html -
terraform state命令的文档可在此处找到:www.terraform.io/docs/commands/state/index.html -
一篇很好地解释了
taint和untaint命令的文章可以在此找到:www.devopsschool.com/blog/terraform-taint-and-untaint-explained-with-example-programs-and-tutorials/
生成依赖图
Terraform 的一个有趣功能之一是能够生成资源依赖关系的依赖图,该依赖关系在 Terraform 配置中提到。
在此示例中,我们将看到如何生成和可视化此依赖图。
准备工作
对于此示例,我们需要使用一个名为Graphviz的第三方绘图工具,可以在graphviz.gitlab.io/download/下载。您需要下载并安装适合您操作系统的软件包。
此外,举例来说,我们将采用在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP04/sample-app上提供的 Terraform 配置。
如何操作…
要生成依赖图,请执行以下步骤:
- 执行
terraform graph命令:
terraform graph | dot -Tsvg > graph.svg
- 打开文件资源管理器,进入包含 Terraform 配置文件的文件夹,并打开名为
graph.svg的文件。
工作原理…
在 步骤 1 中,我们将执行 terraform graph 命令。然后,我们将此图形命令的结果发送到之前使用 Graphviz 安装的 dot 工具。此 dot 工具将生成一个 graph.svg 文件,其中包含 Terraform 配置的图形表示。
在 步骤 2 中,我们打开 graph.svg 文件,并看到依赖图,如以下图所示:

在前面的图示中,我们可以看到变量、资源和提供者之间的依赖关系。
参见
-
terraform graph命令的文档可以在这里查看:www.terraform.io/docs/commands/graph.html -
关于 Graphviz 的文档可以在这里找到:
graphviz.gitlab.io/ -
有一个关于生成图形依赖关系的优秀视频,您可以在这里找到:
techsnips.io/snips/how-to-use-graphviz-with-terraform-to-visualize-your-infrastructure/
调试 Terraform 执行
当我们执行 Terraform 命令时,控制台的显示输出非常简单明了。
在本指南中,我们将学习如何在 Terraform 中启用调试模式,这将帮助我们显示有关其执行的更多信息。
准备工作
对于本指南,我们将使用位于 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP04/sample-app. 的 Terraform 配置。
此外,出于本演示的目的,我们将在 Windows 操作系统上运行它,但在其他操作系统上的操作完全相同。
如何操作……
要启用 Terraform 的调试模式,请执行以下步骤:
- 在 PowerShell 命令行终端中,执行以下命令:
$env:TF_LOG = "TRACE"
- 现在,我们可以执行 Terraform 工作流命令,并启用显示日志:
> terraform init
> terraform plan -out=app.tfplan
> terraform apply app.tfplan
它是如何工作的……
在 步骤 1 中,我们创建了一个 Terraform 环境变量 TF_LOG,该变量启用 Terraform 的详细模式,表示我们希望看到 Terraform 执行的所有跟踪信息。
在这篇指南中,我们使用了 $env 命令来设置此环境变量,因为我们在 Windows 上工作。当然,您可以在其他操作系统上使用正确的语法执行相同的操作。
然后,在 步骤 2 中,我们执行 Terraform 工作流命令,并可以在输出中看到该执行的所有跟踪,如以下截图所示:

在这张截图中,这是 Terraform 执行过程的摘录,您可以看到 Terraform 执行过程中的所有步骤。
还有更多……
不仅可以在控制台输出中显示所有这些跟踪信息,还可以将它们保存在一个文件中。
为此,只需创建第二个环境变量 TF_LOG_PATH,其值将是日志文件的路径。实际上,日志通常非常冗长,并且在控制台输出中很难阅读。因此,我们更倾向于将日志输出写入文件,以便更容易地阅读。
此外,要禁用这些跟踪,必须通过将 TF_LOG 环境变量赋为空值来清空该变量,如下所示:
$env:TF_LOG = ""
参见
-
关于 Terraform 调试的文档可以在这里查看:
www.terraform.io/docs/internals/debugging.html -
关于 Terraform 环境变量的文档可以在这里查看:
www.terraform.io/docs/commands/environment-variables.html
使用模块共享 Terraform 配置
近年来,开发人员和软件工厂面临的真正挑战是停止编写在应用程序之间,甚至在团队之间重复的代码。因此,出现了可以在多个应用程序中轻松重用、并且能够在多个团队之间共享的语言、框架和软件包(如 NuGet、NPM、Bower、PyPI、RubyGems 等)。在基础设施即代码(IaC)领域,我们也面临着相同的代码结构问题、代码的同质化及其在公司内的共享问题。
我们在第二章《编写 Terraform 配置》的在多个环境中配置基础设施配方中学到了 Terraform 配置结构的一些拓扑结构,部分解答了如何合理构建 Terraform 配置的问题。但这并不是终点——Terraform 还允许你创建模块,进而在多个应用程序和多个团队之间共享 Terraform 配置。
本章将学习模块的主要阶段,包括:Terraform 模块的创建、使用和发布。我们将了解如何创建 Terraform 模块及其本地使用,以及如何快速引导模块代码的工作。我们还将研究如何使用公共注册表或 Git 仓库来使用 Terraform 模块。最后,我们将学习如何测试模块,并举一个 Terraform 模块在 Azure Pipelines 和 GitHub Actions 中的 CI/CD 流水线示例。
本章涵盖以下配方:
-
创建 Terraform 模块并本地使用
-
使用公共注册表中的模块
-
使用 GitHub 分享 Terraform 模块
-
在自定义模块中使用其他文件
-
使用 Terraform 模块生成器
-
生成模块文档
-
使用私有 Git 仓库共享 Terraform 模块
-
应用 Terrafile 模式来使用模块
-
使用 Terratest 测试 Terraform 模块代码
-
在 Azure Pipelines 中构建 Terraform 模块的 CI/CD
-
使用 GitHub Actions 为 Terraform 模块构建工作流
第六章:技术要求
本章中,对于某些配方,我们需要以下一些前置条件:
-
在你的计算机上安装 Node.js 和 NPM:下载网站在这里:
nodejs.org/en/。 -
拥有 GitHub 账户:如果你没有账户,可以在这里免费创建:
github.com/。 -
拥有 Azure DevOps 组织:你可以使用 Live 或 GitHub 账户在这里创建:
azure.microsoft.com/en-in/services/devops/。 -
具备基本的 Git 命令和工作流知识:相关文档可以在这里找到:
git-scm.com/doc。 -
了解 Docker:文档可以在这里找到:
docs.docker.com/。 -
在我们的工作站上安装 Golang:文档可以在这里找到:
golang.org/doc/install。我们将在使用 Terratest 测试 Terraform 模块食谱中看到安装的主要步骤。
本章的完整源代码可以在 GitHub 上找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05。
查看以下视频,了解代码如何运行:bit.ly/3ibKgH2
创建一个 Terraform 模块并在本地使用它
一个 Terraform 模块是一个 Terraform 配置,包含一个或多个 Terraform 资源。创建后,可以在多个 Terraform 配置文件中使用该模块,无论是本地使用还是远程使用。
在本节中,我们将介绍 Terraform 模块的基础知识,包括创建模块和在本地使用模块的步骤。
准备工作
为了开始这个食谱,我们将使用我们在第二章的在多个环境中配置基础设施食谱中已经编写的 Terraform 配置,源代码可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/sample-app。
我们将在本节中创建的模块将负责在 Azure 中提供一个服务计划、一个应用服务和一个应用洞察资源。其源代码可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/moduledemo/Modules/webapp。接下来,我们将编写一个使用该模块的 Terraform 配置,代码可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/moduledemo/MyApp。
如何操作…
要创建模块,请执行以下步骤:
-
在一个名为
moduledemo的新文件夹中,创建Modules和webapp文件夹。 -
在
webapp文件夹中,创建一个新的variables.tf文件,内容如下:
variable "resource_group_name" {
description = "Resource group name"
}
variable "location" {
description = "Location of Azure resource"
default = "West Europe"
}
variable "service_plan_name" {
description = "Service plan name"
}
variable "app_name" {
description = "Name of application"
}
- 然后,创建
main.tf文件,内容如下:
resource "azurerm_app_service_plan" "plan-app" {
name = var.service_plan_name
location = var.location
resource_group_name = var.resource_group_name
sku {
tier = "Standard"
size = "S1"
}
}
resource "azurerm_app_service" "app" {
name = var.app_name
location = var.location
resource_group_name = var.resource_group_name
app_service_plan_id = azurerm_app_service_plan.plan-app.id
app_settings = {
"INSTRUMENTATIONKEY" = azurerm_application_insights.appinsight-app.instrumentation_key
}
}
resource "azurerm_application_insights" "appinsight-app" {
name = var.app_name
location = var.location
resource_group_name = var.resource_group_name
application_type = "web"
}
- 最后,创建
output.tf文件,内容如下:
output "webapp_id" {
value = azurerm_app_service.app.id
}
output "webapp_url" {
value = azurerm_app_service.app.default_site_hostname
}
-
在
moduledemo文件夹内,创建一个名为MyApp的子文件夹。 -
在
MyApp文件夹内,创建一个main.tf文件,内容如下:
resource "azurerm_resource_group" "rg-app" {
name = "RG_MyAPP_demo"
location = "West Europe"
}
module "webapp" {
source = "../Modules/webapp"
service_plan_name = "spmyapp"
app_name = "myappdemo"
location = azurerm_resource_group.rg-app.location
resource_group_name = azurerm_resource_group.rg-app.name
}
output "webapp_url" {
value = module.webapp.webapp_url
}
它是如何工作的…
在步骤 1中,我们创建了moduledemo目录,里面包含所有模块的代码,每个模块有一个子目录。因此,我们为本教程创建了一个WebApp子目录,它将包含webapp模块的 Terraform 配置。接下来在步骤 2、3和4中,我们创建了模块代码,这是标准的 Terraform 配置,包含以下文件:
-
main.tf:该文件包含将由模块提供的资源的代码。 -
variables.tf:该文件包含模块所需的输入变量。 -
outputs.tf:该文件包含可以在主 Terraform 配置中使用的模块输出。
在步骤 5中,我们创建了将包含应用程序 Terraform 配置的目录。最后,在步骤 6中,我们通过main.tf文件创建了应用程序的 Terraform 配置。
在此文件的代码中,我们有三个 Terraform 元素:
-
其中有 Terraform 的
azurerm_resource_group资源,它提供了一个资源组。 -
使用我们创建的模块的 Terraform 配置,采用
module "<module name>"表达式。在这个模块类型的块中,我们使用了 source 属性,其值是包含webapp模块的目录的相对路径。
请注意,如果某些模块的变量已定义默认值,则在某些情况下,调用模块时无需实例化这些变量。
- 我们还获得了 Terraform 输出
webapp_url,它获取模块的输出,并将其作为我们主 Terraform 配置的输出。
还有更多…
在所有这些步骤结束后,我们获得了以下目录树:

要应用此 Terraform 配置,您需要在命令终端中导航到包含 Terraform 配置的MyApp文件夹,然后执行以下 Terraform 工作流命令:
terraform init
terraform plan -out=app.tfplan
terraform apply app.tfplan
当执行terraform init命令时,Terraform 会获取模块的代码,从而将其配置与应用程序的配置集成,如下截图所示:

最后,在执行terraform apply命令结束时,输出的值会显示在终端中,如下截图所示:

我们的 Terraform 配置因此获取了模块的输出,并将其作为主代码的输出。
在这个教程中,我们展示了创建 Terraform 模块及其本地使用的基本知识。在本章中,我们将看到如何生成模块的结构,以及如何在使用 Terraform 模块生成器教程中使用远程模块。
另见
-
模块创建的文档可通过
www.terraform.io/docs/modules/index.html查看。 -
关于模块的一般文档可以在
www.terraform.io/docs/configuration/modules.html找到。 -
Terraform 的模块创建学习实验室可通过
learn.hashicorp.com/terraform/modules/creating-modules访问。
使用公共注册表中的模块
在前一个教程中,我们学习了如何创建一个模块,以及如何编写使用此模块的本地 Terraform 配置。
为了便于 Terraform 配置的开发,HashiCorp 设置了一个公共的 Terraform 模块注册表。
这个注册表实际上解决了几个问题,例如以下几点:
-
通过搜索和筛选提高可发现性
-
通过合作伙伴验证过程提供的质量
-
清晰高效的版本控制策略,否则在其他现有模块源(HTTP、S3 和 Git)中无法普遍解决
这些在此注册表中发布的公共模块是由云服务提供商、出版商、社区,甚至是希望公开分享其模块的个人用户开发的。在本教程中,我们将看到如何访问这个注册表,以及如何使用已在此公共注册表中发布的模块。
准备工作
在本教程中,我们将从零开始编写一个 Terraform 代码,它不需要任何特别的前提条件。
本教程的目的是在 Azure 中配置一个资源组和网络资源,包括一个虚拟网络和子网。我们将看到如何调用公共模块,但不会详细查看该模块的 Terraform 配置。
本教程的代码源可以在此访问:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/publicmodule。
如何操作…
要使用来自公共注册表的 Terraform 模块,请执行以下步骤:
-
在浏览器中,访问网址:
registry.terraform.io/browse/modules。 -
在此页面的左侧面板的“过滤器”列表中,选择 azurerm:

- 在结果列表中,点击第一个结果,即 Azure / 网络模块:

- 然后,在此模块的详细页面上,复制“使用”部分的代码:

- 最后,在你的工作站上创建一个新文件,
main.tf,然后粘贴之前的代码,并根据以下内容进行更新:
resource "azurerm_resource_group" "rg" {
name = "my-rg"
location = "West Europe"
}
module "network" {
source = "Azure/network/azurerm"
resource_group_name = azurerm_resource_group.rg.name
vnet_name = "vnetdemo"
address_space = "10.0.0.0/16"
subnet_prefixes = ["10.0.1.0/24"]
subnet_names = ["subnetdemo"]
}
它是如何工作的…
在步骤 1 到 步骤 2 中,我们浏览了 Terraform 的公共注册表,寻找一个能够为 Azure 提供资源配置的模块(使用 azurerm 过滤器)。
然后,在步骤 3 和 步骤 4 中,我们访问了 Azure 团队发布的网络模块的详细页面。
在步骤 5中,我们通过指定这些必要的输入变量和 source 属性来使用这个模块,该属性值为公共模块特定的别名,Azure/network/azurerm,由注册表提供。
还有更多内容…
我们在本食谱中已经看到,使用公共注册表中的模块能够节省开发时间。在我们的食谱中,我们使用了一个经过验证的模块,但你完全可以使用其他社区模块。
你可以通过在版本下拉列表中选择所需版本,来使用这些模块的版本控制:

因此,在模块调用中,使用 Version 属性并选择所需的版本号。
请注意,像所有模块或社区包一样,使用它们之前,必须通过手动检查其 GitHub 仓库中的代码,确保代码干净且安全。事实上,在每个模块中,都有一个指向 GitHub 仓库的链接,里面包含源代码。
在本章的使用 GitHub 分享 Terraform 模块食谱中,我们将学习如何将模块发布到公共注册表。
此外,在公司项目中使用模块之前,必须考虑到如果需要对模块进行修正或扩展,你需要在该模块的 GitHub 仓库中创建问题或发起拉取请求。这要求等待一段时间(验证等待时间和拉取请求合并时间),才能使用修复或请求的扩展。
然而,值得每天使用这些模块,因为它们非常实用,并能为演示、实验和沙盒项目节省大量时间。
我们在本食谱中已经看到了使用公共注册表的内容;在第八章中,我们将学习如何使用 Terraform 的私有模块注册表来提高协作。
另见
Terraform 模块注册表的文档可以在此找到:www.terraform.io/docs/registry/。
使用 GitHub 分享 Terraform 模块
在本章的创建 Terraform 模块并在本地使用食谱中,我们学习了如何创建模块,在前面的使用公共注册表中的模块食谱中,我们学习了如何使用来自公共注册表的模块。
在本食谱中,我们将学习如何通过将代码存储在 GitHub 上,将模块发布到公共注册表。
准备工作
要应用本食谱,我们需要拥有一个 GitHub 账户(这是目前唯一可用于发布公共模块的 Git 提供者),你可以在此处创建账户:github.com/join。此外,你还需要了解 Git 命令和工作流的基础知识(www.hostinger.com/tutorials/basic-git-commands)。
关于我们将要发布的模块代码,我们将使用本章第一个配方中创建的模块代码,源代码可以在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/moduledemo/Modules/webapp找到。
如何操作……
要在公共注册表中共享我们的自定义模块,请执行以下步骤:
- 在我们的 GitHub 帐户中,创建一个名为
terraform-azurerm-webapp的新存储库,并配置基本信息,如下图所示:

- 在本地工作站上,执行 Git 命令以克隆此存储库:
git clone https://github.com/mikaelkrief/terraform-azurerm-webapp.git
-
从
github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/moduledemo/Modules/webapp复制源代码,并将其粘贴到git clone命令创建的新文件夹中。 -
更新
Readme.md文件的内容,增加关于模块角色的更多描述。 -
提交并推送该文件夹中的所有文件;你可以使用 Visual Studio Code 或 Git 命令(
commit和push)执行此操作。 -
通过执行以下命令,添加并推送一个名为
v0.0.1的 Git 标签:
git tag v1.0.0
git push origin v1.0.0
-
在浏览器中,访问网址
registry.terraform.io/。 -
在此页面上,点击顶部菜单中的“Sign-in”链接:

- 在新弹出的窗口中,点击“Sign in with GitHub”按钮,如果提示,授权 HashiCorp 读取你的存储库:

- 一旦验证成功,点击顶部菜单中的“Publish”链接:

- 在下一页中,选择包含要发布模块代码的 mikaelkrief/terraform-azurerm-webapp 存储库,并勾选我同意使用条款的复选框:

- 点击“PUBLISH MODULE”按钮,并等待模块页面加载。
它是如何工作的……
在步骤 1和步骤 2中,我们在 GitHub 上创建了一个 Git 存储库并将其克隆到本地,而在步骤 3到步骤 6中,我们编写了该模块的 Terraform 配置(使用现有代码)。我们还编辑了将作为模块使用文档的Readme.md文件。
然后,我们进行了提交并将代码推送到远程 Git 存储库,并添加了一个标签,该标签形式为vX.X.X,用于为模块进行版本控制。
最终,在步骤 7到步骤 12中,我们通过在注册表中使用我们的 GitHub 凭据登录,并选择包含模块代码的存储库,成功将该模块发布到公共注册表。
注册表会自动检测模块版本与所推送的 Git 标签(在步骤 6中)之间的关系。
完成所有这些步骤后,模块将可用,并显示在 Terraform 的公共注册表中,如下图所示:

模块是公开可访问的;使用说明显示在右侧面板中,Readme.md 文本作为文档显示在页面内容中。
还有更多内容…
关于将包含模块代码的存储库的名称,它必须按以下方式组成:
terraform-<provider>-<name>
同样,对于 Git 标签,它必须采用 vX.X.X 的格式才能集成到注册表中。要了解更多关于模块简历的信息,请参阅文档:www.terraform.io/docs/registry/modules/publish.html#requirements。
发布后,可以通过从“管理模块”下拉菜单中选择“删除模块”来删除模块:

请小心:从注册表中删除后,模块将无法使用。
另见
-
模块发布文档可以在这里找到:
www.terraform.io/docs/registry/modules/publish.html。 -
关于注册表 API 的文档可以在这里找到:
www.terraform.io/docs/registry/api.html。
在自定义模块中使用另一个文件
在本章的 创建 Terraform 模块并在本地使用 中,我们研究了创建一个基本 Terraform 模块的步骤。
我们可能会遇到需要在模块中使用另一个文件的场景,该文件并不描述基础设施(例如 .tf 扩展名),例如,当模块需要在本地执行一个脚本以操作内部程序时。
在本教程中,我们将研究如何在 Terraform 模块中使用另一个文件。
准备工作
对于本教程,我们不需要任何先决条件;我们将从头开始编写模块的 Terraform 配置。
本教程的目标是创建一个 Terraform 模块,该模块将执行一个 Bash 脚本,脚本将在本地计算机上执行操作(在本教程中,hello world 显示即可)。
由于我们将以 Bash 脚本为例,因此我们将在 Linux 系统下运行 Terraform。
需要牢记的是,像这样的供应程序通过假设 Terraform 运行的系统上安装了 Bash,降低了配置的可重用性。否则,通常 Terraform 并不受此限制,因为它提供了不同操作系统和架构的构建,并支持跨平台运行。
本教程中创建的模块的源代码可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/moduledemo/Modules/execscript。
如何操作…
执行以下步骤以在模块内部使用文件:
- 在名为
execscript的新文件夹(位于Modules文件夹中)内,创建一个新的文件script.sh,并写入以下内容:
echo "Hello world"
- 在此模块中创建一个
main.tf文件,并在其中编写以下代码:
resource "null_resource" "execfile" {
provisioner "local-exec" {
command = "${path.module}/script.sh"
interpreter = ["/bin/bash"]
}
}
- 然后,在 Terraform 配置中,使用以下代码调用此模块:
module "execfile" {
source = "../Modules/execscript"
}
- 最后,在命令行终端中,导航到 Terraform 配置的文件夹,并使用以下命令执行基本的 Terraform 工作流:
terraform init
terraform plan -out="app.tfplan"
terraform apply app.tfplan
它是如何工作的……
在步骤 1和步骤 2中,我们创建了一个使用local_exec资源(www.terraform.io/docs/provisioners/local-exec.html)在本地执行脚本的模块。
local_exec执行的是存储在模块中的script.sh文件中的脚本。为了配置相对于script.sh文件的路径,在执行 Terraform 时可以使用path.module表达式,它返回相对于模块的完整路径。
然后,在步骤 3中,我们编写了调用此模块的 Terraform 配置。最后,在步骤 4中,我们运行了该代码的 Terraform,并得到了以下结果:

你可以看到脚本执行成功,并且在控制台中显示了Hello World。
还有更多……
让我们看看如果我们没有在此模块的代码中使用path.module表达式,而是以以下方式编写模块代码会发生什么:
resource "null_resource" "execfile" {
provisioner "local-exec" {
command = "script.sh"
interpreter = ["/bin/bash"]
}
}
执行apply命令时,将会发生以下错误:

这是因为 Terraform 在main.tf文件中运行,而该文件无法访问模块目录中script.sh文件的relatif路径。
另见
关于Path.Module表达式的文档,请参见:www.terraform.io/docs/configuration/expressions.html#references-to-named-values。
使用 Terraform 模块生成器
我们学会了如何创建、使用和共享 Terraform 模块,并研究了模块文件结构的最佳实践,其中包括有一个主文件、一个变量文件和一个包含模块输出的文件。在使用 GitHub 共享 Terraform 模块食谱中,我们还讨论了如何通过Readme.md文件记录模块的使用。
除了这些标准文件外,我们还可以添加脚本、测试(稍后我们将在使用 Terratest 测试 Terraform 模块代码食谱中看到)和其他文件。
对于需要使用 Terraform 提供大量资源的大型基础设施公司项目,我们需要创建大量的 Terraform 模块。
为了简化模块结构的创建,微软发布了一个工具,允许我们生成 Terraform 模块的基本结构(也称为模板)。
在本教程中,我们将看到如何使用模块生成器创建模块的基础结构。
准备工作
使用这个模块生成器的前提软件如下:
-
本地安装 Node.js(6.0+);其下载文档可以在
nodejs.org/en/download/找到。 -
然后,通过执行以下命令安装
npm包 Yeoman (www.npmjs.com/package/yo):
npm install -g yo
为了说明这个步骤,我们将使用这个生成器来创建一个负责配置资源组的模块结构。
本教程生成的模块样本可以在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/generatedmodule找到。
如何操作…
要生成 Terraform 模块的结构,请执行以下步骤:
- 在命令行终端中,执行以下命令:
npm install -g generator-az-terra-module
-
创建一个名为
terraform-azurerm-rg的新文件夹。 -
然后,在这个文件夹中,在命令行终端中执行此命令:
yo az-terra-module
- 最后,生成器会提出一些问题;按照下面的截图输入相应的回答:

它是如何工作的…
在步骤 1中,我们安装了模块生成器,它是一个名为 generator-az-terra-module 的 npm 包。所以我们使用了经典的 npm 命令行,它会全局安装包,也就是说,安装到整台机器上。
在步骤 2中,我们创建了一个将包含模块代码的文件夹;在我们的教程中,我们使用了 Terraform 注册表要求的命名规范。
在步骤 3 和 4中,我们执行了 az-terra-module 生成器。在执行过程中,这个生成器会提出一些问题,允许定制将生成的模块模板。第一个问题是关于模块名称的。第二个问题是关于模块是否存在于 npm 中;我们回答了否。接下来的三个问题涉及模块元数据。最后,最后一个问题是问我们是否想在模块代码中添加一个 Dockerfile,用于运行模块的测试——我们回答了是。
在所有这些问题的最后,生成器将把所有必要的文件复制到我们目录中的模块:

如你在此屏幕中所见,生成器会在终端中显示已创建的文件夹和文件列表。
最后,在我们的文件资源管理器中,我们可以看到所有这些文件:

我们的 Terraform 模块的基本结构已经生成。
还有更多…
在本教程中,我们看到可以生成 Terraform 模块的文件结构。在此生成器执行完成后,创建的模块目录将包含该模块的 Terraform 文件,随后将使用模块的代码进行编辑。此文件夹还将包含其他测试文件和一个 Dockerfile,其用途将在本章的 使用 Terratest 测试 Terraform 模块 食谱中介绍。
此外,虽然此生成器是由 Microsoft 发布的,但它可以用于生成任何 Terraform 模块的结构,即使它与 Azure 无关。
另见
-
模块生成器的源代码可以在 GitHub 上找到,网址为
github.com/Azure/generator-az-terra-module。 -
生成器使用文档可以在
docs.microsoft.com/en-us/azure/developer/terraform/create-a-base-template-using-yeoman找到。 -
Yeoman 文档可以在
yeoman.io/上找到。 -
生成器的
npm包可以在www.npmjs.com/package/generator-az-terra-module上找到。
生成模块文档
从之前的教程中我们已经了解到,在 Terraform 模块的组成中,有输入变量以及输出变量。
和所有发布给其他团队或公开使用的包一样,记录 Terraform 模块的文档非常重要。
这份文档的问题在于,每次更改时都需要手动更新文档,因此很快就会变得过时。
在 Terraform 工具箱中的所有工具中,有一个名为 terraform-docs 的开源跨平台工具,可以自动生成 Terraform 模块的文档。
在本教程中,我们将讨论如何使用 terraform-docs 自动生成模块的 markdown 文档。
准备工作
对于本教程,我们将生成在本章的 创建 Terraform 模块并在本地使用 食谱中创建的模块的文档,该模块使我们能够在 Azure 中创建一个 Web 应用,源代码位于这里: github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/moduledemo/Modules/webapp。
如果你使用的是 Windows 操作系统,你需要按照以下文档安装Chocolatey:chocolatey.org/install。我们为 webapp 模块生成的文档可以在 github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP05/moduledemo/Modules/webapp/Readme.md 查阅。
如何操作…
按照以下步骤生成模块文档:
- 如果你使用的是 Linux 操作系统,请在命令行终端中执行以下脚本:
curl -L https://github.com/segmentio/terraform-docs/releases/download/v0.9.1/terraform-docs-v0.9.1-linux-amd64 -o terraform-docs-v0.9.1-linux-amd64
tar -xf terraform-docs-v0.9.1-linux-amd64
chmod u+x terraform-docs-v0.9.1-linux-amd64
sudo mv terraform-docs-v0.9.1-linux-amd64 /usr/local/bin/terraform-docs
如果你使用的是 Windows 操作系统,请执行以下脚本:
choco install terraform-docs -y
- 执行以下脚本来测试安装:
terraform-docs --version
- 在命令行终端中,进入
moduledemo文件夹并执行以下命令:
terraform-docs markdown Modules/webapp/ > Modules/webapp/Readme.md
它是如何工作的…
在 第 1 步 中,我们根据操作系统安装 terraform-docs。对于 Linux,提供的脚本从 GitHub 下载 terraform-docs 包,使用 TAR 工具解压,使用 chmod 赋予执行权限,最后将其复制到本地目录 /usr/bin/local(该目录已在 PATH 环境变量中配置)。
以下截图显示了在 Linux 中的安装过程:

对于 Windows,脚本使用 Chocolatey 的 choco install 命令来下载 terraform-docs 包。
以下截图显示了在 Windows 中的安装过程:

然后,在 第 2 步 中,我们通过运行 terraform-docs 并添加 --version 选项来检查其安装情况。此命令将显示已安装的 terraform-docs 版本,如以下截图所示:

最后,在 第 3 步 中,我们执行 terraform-docs,在第一个参数中指定文档的格式类型。在我们的案例中,我们希望它是 markdown 格式。然后,在第二个参数中,指定 modules 目录的路径。在这个阶段,我们可以这样执行命令,并且在执行过程中,文档会显示在控制台上,如以下截图所示:

但为了进一步操作,我们添加了 > Modules/webapp/Readme.md 命令,它表示生成的文档内容将写入模块目录中创建的 Readme.md 文件。
执行该命令结束后,可以在模块文件夹中看到一个新的 Readme.md 文件,里面包含模块文档。生成的文档由模块中使用的提供程序、输入变量和输出组成。
还有更多…
在我们的食谱中,我们选择生成 Markdown 格式的文档,但也可以生成 JSON、XML、YAML 或文本(漂亮的)格式。为此,你需要在terraform-docs命令中添加格式选项。欲了解更多可用的生成格式,请阅读文档:github.com/segmentio/terraform-docs/blob/master/docs/FORMATS_GUIDE.md。
你还可以通过自动化生成文档来改善你的工作流程,每次在 Git 中提交代码时触发terraform-docs的执行。为此,你可以使用一个预提交的 Git 钩子,具体可以参考文档中的说明:github.com/segmentio/terraform-docs/blob/master/docs/USER_GUIDE.md#integrating-with-your-terraform-repository。
此外,要获取最新版本的terraform-docs,请查看这里的发布:github.com/segmentio/terraform-docs/releases,以及这里的CHANGELOG:github.com/segmentio/terraform-docs/blob/master/CHANGELOG.md,以查看变更内容。
如果你想像我们在本章的使用 GitHub 共享 Terraform 模块食谱中所看到的那样,将你的模块发布到 Terraform 注册表中,你不需要生成这些文档,因为它已经包含在注册表的功能中了。
另见
-
terraform-docs的源代码可以在这里找到:github.com/segmentio/terraform-docs。 -
Chocolatey 的
terraform-docs包页面可以在这里找到:chocolatey.org/packages/Terraform-Docs。
使用私人 Git 仓库共享 Terraform 模块
在本章专门讲解 Terraform 模块的内容中,我们已经看到,可以将模块的代码放入 GitHub 仓库中,以便将其发布到 Terraform 公共注册表中。
然而,在企业中,确实需要创建模块,但又不希望将这些模块的代码公开,可以通过将它们归档在 GitHub 的公共仓库中(即每个人都可以访问的仓库)来实现。
你需要知道的是,Terraform 模块有几种不同的源类型,详细内容请见文档:www.terraform.io/docs/modules/sources.html。
在这个食谱中,我们将学习如何通过一个私人 Git 仓库公开一个 Terraform 模块。也就是说,Git 仓库可以是内部安装的(即所谓的本地部署)或云模式的 SaaS,但需要身份验证。
准备工作
对于这个示例,我们将使用Azure Repos(Azure DevOps)中的一个 Git 仓库,它是免费的,并且需要认证才能访问。有关更多信息以及如何创建免费的 Azure DevOps 账户,请访问azure.microsoft.com/en-us/services/devops/。
作为前提条件,我们需要一个已经创建的项目,例如,Terraform-modules,它将包含所有模块的 Git 仓库。
下一张截图展示了创建该 Azure DevOps 项目的表单:

本示例的目的是不专注于 Azure DevOps 的使用;我们仅使用它作为私有仓库的示例。
你还需要了解 Git 的命令和工作流程的基础知识:www.hostinger.com/tutorials/basic-git-commands。
关于我们将放入 Azure Repos 的模块代码,我们将使用本章第一个示例中创建的模块代码,其源代码可在这里找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/moduledemo/Modules/webapp。
如何操作…
为了使用私有模块仓库,我们需要执行以下步骤:
- 在 Azure DevOps 项目
Terraform-modules中,创建一个名为terraform-azurerm-webapp的新 Git 仓库,并进行基本配置,如下图所示:

- 在本地工作站上,执行 Git 命令以克隆该仓库:
git clone https://dev.azure.com/<account>/Terraform-modules/_git/terraform-azurerm-webapp
-
在第一次操作时,你需要输入你的 Azure DevOps 登录名和密码以进行身份验证。
-
从
github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/moduledemo/Modules/webapp复制源代码,并将其粘贴到 Git 克隆创建的新文件夹中。 -
更新
Readme.md文件的内容,增加模块角色的描述。 -
提交并推送此文件夹中的所有文件;要执行此操作,可以使用 VS Code 或 Git 命令:
git add .
git commit -m "add code"
git push origin master
- 添加并推送此提交的 Git 标签,名称为
v0.0.1,通过执行以下命令:
git tag v1.0.0
git push origin v1.0.0
也可以通过 Azure Repos 的网页界面在仓库的 Tags 标签页中执行此操作。
- 最后,在 Terraform 的
main.tf文件中,写入以下代码以使用该模块:
resource "azurerm_resource_group" "rg-app" {
name = "RG_MyAPP_Demo2"
location = "West Europe"
}
module "webapp" {
source = "git::https://dev.azure.com/BookLabs/Terraform-modules/_git/terraform-azurerm-webapp?ref=v1.0.0"
service_plan_name = "spmyapp2"
app_name = "myappdemobook2"
location = azurerm_resource_group.rg-app.location
resource_group_name = azurerm_resource_group.rg-app.name
}
output "webapp_url" {
value = module.webapp.webapp_url
}
它是如何工作的…
在第 1 步和第 2 步中,我们在 Azure Repos 中创建了一个 Git 仓库并将其克隆到本地。然后,在第 3 步到第 7 步中,我们为该模块编写了 Terraform 配置(使用已有的代码)。我们还编辑了将作为模块使用文档的 Readme.md 文件。接着,我们提交并推送了代码到远程 Git Azure 仓库。以下截图展示了远程仓库在 Azure Repos 中的情况:

最后,我们添加了一个 Git 标签,格式为 vX.X.X,用于版本管理模块。
最后,在第 8 步中,我们编写了 Terraform 配置,通过 Git 类型的源来远程使用这个模块。为此,我们在模块的 source 属性中指定了仓库的 Git URL。除了这个 URL,我们还添加了 ref 参数,并为其指定了我们创建的 Git 标签作为值。
在执行 terraform init 命令时,Terraform 会将仓库克隆到本地:

模块代码将被克隆到 Terraform 上下文目录中,如下图所示:

webapp 模块将被下载到 .terraform 文件夹中。
还有更多内容……
在本食谱中,大部分步骤与之前在使用 GitHub 分享 Terraform 模块食谱中所学的步骤相同,其中我们将模块代码存储在 GitHub 中并将其共享到公共注册表。不同之处在于,在本食谱的第 8 步中,我们在 source 属性中填写了 Git 仓库的 URL。
使用私有 Git 仓库的优势,一方面是只有具有权限的人才能访问该仓库;另一方面,在我们为模块调用 URL 设置的 ref 参数中,我们使用了一个特定版本的模块,使用的是 Git 标签。我们也可以完全指定一个特定的 Git 分支,这在我们想要开发模块而不影响主分支时非常有用。
我们也可以将模块的代码存储在 GitHub 仓库中,并将 source 属性填写为 GitHub 仓库的 URL,具体说明请见这篇文档:www.terraform.io/docs/modules/sources.html#github。
在本食谱中,我们以 Azure DevOps 中的 Git 仓库为例,但它也能很好地与其他 Git 仓库提供商(如 Bitbucket)兼容(www.terraform.io/docs/modules/sources.html#bitbucket)。
关于 Git 仓库的身份验证,您可以查看这篇文档:www.terraform.io/docs/modules/sources.html#generic-git-repository,了解通过 HTTPS 或 SSH 访问和验证 Git 仓库的信息。
另见
模块源文档可以在这里找到:www.terraform.io/docs/modules/sources.html。
应用 Terrafile 模式以使用模块
在本章的教程中,我们已经学习了如何创建 Terraform 模块,并且如何使用它们,无论是本地使用还是通过公共注册表或 Git 仓库远程使用。
然而,当你拥有一个使用多个模块的 Terraform 配置时,管理这些模块可能变得复杂。特别是当这些模块的版本发生变化时,我们需要遍历所有 Terraform 配置来进行版本更新。此外,我们也无法全面了解这个 Terraform 配置中调用的所有模块及其版本。
类似于经典的包管理工具(NPM 和 NuGet),有许多人提出了一种模式,允许用户将 Terraform 配置中使用的模块的配置集中在一个名为Terrafile的文件中。
在本教程中,我们将学习如何使用 Terrafile 模式来管理 Terraform 模块的源。
准备工作
对于这个教程,我们将使用已经编写好的 Terraform 源代码,代码可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP05/terrafile/initial/main.tf。这个 Terraform 配置一开始是经典配置的——它调用了多个模块,并且在每次调用这些模块时,我们使用了 GitHub 仓库的 URL 作为 source 属性。
此外,我们还将执行用 Ruby 编写的代码,并使用 Rake 工具(github.com/ruby/rake)。为此,我们需要在计算机上安装 Ruby。安装文档可以在这里找到:www.ruby-lang.org/en/documentation/installation/。不过,不需要提前了解 Ruby;本教程中的完整脚本已经提供。
本教程的目标是通过将模块的管理集中化,集成 Terrafile 模式。此教程的源代码可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/terrafile。
如何操作...
执行以下步骤以使用 Terrafile 模式:
-
将
github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP05/terrafile/initial/main.tf中的main.tf内容复制到一个新文件夹中。 -
在这个新文件夹中,创建一个名为
Terrafile(无扩展名)的新文件,内容如下:
rg-master:
source: "https://github.com/mikaelkrief/terraform-azurerm-resource-group.git"
version: "master"
webapp-1.0.0:
source: "https://github.com/mikaelkrief/terraform-azurerm-webapp.git"
version: "v1.0.0"
network-3.0.1:
source: "https://github.com/Azure/terraform-azurerm-network.git"
version: "v3.0.1"
- 创建另一个新文件,
Rakefile(没有扩展名),其内容如下(完整的源代码见github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP05/terrafile/new/Rakefile):
.....
desc 'Fetch the Terraform modules listed in the Terrafile'
task :get_modules do
terrafile = read_terrafile
create_modules_directory
delete_cached_terraform_modules
terrafile.each do |module_name, repository_details|
source = repository_details['source']
version = repository_details['version']
puts "[*] Checking out #{version} of #{source} ...".colorize(:green)
Dir.mkdir(modules_path) unless Dir.exist?(modules_path)
Dir.chdir(modules_path) do
#puts "git clone -b #{version} #{source} #{module_name} &> /dev/null".colorize(:green)
'git clone -q -b #{version} #{source} #{module_name}'
end
end
end
- 在
main.tf中,使用以下内容更新所有源模块属性:
module "resourcegroup" {
source = "./modules/rg-master"
...
}
module "webapp" {
source = "./modules/webapp-1.0.0"
...
}
module "network" {
source = "./modules/network-3.0.1"
...
}
- 在命令行终端中,在此文件夹内执行以下脚本:
bundle install
rake get_modules
它是如何工作的……
Terrafile 模式的机制是,在模块调用中不直接使用 Git 源,而是在一个 Terrafile YAML 格式的文件中引用它们。在 Terraform 配置中,在模块调用时,我们改为使用相对于modules文件夹的本地路径。最后,在执行 Terraform 工作流之前,我们执行一个脚本,遍历此Terrafile文件,并在其指定的文件夹中本地克隆每个引用的模块(该文件夹在模块调用中)。
在步骤 1中,我们创建了Terrafile文件,该文件采用 YAML 格式,并包含我们将在 Terraform 配置中使用的模块的仓库。对于每个模块,我们指定以下内容:
-
模块将被复制到的文件夹名称
-
模块的 Git 仓库的 URL
-
它的版本,即 Git 标签或分支
在步骤 2中,我们编写了名为 Rakefile 的 Ruby Rake 脚本,该脚本执行时会浏览 Terrafile 并对所有模块执行git clone命令,将它们克隆到指定文件夹中。
然后,在步骤 3中,我们修改了 Terraform 配置,调用这些模块,使用的不是 Git URL,而是之前在 Terrafile 中指定文件夹的相对路径。
最后,在步骤 4中,我们通过调用此脚本的get_modules函数来执行 Rakefile 脚本,该函数会在各自的文件夹中进行 Git 克隆所有这些模块。
完成这些步骤后,我们可以使用init、plan和apply等经典的 Terraform 工作流命令。
还有更多……
正如我们在本食谱中学到的,我们创建了一个Terrafile文件,它作为我们在 Terraform 配置中将使用的模块的仓库。它允许更好地管理和维护将要使用的模块和版本。
在这个 Terrafile 中,对于每个模块,我们指定了它的目标文件夹,正如您所看到的,我们在文件夹名称中添加了版本号。因此,文件夹名称是唯一的,这将允许我们在 Terraform 配置中使用同一模块的多个版本。以下代码展示了一个包含同一模块的两个不同版本的 Terrafile 摘录:
network-3.0.1:
source: "https://github.com/Azure/terraform-azurerm-network.git"
version: "v3.0.1"
network-2.0.0:
source: "https://github.com/Azure/terraform-azurerm-network.git"
version: "v2.0.0"
此外,它还允许您在必要时指定授权的 Git 凭据来克隆模块代码。但请注意,不要在此文件中写入密码,因为该文件会被存档到 Git 仓库中。
在本食谱中,提供了 Rakefile 脚本,并且它可以在 Terrafile 模式的原始文章中找到(bensnape.com/2016/01/14/terraform-design-patterns-the-terrafile/)。你可以根据需要自由调整它。
最后,Terrafile 模式的核心不在于脚本、语言或所使用的格式,而在于其工作原理。比如说,这个 Rakefile 有替代的脚本和工具,比如可以在github.com/claranet/python-terrafile找到的 Python 脚本,或者可以在github.com/coretech/terrafile找到的 Go 编写的工具。
另见
-
Terrafile 模式的主要参考文章可以在这里找到:
bensnape.com/2016/01/14/terraform-design-patterns-the-terrafile/。 -
Python Terrafile 包可以在这里找到:
pypi.org/project/terrafile/并且其使用方法可以在这里查看github.com/claranet/python-terrafile。 -
用 Go 编写的 Terrafile 工具可以在这里获取:
github.com/coretech/terrafile。
使用 Terratest 测试 Terraform 模块代码
在开发将用于多个 Terraform 配置并与其他团队共享的 Terraform 模块时,有一个步骤常常被忽略,那就是模块的测试。
在 Terraform 框架和测试工具中,有一个Terratest框架,由Gruntwork社区(gruntwork.io/static/)创建,它非常流行,并允许对用 Go 语言编写的代码进行测试。
在本食谱中,我们将学习如何使用 Terratest 编写并运行针对 Terraform 配置和模块的集成测试。
准备工作
Terratest测试框架是用 Golang(Go 语言)编写的,测试在 Go 运行时上运行。因此,作为前提条件,我们需要通过访问golang.org/来安装 Go。
Terratest 所需的最小 Go 版本可以在这里查看:terratest.gruntwork.io/docs/getting-started/quick-start/#requirements。
以下是安装 Go 的一些重要步骤:对于 Windows 操作系统,你可以使用 Chocolatey 安装 Golang 包(chocolatey.org/packages/golang),通过执行以下命令:
choco install golang -y
对于 Linux 操作系统,运行以下脚本:
GOLANG_VERSION="1.14.6"
GOLANG_OS_ARCH=linux-amd64
mkdir "$HOME/go"
mkdir "$HOME/go/bin"
mkdir "$HOME/go/src"
curl -Os https://storage.googleapis.com/golang/go${GOLANG_VERSION}.${GOLANG_OS_ARCH}.tar.gz >/dev/null 2>&1 &&
tar -zxvf go${GOLANG_VERSION}.${GOLANG_OS_ARCH}.tar.gz -C /usr/local/ >/dev/null
# Refresh Go environment.
export GOPATH="$HOME/go"
export PATH="/usr/local/go/bin:$GOPATH/bin:$PATH"
上述脚本会创建 Go 的工作区目录(bin和src),然后下载 Go SDK,并设置环境变量,GOPATH和PATH。
如果你使用的是 Linux,你还需要安装gcc包,可以通过运行apt install gcc命令来安装。
这个食谱的目标是为一个非常简单的模块编写集成测试,我们将在这个食谱中编写该模块作为演示。
本章的源代码,包括模块及其测试,可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/testing-terratest。
如何实现…
这个食谱分为两部分。第一部分是关于编写模块及其测试,第二部分是关于执行测试。
为了编写模块及其测试,我们执行以下步骤:
-
我们创建了一个新的
module文件夹,该文件夹将包含模块的 Terraform 配置。 -
在这个
module文件夹中,我们创建了一个main.tf文件,其中包含以下代码:
variable "string1" {}
variable "string2" {}
## PUT YOUR MODULE CODE
##_____________________
output "stringfct" {
value = format("This is test of %s with %s", var.string1, upper(var.string2))
}
-
在这个
module文件夹中,我们在test文件夹内创建了fixture文件夹。 -
然后,在这个
fixture文件夹中,我们创建了一个main.tf文件,其中包含以下 Terraform 配置:
module "demo" {
source = "../../"
string1 = "module"
string2 = "terratest"
}
output "outmodule" {
value = module.demo.stringfct
}
- 在测试文件夹中,我们创建了一个
test_module.go文件,其中包含以下代码:
package test
import (
"testing"
"github.com/gruntwork-io/terratest/modules/terraform"
"github.com/stretchr/testify/assert"
)
func TestTerraformModule(t *testing.T) {
terraformOptions := &terraform.Options{
// path to the terraform configuration
TerraformDir: "./fixture",
}
// lean up resources with "terraform destroy" at the end of the test.
defer terraform.Destroy(t, terraformOptions)
// Run "terraform init" and "terraform apply". Fail the test if there are any errors.
terraform.InitAndApply(t, terraformOptions)
// Run `terraform output` to get the values of output variables and check they have the expected values.
output := terraform.Output(t, terraformOptions, "outmodule")
assert.Equal(t, "This is test of module with TERRATEST", output)
}
为了执行测试,我们执行以下步骤:
- 第一步是通过执行以下命令来下载
terratestGo 包:
go get github.com/gruntwork-io/terratest/modules/terraform
- 然后,在包含
test_module.go文件的目录中,我们执行以下命令:
go test -v
它是如何工作的…
在这个食谱的第一部分,我们使用 Terratest 框架开发了模块及其测试。在步骤 2中,我们编写了模块的代码,这非常简单,主要关注模块的输出。在步骤 3中,在fixture文件夹中,我们编写了一个 Terraform 配置,它本地使用该模块,并将用于测试模块。这个配置中的关键是确保模块有输出。事实上,在 Terratest 中,我们将使用这些输出测试模块是否正确返回了预期的值。
在步骤 4中,我们在一个用 Golang 编写的文件中编写了模块测试。代码如下:在代码的前几行,我们导入了运行测试所需的库,包括terratest和assert库。然后,我们创建了一个TestTerraformModule函数,该函数接受testing.T作为参数,testing.T是一个 Go 指针,表示这是一个测试代码。
以下是该函数代码的详细内容,包含五行代码。
在第一行,我们定义了测试选项,指定包含将在测试过程中执行的 Terraform 配置的文件夹:
terraformOptions := &terraform.Options{
// path to the terraform configuration
TerraformDir: "./fixture",
}
然后,我们定义了terrafom.Destroy函数,该函数允许我们在测试结束时执行terraform destroy命令,如下代码所示:
defer terraform.Destroy(t, terraformOptions)
然后,我们调用了terraform.InitAndApply函数,该函数允许我们执行terraform init和apply命令,如下代码所示:
terraform.InitAndApply(t, terraformOptions)
执行apply命令后,我们将获取输出的值,称为outmodule:
output := terraform.Output(t, terraformOptions, "outmodule")
最后,我们使用assert,将之前获取的输出值与我们预期的值进行测试:
assert.Equal(t, "This is test of module with TERRATEST", output)
然后,在本食谱的第二部分,我们开始测试的执行。第一步是使用go get <package source>命令下载 Terratest Go 包。
我们还可以运行go get -v -t -d ./...命令来获取所有所需的包依赖。
然后,在test文件夹内,我们通过执行以下命令运行测试:
go test -v
在执行过程中,Terratest 将按顺序执行以下操作:
-
在位于 fixture 文件夹中的 Terraform 测试代码上执行
terraform init和terraform apply命令。 -
获取
outmodule输出的值。 -
将此值与预期值进行比较。
-
执行
terraform destroy命令。 -
显示测试结果。
下一张截图展示了我们模块的测试执行情况:

你可以在这个截图中看到go test -v命令执行的不同操作以及测试结果。
还有更多…
Terratest 允许你对 Terraform 配置执行集成测试,拥有一个强大的流程,可以使你配置资源、执行测试,并最终销毁资源。
我们在本食谱的前提条件中看到,Golang 开发环境的设置需要执行的操作可能因操作系统不同而有所不同。为了简化这个任务,你可以在一个已经配置好环境的 Docker 容器中执行 Terratest 测试。该容器对应的 Dockerfile 可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP05/testing-terratest/Dockerfile。
如果 Terraform 模块提供了云服务提供商的资源,在运行测试之前必须设置身份验证参数。
最后,正如本食谱开头所说,Terratest 不仅限于 Terraform,它还支持 Packer、Docker 和 Kubernetes 代码的测试。更进一步,它还可以对云服务提供商如 AWS、Azure 和 GCP 进行测试。
以下代码片段展示了如何根据 Terraform 的输出测试 Azure 中虚拟机的存在:
azureVmName := terraform.Output(t, terraformOptions, "vm_name")
resourceGroupName := terraform.Output(t, terraformOptions, "rg_name")
actualVMSize := azure.GetSizeOfVirtualMachine(t, vmName, resourceGroupName, "")
expectedVMSize := compute.VirtualMachineSizeTypes("Standard_DS2_v2")
在接下来的食谱中,我们将研究它在 Azure Pipelines 中的 CI/CD 流程集成,然后是 GitHub Actions 中的集成。
另见
-
Terratest 的官方网站可以在这里找到:
terratest.gruntwork.io/。 -
Terratest 的文档可以在这里查看:
terratest.gruntwork.io/docs/。 -
示例 Terratest 代码可以在这里找到:
github.com/gruntwork-io/terratest/tree/master/examples。 -
阅读这篇关于 Terratest 的精彩文章:
blog.octo.com/en/test-your-infrastructure-code-with-terratest/。
在 Azure Pipelines 中为 Terraform 模块构建 CI/CD
在本章中,我们研究了创建、使用和测试 Terraform 模块的配方。另一方面,在本章中使用私人 Git 仓库共享 Terraform 模块这一配方中,我们讨论了使用私人 Git 仓库(例如 Azure DevOps)存储和管理 Terraform 模块版本的可能性。
在 DevOps 环境中,当模块创建完成并且测试编写完成后,我们需要创建一个 DevOps CI/CD 管道,该管道将自动执行我们之前手动执行的测试步骤。
有许多 CI/CD 管道平台;在本配方中,我们将看到如何在 Azure Pipelines 中实现一个 CI/CD 管道,以自动化测试并发布 Terraform 模块。
准备工作
要开始这个配方,我们必须首先创建一个 Terraform 模块并使用 Terratest 进行测试。为此,我们将使用在前一个配方中创建的相同模块及其测试,源代码可从github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/testing-terratest/module获取。
此外,就 Azure Pipeline 而言,您需要假设我们已经将模块代码归档到 Azure Repos 中,正如本章中使用私人 Git 仓库共享 Terraform 模块这一配方所示。
为了避免在 Azure Pipelines 代理中安装运行测试所需的工具,我们将使用 Docker 镜像。因此,您应该具备 Docker 和 Docker Hub 的基本知识,可以通过参考此文档了解:docs.docker.com/。
最后,在 Azure Pipelines 中,我们将使用 YAML 管道,这使我们能够将管道作为代码,相关文档请参见:docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema%2Cparameter-schema。
如何做到……
执行以下步骤,以在 Azure Pipelines 中为该模块创建一个管道:
- 在
module目录中,我们将创建一个包含以下内容的 Dockerfile:
FROM mikaelkrief/go-terraform:0.12.25
ARG ARG_MODULE_NAME="module-test"
ENV MODULE_NAME=${ARG_MODULE_NAME}
# Set work directory.
RUN mkdir /go/src/${MODULE_NAME}
COPY ./module /go/src/${MODULE_NAME}
WORKDIR /go/src/${MODULE_NAME}
RUN chmod +x runtests.sh
ENTRYPOINT [ "./runtests.sh" ]
- 在同一目录中,我们创建一个包含以下内容的
runtests.sh文件:
#!/bin/bash
echo "==> Get Terratest modules"
go get github.com/gruntwork-io/terratest/modules/terraform
echo "==> go test"
go test -v ./tests/ -timeout 30m
- 然后,我们创建一个包含以下 YAML 代码片段的
azure-pipeline.yaml文件:
- script: |
docker build -t module-test:$(tag) .
workingDirectory: "$(Build.SourcesDirectory)/CHAP05/testing-terratest/"
displayName: "Docker build"
- script: |
docker run module-test:$(tag)
workingDirectory: "$(Build.SourcesDirectory)/CHAP05/testing-terratest/"
displayName: "Docker run"
- task: PowerShell@2
displayName: "Tag code"
inputs:
targetType: 'inline'
script: |
$env:GIT_REDIRECT_STDERR` = '2>&1'
$tag = "v$(Build.BuildNumber)"
git tag $tag
Write-Host "Successfully created tag $tag"
git push --tags
Write-Host "Successfully pushed tag $tag"
failOnStderr: false
此文件的完整源代码可在此处查看:github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP05/testing-terratest/azure-pipeline.yaml。
-
我们将这三文件
commit并push到 Terraform 模块的 Azure Repos。 -
在 Azure Pipelines 中,在流水线部分,我们点击创建流水线按钮:

- 然后,我们选择 Azure Repos 中包含模块代码的仓库:

- 然后,在流水线配置窗口中选择现有的 Azure Pipelines YAML 文件选项:

- 在右侧打开的布局中,我们选择了在步骤 3中编写的
azure-pipeline.yaml文件,然后通过点击继续按钮进行验证:

- 最后,下一页面显示了我们选择的流水线 YAML 文件的内容。要触发流水线,我们点击运行按钮:

它是如何工作的……
在步骤 1中,我们编写了将运行 Terratest 测试的 Dockerfile—这个 Docker 镜像是基于我创建的名为go-terraform的镜像,该镜像已经包含了 Terraform 和 Go SDK。
这个go-terraform镜像在 Docker Hub 上公开可用(hub.docker.com/repository/docker/mikaelkrief/go-terraform),源代码可在github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP05/testing-terratest/Dockerfile-go-terraform找到。
以下代码是 Dockerfile 中的FROM指令:
FROM mikaelkrief/go-terraform:0.12.25
然后,在此 Dockerfile 中,我们创建了src文件夹,并将模块源代码复制到该src目录,如下代码所示:
RUN mkdir /go/src/${MODULE_NAME}
COPY ./module /go/src/${MODULE_NAME}
WORKDIR /go/src/${MODULE_NAME}
最后,我们为runtest.sh脚本赋予执行权限,并在该脚本上定义entrypoint,如下代码所示:
RUN chmod +x runtests.sh
ENTRYPOINT [ "./runtests.sh" ]
在步骤 2中,我们编写了 shell 脚本runtest.sh的代码,该脚本负责使用dep ensure和go test -v命令执行 Terratest 测试,正如我们在本章的使用 Terratest 测试 Terraform 模块食谱中所学的那样。
在步骤 3中,我们编写了 Azure DevOps 流水线的 YAML 代码,其中包括三个步骤:
-
使用
docker build命令构建 Docker 镜像。 -
使用
docker run命令实例化一个新容器,并运行测试。 -
通过为模块代码添加标签来对模块代码进行版本控制。
然后,我们将这些文件commit并推送到模块的 Azure Repos 仓库。
最后,在步骤 5到步骤 8中,我们通过选择模块仓库和包含流水线定义的 YAML 文件,在 Azure Pipelines 中创建一个新流水线。
在步骤 9中,我们执行流水线并等待其执行完成。一旦流水线结束,你可以看到所有步骤都已成功执行,如下截图所示:

新的标签版本已经应用到代码中:

这个新增的标签将用于版本管理 Terraform 模块,以便在调用该模块时使用。
因此,通过此实现,在调用模块时,我们将使用已由管道自动测试的模块版本。
还有更多…
在这篇食谱中,我们研究了 Azure Pipelines 中 YAML 管道安装的基本步骤。你可以通过在管道中额外使用测试报告来进一步学习。欲了解更多信息,请阅读这篇博客文章:blog.jcorioland.io/archives/2019/09/25/terraform-microsoft-azure-ci-docker-azure-pipeline.html。
在下一个食谱中,我们将看到相同的管道过程,但用于一个存储在 GitHub 中并且我们希望发布到 Terraform 公共注册表中的 Terraform 模块。
另请参见
Azure Pipelines 的文档可在此找到:docs.microsoft.com/en-us/azure/devops/pipelines/?view=azure-devops.
使用 GitHub Actions 为 Terraform 模块构建工作流
在本章的使用 GitHub 共享 Terraform 模块食谱中,我们研究了如何通过将模块代码放到 GitHub 上,将 Terraform 模块发布到 Terraform 公共注册表。然后,我们在使用 Terratest 测试 Terraform 模块食谱中学习了如何使用 Terratest 编写和运行模块测试。
在本食谱中,我们将进一步研究如何使用 GitHub Actions 实现自动化模块发布工作流。
准备工作
要开始本食谱,您必须掌握前两个食谱:使用 GitHub 共享 Terraform 模块 和 使用 Terratest 测试 Terraform 模块,这两个食谱包含了本食谱所需的所有基础和工件。
在这个食谱中,我们将使用我们在使用 Terratest 测试 Terraform 模块代码一文中编写的模块代码,其源代码可以在此找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/testing-terratest/module。
此外,我们将使用 GitHub Actions,它是一个适用于公共 GitHub 仓库的免费服务,其文档可在此找到:github.com/features/actions。
本食谱的源代码可以在此找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/githubaction。
如何操作…
执行以下步骤以在我们的 Terraform 模块中使用 GitHub Actions:
- 在包含模块代码的 GitHub 仓库根目录中,我们通过 GitHub 网页界面,在
.github|workflows文件夹中创建一个名为integration-test.yaml的新文件:

- 在此文件中,我们编写以下 YAML 代码(完整代码可以在这里找到:
github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP05/githubaction):
...
steps:
- name: Check out code
uses: actions/checkout@v1
- name: Set up Go 1.14
uses: actions/setup-go@v1
with:
go-version: 1.14
id: go
- name: Get Go dependencies
run: go get -v -t -d ./...
- name: Run Tests
working-directory: "CHAP05/testing-terratest/module/tests/"
run: |
go test -v -timeout 30m
- name: Bump version and push tag
uses: mathieudutour/github-tag-action@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
- 然后,我们通过点击页面底部的提交新文件按钮来验证页面:

- 最后,我们点击仓库的 Actions 选项卡,就可以看到已触发的工作流:

它是如何工作的…
为了在 GitHub Actions 中创建工作流,我们在包含模块代码的仓库中创建了一个新的 YAML 文件,位于特定的 .github | workflows 文件夹中,包含了 GitHub Action 代理将执行的步骤。
我们工作流的步骤如下:
- 第一步是进行检出操作以获取仓库代码:
- name: Check out code
uses: actions/checkout@v1
- 然后,我们使用以下代码安装 Go SDK:
- name: Set up Go 1.14
uses: actions/setup-go@v1
with:
go-version: 1.14 #need to be >=1.13
id: go
关于最小 Go 版本的更多信息,请阅读此文档:terratest.gruntwork.io/docs/getting-started/quick-start/#requirements。
- 然后,我们使用以下代码下载依赖项:
- name: Get Go dependencies
run: go get -v -t -d ./...
- 我们使用以下代码运行 Terratest 测试:
- name: Run Tests
working-directory: "CHAP05/testing-terratest/module/tests/"
run: |
go test -v -timeout 30m
- 最后,最后一步是为代码添加标签。为此,我们使用由
mathieudutour/github-tag-action@v4仓库提供的Github-tag操作,并使用内置的GITHUB_TOKEN变量,允许代理进行身份验证并执行 Git 仓库中的 Git 命令:
- name: Bump version and push tag
uses: mathieudutour/github-tag-action@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
在工作流执行结束时,您可以看到如下截图所示的结果:

如果工作流运行正常,代码中将添加一个新标签,如下图所示:

如果该模块已发布到公共注册表中,则该模块的新版本将可用。
还有更多…
仓库中标签的递增(主版本、次版本或修复版本)是自动完成的,具体取决于触发操作的提交描述的内容。更多信息,请阅读文档:github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines。
参见
-
GitHub-Tag 操作的文档可以在这里找到:
github.com/marketplace/actions/github-tag。 -
阅读这篇关于 Terratest 和 GitHub Actions 的博客文章,由 HashiCorp 提供:
www.hashicorp.com/blog/continuous-integration-for-terraform-modules-with-github-actions/.
使用 Terraform 配置 Azure 基础设施
Terraform 包含多种提供者,可以实现各种类型基础设施的配置,无论是在云端还是本地。
在本书的前几章中,我们已经学习了 Terraform 语言的基本概念,以及主要的 Terraform 命令行,并且我们也看到了如何通过模块共享 Terraform 配置。此外,我们在前面章节中看到的所有示例都是通用的,适用于所有 Terraform 提供者。
本章将重点介绍如何使用 Terraform 在 Azure 中配置云基础设施。我们将从其在 Azure Cloud Shell 中的集成、其安全认证、以及在 Azure 存储中保护 Terraform 状态文件开始。你将学习如何在 Terraform 中运行 ARM 模板和 Azure CLI 脚本,并且如何通过 Terraform 获取 Azure 资源列表。接着,我们将探讨如何通过 Terraform 在 Azure Key Vault 中保护敏感数据。我们将以两个案例研究为例,第一个展示了包含虚拟机的 IaaS 基础设施的配置,第二个展示了 Azure 中 PaaS 基础设施的配置。最后,我们将进一步探讨如何从现有基础设施生成 Terraform 配置。
本章将覆盖以下教程:
-
在 Azure Cloud Shell 中使用 Terraform
-
保护 Azure 凭据提供者
-
在 Azure 远程后端保护状态文件
-
在 Terraform 中执行 ARM 模板
-
在 Terraform 中执行 Azure CLI 命令
-
使用 Azure Key Vault 和 Terraform 来保护机密
-
获取 Azure 资源列表的 Terraform 配置
-
使用 Terraform 配置和管理 Azure 虚拟机
-
使用 Terraform 构建 Azure 无服务器基础设施
-
为现有的 Azure 基础设施生成 Terraform 配置
第七章:技术要求
要应用本章中的教程,你必须拥有 Azure 订阅。如果没有,你可以在此网站免费创建一个 Azure 帐户:azure.microsoft.com/en-us/free/
本章的完整源代码可以在此处获取:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP06
查看以下视频,了解代码的实际应用:bit.ly/2ZmEcnJ
在 Azure Cloud Shell 中使用 Terraform
在本书的第一章,设置 Terraform 环境,我们学习了在本地机器上安装 Terraform 的步骤。
在 Azure Shell 控制台中,微软已将 Terraform 集成到默认安装的工具列表中,该控制台被称为Azure Cloud Shell。
在本教程中,我们将学习如何编写 Terraform 配置并在 Azure Cloud Shell 中使用 Terraform。
准备工作
本教程的前提是拥有 Azure 订阅,并通过 Azure 门户连接到该订阅,访问地址为:portal.azure.com/
请注意,这个前提条件适用于本章的所有教程。
此外,您需要将 Cloud Shell 与现有的 Azure 存储帐户关联,或根据以下文档创建一个新的存储帐户:docs.microsoft.com/en-us/azure/cloud-shell/persisting-shell-storage。
如何执行…
为了在 Azure Cloud Shell 中使用 Terraform,请执行以下步骤:
- 在 Azure 门户中,通过点击顶部菜单中的 Cloud Shell 按钮打开 Azure Cloud Shell,如下图所示:

- 在 Cloud Shell 面板中,在顶部菜单的下拉框中选择 Bash 模式:

- 在 Cloud Shell 终端中,通过执行以下命令,在默认的
clouddrive文件夹内创建一个新的demotf文件夹:
mkdir clouddrive/demotf
在这个新文件夹中,输入cd clouddrive/demotf命令。
-
要在集成的 Visual Studio Code 实例中编写 Terraform 配置,执行
code命令。 -
在编辑器中,在打开的空白页面中,编写以下示例代码的 Terraform 配置:
terraform {
required_version = ">= 0.12"
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "rg-app" {
name = "RG-TEST-DEMO"
location = "westeurope"
}
-
使用Ctrl + S快捷键保存此文件,并将其命名为
main.tf。 -
最后,要将此 Terraform 配置应用到 Cloud Shell 终端,按照以下经典的 Terraform 工作流执行:
> terraform init
> terraform plan -out=app.tfplan
> terraform apply app.tfplan
它是如何工作的…
在本教程中,我们使用了 Azure Cloud Shell 的集成环境,其中包括一个命令行终端,我们选择使用 Bash 模式。此外,在步骤 5和步骤 6中,我们使用了内置的 Visual Studio Code 编辑器,使用code命令编写了 Terraform 配置,它还为 Terraform 文件提供了语法高亮显示。最后,在步骤 7中,我们使用了已安装在 Cloud Shell 环境中的 Terraform 客户端,通过执行 Terraform 工作流命令来部署我们的基础设施。
以下截图展示了带有 Terraform 执行的 Azure Cloud Shell:

我们可以在上面的截图中看到,上面面板是集成的 Visual Studio Code 编辑器,下面面板是执行 Terraform 命令的命令行。
还有更多…
对于这个教程,我们选择了直接在 Cloud Shell 中的 Visual Studio Code 编辑 Terraform 文件,但也有以下可选方案:
-
Terraform 文件可以使用Vim工具(Linux 编辑器:
www.linux.com/training-tutorials/vim-101-beginners-guide-vim/)创建和编辑,该工具已内置于 Cloud Shell。 -
我们也可以在本地计算机上编辑 Terraform 文件,然后将它们复制到与 Azure Cloud Shell 连接的 Azure 存储服务中。
-
如果文件存储在 Git 仓库中,我们也可以通过在 Cloud Shell 命令行终端运行
git clone命令,将仓库直接克隆到 Cloud Shell 存储中。
同样,关于 Terraform 在 Azure 中执行操作的身份验证,我们没有采取任何措施,因为 Azure Cloud Shell 允许直接对我们的 Azure 订阅和 Terraform 进行身份验证,而在 Cloud Shell 中的 Terraform 会自动继承该身份验证。
另一方面,如果你有多个订阅,在执行 Terraform 工作流之前,你必须通过执行以下命令来选择订阅目标:
az account set -s <subscription _id>
这个选定的订阅将在执行过程中成为默认订阅。请参考文档:docs.microsoft.com/en-us/cli/azure/account?view=azure-cli-latest#az-account-set。
关于 Cloud Shell 上安装的 Terraform 版本,通常是最新的公开版本,你可以通过运行 terraform --version 命令来检查。你需要在执行之前检查你的 Terraform 配置是否与此版本兼容。
最后,关于推荐使用 Azure Cloud Shell 进行 Terraform 操作,它仅限于开发和测试使用。它不能集成到 CI/CD 管道中,并且使用你在 Azure 上的个人权限来配置资源。因此,在下一个食谱中,我们将研究如何安全地将 Terraform 认证到 Azure。
另见
-
请参考这篇博客文章,其中也展示了在 Azure Cloud Shell 中使用 Terraform:
cloudskills.io/blog/terraform-azure-01 -
解释如何使用 Azure Cloud Shell 的文档:
docs.microsoft.com/en-us/azure/cloud-shell/using-cloud-shell-editor -
这是一个教程,展示了如何使用和配置本地安装的 Visual Studio Code,以便在 Azure Cloud Shell 中执行 Terraform 配置:
docs.microsoft.com/en-us/azure/developer/terraform/configure-vs-code-extension-for-terraform
保护 Azure 凭证提供者
为了让 Terraform Azure 提供者能够在 Azure 中配置和操作资源,提供者必须使用 Azure 账户进行身份验证,并且该账户必须具有正确的授权。
在前面的步骤中,我们学习了如何使用个人账户和权限在 Azure Cloud Shell 中自动认证 Terraform 上下文。然而,在企业项目和生产环境中,使用个人账户是非常不推荐的做法,因为个人账户可能会过期、被删除,甚至更糟糕的是被滥用。
这就是我们在 Azure 中运行 Terraform 时的选项之一,使用应用注册账户(也称为服务主体),该账户不与任何物理人挂钩。
在本配方中,我们首先学习如何创建服务主体,然后我们将看到如何安全地使用它来运行 Terraform 配置。
准备工作
要应用此配方的第一部分,你必须拥有在 Azure Active Directory 中创建用户账户的权限。此外,要创建此服务主体,我们将使用命令行工具az cli来操作,相关文档可参考docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest。
此外,我们还需要检索我们将要配置资源的订阅 ID。为此,我们可以通过运行az account list命令在 Azure Cloud Shell 中显示订阅详细信息:

同时,获取相关订阅的id属性。
如何操作…
本配方包括两部分,分别是:创建服务主体和使用该服务主体配置 Terraform 认证。
要创建此服务主体,请执行以下步骤:
- 打开 Azure Cloud Shell 并执行以下命令:
az ad sp create-for-rbac --name="BookDemoTerraform" --role="Contributor" --scopes="/subscriptions/<Subscription Id>"
- 从前一个命令的输出中检索所有标识信息,记下
appId、password和tenant(因为关闭此控制台后,我们将无法检索密码),如以下截图所示:

现在服务主体已经创建,我们可以使用它来通过 Terraform 配置 Azure 基础设施,执行以下步骤:
- 在命令行终端中,设置以下四个新的环境变量:
export ARM_SUBSCRIPTION_ID =<subscription_id>
export ARM_CLIENT_ID=<appId>
export ARM_CLIENT_SECRET=<password>
export ARM_TENANT_ID=<tenant id>
- 然后,我们可以通过执行以下 Terraform 工作流来应用 Terraform 配置:
> terraform init
> terraform plan -out=app.tfplan
> terraform apply app.tfplan
它是如何工作的…
在本配方的第一部分,我们创建了一个服务主体,并使用命令行az ad sp为其赋予了订阅权限。我们为此命令添加了以下参数:
-
name,即我们将要创建的服务主体的名称。 -
role,即服务主体在订阅中所拥有的角色;在此,我们指定为Contributor。 -
scopes,我们在此指定服务主体将拥有贡献者权限的 Azure 资源 ID。在我们的案例中,这是 Terraform 将配置资源的订阅 ID。
因此,此命令将创建一个服务主体,并为其生成密码,并在指定的订阅上授予 Contributor 角色。
执行完毕后,此命令会显示服务主体的信息,包括 AppId、password 和 tenant。如 步骤 2 中所解释的,我们需要获取这些信息并妥善保存,因为此密码无法以后重新获取。然后,我们检查服务主体是否具有订阅权限,您可以在以下截图中看到:

在本配方的第二部分,我们使用此服务主体来认证 Terraform Azure 提供程序。为此,有几种解决方案,其中最安全的方式是使用特定的 Azure 提供程序环境变量,因为这些环境变量在代码中不可见,且仅在执行会话期间保持有效。因此,我们设置了四个环境变量,具体如下:
-
ARM_SUBSCRIPTION_ID:此字段包含 Azure 订阅 ID。 -
ARM_CLIENT_ID:此字段包含服务主体 ID,称为AppId。 -
ARM_CLIENT_SECRET:此字段包含服务主体的密码。 -
ARM_TENANT_ID:此字段包含 Azure Active Directory 租户的 ID。
在此配方中,我们使用了 Linux 系统的 export 命令。在 Windows PowerShell 中,我们可以使用 $env 命令。此外,在本章的所有配方中,我们将在执行 Terraform 之前使用这些环境变量。
然后,一旦设置了这些环境变量,我们就可以执行基本的 Terraform 工作流。
还有更多……
关于创建服务主体,我们选择使用 Azure CLI 工具,但也可以直接通过 Azure 门户进行创建,详细信息请参见以下文档:docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal,或者我们也可以使用 Azure PowerShell 命令(docs.microsoft.com/en-us/azure/active-directory/develop/howto-authenticate-service-principal-powershell)。
此外,关于 Terraform Azure 提供程序的配置,我们使用了环境变量,但也可以直接将这些信息写入 Terraform 配置文件,如下所示的代码片段所示:
provider "azurerm" {
...
subscription_id = "<Subscription ID>"
client_id = "<Client ID>"
client_secret = "<Client Secret>"
tenant_id = "<Tenant ID>"
}
该解决方案不要求在执行 Terraform 配置之前实现额外的步骤(如设置环境变量),但它将身份信息以明文形式保留在代码中,而在安全角度来看,将凭证硬编码在代码中通常被认为是不好的做法,因为代码泄漏也会导致凭证泄漏,并且无法在不暴露凭证的情况下与他人共享代码。如果 Terraform 配置为不同订阅中的多个环境提供资源,则需要添加 Terraform 变量,这可能会增加代码的复杂性。
最后,使用环境变量的优点是它能够轻松集成到 CI/CD 管道中,同时保持身份验证数据的安全性。
另请参阅
-
Azure 文档和 Terraform 配置可在此处查看:
docs.microsoft.com/en-us/azure/developer/terraform/install-configure -
关于 Azure 提供者的配置文档以及其他身份验证选项,请参阅:
www.terraform.io/docs/providers/azurerm/index.html
保护 Azure 远程后端中的状态文件
在执行 Terraform 工作流命令时,主要是 terraform plan、terraform apply 和 terraform destroy,Terraform 有一个机制,它可以识别哪些资源需要更新、添加或删除。为了执行这个机制,Terraform 维护一个名为 Terraform 状态文件的文件,其中包含所有由 Terraform 提供的资源的详细信息。这个 Terraform 状态文件在首次运行 terraform plan 命令时创建,并在每次操作(apply 或 destroy)后更新。
在企业中,这个文件包含某些有趣的方面,如下所示:
-
提供资源的敏感信息以明文形式显示。
-
如果几个人在一起工作,这个文件必须由每个人共享,否则默认情况下,这个文件会在本地工作站或包含 Terraform 二进制文件的工作站上创建。
-
即使该文件被归档在 Git 源代码管理工具中,一旦在本地工作站上检索,它也不允许多人在同一个文件上进行工作。
-
使用本地存储的文件来管理多环境可能会迅速变得复杂且具有风险。
-
任何对该本地文件的删除或不当手动编辑都可能影响 Terraform 配置的执行。
解决所有这些问题的一种方法是使用远程后端,即将该文件存储在远程、共享和安全的存储中。
在 Terraform 的使用中,有多种远程后端类型,例如 S3、azurerm、Artifactory 等,这些类型在以下页面的菜单中列出:www.terraform.io/docs/backends/types/index.html
在本食谱中,我们将研究在 Azure 中使用远程后端 azurerm,通过将 Terraform 状态文件存储在 Azure 存储帐户中。
准备就绪
对于这个食谱,我们将使用 Azure Cloud Shell 和 Azure CLI 命令来创建存储帐户。
本食谱的源代码和所使用的脚本可以在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP06/remotebackend 获取。
如何操作……
这个食谱由两部分组成。在第一部分,我们将创建存储帐户,在第二部分,我们将配置 Terraform 使用 azurerm 远程后端:
- 在 Azure Cloud Shell 中,执行以下 Azure CLI 脚本(可通过
github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP06/remotebackend/create-backend.sh获取)来在资源组中创建存储帐户及其 blob 容器:
# 1- Create Resource Group
az group create --name "RG-TFBACKEND" --location westeurope
# 2- Create storage account
az storage account create --resource-group "RG-TFBACKEND" --name "storagetfbackend" --sku Standard_LRS --encryption-services blob
# 3- Create blob container
az storage container create --name "tfstate" --account-name "storagetfbackend"
# 4- Get storage account key
ACCOUNT_KEY=$(az storage account keys list --resource-group "RG-TFBACKEND" --account-name "storagetfbackend" --query [0].value -o tsv)
echo $ACCOUNT_KEY
- 然后,我们通过将以下代码添加到
main.tf文件中来配置 Terraform 状态后端:
terraform {
backend "azurerm" {
resource_group_name = "RG-TFBACKEND"
storage_account_name = "storagetfbackend"
container_name = "tfstate"
key = "myapp.tfstate"
}
}
- 最后,为了执行 Terraform 配置,我们设置一个新的环境变量
ARM_ACCESS_KEY,通过以下命令:
export ARM_ACCESS_KEY = <access key>
然后,我们执行 Terraform 工作流的基本命令。
工作原理……
在第一步中,我们使用了一个脚本,按顺序执行以下操作:
-
它创建一个名为
RG-TFBACKEND的资源组。 -
在这个资源组中,我们使用
az storage account create命令创建一个名为storagetfbackend的存储帐户。 -
然后,脚本通过
az storage container create命令在这个存储帐户中创建一个 blob 容器。 -
最后,我们获取已创建的存储帐户的帐户密钥并显示其值。
然后,在步骤 2中,我们配置 Terraform 使用这个存储帐户作为远程后端来存储 Terraform 状态文件。在这个配置中,它位于 backend "azurerm" 块中,我们一方面指明存储帐户信息,另一方面指明 blob 及其属性:
-
resource_group_name:这是包含存储帐户的资源组的名称。 -
storage_account_name:这是存储帐户的名称。 -
container_name:这是 blob 容器的名称。 -
key:这是 Terraform 状态文件的名称。
最后,我们定义了一个新的环境变量ARM_ACCESS_KEY,它包含我们从步骤 1中运行的脚本中获取的存储帐户密钥。此变量用于身份验证存储帐户。然后,我们执行 Terraform 的init、plan和apply命令。
根据我们在前一个教程中学到的内容,保护 Azure 凭证提供者,这是在 Azure 中执行此 Terraform 脚本的完整脚本:
export ARM_SUBSCIPTION_ID =<subscription_id>
export ARM_CLIENT_ID=<appId>
export ARM_CLIENT_SECRET=<password>
export ARM_TENANT_ID=<tenant id>
export ARM_ACCESS_KEY=<account key>
> terraform init
> terraform plan -out=app.tfplan
> terraform apply app.tfplan
所以,我们使用了四个身份验证环境变量,以及ARM_ACCESS_KEY环境变量,用于对存储帐户进行身份验证,并执行了 Terraform 命令。
还有更多…
在这个教程中,我们使用了一个环境变量来指定访问密钥的值,以保护这些敏感数据。我们本可以在远程后端配置中使用access_key属性来指定它,如下例所示(但正如本章中保护 Azure 凭证提供者部分所提到的,将敏感密钥以明文形式保留并不是一个好做法):
terraform {
backend "azurerm" {
resource_group_name = "RG-TFBACKEND"
storage_account_name = "storagetfbackend"
container_name = "tfstate"
key = "myapp.tfstate"
access_key = xxxxxx-xxxxx-xxx-xxxxx
}
}
此外,如果我们的 Terraform 配置旨在部署到多个环境,我们可以通过以下步骤创建N个azurerm后端配置:
main.tf文件包含以下代码,其中backend "azurerm"块为空:
terraform {
required_version = ">= 0.12"
backend "azurerm" {
}
}
- 我们为每个环境创建一个
backend.tfvars文件(位于该环境的特定文件夹中),并包含以下代码:
resource_group_name = "RG-TFBACKEND"
storage_account_name = "storagetfbackend"
container_name = "tfstate"
key = "myapp.tfstate"
- 最后,在执行
init命令时,我们使用以下命令指定要使用的backend.tfvars文件,如init命令文档中所指定,相关文档可在此查看:www.terraform.io/docs/backends/config.html#partial-configuration:
terraform init -backend-config="<path>/backend.tfvars"
最后,如果用于与 Terraform 进行身份验证的服务主体(Service Principal)对该存储帐户具有权限,则此环境变量不是必需的。
另见
-
有关
azurerm远程后端的文档,请参见此处:www.terraform.io/docs/backends/types/azurerm.html -
使用
azurerm远程后端的 Terraform 学习模块请参见此处:learn.hashicorp.com/terraform/azure/remote_az#azurerm -
有关 Terraform 远程后端的 Azure 文档,请参见此处:
docs.microsoft.com/en-us/azure/developer/terraform/store-state-in-azure-storage
在 Terraform 中执行 ARM 模板
在所有基础设施即代码(IaC)工具和语言中,Azure 提供了一个名为Azure 资源管理器(ARM)的工具,它基于 JSON 格式文件,包含要配置的资源的描述。
要了解更多有关 ARM 模板的信息,请阅读以下文档:docs.microsoft.com/en-us/azure/azure-resource-manager/templates/overview
在使用 Terraform 部署 Azure 资源时,可能需要使用尚未在 Terraform azurerm提供者中支持的资源。事实上,azurerm提供者是开源的,且基于 GitHub 的社区驱动,拥有庞大的贡献者社区,但这还不足以实时跟进 Azure 功能的所有变化。造成这种情况的原因有几个:
-
Azure 资源的新版本发布非常频繁。
-
Terraform
azurerm提供者高度依赖 Azure Go SDK(github.com/Azure/azure-sdk-for-go),而该 SDK 不包含有关新功能或仍处于预览阶段的功能的实时更新。
为了部分解决这个问题,并且对于希望完全使用 Terraform 的组织,可以使用 Terraform azurerm_template_deployment资源,该资源允许通过 Terraform 执行 ARM 代码。
在这个食谱中,我们将讨论如何使用 Terraform 执行 ARM 代码。
准备工作
这个食谱的 Terraform 配置将部署一个包括扩展的 Azure App Service。由于在编写本书时,azurerm提供者中尚不支持扩展 App Service 资源,因此 Azure App Service 代码将使用HashiCorp 配置语言(HCL)编写,扩展将通过 ARM 模板来部署,并使用 Terraform 执行。
这个食谱的目的是不是详细描述扩展的 ARM 模板代码,而是研究如何用 Terraform 执行它。
在这个食谱中,我们只展示关键代码片段。本章的完整源代码可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP06/arm-template.
如何做到这一点…
要使用 Terraform 执行 ARM 模板,请按照以下步骤操作:
- 在将包含 Terraform 配置的文件夹内,创建一个名为
ARM_siteExtension.json的新文件,其中包含以下 ARM JSON 模板片段:
{
...
"parameters": {
"appserviceName": { ... },
"extensionName": { ... },
"extensionVersion": { ... }
},
"resources": [
{
"type": "Microsoft.Web/sites/siteextensions",
"name": "[concat(parameters('appserviceName'), '/', parameters('extensionName'))]",
...
"properties": {
"version": "[parameters('extensionVersion')]"
}
}
]
}
该文件的完整源代码可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP06/arm-template/ARM_siteExtension.json
- 在
main.tf文件中,添加以下 Terraform 代码片段:
resource "azurerm_template_deployment" "extension" {
name = "extension"
resource_group_name = azurerm_resource_group.rg-app.name
template_body = file("ARM_siteExtension.json")
parameters = {
appserviceName = azurerm_app_service.app.name
extensionName = "AspNetCoreRuntime.2.2.x64"
extensionVersion = "2.2.0-preview3-35497"
}
deployment_mode = "Incremental"
}
该文件的完整源代码可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP06/arm-template/main.tf
-
然后,我们可以使用以下命令执行基本的 Terraform 工作流:
-
本章中讨论的保护 Azure 凭证提供程序食谱中,使用四个 Azure 变量环境进行身份验证
-
如前所述以及在早期章节中提到的,执行
init、plan和apply命令
它是如何工作的……
在步骤 1中,在包含 Terraform 配置的目录中,我们创建了一个 JSON 文件,包含用于创建应用服务扩展的 ARM 代码。在此 ARM 文件中,我们有以下三个输入参数:
-
appserviceName:这对应于应用服务的名称。 -
extensionName:这对应于要添加的扩展的名称(来自扩展目录)。 -
extensionVersion:这对应于要添加的扩展版本。
然后,本文件的其余部分描述了在 Azure 中使用这三个参数添加的站点扩展资源。
然后,在步骤 2中,在 Terraform 配置中,我们使用了 Terraform 资源
azurerm_template_deployment,该资源允许执行一个 ARM 模板,具有以下属性:-
template_body:这是 JSON 格式的 ARM 代码。在我们的示例中,我们使用文件函数来指示它是一个文件。 -
parameters:在此块中,我们填写了 ARM 模板的输入属性,包括appserviceName、extensionName和extensionVersion。在我们的食谱中,我们安装了版本为2.2.0-preview3-35497的AspNetCoreRuntime.2.2.x64扩展。
最后,为了预配这个 Azure 应用服务及其扩展,执行 Terraform 工作流命令。
以下截图显示了 Azure 门户中的结果:
![]()
我们可以在应用服务中看到已配置的扩展。
还有更多内容……
在本食谱中,我们研究了使用 Terraform 运行 ARM 模板的可能性。这种方法允许你在 Azure 中预配
azurerm提供程序中不可用的元素,但重要的是要知道,Terraform 会识别在执行此 ARM 模板时描述的资源。也就是说,这些资源(在这里,我们的资源是扩展)不遵循 Terraform 工作流的生命周期,也没有在 Terraform 状态文件中注册。唯一写入 Terraform 状态文件的内容是资源的配置,
azurerm_template_deployment,因此,例如,如果你在 Terraform 配置上运行terraform destroy命令,这些由 ARM 模板提供的资源将不会被销毁。相反,只有azurerm_template_deployment资源会从 Terraform 状态文件中移除。因此,建议你仅在用 Terraform HCL 代码预配的资源上使用这种类型的部署。这确实是我们的情况,因为该扩展是 App Service 的一个集成补充,如果我们运行
terraform destroy,不仅会销毁 App Service,还会销毁所有集成在其中的扩展。我们已经看到
destroy命令的影响,但在其他命令上也存在相同的问题,例如plan、import或refresh命令。ARM 模板提供的资源 Terraform 并不认识。此外,在 Terraform 中使用 ARM 模板仅对完整的 ARM 资源有效,正如这里的扩展所示,Azure 提供的 ARM 脚本可以直接执行。
在下一个食谱中,我们将继续这个话题,但我们将不再使用 ARM 模板,而是看看如何在 Terraform 中使用 Azure CLI 命令。
另见
-
有关
azurerm提供程序的azurerm_template_deployment资源的文档可以在此查看:www.terraform.io/docs/providers/azurerm/r/template_deployment.html -
另一篇博客文章也解释了如何获取 ARM 模板的 JSON 代码,文章链接为:
www.phillipsj.net/posts/applying-azure-app-service-extensions-with-arm/
在 Terraform 中执行 Azure CLI 命令
在前面的食谱中,我们研究了如何在
azurerm提供程序中,所提供的资源尚未可用的情况下,使用 Terraform 运行 ARM 模板。然而,存在无法使用 ARM 模板的情况,例如以下几种情况:
-
我们希望填写资源的一个或多个属性,这些属性在 ARM 模板中并不是自治的。
-
资源的 ARM 模板不可用。
对于这些情况,另一种解决方案是通过 Terraform 执行 Azure CLI 命令。
本食谱是 使用 Terraform 执行本地程序 食谱的实际应用,来自第二章,编写 Terraform 配置。我们将研究 Terraform 配置及其执行,以便将 Azure CLI 命令与 Terraform 集成。
准备工作
对于这个食谱,必须预先阅读 使用 Terraform 执行本地程序 食谱,来自第二章,编写 Terraform 配置,它为我们将要编写的 Terraform 配置提供了基础。
此外,还需要提前安装 Azure CLI 工具,相关文档可以在此查看:
docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest为了展示如何在 Terraform 中使用 Azure CLI 命令,在本食谱中,我们将通过配置静态网站功能的属性来设置 Azure 存储帐户。
与前面的配方一样,本配方的目的是展示如何使用 Azure CLI 命令与 Terraform 配合使用,但我们不会聚焦于使用的 Azure CLI 命令,因为自
azurerm提供程序的 2.0.0 版本以来,静态网站的属性已被添加到 Terraform 资源中(github.com/terraform-providers/terraform-provider-azurerm/blob/master/CHANGELOG-v2.md#200-february-24-2020)。本配方的源代码可以在
github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP06/az%20cli找到。如何操作……
执行以下步骤以使用 Terraform 执行 Azure CLI 命令:
- 在包含 Terraform 代码的
main.tf文件中,编写以下配置来为存储账户提供配置:
resource "azurerm_storage_account" "sa" { name = "saazclidemo" resource_group_name = azurerm_resource_group.rg.name location = "westeurope" account_tier = "Standard" account_kind = "StorageV2" account_replication_type = "GRS" }- 在同一 Terraform 配置中,添加代码以使用 Azure CLI 命令配置静态网站:
resource "null_resource" "webapp_static_website" { triggers = { account = azurerm_storage_account.sa.name } provisioner "local-exec" { command = "az storage blob service-properties update --account-name ${azurerm_storage_account.sa.name} --static-website true --index-document index.html --404-document 404.html" } }- 然后,在我们的命令行终端中,我们通过执行以下命令登录到 Azure:
az login --service-principal --username APP_ID --password PASSWORD --tenant TENANT_ID-
最后,我们可以通过以下方式执行基本的 Terraform 工作流:
-
本章中的保护 Azure 凭证提供程序配方讨论了使用四个 Azure 变量环境进行身份验证。
-
执行
init、plan和apply命令,如前文及早期章节中提到的。
-
它是如何工作的……
在步骤 1中,没有什么特别的。我们只编写了用于提供
StorageV2存储账户的 Terraform 配置,这对于激活静态网站功能是必需的。在步骤 2中,我们通过添加包含
local-exec提供程序的null_resource完成了此代码。在local-exec的命令属性中,我们输入了必须执行的 Azure CLI 命令,用于激活并配置我们在步骤 1中写入的存储账户的静态网站功能。我们添加了触发器块,并将存储名称作为参数,这样如果存储名称发生变化,配置将重新执行。
然后,在步骤 3中,我们执行了
az login命令来验证 Azure CLI 的上下文。在此命令中,我们添加了使用服务主体的认证参数(详见本章中的保护 Azure 凭证提供程序配方),文档记录在这里:docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest#sign-in-using-a-service-principal。还有更多内容……
在本配方的实现中,有两个重要点:
-
第一点是我们使用了
null_resource和已经在 使用 Terraform 执行本地程序 配方中详细研究过的local-exec提供程序,第二章《编写 Terraform 配置》中也有讲解。这里唯一的新意是执行的命令是 Azure CLI 命令,也可以是包含多个 Azure CLI 命令的脚本文件。 -
第二点是,Terraform 使用四个环境变量进行 Azure 身份验证时,无法验证将由 Terraform 执行的 Azure CLI 上下文。这就是为什么在 第 3 步 中,我们还需要通过
az login命令使用服务主体的凭证作为参数来验证 Azure CLI 上下文。
通过这种方式执行 Azure CLI 命令的优点在于,我们可以将 Terraform 语言的变量和表达式集成到其中,就像我们在将存储帐户名称作为参数传递时所做的那样。
请注意,与任何本地提供程序一样,这会限制配置应用的地方,因为它假定 Azure CLI 已存在(Azure CLI 成为一个隐藏的依赖项)。
与之前的 ARM 模板示例 在 Terraform 中执行 ARM 模板 一样,我们了解到 Terraform 无法识别在 Azure CLI 命令或脚本中操作的资源。这些资源不遵循 Terraform 生命周期,也未注册在 Terraform 状态文件中。另一方面,在
null_resource的local-exec提供程序中,我们可以指定一个命令,在执行terraform destroy命令时执行。以下是我用来创建 CosmosDB 数据库的配置示例(在
azurerm提供程序支持之前),展示了以下内容:resource "null_resource" "cosmosdb_database" { provisioner "local-exec" { command = "az cosmosdb database create --name ${var.cosmosdb_name} --db-name ${var.app_name} --resource-group ${var.cosmosdb_rg} --throughput ${var.cosmosdb_throughput}" } provisioner "local-exec" { when = "destroy" command = "az cosmosdb database delete --name ${var.cosmosdb_name} --db-name ${var.app_name} --resource-group ${var.cosmosdb_rg}" } }在这个示例中,在提供程序中,我们使用了
When=destroy属性,指定在执行terraform destroy时,Azure CLI 命令az cosmosdb database delete会被执行以删除 CosmosDB 数据库。另见
-
az login命令及其参数的文档可以在此处找到:docs.microsoft.com/en-us/cli/azure/authenticate-azure-cli?view=azure-cli-latest -
Terraform
provisioner相关文档可以在此处找到:www.terraform.io/docs/provisioners/index.html -
provisioner的when属性的文档可以在此处找到:www.terraform.io/docs/provisioners/index.html#destroy-time-provisioners
使用 Azure Key Vault 和 Terraform 保护机密
IaC 的挑战之一是保护基础设施中的敏感信息。
实际上,基础设施即代码(IaC)的一个优势是能够将代码版本化到 Git 仓库中,因此,这些代码可以受益于 Git 工作流中的版本控制和代码验证。然而,采用这种方法时,我们往往会在代码中写下所有内容,有时会忘记一些敏感数据,比如密码或登录字符串,如果这些数据落入错误的手中,可能会被滥用。
在本配方中,我们将学习如何通过将敏感数据存储在 Azure 的密钥管理器中(即 Azure Key Vault),然后在 Terraform 配置中使用它,从而保护这些敏感数据。
准备工作
对于本配方,我们假设使用 Azure Key Vault。有关更多信息,您可以参考以下文档:
docs.microsoft.com/en-us/azure/key-vault/。在我们在 Azure 中创建的 Azure Key Vault 中,就本配方的应用而言,我们存储了一个保护 SQL Server 数据库连接字符串的密钥,该数据库托管在 Azure Web 应用程序中。
这个连接字符串如下所示:
Data Source=mysever.com;initial catalog=databasedemo;User ID=useradmin;Password=demobook这是 Azure CLI 命令
az keyvault secret show的输出,它显示了该密钥在 Azure Key Vault 中的存储位置和属性:![]()
在前面的屏幕截图中,我们可以看到存储在
secret对象的value属性中的数据库连接字符串。本配方的目标是编写 Terraform 配置,请求此密钥的值,并在 Azure App Service 的属性中使用它。
本配方的源代码可以在这里找到:
github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP06/keyvault如何操作……
要在 Terraform 中获取和使用 Azure Key Vault 密钥,请执行以下步骤:
- 在 Azure Key Vault 中,我们通过授予将由 Terraform 用于 Azure 的 Service Principal 访问权限,来添加访问策略属性,以便它能够获取和列出密钥:
![]()
- 在
main.tf文件中,我们添加以下代码以获取 Key Vault 密钥:
data "azurerm_key_vault" "keyvault" { name = "keyvdemobook" resource_group_name = "rg_keyvault" } data "azurerm_key_vault_secret" "app-connectionstring" { name = "ConnectionStringApp" key_vault_id = data.azurerm_key_vault.keyvault.id }- 然后,在 App Service 资源的 Terraform 配置中,在
main.tf文件中,我们添加以下代码:
resource "azurerm_app_service" "app" { name = "demovaultbook" location = azurerm_resource_group.rg-app.location resource_group_name = azurerm_resource_group.rg-app.name app_service_plan_id = azurerm_app_service_plan.plan-app.id connection_string { name = "Database" type = "SQLServer" value = data.azurerm_key_vault_secret.app-connectionstring.value } }- 最后,我们按之前的描述以及前面章节中提到的,运行基本的 Azure Terraform 工作流,设置变量环境并执行
init、plan和apply。
它是如何工作的……
在第 1 步中,我们授予了 Terraform 使用的 Service Principal 读取和列出 Azure Key Vault 密钥的权限。
我们可以通过 Azure 门户或使用 Azure CLI 在命令行上执行此操作,具体方法请参见以下文档:
docs.microsoft.com/en-us/cli/azure/keyvault?view=azure-cli-latest#az-keyvault-set-policy如果我们没有执行此步骤,在执行
terraform plan命令时将会出现以下错误:![]()
然后,在步骤 2中,我们编写了包含两个数据源的 Terraform 配置:
-
第一个数据源
azurerm_key_vault允许检索 Azure Key Vault 资源的 Azure ID。 -
第二个数据源
azurerm_key_vault_secret用于获取包含数据库连接字符串的密钥作为值。
有关 Terraform 数据块的更多信息,请阅读第二章中的使用数据块与外部资源配方,编写 Terraform 配置。
在步骤 3中,我们继续编写 Terraform 配置,将
connection_string块的属性值填入应用服务中,使用表达式data.azurerm_key_vault_secret.app-connectionstring.value,这是从在步骤 2中编写的azurerm_key_vault_secret数据块中获取的值。最后,在最后一步中,我们执行此 Terraform 配置。在此操作中,Terraform 会首先获取在数据块中请求的值(Key Vault,然后是 Key Vault 秘密),然后将从密钥中获取的值注入到应用服务的配置中。
该结果在 Azure 中获得,并显示在以下截图中:
![]()
我们可以看到,连接字符串已在应用服务配置中正确填充。
还有更多…
我们在本配方中了解到,作为敏感数据的连接字符串已存储在 Azure Key Vault 中,并且在 Terraform 执行时会自动使用它。因此,感谢 Azure Key Vault,我们无需将敏感数据以明文形式放入 Terraform 配置中。
然而,仍然需要小心。虽然这些数据在 Terraform 配置中不是以明文形式写入,但它们会以明文形式写入 Terraform 状态文件,如以下从本配方提取的 Terraform 状态文件内容所示:
![]()
这就是为什么,如果我们需要检查该文件的内容,建议使用
terraform state show或terraform show命令,它们可以保护敏感数据的显示,正如以下截图所示:![]()
这是需要通过将
tfstate文件存储在安全的远程后端来保护该文件的原因之一,正如我们在本章的保护 Azure 远程后端中的状态文件配方中所见,并且在以下文档中做了详细说明:www.terraform.io/docs/state/sensitive-data.html在这个教程中,尽管我们将敏感数据存储在 Azure Key Vault 中,我们也可以将其存储在与 Terraform 集成良好的 HashiCorp Vault 实例中。为此,建议你阅读 vault 提供程序文档,链接在这里:
www.terraform.io/docs/providers/vault/index.html最后,作为本教程的前提,我们在 Azure Key Vault 中手动创建了连接字符串的密钥。此操作可以通过 Terraform 完成,文档可参考这里:
www.terraform.io/docs/providers/azurerm/r/key_vault_secret.html,也可以通过 Azure CLI 命令完成,文档参考这里:docs.microsoft.com/en-us/cli/azure/keyvault/secret?view=azure-cli-latest#az-keyvault-secret-set。另一方面,由于数据将在代码中以明文形式写入,因此必须通过仅授权人员授予读写权限来确保安全。另见
- 有关
azurerm_key_vault_secret块数据的文档,请参见这里:www.terraform.io/docs/providers/azurerm/d/key_vault_secret.html
在 Terraform 中获取 Azure 资源列表
在前面的教程中,我们学习了使用
data块来获取 Azure 资源属性的实际案例。在本教程中,我们将查看
azurerm提供程序中的一个通用数据源,它允许你获取 Azure 中任何已配置资源的信息。准备工作
在这个教程中,我们将编写一个 Terraform 配置,向多个已经预配置的 Azure 网络安全组(NSGs)添加安全规则(这些 NSGs 可能是手动创建的,或者通过 Terraform 创建的)。其目的是向所有具有标签
DEFAULTRULES=TRUE的 NSGs 添加这些规则。此外,我们已经在名为
RG-DEMO的资源组中创建了三个 NSGs。在这些 NSGs 中,只有 NSG1 和 NSG2 具有标签DEFAULTRULES=TRUE。本教程的源代码可以在这里找到:
github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP06/listresources如何操作…
执行以下步骤以获取 Azure 资源:
- 在包含 Terraform 配置的
main.tf文件中,添加以下代码以获取已配置的 NSGs:
data "azurerm_resources" "nsg" { type = "Microsoft.Network/networkSecurityGroups" resource_group_name = "RG-DEMO" required_tags = { DEFAULTRULES = "TRUE" } }- 然后,在同一个文件中,添加以下代码来创建规则:
resource "azurerm_network_security_rule" "default-rules" { for_each = { for n in data.azurerm_resources.nsg.resources : n.name => n } name = "${each.key}-SSH" priority = 100 direction = "InBound" access = "Allow" protocol = "Tcp" source_port_range = "*" destination_port_range = "22" source_address_prefix = "*" destination_address_prefix = "*" resource_group_name = "RG-DEMO" network_security_group_name = each.key }- 最后,我们按照之前和前几章所提到的步骤,运行了适用于 Azure 的基本 Terraform 工作流,设置了变量环境,并执行了
init、plan和apply。
它是如何工作的…
在步骤 1 中,我们使用了
azurerm_resources数据对象,允许您获取任何已经配置的 Azure 资源的基本属性。对于此对象,我们配置了以下属性:-
type:这是请求资源类型。类型列表在此处有文档记录:docs.microsoft.com/zh-cn/azure/azure-resource-manager/management/azure-services-resource-providers。在这里,我们填入了值Microsoft.Network/networkSecurityGroups。 -
resource_group_name:这是已经配置了 NSG 的资源组的名称。 -
required_tag:这是我们要过滤 NSG 的标签列表。
然后,在步骤 2 中,我们通过添加
azurerm_network_security_rule资源完成了此 Terraform 配置,该资源允许在已存在的 NSG 中配置规则。在此资源中,我们首先添加了for_each表达式,它便于对从先前实例化的数据块返回的所有 NSG 进行循环。在构建此循环时,我们使用了表达式data.azurerm_resources.nsg.resources,其中包含从data块检索的 NSG 列表。此外,在name属性中,我们添加了一个前缀,即 NSG 的名称,使用表达式each.key。最后,在network_security_group_name属性中,我们同样使用each.key作为循环中每个 NSG 的名称。关于
for_each表达式和 Terraform 中的循环更多信息,请参考第三章 的配方,使用 Terraform 构建动态环境。最后,在步骤 3 中,我们执行 Terraform 工作流命令来应用这个 Terraform 配置。
还有更多…
我们在本配方中学到,通过使用
azurerm_resources在 Terraform 中,我们可以使用任何已经配置的 Azure 资源。在 Terraform 配置中,我们还可以添加以下
output变量,该变量允许可视化通过azurerm_resource数据块返回的资源:output "nsg" { value = { for n in data.azurerm_resources.nsg.resources : n.name => n } }以下截图显示了此输出的结果:
![]()
我们可以在此输出结果中看到,
data块已经恢复了两个已经配置的 NSG,并具有标签DEFAULTRULEs=TRUE。此外,您还可以看到每个资源的属性,可以在 Terraform 配置的其余部分中利用。但是,必须确保这些资源是在与环境变量
ARM_SUBSCRIPTION_ID中定义的相同订阅中进行配置的,这些变量在执行 Terraform 命令之前设置,正如我们在本章节的 保护 Azure 凭据提供者 配方中看到的。另请参阅
- Terraform 的
azurerm_resources对象数据的文档可以在此处找到:www.terraform.io/docs/providers/azurerm/d/resources.html
使用 Terraform 部署和配置 Azure 虚拟机
在本节中,我们将研究 Terraform 在 Azure 中的一个典型使用案例,我们将使用 Terraform 在 Azure 中配置和部署虚拟机(VM)。
准备工作
对于本节,您不需要任何特殊的前提条件。我们将从零开始编写 Terraform 配置。本节仅涉及编写 Terraform 配置。在实现的各个阶段,我们将学习如何编写这段代码。至于 Azure 的架构,我们已经提前构建了一个网络,其中将包含这个虚拟机,并由以下资源组成:
-
一个名为虚拟网络(VNet)的VNET-DEMO。
-
在这个虚拟网络(VNet)中,注册了一个名为
Subnet1的子网。
此外,将要部署的虚拟机将具有一个公网 IP 地址,以便可以公开访问。
最后,为了在代码中保持虚拟机(VM)密码的机密性,我们将其保存在 Azure Key Vault 中,正如本章节的使用 Azure Key Vault 与 Terraform 保护密钥一节中所讲解的那样。
本章的源代码可以在此处找到:
github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP06/vm如何操作…
编写以下 Terraform 配置以通过 Terraform 部署虚拟机:
- 第一个要构建的资源是资源组,以下代码可以帮助完成这一步:
resource "azurerm_resource_group" "rg" { name = "RG-VM" location = "West Europe" }- 然后,我们编写以下代码来部署公网 IP:
resource "azurerm_public_ip" "ip" { name = "vmdemo-pip" resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location allocation_method = "Dynamic" }- 接下来,我们继续编写网络接口的代码:
data "azurerm_subnet" "subnet"{ name = "Default1" resource_group_name = "RG_NETWORK" virtual_network_name = "VNET-DEMO" } resource "azurerm_network_interface" "nic" { name = "vmdemo-nic" resource_group_name = azurerm_resource_group.rg.name location = azurerm_resource_group.rg.location ip_configuration { name = "vmipconf" subnet_id = data.azurerm_subnet.subnet.id private_ip_address_allocation = "Dynamic" public_ip_address_id = azurerm_public_ip.ip.id } }- 我们使用
keyvault数据块获取虚拟机密码:
data "azurerm_key_vault" "keyvault" { name = "keyvdemobook" resource_group_name = "rg_keyvault" } data "azurerm_key_vault_secret" "vm-password" { name = "vmdemoaccess" key_vault_id = data.azurerm_key_vault.keyvault.id }- 最后,我们编写虚拟机资源的代码,如下所示:
resource "azurerm_linux_virtual_machine" "vm" { name = "myvmdemo" ... admin_username = "adminuser" admin_password = data.azurerm_key_vault_secret.vm-password.value network_interface_ids = [azurerm_network_interface.nic.id] source_image_reference { publisher = "Canonical" offer = "UbuntuServer" sku = "18.04-LTS" version = "latest" } ... provisioner "remote-exec" { inline = [ "apt update", ] connection { host = self.public_ip_address user = self.admin_username password = self.admin_password } } }这些虚拟机资源的完整源代码可以在
github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP06/vm/main.tf查看。它是如何工作的…
在步骤 1中,我们编写了 Terraform 配置,创建包含虚拟机的资源组。此步骤是可选的,因为你可以将虚拟机部署到现有的资源组中,在这种情况下可以使用
azurerm_resource_group数据块,其文档可以在此处找到:www.terraform.io/docs/providers/azurerm/d/resource_group.html然后,在步骤 2和步骤 3中,我们编写了 Terraform 配置,提供了以下内容:
-
一个动态类型的公网 IP,以便我们不必手动设置 IP 地址(这个 IP 地址将是子网中第一个可用地址)。
-
虚拟机使用此 IP 地址的网络接口,并将在已创建的子网中注册。为了获取子网 ID,我们使用了
azurerm_subnet数据源。
在第 4 步中,我们使用
azurerm_key_vault_secret数据源获取虚拟机的密码(更多详情请参见使用 Azure Key Vault 与 Terraform 保护密钥食谱)。最后,在第 5 步中,我们编写了用于配置虚拟机的代码。在这段代码中,我们定义了虚拟机的以下属性:
-
它的名称和大小(包括其 RAM 和 CPU)
-
使用的基本镜像,通常是 Ubuntu 镜像
-
用于虚拟机的身份验证信息,包括登录名和密码(也可以使用 SSH 密钥)
在这个资源中,我们还添加了
remote-exec提供器,它允许你直接在将要配置的虚拟机上远程执行命令或脚本。使用这个提供器可以帮助你配置虚拟机的管理、安全或中间件安装任务。还有更多……
这个方法的新颖之处在于加入了
remote-exec提供器,它允许通过命令或脚本配置虚拟机(VM)。这种方法在执行虚拟机的初始管理步骤时非常有用,比如打开防火墙端口、创建用户和执行其他基本任务。在我们的示例中,我们用它来通过执行apt update命令更新软件包。不过,这种方法要求虚拟机能够从运行 Terraform 的计算机访问,因为它需要连接到虚拟机(通过 SSH 或 WinRM)并执行命令。如果你想保持真正的基础设施即代码(IaC),最好使用如 Ansible、Puppet、Chef 或 PowerShell DSC 等工具进行配置。因此,在使用 Ansible 配置 Windows 虚拟机的情况下,
remote-exec提供器可以很好地用于授权虚拟机上的 WinRM SSL 协议,因为这个端口是 Ansible 用于配置 Windows 机器的端口。此外,在 Azure 中,你还可以使用自定义脚本虚拟机扩展,这是另一种使用脚本配置虚拟机的方式。在这种情况下,你可以使用
azurerm_virtual_machine_extension资源通过 Terraform 来配置此虚拟机扩展,具体说明请参考以下文档:www.terraform.io/docs/providers/azurerm/r/virtual_machine_extension.html警告:每个虚拟机只能有一个自定义脚本扩展。因此,你必须将所有配置操作放在一个脚本中。
除了提供
remote-exec和虚拟机扩展之外,另一种解决方案是使用 Terraform 资源azurerm_virtual_machine的custom_data属性。关于custom_data属性的文档可以在www.terraform.io/docs/providers/azurerm/r/linux_virtual_machine.html#custom_data找到,完整的代码示例可以在github.com/terraform-providers/terraform-provider-azurerm/blob/master/examples/virtual-machines/linux/custom-data/main.tf查看。最后,作为虚拟机配置的另一种选择,我们还可以使用Packer预配置虚拟机镜像,Packer 是 HashiCorp 的另一个开源工具,允许你使用 JSON 或 HCL2(如
www.packer.io/guides/hcl文档中所述)创建自己的虚拟机镜像。创建好镜像后,在 Terraform 的虚拟机配置中,我们将使用 Packer 创建的镜像名称,而不是市场提供的镜像(如 Azure 或其他云提供商)。关于 Packer 的更多信息,请参阅以下文档:www.packer.io/另请参见
在 Azure 文档中提供了各种教程和指南,可以在这里找到:
docs.microsoft.com/en-us/azure/developer/terraform/create-linux-virtual-machine-with-infrastructure使用 Terraform 构建 Azure 无服务器基础设施
在之前的教程中,我们研究了允许在 Azure 中配置 IaaS(即虚拟机)基础设施的 Terraform 配置实现。
在本教程中,我们将保持与上一个教程相同的领域,但这次我们将专注于编写用于配置 PaaS 无服务器基础设施的 Terraform 配置,同时配置一个 Azure 应用服务。
准备工作
本教程的目的是配置一个 Web 应用类型的 Azure 应用服务。在配置过程中,我们将使用 Terraform 在应用服务创建的同时部署一个应用程序。
本教程所需的大部分 Terraform 配置已经在本书的多个教程中讲解过。我们只需要研究部署应用程序到 Web 应用所需的 Terraform 配置。
关于应用程序,它必须打包成格式为
<appname>_<version>.zip的 ZIP 文件,例如myapp_v0.1.1.zip,然后我们将这个 ZIP 文件上传到 Azure Blob 存储中。这个 ZIP 文件可以通过命令行 Azure CLI 上传,具体操作请参见文档docs.microsoft.com/en-us/cli/azure/storage/blob?view=azure-cli-latest#az-storage-blob-upload,或者通过 Terraform 使用azurerm_storage_blob资源上传,文档可以在这里找到:www.terraform.io/docs/providers/azurerm/r/storage_blob.html。我们将在本配方中编写的 Terraform 配置将以安全的方式使用这个 ZIP 文件。该配方的源代码可以在
github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP06/webapp.获取。如何操作…
按照以下步骤使用 Terraform 配置 Web 应用:
-
将以下 Web 应用的 Terraform 代码复制并粘贴到一个新的 Terraform 文件中,地址为
github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP06/sample-app。 -
在这个 Terraform 文件中,我们添加了一个新的
azurerm_storage_account数据块,代码如下:
data "azurerm_storage_account" "storagezip" { name = "storappdemo" resource_group_name = "RG-storageApp" }- 然后,我们添加另一个
azurerm_storage_account_sas数据块,以获取安全令牌,代码如下:
data "azurerm_storage_account_sas" "storage_sas" { connection_string = data.azurerm_storage_account.storagezip.primary_connection_string ... services { blob = true ... } start = "2020–06–15" expiry = "2021–03–21" permissions { read = true write = false ... } }这个代码块的完整代码可以在
github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP06/webapp/main.tf获取。- 最后,我们通过添加以下代码来更新
azurerm_app_resource中的 Azure Web 应用代码:
resource "azurerm_app_service" "app" { ... app_settings = { "WEBSITE_RUN_FROM_PACKAGE" = "https://${data.azurerm_storage_account.storagezip.name}.blob.core.windows.net/app/myapp_v1.0.0/zip${data.azurerm_storage_account_sas.storage_sas.sas}" } }它是如何工作的…
在第 1 步中,我们检索了允许 Web 应用程序部署的 Terraform 配置。在接下来的配方步骤中,我们将完成配置,以便能够在该 Web 应用中直接通过 Terraform 部署 Web 应用程序,同时进行资源的预配。
然后,在第 2 步中,我们添加了
azurerm_storage_account数据块,这将使我们能够从包含应用程序 ZIP 文件的存储账户中检索属性。在第 3 步中,我们添加了azurerm_storage_account_sas数据块,它将返回一个安全令牌到 Blob。在这个令牌中,我们指明访问将是只读的,并且我们只授予对 Blob 服务的访问权限。
最后,在第 4 步中,我们通过在
azurerm_app_service资源中添加WEBSITE_RUN_FROM_PACKAGE键的应用程序设置来完成配置,该键的值是包含 ZIP 文件完整 URL 的字符串,并且我们将返回的令牌密钥与该 URL 拼接在一起。还有更多…
在这个配方中,我们研究了如何通过 Terraform 配置部署 Azure Web App。尽管如此,仍有几种其他方式可以将此应用程序部署到 Web 应用中,详细内容请参见文档:
docs.microsoft.com/en-us/azure/app-service/deploy-zip。我们将在第七章的Terraform 深度探讨配方中学习如何在 Azure Pipelines 中通过 CI/CD 管道自动化这一部署过程。
另见
-
有关 Web 应用的
WEBSITE_RUN_FROM_PACKAGE应用设置的文档,请参见:docs.microsoft.com/en-us/azure/app-service/deploy-run-package -
有关
azurerm_storage_account_sas块数据的文档,请参见:www.terraform.io/docs/providers/azurerm/d/storage_account_sas.html -
有关 Terraform 资源
azurerm_app_service的文档,请参见:www.terraform.io/docs/providers/azurerm/r/app_service.html
为现有的 Azure 基础设施生成 Terraform 配置
当企业希望自动化他们的流程并采用基础设施即代码(IaC)实践(例如,使用 Terraform)时,他们面临着如何为已经配置好的基础设施生成代码的挑战。
实际上,对于新的基础设施,写好相应的 Terraform 配置后执行它即可完成资源的配置。而对于已经配置的资源,根据其数量和配置情况,编写所有 Terraform 配置并执行它以生成相应的 Terraform 状态文件可能会很长且繁琐。此外,这一 Terraform 配置的执行可能会对这些资源产生副作用,尤其是当它们已经在生产环境中使用时。
作为这个问题的部分答案,我们已经在第四章的导入现有资源配方中看到,使用 Terraform CLI,我们可以使用
terraform import命令将已经配置的资源导入到 Terraform 状态文件中。然而,这个命令要求:一方面,必须提前写好相应的 Terraform 配置,因为这个命令只会更新 Terraform 状态文件;另一方面,每个资源都必须执行该命令才能导入。鉴于此,并且在许多客户提出类似请求后,我问自己这个问题:是否有工具或脚本可以用于为已经在 Azure 中配置的资源生成 Terraform 配置及其 Terraform 状态文件?
在本教程中,我将与您分享我使用其中一个 Terraform 配置生成工具的研究结果,这个工具叫做Terraformer,它托管在 Google Cloud Platform 的 GitHub 仓库中,网址是
github.com/GoogleCloudPlatform/terraformer。准备就绪
要使用Terraformer,首先必须下载与您希望生成代码的 Terraform 提供程序版本相对应的版本。在我们的案例中,我们希望为 Azure 基础设施生成 Terraform 配置,因此我们运行以下 Linux 脚本:
curl -LO https://github.com/GoogleCloudPlatform/terraformer/releases/download/$(curl -s https://api.github.com/repos/GoogleCloudPlatform/terraformer/releases/latest | grep tag_name | cut -d '"' -f 4)/terraformer-azure-linux-amd64 chmod +x terraformer-azure-linux-amd64 sudo mv terraformer-azure-linux-amd64 /usr/local/bin/terraformer此脚本下载Terraformer ZIP 包,解压缩后将其复制到
/usr/local/bin本地文件夹中。本教程将使用 Linux 终端进行,但 Terraformer 在 Windows 上也能以相同的方式工作,具体如以下文档所述:
github.com/GoogleCloudPlatform/terraformer#installation安装完成后,可以通过执行
terrafor``mer --help命令来检查其是否安装成功,并显示 Terraformer 命令列表:![]()
本教程的目的是生成 Azure 基础设施的 Terraform 配置和 Terraform 状态文件,Azure 基础设施由多个资源组组成,如下所示的屏幕截图所示:
![]()
本教程中,我们将把 Terraform 配置的生成限制为这些资源组,而不是它们的内容。
如何操作…
要使用Terraformer生成 Terraform 配置,请执行以下步骤:
- 在将包含生成代码的文件夹中,我们创建了一个名为
provider.tf的文件,并在其中声明了 Terraform 提供程序的代码,如下所示:
provider "azurerm" { features {} }-
在命令行终端中,在此文件夹内,我们需要通过运行
terraform init命令下载azurerm提供程序。 -
然后,我们为 Terraform 身份验证设置四个 Azure 环境变量:
export ARM_SUBSCRIPTION_ID="xxxxxx-xxx-xxxxx-xxxx" export ARM_CLIENT_ID="xxxxx-xxxx-xxxx-xxxxx" export ARM_CLIENT_SECRET="xxxx-xxxxxx-xxxxxx-xxxxx" export ARM_TENANT_ID="xxxxx-xxxxxx-xxxxx-xxxxx"- 然后,我们通过执行以下Terraformer命令来生成 Terraform 配置:
terraformer import azure --resources=resource_group --compact --path-pattern {output}/{provider}/- 一旦生成了 Terraform 配置和 Terraform 状态文件,我们将进入生成的
generate/azurerm文件夹,并将features {}表达式添加到provider.tf文件中,如下所示:
provider "azurerm" { version = "~>v2.14.0" features {} }- 最后,在这个文件夹中,我们将通过运行基本的 Terraform 工作流,使用
terraform init和terraform plan命令来测试生成的配置:
![]()
如果输出成功生成,我们应该能看到配置&生成并没有应用任何更改。它与我们的基础设施完全一致。
它是如何工作的……
在第 1 步中,我们创建了一个包含提供者声明的文件用于下载。然后,我们使用
terraform init命令下载它,这个命令是在第二步执行的。第 3 步对应于我们在本章的保护 Azure 凭证提供者部分中详细描述的 Azure 认证环境变量集。然后,在第 4 步中,我们使用 Terraformer 为已提供的 Azure 组资源生成了 Terraform 配置和 Terraform 状态文件,在使用的命令行中,我们指定了以下选项:
-
resources:这是需要生成 Terraform 配置的 Azure 资源列表。 -
compact:这使得可以指定所有 Terraform 配置将生成在一个单一文件中。 -
path-pattern:此项指定了将包含生成代码的文件夹的模式。
以下截图展示了 Terraformer 的执行情况:
![]()
generated/azurerm文件夹已经生成了 Terraform 文件,如下截图所示:![]()
在此文件夹中,我们看到以下生成的文件:
-
Terraform 配置的
.tf文件 –resources.tf、provider.tf、outputs.tf和variables.tf -
tfstate文件 –terraform.tfstate
在第 5 步中,我们将
features {}表达式添加到了在generated/azurerm文件夹中生成的提供者声明中。最后,在第 6 步中,我们通过执行
terraform plan命令的预览,验证了生成的代码是基础设施代码。在执行过程中,不会应用任何更改。Terraform 配置与我们的基础设施高度一致。还有更多……
Terraformer 还包含一个选项,可以进行干运行,预览将要生成的代码。
为此,我们将执行以下命令,生成一个
plan.json文件,并包含将要生成的资源的描述:terraformer plan azure --resources=resource_group --compact --path-pattern {output}/{provider}/我们查看这个创建的 JSON 文件的内容以检查其一致性,然后,为了执行生成,我们执行以下命令:
terraformer import plan generated/azurerm/plan.json此外,在使用 Terraformer 之前,有必要检查要生成的资源是否得到良好的支持。例如,在 Azure 的情况下,资源列表可以在这里找到:
github.com/GoogleCloudPlatform/terraformer#use-with-azure。最后,在其他 Terraform 配置生成工具中,有一个非常好的工具叫做az2tf (
github.com/andyt530/py-az2tf),它曾经基于相同的 Terraformer 原则,但不幸的是,这个工具已经不再维护。此外,还有TerraCognita (github.com/cycloidio/terracognita/),它仍然集成了多个 Azure 资源,以及Terraforming (github.com/dtan4/terraforming),该工具仅适用于 AWS。这些工具的问题在于,它们需要跟随 Terraform 语言和不同提供商的演进,这需要大量的开发和维护时间。另请参见
Terraformer 的源代码和文档可以在此处找到:
github.com/GoogleCloudPlatform/terraformer。 -
深入探索 Terraform
在本书中,我们从与 Terraform 安装相关的配方开始,涵盖了 Terraform 配置的编写以及使用 Terraform CLI 命令。接着我们研究了通过使用模块共享 Terraform 配置。最后,我们专注于使用 Terraform 构建 Azure 基础设施。
现在,在本章中,我们将讨论一些配方,帮助我们更深入地使用 Terraform。我们将学习如何通过 Terraform 模板生成 Ansible 库存,并使用 kitchen-terraform 插件测试 Terraform 配置。我们将讨论如何防止资源被销毁,如何使用 Terraform 实现零停机部署技术,以及如何检测在 Terraform 应用更改时资源的删除。
然后我们将讨论使用 Terragrunt 来管理 Terraform 配置的依赖关系,并将其用作 Terraform CLI 的封装器。最后,我们将研究 Terraform 运行时的集成以及在 CI/CD 流水线中管理工作区。
在本章中,我们涵盖了以下配方:
-
使用 Terraform 创建 Ansible 库存
-
使用 kitchen-terraform 测试 Terraform 配置
-
防止资源被销毁
-
使用 Terraform 实现零停机部署
-
检测由计划命令删除的资源
-
使用 Terragrunt 管理 Terraform 配置依赖
-
使用 Terragrunt 作为 Terraform 的封装器
-
在 Azure Pipelines 中为 Terraform 配置构建 CI/CD 流水线
-
在 CI/CD 中使用工作区
第八章:技术要求
本章的配方需要以下前提条件:
-
kitchen-terraform,可以在github.com/newcontext-oss/kitchen-terraform上找到,同时还需要 Ruby,可以从www.ruby-lang.org/en/下载。 -
Terragrunt 的文档可以在
terragrunt.gruntwork.io/找到。 -
此外,我们还将使用 jq 工具来解析 JSON。你可以从
stedolan.github.io/jq/下载它。 -
最后,在与 CI/CD 一起工作时,我们将使用 Azure Pipelines 作为我们的 CI/CD 平台。Azure Pipelines 是 Azure DevOps 的一项服务。你可以通过
azure.microsoft.com/en-us/services/devops/创建一个免费账户。
本章的源代码可以在本书的 GitHub 仓库中找到,地址为 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP07。
查看以下视频以查看代码实例:
"YouTube Bitly 链接"
使用 Terraform 创建 Ansible 库存
Terraform 是一个非常好的基础设施即代码(IaC)工具,它允许我们通过代码构建复杂的基础设施。
正如我们在第六章的使用 Terraform 配置 Azure 基础设施中研究过的,关于虚拟机的构建,所有云提供商中,Terraform 的共同目标是构建一个虚拟机,但不进行配置,这包括其中间件的安装和管理。
在允许我们使用 Terraform 配置虚拟机(VM)创建后进行配置的配置即代码(CaC)工具中,有一个非常受欢迎的工具是Ansible (www.ansible.com/),它在开源世界中非常流行(与 Chef 和 Puppet 类似)。
Ansible 的一个优点是它不需要代理,这意味着你无需在要配置的虚拟机上安装代理。因此,为了知道哪些虚拟机需要配置,Ansible 使用一个名为inventory的文件,该文件包含需要配置的虚拟机列表。
在本配方中,我们将学习如何使用 Terraform 的模板功能生成这个inventory文件。
准备工作
本配方的目的是讨论如何自动创建 Ansible 的inventory文件,而不是讨论 Ansible 的安装和使用。
若要了解更多关于 Ansible 的信息,我邀请你阅读我书中的第三章,使用 Ansible 配置 IaaS 基础设施,这本书名为学习 DevOps,也可以在 Packt 上找到,网址为 www.packtpub.com/eu/cloud-networking/learning-devops。
我们配方的起点是使用 Terraform 在 Azure 中创建虚拟机,而这些虚拟机的私有 IP 地址在创建之前是未知的。在此 Terraform 配置中,我们使用了在第六章的使用 Terraform 配置和部署 Azure 虚拟机配方中已经学习过的配置,使用 Terraform 配置 Azure 基础设施。因此,为了简化起见,我们使用了公共注册表中发布的 Terraform 模块,并采用以下 Terraform 配置:
- 实例化一个
vmhosts变量,该变量指定我们希望创建的虚拟机的主机名:
variable "vmhosts" {
type = list(string)
default = ["vmwebdemo1", "vmwebdemo2"]
}
- 然后,使用
network模块并从公共注册表计算来创建网络中的虚拟机:
module "network" {
source = "Azure/network/azurerm"
resource_group_name = "rg-demoinventory"
subnet_prefixes = ["10.0.2.0/24"]
subnet_names = ["subnet1"]
}
module "linuxservers" {
source = "Azure/compute/azurerm"
resource_group_name = "rg-demoinventory"
vm_os_simple = "UbuntuServer"
nb_instances = 2
nb_public_ip = 2
vm_hostname = "vmwebdemo"
public_ip_dns = var.vmhosts
vnet_subnet_id = module.network.vnet_subnets[0]
}
在前面的 Terraform 配置中,我们创建了一个虚拟网络和一个子网,并创建了两个将具有私有 IP 地址的 Linux 虚拟机。
本配方的目标是在相同的 Terraform 配置中生成一个inventory文本文件,该文件将包含 Terraform 创建的主机列表(以及它们的 IP 地址)。这个清单文件将采用以下格式:
[vm-web]
<host1> ansible_host=1<ip 1>
<host2> ansible_host=<ip 2>
本配方的完整源代码可以在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP07/ansible-inventory找到。
如何操作……
要通过 Terraform 生成 Ansible inventory,请执行以下步骤:
- 在包含 Terraform 配置的文件夹内,我们创建一个名为
template-inventory.tpl的新文件,内容如下:
[vm-web]
%{ for host, dns in vm_dnshost ~}
${host} ansible_host=${dns}
%{ endfor ~}
- 然后,在创建 VM 的 Terraform 配置的
main.tf文件中,我们添加以下代码来生成inventory文件:
resource "local_file" "inventory" {
filename = "inventory"
content = templatefile("template-inventory.tpl",
{
vm_dnshost = zipmap(var.vmhosts,module.linuxservers.network_interface_private_ip)
})
}
- 最后,为了创建 VM 并生成
inventory文件,我们运行基本的 Terraforminit、plan和apply工作流命令。
它是如何工作的……
我们首先创建一个 template-inventory.tpl 文件,该文件使用 Terraform 的模板格式。在这个文件中,我们使用一个 for 循环,语法为 %{ for host, ip in vm_dnshost ~},它允许我们循环遍历 vm_dnshost 变量的元素。对于循环中的每个 VM,我们使用以下语法:
${host} ansible_host=${ip}
我们通过 %{ endfor ~} 语法结束循环。
有关此模板格式的更多详细信息,请阅读文档:www.terraform.io/docs/configuration/expressions.html#string-templates。
然后在 第 2 步 中,我们在 Terraform 配置中添加一个 local_file 资源(我们已经在第二章的 使用 Terraform 操作本地文件 配方中学习过),在其中填写以下属性:
filename:它的值为inventory,即将生成的文件名。
在这个示例中,文件将在当前包含此 Terraform 配置的目录中生成。你可以选择进入另一个文件夹进行生成和存储。
-
content:包含将填充此文件的元素。这里,我们使用templatefile函数,传递以下作为参数:-
模板文件的名称
template-inventory.tpl,这是我们在 第 1 步 中创建的文件 -
vm_dnshost变量将填充模板文件的内容。我们使用内置的 Terraformzipmap函数,它允许我们从两个列表构建一个映射,一个是键列表,另一个是值列表。
-
关于 zipmap 函数的文档可以在www.terraform.io/docs/configuration/functions/zipmap.html找到。
depend_on:这个参数是 Terraform 语言的一部分,表示两个或多个资源之间的依赖关系(关于 Terraform 依赖关系的文档可以在learn.hashicorp.com/terraform/getting-started/dependencies找到)。在这里,我们表示local_file资源与 VM 模块之间的依赖关系,这样 Terraform 只有在创建 VM 后才会创建inventory文件。
最后,在最后一步中,我们执行 Terraform 工作流的命令,在执行结束时,我们可以看到inventory文件确实已经生成,内容如下:
[vm-web]
vmwebdemo1 ansible_host=10.0.2.5
vmwebdemo2 ansible_host=10.0.2.4
现在,所有新添加到此 Terraform 配置中的虚拟机将动态地添加到此 Ansible 库存中。
还有更多……
本食谱的主要目的是展示我们在 Ansible 库存中应用的 Terraform 模板的使用。这些模板可以有多种其他用途,比如使用cloud-init文件来配置虚拟机,具体内容可以参考文章grantorchard.com/dynamic-cloudinit-content-with-terraform-file-templates/。
另见
-
Terraform
templatefile函数的文档可以在www.terraform.io/docs/configuration/functions/templatefile.html找到。 -
local提供者的local_file资源的文档可以在registry.terraform.io/providers/hashicorp/local/latest/docs/resources/file找到。 -
Packt 出版的关于 Ansible 的书籍列表可以在
subscription.packtpub.com/search?query=ansible找到。 -
这里有一系列关于 Ansible 库存由 Terraform 生成的网页文章,提供了不同的解决方案:
使用 kitchen-terraform 测试 Terraform 配置
我们已经在第五章的使用 Terratest 测试 Terraform 模块代码食谱中,共享 Terraform 配置与模块,学习了如何使用 Terratest 框架测试 Terraform 模块。
在这个食谱中,我们将使用另一个工具测试 Terraform 配置:KitchenCI及其kitchen-terraform插件。
准备工作
kitchen-terraform是用 Ruby 编写的,并且是KitchenCI(简称Kitchen)的一个插件,Kitchen 是一个基础设施即代码(IaC)测试工具。为了正确应用此食谱,您必须首先理解 Kitchen 的原理和工作流程,这些内容有文档记录在kitchen.ci/index.html。
由于 Kitchen 是用Ruby编写的,你需要在计算机上安装 Ruby(可以从www.ruby-lang.org/en/下载,确保使用至少 2.4 版本),并按照www.ruby-lang.org/en/documentation/installation/提供的安装文档进行安装。
除了 Ruby,我们还需要安装Bundle,可以从bundler.io/获得。它是 Ruby 包的依赖管理工具。
我们可以通过使用 RubyGems(Ruby 包管理器)来安装kitchen-terraform,可以通过运行以下命令来完成:
gem install kitchen-terraform
或者,第二种方式是,我们可以使用 Kitchen 推荐的使用 gem 和 bundle 的方法,按照以下步骤进行:
- 在包含要测试的 Terraform 配置的文件夹中,我们创建一个 Gemfile,其中包含要安装的包的列表(此处指定
kitchen-terraform包),内容如下:
source "https://rubygems.org/" do
gem "kitchen-terraform", "~> 5.4"
end
- 在终端中,执行以下命令来安装 Gemfile 中引用的包:
bundle install
执行上述命令会安装运行kitchen-terraform所需的所有包。
最后,关于编写测试,我们将使用Inspec,这是一个基于 Rspec 的测试框架。Inspec 允许你测试本地系统甚至是云中的基础设施。有关 Inspec 的更多信息,建议你阅读其文档:www.inspec.io/。
为了简单地说明如何使用kitchen-terraform,我们将在本食谱中测试 Terraform 配置的正确性,该配置会生成一个 Ansible 清单文件,我们在之前的食谱中已经学习过。我们编写的测试目的是验证inventory文件是否确实已经生成并且不是空的。
在本食谱中,目标不是测试网络和虚拟机的创建,而是仅测试inventory文件。
最后,和所有集成测试一样,最好有一个隔离的系统或环境来运行测试。
本食谱的源代码可以在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP07/kitchen找到。
如何实现…
要测试使用kitchen-terraform执行的 Terraform 配置,执行以下步骤:
- 在包含 Terraform 配置的文件夹内,创建 Inspec 的
test文件夹,文件树结构如下:
test > integration > kt_suite
- 在
kt_suite文件夹中,添加一个名为inspec.yml的 Inspec 配置文件,文件内容如下:
---
name: default
- 在
kt_suite文件夹中,创建一个名为controls的新文件夹,该文件夹将包含 Inspec 测试。然后,在controls文件夹中,添加一个名为inventory.rb的文件,文件内容如下:
control "check_inventory_file" do
describe file('./inventory') do
it { should exist }
its('size') { should be > 0 }
end
end
- 在 Terraform 配置文件夹的根目录下,我们创建一个名为
kitchen.yml的 Kitchen 配置文件,内容如下:
---
driver:
name: terraform
provisioner:
name: terraform
verifier:
name: terraform
systems:
- name: basic
backend: local
controls:
- check_inventory_file
platforms:
- name: terraform
suites:
- name: kt_suite
- 在终端(位于 Terraform 配置文件夹的根目录下)中,运行以下
kitchen命令:
kitchen test
它是如何工作的…
该配方的执行分为三个阶段:
-
编写检查测试
-
编写 Kitchen 配置
-
Kitchen 执行
从 步骤 1 到 3,我们通过以下步骤编写了检查测试:
- 首先,我们创建了一个文件夹结构,用于存放配置文件和 Inspec 测试。在
kt_suite文件夹中,我们创建了inspec.yml文件,这是 Inspec 配置文件。在我们的案例中,这个文件仅包含name属性,并且其值为default。
若要了解更多关于 Inspec 配置文件的信息,请参阅文档:www.inspec.io/docs/reference/profiles/。
-
然后,在
controls > inventory.rb文件中,我们通过创建一个control "check_inventory_file" do控制项来编写 Inspec 测试(使用 Rspec 格式),该控制项包含了这些测试。在这些测试中,我们使用了资源fileInspec(参见文档:www.inspec.io/docs/reference/resources/file/),它允许我们对文件执行测试。在这个控制项中,资源的属性是inventory,这是由 Terraform 生成的库存文件的名称。在这个控制项中,我们编写了两个测试:-
it { should exist }:此库存文件必须存在于磁盘上。 -
its('size') { should be > 0 }:此文件的大小必须大于0,因此它必须包含一些内容。
-
测试编写完成后,在 第 4 步 中,我们创建了 kitchen.yml 文件,该文件包含 Kitchen 配置,由三部分组成,第一部分是驱动程序:
driver:
name: terraform
驱动程序是用于测试的平台。Kitchen 支持多种虚拟和云平台。在我们的案例中,我们使用由 kitchen-terraform 插件提供的 terraform 驱动程序。
Kitchen 支持的驱动程序文档可以在此查看:kitchen.ci/docs/drivers/。
kitchen.yml 文件的第二部分是 provisioner:
provisioner:
name: terraform
provisioner 是一个用于配置虚拟机的工具。它可以使用脚本、Chef、Ansible 或 Desired State Configuration(DSC)。在我们的案例中,由于我们在测试中不进行虚拟机配置,因此我们使用由 kitchen-terraform 提供的 terraform 提供者。
Kitchen 支持的提供者文档可以在此查看:kitchen.ci/docs/provisioners/。
第三部分是 verifier:
verifier:
name: terraform
systems:
- name: basic
backend: local
controls:
- check_inventory_file
platforms:
- name: terraform
suites:
- name: kt_suite
verifier 是一个用于测试由提供者应用的组件的系统。我们可以使用 Inspec、Chef、shell 或 pester 作为我们的测试框架。在我们的案例中,我们在控制节点上配置了验证器,并编写了 第 2 步 中的 Inspec 测试套件。此外,control 属性是可选的——它允许我们在测试过程中筛选需要执行的 Inspec 控制项。
关于 Kitchen 支持的验证器的文档可以在 kitchen.ci/docs/verifiers/ 上查看。
-
最后,在最后一步,我们通过执行
kitchen test命令来进行测试,该命令将根据 YAML 配置文件执行以下操作:-
执行 Terraform 工作流的
init和apply命令。 -
运行 Inspec 测试。
-
执行
destroyTerraform 命令来删除为测试提供的所有资源。
-
此次执行的结果显示在接下来的三张截图中。
实际上,这些操作是在同一控制台和工作流中执行的。我将其拆分为三张截图,以便更好地展示,因为仅用一张截图无法看到全部内容。
以下截图显示了 init 和 apply 命令的执行过程:

以下截图显示了 Inspec 的执行过程:

最后一张截图显示了 destroy 命令:

这三张截图显示了 Terraform 的执行过程,接着是 Inspec 测试的成功执行,表明我的 inventory 文件确实是由 Terraform 生成的,最后是销毁为测试分配的资源。
还有更多……
若要深入了解测试的编写,我们可以添加 Inspec 的 its('content') 表达式,这样我们就可以测试文件的内容,具体说明请参考 Inspec 文档 www.inspec.io/docs/reference/resources/file/。
关于本示例中测试的执行,我们需要执行 kitchen test 命令。如果是集成测试,在执行测试后,我们不想销毁通过 Terraform 创建的资源,可以执行 kitchen verify 命令。
最后,如前言中所述,在本示例中我们使用 kitchen-terraform 来测试 Terraform 配置,但我们也可以使用它来测试 Terraform 模块。
另见
-
KitchenCI 文档可以在
kitchen.ci/上查看。 -
kitchen-terraform插件的源代码可以在github.com/newcontext-oss/kitchen-terraform上找到。 -
你可以在
newcontext-oss.github.io/kitchen-terraform/tutorials/上找到关于kitchen-terraform的教程。 -
有关
kitchen test命令的更多信息,请参阅文档kitchen.ci/docs/getting-started/running-test/。
防止资源被销毁
使用 IaC 时需要注意某些情况。实际上,当 IaC 集成到 CI/CD 管道中时,包含重要数据的资源可能会被自动删除。这可以通过更改 Terraform 资源的属性来完成,这需要删除并重新创建该资源,或者执行 terraform destroy 命令。幸运的是,Terraform 在其语言中包含了一项配置,防止敏感资源被销毁。
在本食谱中,我们将展示如何防止销毁 Terraform 配置中管理的资源。
准备工作
对于本食谱,我们将使用一个 Terraform 配置,代码可在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP07/sample-app 找到。该配置旨在管理 Azure 中的以下资源:
-
资源组
-
一个服务计划
-
一个 Azure 应用服务(Web 应用)实例
-
一个 Azure Application Insights 实例
在公司项目中,我们常常遇到一个问题,涉及到包含数据的资源。在我们的示例中,这是包含应用程序日志和指标的 Application Insights 实例,该实例属于 Web 应用并且不应被自动删除。
让我们设想一个情境:一家公司决定更改其资源的命名规则,我们需要使用新的命名规则更新 Terraform 配置。当运行 Terraform 时,我们将从 terraform plan 命令中获得以下结果:

如你所见,名称更改需要删除包含重要日志指标的 Application Insights 实例。
本食谱的目的是更改 Terraform 配置,使得 Application Insights 资源永远不会被删除。
本食谱的源代码可以在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP07/preventdestroy 找到。
如何操作…
为了防止 Terraform 删除资源,请执行以下步骤:
- 在 Application Insights 资源的 Terraform 配置中,添加以下
lifecycle块:
resource "azurerm_application_insights" "appinsight-app" {
...
lifecycle {
prevent_destroy = true
}
}
-
在
variables.tf中,将app_name变量的默认值更改为另一个名称,如MyApp2-DEV1。 -
执行 Terraform CLI 工作流,并且结果如下图所示:

工作原理…
在本示例中,我们添加了 Terraform lifecycle 块,其中包含允许与资源管理交互的属性。在这里,我们使用了 prevent_destroy 属性,顾名思义,它可以防止指定资源被销毁。
还有更多…
正如我们讨论过的,prevent_destroy属性允许你禁止资源的删除。
请注意,在我们以 Azure 为例的情况下,此属性并不会禁止通过 Azure 门户或 Azure CLI 删除资源。
但需要注意的是,如果 Terraform 配置中的资源包含此属性,并且在执行terraform apply命令时必须删除此属性,那么prevent_destroy属性会阻止应用程序对 Terraform 配置中描述的所有资源进行更改。这会阻止我们对资源应用更改。这也是我个人将 Terraform 配置拆分的原因之一,将不能被销毁的敏感资源配置放在一个文件夹中(因此会有一个单独的 Terraform 状态文件),而其他资源则放在另一个文件夹中。这样,我们可以应用资源的更改,而不会被资源销毁防护设置所阻塞。
在这里,我写的是关于拆分 Terraform 配置和状态文件的内容,但在 CI/CD 流水线中,也有必要拆分工作流,一个流水线负责应用更改,另一个负责销毁资源。
此外,主要是为了防止人为错误,无法通过想要使该属性的值动态化的方式将变量添加到lifecycle块的属性值中。你可能尝试使用bool类型的变量,例如以下代码:
lifecycle {
prevent_destroy = var.prevent_destroy_ai
}
然而,在执行terraform apply命令时,会发生以下错误:

这些错误表示在lifecycle块中不允许使用变量,因此你必须在代码中保持 true/false 的值。
另请参见
-
prevent_destroy属性的文档可以在www.terraform.io/docs/configuration/resources.html#prevent_destroy找到。 -
有关漂移管理的有趣文章可以在
www.hashicorp.com/blog/detecting-and-managing-drift-with-terraform/找到。 -
阅读 HashiCorp 的这篇文章,了解如何使用 Terraform 实现功能切换、蓝绿部署和金丝雀测试,文章地址:
www.hashicorp.com/blog/terraform-feature-toggles-blue-green-deployments-canary-test/。
使用 Terraform 进行零停机部署
如前一教程所讨论,改变 Terraform 配置中描述的某些资源属性可能导致资源被销毁并随后重新创建。资源按 Terraform 中执行的顺序被销毁和重新创建。换句话说,第一个执行的资源将首先被销毁,然后重新创建,在生产环境中,在这段时间内,可能会导致停机,也就是服务中断。停机时间的长短取决于需要销毁并重建的资源类型。
例如,在 Azure 中,虚拟机(VM)销毁和重建的时间要比 Web 应用或网络安全组(NSG)规则更长。
在 Terraform 中,有一种机制可以实现零停机时间,从而避免在删除资源时出现服务中断。
在本教程中,我们将研究如何在 Terraform 配置中描述的资源上实现零停机时间。
准备工作
对于本教程,我们将使用来自 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP07/sample-app 的 Terraform 配置。此配置的目的是管理 Azure 中的以下资源:
-
一个资源组
-
一个服务计划
-
一个应用服务(Web 应用)实例
-
一个应用程序洞察实例
此外,这个 Terraform 配置已经应用于 Azure 云。
对于我们的使用案例,假设某公司决定更改资源名称,我们需要使用新名称更新 Terraform 配置。运行 Terraform 时,使用 terraform plan 命令将获得以下结果:

如你所见,名称更改需要删除托管我们 Web 应用的 Web 应用程序。这次删除将在重新创建时导致应用程序在短时间内无法访问。本教程的目的是修改 Terraform 配置,即使删除应用服务资源,Web 应用仍然可用。
本教程的源代码可以在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP07/zerodowntime 获取。
怎么做……
为了在 Terraform 配置中实现零停机时间,执行以下步骤:
- 在 Terraform 配置中,在
azurerm_app_service资源内,添加lifecycle块,如下所示的代码:
resource "azurerm_app_service" "app" {
name = "${var.app_name}-${var.environement}"
...
lifecycle {
create_before_destroy = true
}
}
-
更改应用服务的
name属性以应用新的命名法。 -
执行 Terraform CLI 工作流,
terraform apply结果将显示如下截图所示:

它是如何工作的……
在第 2 步中,我们为azurerm_app_service资源添加了lifecycle块。在这个块中,我们添加了create_before_destroy属性,并将其值设置为true。这个属性使得在资源被销毁时,Terraform 会先重新创建资源,然后再删除原始资源,从而实现资源的再生成。
还有更多…
正如我们所看到的,使用这个属性后,服务不再中断。只要新资源未创建,旧资源就不会被删除,应用程序会继续在线。
然而,在使用create_before_destroy之前,需要考虑以下几点:
-
create_before_destroy属性仅在配置更改需要删除并重新生成资源时有效。它仅在执行terraform apply命令时有效;在执行terraform destroy命令时无效。 -
你必须小心确保将要创建的资源的名称与之后将被销毁的资源名称不同。否则,如果名称相同,资源可能不会被创建。
此外,零停机时间技术只有在受影响的资源在创建完成时完全可用时才真正有效。例如,以虚拟机为例:尽管 Terraform 可以快速创建它,但它在所有配置(中间件安装和应用程序部署)完成后仍然存在。所有这些配置可能会产生停机时间,因此,在这种情况下,我建议使用 HashiCorp 的 Packer(www.packer.io/),它可以创建已经完全配置好的虚拟机镜像。
要在 Azure 中使用 Packer 和 Terraform 实现零停机时间,请阅读教程docs.microsoft.com/en-us/azure/developer/terraform/create-vm-scaleset-network-disks-using-packer-hcl。
最后,我们已经在本教程中看到如何使用 Terraform 实现零停机时间的部署,但根据你的提供者,可能还有其他本地的实践。例如,我们还可以使用负载均衡器,对于 Azure 上的 App Service 实例,我们可以使用插槽,正如在文档中所解释的docs.microsoft.com/en-us/azure/app-service/deploy-staging-slots。
另请参见
-
阅读关于
create_before_destroy属性的 HashiCorp 博客文章,链接:www.hashicorp.com/blog/zero-downtime-updates-with-terraform/。 -
关于零停机时间的好文章可以在
dzone.com/articles/zero-downtime-deployment找到。
检测由plan命令删除的资源
Terraform 的一个关键特性是可以通过terraform plan命令预览将要应用到特定基础设施上的更改。
在本书中,我们经常讨论如何在终端中显示更改的可视化效果,但我们较少讨论如何自动评估和分析terraform plan命令的结果。
在这个食谱中,我们将学习如何分析terraform plan命令的结果。
准备工作
要应用这个食谱,我们需要安装jq工具,所有平台均可从stedolan.github.io/jq/下载。
在这个食谱中,我们将在 Windows 上使用 PowerShell 运行 jq,但其他操作系统上的所有步骤都是一样的。
使用的 Terraform 配置可以在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP07/detectdestroy找到,并且我们必须在基础设施上预先运行它。
这个食谱的目的是通过脚本检测terraform plan的执行是否会导致资源被销毁。
如何操作…
执行以下步骤以分析plan命令的结果:
- 在 Terraform 配置中,我们更改了 App Service 实例的
name属性的值(通过添加test一词),如下所示:
resource "azurerm_app_service" "app" {
name = "${var.app_name}-test-${var.environement}"
....
}
- 然后,我们执行
terraform init命令,再执行terraform plan命令:
terraform plan -out="tfout.tfplan"
- 最后,我们执行以下 PowerShell 脚本,分析被删除资源的数量:
$tfplan = terraform show -json tfout.tfplan
$actions = $tfplan | jq.exe .resource_changes[].change.actions[]
$nbdelete = $actions -match 'delete' | Measure-Object | Select-Object Count
Write-Host $nbdelete.Count
此脚本也可以在github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP07/detectdestroy/detectdestroy.ps1找到。
它是如何工作的…
在第 1 步,我们通过更改 App Service 实例的名称来修改 Terraform 配置,这将在apply命令执行时导致该资源被销毁。
然后,在第 2 步,我们执行terraform plan命令,并使用-out="tfout.tfplan"选项,这样我们可以将plan的输出保存到文件中(以二进制格式)。
最后,在第 3 步,我们编写 PowerShell 脚本,分析生成的计划中在应用过程中将要销毁的资源数量。该脚本由四行代码组成。以下是详细信息:
-
在第 1 行,我们在第 2 步生成的
tfout.tfplan文件上使用terraform show命令。我们在此命令中添加了-json选项,以便以 JSON 格式获取输出。 -
在第 2 行,我们在前一行获得的 JSON 结果上使用jq,并过滤出 Terraform 将要应用的操作列表,以获得操作数组(
add、delete和no-op)。 -
在第 3 行,我们在此数组上筛选所有
delete操作,并使用CountPowerShell 对象获取该筛选数组的数量。 -
在最后一行,我们显示了计数值,该值对应于已删除的资源数量。
以下截图展示了此脚本的结果:

$nbdelete.Count返回1,表示将被删除的资源数量。
还有更多……
关于此食谱所使用的脚本语言,我们使用了 PowerShell 编写,但当然也可以用任何脚本语言编写,如 Bash 或 Python。我们还可以将此脚本放入一个函数中,该函数在执行terraform plan命令后立即调用,并且在该函数返回正数删除数量时不apply Terraform 配置。
还有其他工具用于解析和处理terraform plan命令生成的计划。在这些工具中,包括npm包,如terraform-plan-parser,可在github.com/lifeomic/terraform-plan-parser上获取,或Terraform 的 Open Policy Agent,可在www.openpolicyagent.org/docs/latest/terraform/上查看。
另请参阅
-
有关
terraform plan命令的 JSON 格式的详细文档,可在www.terraform.io/docs/internals/json-format.html查阅。 -
terraform show命令的文档可以在www.terraform.io/docs/commands/show.html查阅。
使用 Terragrunt 管理 Terraform 配置依赖关系
在本书的几个食谱中,我们讨论了包含 Terraform 配置的文件组织。在第二章的在多个环境中配置基础设施食谱中,我们对此进行了更具体的探讨,编写 Terraform 配置,概述了几种架构解决方案。
关于配置结构的最佳实践之一是将 Terraform 配置分为基础设施和应用程序组件,如www.cloudreach.com/en/resources/blog/how-to-simplify-your-terraform-code-structure/文章中所述。将结构拆分为多个配置的挑战在于维护这些组件之间的依赖关系和运行计划。
在所有与 Terraform 相关的第三方工具中,有一个由 Gruntwork 开发的Terragrunt(terragrunt.gruntwork.io/)。Terragrunt 是开源的,并为 Terraform 配置的组织和执行提供了许多附加功能。
在本示例中,我们将学习如何使用 Terragrunt 管理不同 Terraform 配置之间的依赖关系。
准备工作
对于本示例,我们必须提前在工作站上安装 Terragrunt 二进制文件,具体操作可以参考terragrunt.gruntwork.io/docs/getting-started/install/#install-terragrunt。
在本示例中,我们将构建一个由以下元素组成的基础设施:
-
一个资源组
-
一个包含虚拟网络和子网的网络
-
一台虚拟机
包含此 Terraform 配置的文件夹架构如下:

这个架构的问题在于配置之间的依赖关系,以及它们必须按照特定顺序执行。实际上,为了应用网络,必须先应用资源组,虚拟机也是如此:网络必须先创建。使用 Terraform 时,在有多个更改的情况下,Terraform 工作流必须针对每个配置多次执行,并确保顺序正确。
本示例的目的是演示 Terragrunt 的一个功能:简化当 Terraform 配置被拆分到多个文件夹并通过依赖关系链接时的 Terraform 执行,而不是详尽地解释 Terragrunt 的所有功能。
本示例的源代码可以在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP07/demogrunt/dev找到。
如何做到这一点…
执行以下步骤以使用 Terragrunt 和 Terraform 的依赖关系:
- 要在
network和rg配置之间添加依赖关系,在network文件夹内添加一个名为terragrunt.hcl的新文件,内容如下:
dependencies {
paths = ["../rg"]
}
- 在
vm-dev文件夹内,添加一个名为terragrunt.hcl的新文件,内容如下:
dependencies {
paths = ["../rg", "../network"]
}
- 在终端中,在
network文件夹内运行以下terragrunt命令来创建资源组和网络:
> terragrunt init
> terragrunt plan-all
> terragrunt apply-all
它是如何工作的…
我们添加的terragrunt.hcl文件包含了 Terragrunt 的配置。
在我们在第 2 步中编写的配置中,我们指明了网络配置和资源组配置之间的依赖关系。这是因为在执行网络配置之前,必须先创建资源组。
在第 3 步中,我们执行了 Terragrunt 命令(terragrunt init,terragrunt plan-all和terragrunt apply-all),执行时,凭借我们编写的配置,Terragrunt 会先对资源组执行 Terraform,再自动对网络执行 Terraform。这使得我们无需多次在多个 Terraform 配置上执行 Terraform 工作流并确保正确顺序。
还有更多…
在本教程中,我们学习了如何通过使用 Terragrunt 和其配置,改善 Terraform 配置之间的依赖关系。我们可以通过外部化配置(即在每个环境之间冗余的部分),进一步改进这一点,相关文档可以在terragrunt.gruntwork.io/docs/features/execute-terraform-commands-on-multiple-modules-at-once/找到。
然而,由于 Terragrunt 运行的是安装在本地计算机上的 Terraform 二进制文件,您需要确保安装与已安装的 Terraform 二进制文件版本兼容的 Terragrunt 版本。
在下一个教程中,我们将完成 Terragrunt 的配置,使其能够作为 Terraform 的封装器,通过简化 Terraform 命令行来使用。
另见
-
Terragrunt 的详细文档可以在
terragrunt.gruntwork.io/docs/#features找到。 -
Terragrunt 的源代码可以在 GitHub 上找到,地址为
github.com/gruntwork-io/terragrunt。 -
有关 Terraform 配置架构的有用博客文章可以在
www.hashicorp.com/blog/structuring-hashicorp-terraform-configuration-for-production/找到。
使用 Terragrunt 作为 Terraform 的封装器
在我多年的 Terraform 工作和客户支持过程中,出现了一个反复出现的问题,这使得用户无法充分利用 Terraform 的功能。我注意到,这些用户在编写提供商资源配置时并不会遇到任何问题,但他们在通过命令行自动化 Terraform 客户端的工作流时却遇到了困难。
为了简化 Terraform 工作流的自动化,无论是在本地工作站上使用,还是在 CI/CD 流水线中使用,我们可以将 Terragrunt 用作 Terraform 的封装器,整合 Terraform 工作流。
在本教程中,我们将学习如何使用 Terragrunt(在之前的教程中已经学习过)作为 Terraform 的封装器。
准备工作
对于本教程,我们必须事先在工作站上安装 Terragrunt 二进制文件,具体安装步骤请参阅terragrunt.gruntwork.io/docs/getting-started/install/#install-terragrunt。
本教程中使用的 Terraform 配置可以在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP07/demogrunt-wrapper 上找到。它允许我们在 Azure 中构建资源。它使用一个 env-dev.tfvars 变量文件和一个远程后端配置文件(azurerm),该文件位于 backend.tfvars 文件中。要创建此基础设施,必须执行以下 Terraform 命令:
> terraform init -backend-config="backend.tfvars"
> terraform plan -var-file="env-vars.tfvars"
> terraform apply -var-file="env-vars.tfvars"
该资源的 Terraform 配置在 Azure 中创建资源,但我们在本教程中研究的内容适用于任何 Terraform 配置。
本文档的目的是使用 Terragrunt 配置帮助在自动化环境中执行这些 Terraform 命令。
如何操作…
执行以下步骤以将 Terragrunt 用作 Terraform CLI 的包装器:
-
在包含 Terraform 配置的文件夹中,创建一个名为
terragrunt.hcl的新文件。 -
在此文件中,添加以下配置部分以配置
init命令:
extra_arguments "custom_backend" {
commands = [
"init"
]
arguments = [
"-backend-config", "backend.tfvars"
]
}
- 添加以下代码以配置
plan和apply命令:
extra_arguments "custom_vars-file" {
commands = [
"apply",
"plan",
"destroy",
"refresh"
]
arguments = [
"-var-file", "env-vars.tfvars"
]
}
- 在命令行终端中,从包含 Terraform 配置的文件夹中运行以下 Terragrunt 命令以初始化 Terraform 上下文:
terragrunt init
- 最后,运行以下 Terragrunt 命令以应用我们所做的更改:
> terragrunt plan
> terragrunt apply
它是如何工作的…
在步骤 1中,我们创建了 terragrunt.hcl 文件,该文件将包含 Terraform 包装器的 Terragrunt 配置。在步骤 2中,我们在该文件中描述了 init 命令的 Terraform 执行配置。在命令列表中,我们指明该配置适用于 init 命令,并在参数列表中为 --backend-config 选项添加了一个条目,该选项的值为 backend.tfvars 文件。
然后在步骤 3中,我们对 plan 和 apply 命令进行了相同的操作。在此配置中,我们指定了命令列表:plan、apply、destroy 和 refresh。对于参数,我们指明了 -var-file="env-vars.tfvars" 选项。
配置文件编写完成后,我们使用它来运行 Terragrunt。在步骤 4中,我们执行 terragrunt init 命令,它将使用我们编写的配置,因此会执行以下命令:
terraform init -backend-config="backend.tfvars"
你可以在以下截图中看到:

最后,为了预览更改,我们执行 terragrunt plan 命令,它将使用我们编写的配置,因此会执行以下命令:
terraform plan -var-file="env-vars.tfvars"
你可以在以下截图中看到:

如果这些更改符合你的预期,你可以使用以下 Terragrunt 命令来应用这些更改:
terragrunt apply
另见
详细的 CLI 配置文档可在terragrunt.gruntwork.io/docs/features/keep-your-cli-flags-dry/查看。
在 Azure Pipelines 中为 Terraform 配置构建 CI/CD 流水线
在本书的所有之前的食谱中,我们已经讨论了 Terraform 配置、CLI 执行以及其在 IaC 中的好处。
现在,在这个食谱中,我们将讨论如何将此 Terraform 工作流集成到 Azure Pipelines 的 CI/CD 流水线中,使用 Azure DevOps 的 Terraform 扩展和 Pipelines YAML。
准备就绪
在任何 CI/CD 流水线中自动化 Terraform 之前,建议阅读 HashiCorp 的自动化指南,其中包含对 Terraform 的建议。这些指南可在此处查看:
-
learn.hashicorp.com/terraform/development/running-terraform-in-automation -
www.terraform.io/docs/cloud/guides/recommended-practices/part3.html
本食谱的目的不是详细解释 Azure Pipelines 的工作原理,而是专注于在 Azure Pipelines 中执行 Terraform。要了解更多关于 Azure Pipelines 的信息,建议你查看官方文档:docs.microsoft.com/en-us/azure/devops/pipelines/index?view=azure-devops。
在 Azure Pipelines 中使用 Terraform,有几种解决方案:
-
使用自定义脚本(PowerShell 和 Bash)执行 Terraform CLI 命令
-
使用 Azure DevOps 的 Terraform 扩展
在本食谱中,我们将学习如何使用由 Charles Zipp 发布的 Azure DevOps Terraform 扩展(当然,在知识库中还有其他发布者提供的扩展)。
要安装此扩展,请执行以下步骤:
-
在浏览器中访问
marketplace.visualstudio.com/items?itemName=charleszipp.azure-pipelines-tasks-terraform并点击“Terraform 构建与发布任务”。 -
在页面顶部,点击“免费获取”。
-
在安装页面中,在“组织”下拉菜单中选择将安装该扩展的组织(1),然后点击“安装”按钮(2):

扩展将被安装到你的 Azure DevOps 组织中。
此外,对于 Terraform 状态文件,我们将使用远程后端。为了能够在 Azure 中(准确来说是 Azure 存储)使用它,我们在第六章的《使用 Terraform 配置 Azure 基础设施》中的保护 Azure 远程后端中的状态文件食谱中,了解到必须创建一个 Azure 服务主体。
要与 Azure 创建此连接,在 Azure Pipelines 中,我们需要设置一个服务连接,使用创建的 Azure 服务主体信息。为此,在项目设置中,我们导航到“服务连接”菜单。然后,我们创建一个新的 Azure RM 服务连接,并使用服务属性进行配置。
以下截图显示了我 Azure Terraform 演示配置的服务连接:

最后,包含 Terraform 配置的代码必须存储在 Git 仓库中,如 GitHub 或 Azure Repos(在本食谱中,我们将使用 GitHub)。
请注意,在本食谱中,我们不会研究已部署的 Terraform 配置代码,因为它非常基础(它生成一个随机字符串)——它的目的是演示流水线的实现。
本食谱中将使用的 Terraform 配置源代码可以在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP07/pipeline找到。
如何操作…
要创建流水线,我们执行以下步骤:
- 在 Azure Pipelines 菜单中,点击“流水线”:

- 点击创建流水线按钮:

- 对于代码源,选择包含 Terraform 配置的 Git 仓库。对于本食谱,我们选择我们的 GitHub 仓库,并选择“Starter pipeline”选项,从头开始创建新的流水线:

- 流水线编辑器打开后,你可以直接在线编写 CI/CD 步骤。我们来看看这个流水线的代码,它是 YAML 格式的。首先,我们将使用以下代码配置流水线选项,选择 Ubuntu 代理:
trigger:
- master
pool:
vmImage: 'ubuntu-latest'
- 然后,我们通过添加以下代码,告诉流水线下载所需版本的 Terraform 二进制文件:
- task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-installer.TerraformInstaller@0
displayName: 'Install Terraform 0.12.26'
inputs:
terraformVersion: 0.12.26
- 我们继续执行 Terraform 工作流的第一个命令,并执行
terraform init命令:
- task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
displayName: 'terraform init'
inputs:
command: init
workingDirectory: "CHAP07/pipeline/"
backendType: azurerm
backendServiceArm: '<Your Service connection name>'
backendAzureRmResourceGroupName: 'RG_BACKEND'
backendAzureRmStorageAccountName: storagetfbackendbook
backendAzureRmContainerName: tfstate
backendAzureRmKey: myappdemo.tfstate
- 在
init步骤之后,流水线执行terraform plan命令进行预览,并显示流水线将要应用的更改:
- task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
displayName: 'terraform plan'
inputs:
command: plan
workingDirectory: "CHAP07/pipeline/"
commandOptions: '-out="out.tfplan"'
- 最后,流水线执行
terraform apply命令以应用更改:
- task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
displayName: 'terraform apply'
inputs:
command: apply
workingDirectory: "CHAP07/pipeline/"
commandOptions: 'out.tfplan'
这个流水线的完整源代码(YAML 格式)可以在github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP07/pipeline/azure-pipelines.yml找到。
- 在编辑完流水线的 YAML 代码后,我们可以测试它,并通过点击页面右上角的“保存并运行”来触发流水线执行:

- 当流水线执行完成后,我们将能够看到执行的日志结果:

工作原理…
在本食谱的步骤 1 到 3 中,我们通过网页界面使用 Azure Pipelines 创建了一个新管道,并在我们的 GitHub 仓库上进行了配置。此外,我们选择从一个新的 YAML 文件开始进行配置。
步骤 4 主要用于编写我们管道的 YAML 代码,在其中我们定义了以下步骤:
- 我们下载了 Terraform 二进制文件,并指定了与我们的 Terraform 配置兼容的版本。
即使你使用的是 Microsoft 托管的代理,并且已经安装了 Terraform,我仍然建议你下载 Terraform,因为默认安装的版本可能与你的配置不兼容。
- 然后,使用先决条件中安装的扩展,我们执行 Terraform 工作流,其中包括执行
terraform init命令的步骤,并使用 Azure 远程后端。接着,我们执行terraform plan命令,并使用out参数生成一个plan输出文件。最后,我们通过执行terraform apply命令,使用生成的计划文件应用更改。
该最后任务使用的 terraform apply 命令具有 -auto-approve 选项,以便自动应用更改。
最后,在本食谱的最后一步,管道被触发,并且从输出日志中可以清楚地看到 Terraform 配置中描述的更改已被应用。
还有更多…
我们已经在本食谱中展示了如何从空的 YAML 文件创建一个 Terraform 管道,但你也可以使用存档在 Git 仓库中的预编写 YAML 文件来创建管道。
如果你想在 Azure DevOps 管道中使用 Terraform 的经典模式(即在没有 YAML 的图形模式下),你可以参考www.azuredevopslabs.com/labs/vstsextend/terraform/上的动手实验。
如果你的 Terraform 配置部署了 Azure 中的基础设施,并且你希望在管道中使用自定义脚本,而不是使用 Terraform 任务,那么你需要在“变量”选项卡中添加四个 Azure 认证服务的环境变量(我们在第六章的保护 Azure 凭证提供程序食谱中学习了这些内容,使用 Terraform 提供 Azure 基础设施),如下图所示:

这四个变量将作为环境变量自动加载到管道执行会话中。
此外,在本食谱中,我们使用了 CI/CD Azure Pipelines 平台作为示例,但自动化原理在所有 DevOps 工具中都是相同的,包括 Jenkins、GitHub Actions、GitLab 等等。
另见
以下是与此主题相关的文章和视频链接列表:
-
在 Microsoft Azure 上使用 Terraform – 使用 Azure Pipelines 进行持续部署:
blog.jcorioland.io/archives/2019/10/02/terraform-microsoft-azure-pipeline-continuous-deployment.html -
使用 Azure DevOps 和 Terraform 进行 CI/CD 之旅:
medium.com/faun/a-ci-cd-journey-with-azure-devops-and-terraform-part-3-8122624efa97(请参见第一部分和第二部分) -
使用 Azure DevOps Pipelines 步骤部署 Terraform 基础设施:
medium.com/@gmusumeci/deploying-terraform-infrastructure-using-azure-devops-pipelines-step-by-step-d58b68fc666d -
使用 Azure DevOps 部署 Terraform:
www.starwindsoftware.com/blog/azure-devops-terraform-deployment-with-azure-devops-part-1 -
使用 Terraform 和 Azure DevOps 实现基础设施即代码 (IaC):
itnext.io/infrastructure-as-code-iac-with-terraform-azure-devops-f8cd022a3341 -
使用 VSTS 完成所有 Terraform 工作:
www.colinsalmcorner.com/terraform-all-the-things-with-vsts/ -
Terraform CI/CD 与 Azure DevOps:
www.youtube.com/watch?v=_oMacTRQfyI -
使用 Terraform 部署你的 Azure 基础设施:
www.youtube.com/watch?v=JaesylupZa8 -
在 Azure DevOps 中将企业部署到 Azure 和 AWS:
www.hashicorp.com/resources/enterprise-deployment-to-azure-and-aws-in-azure-devops/
在 CI/CD 中使用工作区
在第四章的使用工作区管理环境教程中,使用 Terraform CLI,我们研究了如何使用 Terraform 命令管理和创建工作区。在 Terraform 的设计理念中,工作区使得通过为相同的 Terraform 配置创建多个 Terraform 状态文件来管理多个环境成为可能。
在本教程中,我们将通过在 CI/CD 管道中自动化工作区的创建,进一步了解如何使用工作区。
准备工作
本教程的前提是需要了解 Terraform 命令行选项中关于工作区的内容,其文档可以参考 www.terraform.io/docs/commands/workspace/index.html。
关于 CI/CD 管道,我们将在 Azure Pipelines 中实现它,我们已经在本章中看到过,在为 Terraform 配置在 Azure Pipelines 中构建 CI/CD 管道的食谱中。
本食谱的目的是展示我最近实现的一个场景,即使用 Terraform 创建按需环境。这些环境将用于在应用程序开发过程中测试功能。
总结来说,我们希望将 Git 仓库中某个分支的代码部署到特定环境中,该环境将用于测试此开发。
在这个食谱中,我们将使用位于github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP07/workspace-pipeline的 Terraform 配置文件以及位于github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP07/pipeline/azure-pipelines.yml的 YAML 管道文件。我们只需要通过工作区管理实践来完成它。我们假设我们将创建的工作区名称将是要部署的 Git 分支的名称。
本食谱的完整源代码可以在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP07/workspace-pipeline找到。
如何实现…
要在 YAML 管道中管理工作区,请执行以下步骤:
- 在包含 Terraform 配置的文件夹中,添加一个
ManageWorkspaces.ps1文件,内容如下:
$envName=$args[0]
$countws = terraform workspace list -no-color | Select-String $envName -AllMatches
if ($countws.Matches.Count -eq 0) {
Write-Host "Create new Workspace $envName"
terraform workspace new $envName
}else{
Write-Host "The Workspace $envName already exist and is selected"
terraform workspace select $envName
}
- 在
azure-pipelines.yaml文件中,在 Terraform 的init步骤之后添加以下代码:
- task: PowerShell@2
inputs:
filePath: 'CHAP07/workspace-pipeline/ManageWorkspaces.ps1'
arguments: '$(Build.SourceBranchName)'
workingDirectory: "CHAP07/workspace-pipeline/"
-
提交并推送我们刚刚创建的 PowerShell 脚本和 YAML 管道文件的更改到您的 Git 仓库中。
-
在 Azure Pipelines 中运行管道,在配置步骤中,从分支/标签下拉菜单中选择要部署的正确分支:

最后,通过点击运行按钮来运行管道。
它是如何工作的…
在步骤 1中,我们创建一个 PowerShell 脚本,该脚本将环境名称作为输入参数(对应于要部署的分支名称)。然后,在第 2 行,该脚本执行terraform workspace list命令,列出所有工作区,并搜索是否存在与传入的环境名称相匹配的工作区。如果未找到工作区,则执行terraform workspace new命令来创建它。否则,如果该工作区已存在,脚本会执行terraform workspace select命令来选择它。
请注意,terraform workspace create命令既会创建工作区,也会选择它。
在步骤 2中,我们通过在 init 和 plan 之间插入 PowerShell 脚本的执行,并传递作为参数的我们划分的分支名称,来完成之前配方中创建的 YAML 管道。
然后,我们将这些代码更改(PowerShell 脚本和管道 YAML 文件)提交到 Git 仓库。
最后,在步骤 4中,我们通过选择要部署的分支并将其作为工作区名称来执行 Azure Pipelines 中的管道。
以下截图显示了管道日志中的执行结果:

最终,我们可以看到自动创建的 Terraform 状态文件:

如你所见,由工作区创建的 Terraform 状态文件在末尾包含了工作区名称。
还有更多…
在本配方中,我们使用 PowerShell 脚本来管理工作区,但你当然可以选择用你喜欢的其他脚本语言编写,例如 Bash 或 Python。
另见
-
在使用多个工作区之前,请确保通过按照
www.terraform.io/docs/state/workspaces.html上的说明检查它们与后端的兼容性。 -
有关 Terraform 中工作区 CLI 命令的文档,请访问
www.terraform.io/docs/commands/workspace/index.html。
使用 Terraform Cloud 改善协作
在本书中,我们已经学习了如何编写 Terraform 配置,并在不同的实例中使用 Terraform CLI。所有这些都适用于小型项目和小型团队,但在企业环境中,当涉及到大型基础设施项目时,需要一个真正的平台来共享模块和进行集中部署。这个平台必须能够连接到一个带有版本控制系统(VCS)如 Git 的源代码控制库,并且必须支持以自动化和集中化的方式对 Terraform 进行基础设施更改,供所有团队成员使用。这就是为什么自 2019 年以来,HashiCorp 发布了一个名为 Terraform Cloud 的 SaaS 平台(也叫云平台)。欲了解更多关于 Terraform Cloud 及其历史的信息,请参阅此文档:www.terraform.io/docs/cloud/index.html。
这个 Terraform Cloud 平台(也有一个本地版本,称为 Terraform Enterprise)特别是在其免费计划中,提供了remote后端类型的功能、私有模块注册表、最多五名用户的团队管理以及对存储在 VCS 仓库中的 Terraform 配置的远程执行。在付费计划中,Terraform Cloud 还集成了更先进的团队管理功能,提供了 Terraform 将要管理的资源的成本估算,并提供与 Sentinel 的集成,Sentinel 是一个合规性框架。Terraform Cloud 功能的完整详细列表可以在文档中查看:www.hashicorp.com/products/terraform/pricing/。
在本书的最后一章中,我们将学习如何使用 Terraform Cloud 的 remote 后端,如何在 Terraform Cloud 上的私有注册表中共享 Terraform 模块。接着,我们将学习如何在 Terraform Cloud 中直接执行 Terraform 配置的远程执行,并且如何使用 Terraform Cloud 的 API。最后,我们将通过探索付费功能和使用 Sentinel 来进行合规性测试和可视化成本估算,结束本章内容。
在本章中,我们将介绍以下内容:
-
在 Terraform Cloud 中使用远程后端
-
将 Terraform Cloud 用作私有模块注册表
-
在 Terraform Cloud 中远程执行 Terraform 配置
-
使用 API 自动化 Terraform Cloud
-
使用 Sentinel 测试 Terraform 配置的合规性
-
使用云成本资源治理的成本估算
让我们开始吧!
第九章:技术要求
本章的主要前提条件是拥有一个 Terraform Cloud 平台的账户。创建账户非常简单,并且提供免费计划。你可以在 app.terraform.io/signup/account 上完成注册。
对于本章中的所有食谱,我们将已经通过 Web 浏览器连接到 Terraform Cloud。
注册账户后,如果尚未创建组织,则需要通过点击“创建组织”链接来创建一个组织。
有关如何创建账户和组织的详细步骤,请参考learn.hashicorp.com/terraform/cloud-getting-started/signup上的 Terraform 学习流程。有关组织的更多信息,请阅读www.terraform.io/docs/cloud/users-teams-organizations/organizations.html#creating-organizations的文档。
最后,了解 Terraform Cloud 中的工作空间概念非常重要(与我们在第四章中使用工作空间管理环境食谱中研究的工作空间略有不同)。请参考www.terraform.io/docs/cloud/workspaces/index.html的文档,了解更多信息。
在创建 Terraform Cloud 与 Git 仓库之间的连接时,我们将使用 GitHub。你可以在github.com/上创建一个免费的 GitHub 账户。
本章的源代码可以在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP08找到。
查看以下视频,查看代码的实际操作:bit.ly/2Zl3tyA
在 Terraform Cloud 中使用远程后端
本书中,我们讨论了后端及其在存储和共享 Terraform 状态文件中的重要性。
在第六章的保护 Azure 远程后端中的状态文件食谱中,使用 Terraform 配置 Azure 基础设施,我们已经有了一个具体的案例,演示了如何在 Azure 中设置和使用后端(使用 Azure 存储)。然而,本食谱仅适用于拥有 Azure 订阅的用户。不同类型的后端列在www.terraform.io/docs/backends/types/index.html上,其中大多数需要你购买平台或工具。
Terraform 的主要功能之一是允许你将 Terraform 状态文件托管在一个托管服务中,这个服务被称为remote后端。
在本食谱中,我们将学习如何在 Terraform Cloud 中使用remote后端。
准备工作
本食谱的前提条件(与本章中的所有食谱相同)是你已在 Terraform Cloud 上拥有一个帐户(app.terraform.io/)并已登录。此外,你需要按照www.terraform.io/docs/cloud/workspaces/creating.html中的文档,从 Terraform Cloud UI 手动创建一个名为demo-app的工作区,并在配置时不选择 VCS 存储库。
本食谱的目标是配置并使用remote后端来进行一个简单的 Terraform 配置(为了更好地理解,这不依赖于云提供商)。此外,这个 Terraform 配置的执行将在 Terraform Cloud 中配置为本地模式执行;即,在 Terraform Cloud 之外的机器上执行(这可以是本地工作站或 CI/CD 流水线代理)。
请注意,在本食谱中,我们将使用本地模式。在下一个食谱中,我们将解释如何在远程模式下执行。
本食谱的源代码可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP08/app。
如何操作…
本食谱将分为以下三部分:
-
在 Terraform Cloud 中配置本地模式执行
-
生成新的 API 令牌
-
配置并使用
remote后端
对于第一部分,我们将配置本地模式执行,如下所示:
- 在我们的新 Terraform Cloud 工作区(
demo-app)中,进入设置 | 常规选项卡,将执行模式选项更改为本地模式:

- 点击保存设置按钮以应用这些更改。
现在,进入第二部分,我们需要生成一个新的 API 令牌,以便与 Terraform Cloud 进行身份验证。请按照以下步骤操作:
- 在
demoBook组织的设置选项卡中,进入 API 令牌选项卡:

-
向下滚动到此页面底部,点击“创建身份验证令牌”按钮以生成新的 API 令牌。
-
请将此生成的令牌保管好。
最后,最后一部分是配置并使用remote后端。请按照以下步骤操作:
- 在 Terraform 配置的
main.tf文件中,添加以下后端配置:
terraform {
backend "remote" {
hostname = "app.terraform.io"
organization = "demoBook"
workspaces {
name = "demo-app"
}
}
}
- 然后,在适当的 Terraform CLI 配置文件夹中,即主目录(这里有文档说明:
www.terraform.io/docs/commands/cli-config.html),创建一个名为terraform.rc(适用于 Windows 操作系统)或.terraform.rc(适用于 Linux 操作系统)的新文件。这将是 Terraform CLI 配置文件。在该文件中,添加以下内容:
credentials "app.terraform.io" {
token = "<your api token generated>"
}
- 在本地工作站上执行基本的 Terraform 工作流命令,包括
init、plan和apply。
它的工作原理是…
在这个食谱的第一部分,我们配置了工作区的 Terraform 执行模式。在这个配置中,我们选择了本地模式,这意味着 Terraform 将确保配置安装在私有机器上(无论是本地开发站还是 CI/CD 流水线代理)。在这种情况下,创建的工作区仅用于存储 Terraform 状态。
然后,在第二部分中,我们创建了一个令牌,允许 Terraform 二进制文件与我们的 Terraform Cloud 工作区进行身份验证。
在最后部分,我们编写了 Terraform 配置,描述了我们的 remote 后端的设置。在这个配置中,我们使用了 remote 后端,并添加了以下参数:
-
hostname,值为"app.terraform.io",它是 Terraform Cloud 的域名 -
organization,它包含了组织的demoBook名称 -
workspaces,包含我们在本食谱先决条件中手动创建的demo-app工作区名称
然后,在第 2 步中,我们通过添加在本食谱第二部分中生成的认证令牌,创建了 Terraform CLI 配置文件。
最后,在最后一步中,我们执行了 Terraform 命令工作流。
执行这些命令后,在我们工作区的 States 标签下,我们将看到我们的状态文件已经创建:

通过点击这个文件,你可以查看其内容或下载它。
还有更多……
在本食谱中,我们学习了如何将报告文件集中存储在 Terraform Cloud 后端。
为此,我们手动创建了一个工作区,以便我们可以配置它并选择本地执行模式。在本章的在 Terraform Cloud 中执行 Terraform 配置食谱中,如果我们想使用远程模式,只需将工作区名称放入后端的配置中,如果该工作区不存在,它将自动创建。
然后,我们通过 terraform.rc 文件配置了 Terraform CLI。另一种解决方案是使用 terraform login 命令,它会创建令牌,并生成 credential.tfrc.json 配置文件。
以下截图展示了这个命令的执行情况:

这个命令不适用于自动化使用,因为它需要一个网页浏览器和手动干预。
关于令牌,我们在组织级别创建了它,以保护仅限于组织的 Terraform 执行。为了给本账户下的所有组织提供更广泛的权限,你可以在用户设置中的账户设置里创建一个令牌,然后在令牌标签中进行操作:

要了解更多关于 API 令牌的使用,请参考www.terraform.io/docs/cloud/users-teams-organizations/api-tokens.html文档。
最后,你可以阅读以下文档:www.terraform.io/docs/cloud/architectural-details/data-security.html,了解有关在 Terraform Cloud 中存储的数据和 Terraform 状态文件的安全性。如果你已经有存储在其他类型后端中的 Terraform 配置和状态文件,并且想将它们迁移到 Terraform Cloud,请参阅迁移文档:www.terraform.io/docs/cloud/migrate/index.html
另见
-
remote后端的文档可以在这里查看:www.terraform.io/docs/backends/types/remote.html -
terraform login命令的文档可以在这里查看:www.terraform.io/docs/commands/login.html -
关于 Terraform CLI 配置的文档可以在这里查看:
www.terraform.io/docs/commands/cli-config.html
使用 Terraform Cloud 作为私有模块注册表
在上一篇操作步骤中,我们学习了如何使用 Terraform Cloud 作为一个集中的、安全的、免费的远程后端。
在本书中,我们专门 dedicating 第五章,共享 Terraform 配置与模块,介绍了 Terraform 模块的创建、使用和共享。提醒一下,我们学习的内容是将模块发布到 Terraform 公共注册表,这是所有 Terraform 用户都可以公开访问的,并通过 Git 存储库私密地共享 Terraform 模块。
关于私有模块共享,Git 存储库系统是高效的,但没有像公共注册表那样提供一个集中平台来共享和记录模块。为了为公司提供私有 Terraform 模块注册表,HashiCorp 已将这一功能集成到 Terraform Cloud/Enterprise 中。
在这个操作步骤中,我们将学习如何在 Terraform Cloud 的私有注册表中发布和使用 Terraform 模块。
准备工作
为了在 Terraform 注册表中发布模块,你需要将模块代码存储在 Terraform Cloud 支持的 VCS 文件中。支持的文件类型列表可以在这里找到:www.terraform.io/docs/cloud/vcs/index.html
要开始这个操作步骤,在 Terraform Cloud 组织的设置部分,我们需要创建与包含 Terraform 配置的 VCS 提供商的连接,具体步骤可参考文档:www.terraform.io/docs/cloud/vcs/index.html
在我们的场景中,我们将使用包含terraform-azurerm-webapp仓库的 GitHub VCS(该仓库在 Azure 中创建一个服务计划、一个应用服务实例和一个应用程序洞察)。要获取此仓库,您可以 fork github.com/mikaelkrief/terraform-azurerm-webapp。
此外,正如我们在第五章的使用 GitHub 分享 Terraform 模块配方中研究的那样,使用模块共享 Terraform 配置,你需要在此仓库中创建一个 Git 标签,该标签包含模块的版本号。对于这个配方,我们将创建一个v1.0.0标签,如下图所示:

要在 Terraform Cloud 和 GitHub 之间进行集成,请执行此处文档中描述的过程:www.terraform.io/docs/cloud/vcs/github-app.html。完成此集成后,我们将在“设置 | VCS 提供商”下看到以下屏幕:

我们的组织现在已经与所需的 GitHub 账户建立了连接,可以开始在 Terraform Cloud 中发布模块。
如何操作……
要将 Terraform 模块发布到 Terraform Cloud 的私人注册表中,请执行以下步骤:
- 在我们的 Terraform Cloud 组织中,点击位于顶部菜单栏中的“模块”菜单:

- 要添加模块,请点击“添加模块”按钮:

- 然后,在下一个布局中,在向导的第一步中选择 GitHub 作为 VCS 提供商,这是我们在本配方要求中集成的部分:

- 在向导的第二步中,选择包含 Terraform 模块配置的仓库:

- 最后,在向导的最后一步,通过点击“发布模块”按钮来发布模块:

工作原理……
要在 Terraform 的私人注册表中发布模块,您只需按照向导提出的步骤操作,步骤包括选择一个 VCS 提供商,然后选择包含模块的 Terraform 配置的仓库,以便它可以被发布。完成后,模块的详细信息将在公共注册表的布局中显示。在页面的中央,您将看到Readme.md文件的内容,而在右侧,您将看到关于使用此模块的技术信息。
还有更多……
一旦该模块在此注册表中发布,您就可以在 Terraform 配置中使用它。如果您在本地执行模式下使用 Terraform Cloud,则必须按照前面的配方在 terraform.rc 文件中配置 Terraform CLI 的身份验证令牌。然后,您需要在 Terraform 配置中使用此模块,并编写以下内容:
module "webapp" {
source = "app.terraform.io/demoBook/webapp/azurerm"
version = "1.0.0"
...
}
在此配置中,source 属性是 Terraform Cloud 注册表中的模块标识符,而 version 属性对应于在代码库中设置的 Git 标签。完成此操作后,您可以从版本下拉列表中选择希望使用的版本:

如果我们更改了模块的 Terraform 配置并希望升级其版本,只需在此代码库中添加一个 Git 标签,并设置所需的版本。通过这样做,模块将在 Terraform Cloud 注册表中自动更新。
此外,如果您的模块已发布在此私有注册表中,您可以使用 Terraform Cloud 的设计配置功能生成调用这些模块的 Terraform 配置。您可以在此了解更多:www.terraform.io/docs/cloud/registry/design.html。
最后,请注意,如果您在 Terraform Cloud 中有多个组织,并且希望在所有组织中使用相同的模块,您必须在每个组织中发布这些模块。至于升级模块的版本,这将在每个组织中自动完成。
另请参见
关于在 Terraform Cloud 中私有注册模块的文档可以在此查看:www.terraform.io/docs/cloud/registry/index.html。
在 Terraform Cloud 中远程执行 Terraform 配置
在前两个配方中,我们研究了使用本地运行时设置的 Terraform Cloud。此配置表明,应用 Terraform 配置的 Terraform 二进制文件安装在 Terraform Cloud 平台外部的机器上。因此,这台机器是私有的,可以是开发工作站或作为 CI/CD 管道代理的机器(例如在 Azure 管道代理或 Jenkins 节点上)。
Terraform Cloud 的一个重要优势是能够直接在此平台内执行 Terraform 配置。此功能称为远程操作,使得可以在不需要安装、配置和维护作为代理的虚拟机的情况下,免费运行 Terraform 配置执行管道。此外,它为组织内的所有成员提供了共享的 Terraform 执行接口。
在本配方中,我们将查看使用 UI 工作流在 Terraform Cloud 中运行 Terraform 配置的步骤。
准备工作
我们将在本配方中使用的 Terraform 配置源代码可以在 github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP08/remote 找到。这个配置将在 Azure 中创建一个资源组和一个应用服务。如果你想使用这个 Terraform 配置,你需要对这个仓库进行分叉。该配置还使用了 terraform-azurerm-webapp Terraform 模块,这个模块已发布在我们 Terraform Cloud 组织的私有注册表中。有关如何在私有注册表中发布模块的更多信息,请参见前一个配方,将 Terraform Cloud 用作私有模块注册表。
由于在这个 Terraform 配置中,我们将创建 Azure 资源,因此我们需要创建一个具有足够权限的 Azure 服务主体,该服务主体必须在订阅中具有适当权限。有关 Azure 服务主体和 Terraform 如何验证 Azure 的更多信息,请参见 第六章 中的 保护 Azure 凭证提供者 配方,使用 Terraform 配置 Azure 基础架构。
此外,由于我们将公开一个 GitHub 中的 Terraform 配置,我们需要添加 GitHub VCS 提供商,正如文档中所解释的那样:www.terraform.io/docs/cloud/vcs/github-app.html。
最终,本配方的所有步骤将在 Terraform Cloud Web 界面中完成。
如何操作…
在执行 Terraform 配置之前,我们需要创建并配置一个新的工作区。按照以下步骤操作:
-
在我们组织的工作区部分,点击“新建组织”按钮以创建一个新组织。
-
在向导的第一步中,选择我们在需求中注册的 VCS 提供商。在这里,我们将选择 GitHub 提供商:

- 然后,在向导的第二步中,选择包含 Terraform 配置的 GitHub 仓库,以便我们可以在 Terraform Cloud 中执行它:

- 最后,在向导的最后一步中,通过指定强制参数来配置此工作区,该参数是工作区的名称。在我们的例子中,它是
demo-app-remote:

- 在可选的高级选项标签页中,设置 Terraform 配置的文件夹路径(如果 Terraform 配置位于仓库的根目录,则留空)。我们还可以填写自动运行触发器和 VCS 分支参数,以便它们能够运行(我们将 master 分支留空):

- 最后,点击“创建工作区”按钮以完成工作区的创建。
既然我们已经创建了工作区,因我们在 Azure 中部署资源,需要将四个 Azure 身份验证环境变量添加到工作区变量设置中。请按照以下步骤操作:
- 点击“配置变量”按钮:

- 然后,在“环境变量”部分,添加我们的四个 Terraform Azure 提供程序环境变量,如下截图所示:

现在我们已经配置好工作区,可以在 Terraform Cloud 中执行 Terraform 配置。
要在 Terraform Cloud 中远程执行 Terraform 配置,请按照以下步骤操作:
- 要触发 Terraform 配置的执行,点击“队列计划”按钮,输入执行原因,并点击“队列计划”按钮确认:

- Terraform Cloud 将启动一个新的执行任务来执行此 Terraform 配置。通过运行
terraform plan命令,我们将能够看到此执行的日志:

执行plan后,Terraform Cloud 会要求用户确认此操作,才会应用更改。
- 如果我们同意预览中的更改,可以通过点击“确认并应用”按钮来确认这些更改:

添加一些评论,然后点击“确认计划”按钮:

完成后,执行计划的结果将会提供:

它是如何工作的…
在本教程中,我们配置了一个 Terraform Cloud 工作区,以便在 Terraform Cloud 管理的实例中直接运行存储在 GitHub 仓库中的 Terraform 配置。
在这项配置的中间,执行 Terraform 配置之前,我们完成了 Azure 环境变量配置,这是一个可选步骤,取决于你希望管理的资源和云提供商。
还有更多…
在本教程中,我们学习了如何直接通过此平台的 Web 界面运行 plan 和 apply Terraform Cloud 变量。在工作区设置中,你还可以配置是否希望手动应用计划(即像我们教程中的确认步骤)或自动应用。你还可以选择希望使用的 Terraform 二进制版本(默认情况下,它会使用在工作区创建时找到的最新稳定版本;beta 版本不予考虑):

你也可以使用“销毁与删除”功能销毁所有已配置的资源,该功能可以在“设置 | 销毁与删除”菜单中访问,然后点击“队列销毁计划”按钮:

此外,正如你可能已经注意到的,通过使用 UI 在 Terraform Cloud 中运行 Terraform 配置时,我们不需要为状态文件配置remote后端信息,正如我们在本章的使用 Terraform Cloud 中的远程后端一节中讨论的那样。在我们的案例中,Terraform 状态文件的配置与工作区集成。在 States 选项卡中,我们将看到 Terraform 状态文件的存在:

此外,如果你处于开发环境中并希望在提交到仓库之前检查开发情况,你仍然可以使用 Terraform 执行的远程模式来制定计划。这是通过控制在 Terraform Cloud 中执行的过程来实现的,你可以使用 Terraform 二进制文件(或 CLI)来完成。要做到这一点,只需像本章中的使用 Terraform Cloud 中的远程后端一节那样,添加remote后端的配置,并使用我们在该节第一步中创建的工作区名称,对应于以下代码:
terraform {
backend "remote" {
hostname = "app.terraform.io"
organization = "demoBook"
workspaces {
name = "demo-app-remote"
}
}
}
然后,在开发站点上执行terraform plan命令,如下图所示:

在此执行过程中,你的 Terraform CLI 将创建一个配置包并将其上传到 Terraform Cloud 工作区。CLI 触发 Terraform Cloud CLI 在上传的包上运行 Terraform。最后,plan命令的输出也可以在命令行终端中查看。请注意,在这种情况下,你不需要在本地设置环境变量,因为它们已经在工作区中配置好了。
为了确保更改只应用在一个地方,你不能在连接到 VCS 的工作区上运行terraform apply命令。然而,如果你的工作区没有连接到 VCS,你可以从本地 CLI 执行apply命令。
最后,如果你的 Terraform 配置包含了local-exec(我们在第二章的使用 Terraform 执行本地程序一节中研究过),并且在其命令中使用了第三方工具,你需要确保该工具已经存在或安装在执行 Terraform 二进制文件的 Terraform Cloud 代理上。关于在 Terraform Cloud 执行中的其他第三方工具的更多信息,建议阅读www.terraform.io/docs/cloud/run/install-software.html上的文档。
另见
-
关于 Terraform Cloud 中远程执行的文档可以在此处查看:
www.terraform.io/docs/cloud/run/index.html。 -
使用 CLI 进行远程执行的文档可以在此查看:
www.terraform.io/docs/cloud/run/cli.html。
使用 API 自动化 Terraform Cloud
在之前的配方中,我们学习了如何使用 Terraform Cloud 平台将 Terraform 状态文件存储在remote后端。然后,我们使用 Terraform Cloud 作为模块的私有注册表,并学习了如何在 Terraform Cloud 中远程运行 Terraform 配置。
所有这些操作主要通过 Terraform Cloud UI 网页界面完成。在前一个配方的更多...部分,我们讨论了也可以使用 Terraform CLI 本地运行 Terraform 远程操作。
在公司中,我们需要自动化所有这些操作,原因如下:
-
UI 的使用虽然人性化,但需要大量手动操作,在多个项目中,这可能会非常耗时且消耗资源。
-
在 Terraform Cloud 中,远程模式下的执行工作流程是固定的,包括执行
plan命令以及应用操作。无法添加其他操作(如本书中研究过的上游集成测试执行)。因此,无法根据公司的业务需求定制工作流程。
由于自动化和定制化的需求,HashiCorp 发布了 API,允许像使用 UI 一样管理 Terraform Cloud。
在本配方中,我们将通过 API 来自动化 Terraform Cloud 的操作。
准备工作
在开始之前,回顾一下 Terraform Cloud 的工作流程是非常有帮助的,流程如下:
-
编写 Terraform 配置,然后将其提交到 VCS 代码库(如 Git)。
-
在 Terraform Cloud 中,工作区会检索此 Terraform 配置并使用
terraform plan命令执行干运行。 -
在手动模式下,如果用户确认计划,Terraform Cloud 会触发应用并将更改应用到基础设施中。
-
在自动模式(auto-apply)下,
plan命令执行后,变更会自动应用。
在本配方中,我们将使用与前一个配方相同的场景和 Terraform 配置,唯一不同的是我们将使用调用 Terraform Cloud API 的脚本。
然而,作为先决条件,你需要在 Terraform Cloud 用户账户设置中创建一个 API 令牌,用于身份验证:

通过 API 触发运行时,使用的是用户 API 令牌,而非组织令牌。
在本配方中使用的脚本语言为 PowerShell。不过,你可以根据需要使用常用的编程语言(如 shell、Python、C#等)。
本配方的源代码可以在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP08/api获取。
如何实现……
要通过 API 自动化 Terraform Cloud,请在新文件夹中执行以下步骤:
- 创建一个名为
workspace.json的 JSON 文件。此文件将包含我们将要创建的工作区的定义。插入以下内容:
{
"data": {
"attributes": {
"name": "workspace-demo-api",
"auto-apply": true,
"working-directory": "CHAP08/remote",
"vcs-repo": {
"identifier": "mikaelkrief/terraform-Cookbook",
"oauth-token-id": "ot-Jxxxxxxxxxx",
"branch": "",
"default-branch": true,
"queue-all-runs": true
}
},
"type": "workspaces"
}
}
要了解如何获取oauth-token-id,请阅读www.terraform.io/docs/cloud/api/oauth-tokens.html上的文档。
- 创建一个名为
tfcloud-workspaces.ps1的 PowerShell 脚本,包含以下内容:
$apiToken = $args[0] #API TOKEN
$organization = "demoBook"
$headers = @{ }
$headers["Authorization"] = "Bearer $apiToken"
$headers["Content-Type"] = "application/vnd.api+json"
$uriWorkspaces = "https://app.terraform.io/api/v2/organizations/$organization/workspaces"
try
{
$json = Get-Content("workspace.json")
$response = Invoke-RestMethod -Uri $uriWorkspaces -Body $json -Headers $headers -Method Post
$worspaceId = $response.data.id
Write-Host $worspaceId
}
Catch
{
...
}
该脚本的完整源代码可以在github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP08/api/tfcloud-workspaces.ps1找到。
- 在 PowerShell 终端中,运行以下命令以创建工作区:
.\tfcloud-workspaces.ps1 <your api token>
在执行结束时,脚本将显示创建的工作区 ID:

- 创建另一个名为
variables.json的 JSON 文件,包含要创建的环境变量的定义。插入以下内容:
{
"vars": [
{
"data": {
"type": "vars",
"attributes": {
"key": "ARM_SUBSCRIPTION_ID",
"value": "xxxxx-xxxxxxx-xxxxxx-xxxxxx",
"category": "env",
....
}
}
},
....
]
}
该 JSON 脚本的完整源代码可以在github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP08/api/variables.json找到。
- 创建一个名为
tfcloud-variables.ps1的 PowerShell 脚本,包含以下内容:
$apiToken = $args[0] #API TOKEN
$worspaceId = $args[1] # WORKSPACE ID
$headers = @{ }
$headers["Authorization"] = "Bearer $apiToken"
$headers["Content-Type"] = "application/vnd.api+json"
$uriVariables = "https://app.terraform.io/api/v2/workspaces/$worspaceId/vars"
$json = Get-Content("variables.json") | ConvertFrom-Json
$varList = $json.vars
foreach ($var in $varList)
{
$varjson = $var | ConvertTo-Json
Invoke-RestMethod -Uri $uriVariables -Body $varjson -Headers $headers -Method Post
}
该脚本的完整源代码可以在github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP08/api/tfcloud-variables.ps1找到。
- 在 PowerShell 中,运行上述脚本以创建我们在工作区中定义的环境变量,使用以下命令:
.\tfcloud-variables.ps1 <your token id> <workspace id>
- 为了触发 Terraform 配置的新的执行,创建一个名为
run.json的 JSON 文件,包含队列的定义。添加以下内容:
{
"data": {
"attributes": {
"is-destroy": false,
"message": "Run for demo Book"
},
"type":"runs",
"relationships": {
"workspace": {
"data": {
"type": "workspaces",
"id": "ws-xxxxxxxxxx"
}
}
}
}
}
- 创建一个名为
tfcloud-run.ps1的 PowerShell 脚本,包含以下内容:
$apiToken = $args[0] #API TOKEN
$headers = @{ }
$headers["Authorization"] = "Bearer $apiToken"
$headers["Content-Type"] = "application/vnd.api+json"
$uriWorkspaces = "https://app.terraform.io/api/v2/runs"
$json = Get-Content("run.json")
Invoke-RestMethod -Uri $uriWorkspaces -Body $json -Headers $headers -Method Post
- 在 PowerShell 中,运行以下命令以排队新的 Terraform
run:
.\tfcloud-run-plan.ps1 <your yoken api>
它是如何工作的…
在本教程中,我们通过三步自动化了 Terraform 工作流的实现,具体如下:
-
创建和配置工作区
-
在此工作区中创建环境变量
-
在此工作区中触发 Terraform 配置的
run
从步骤 1到步骤 3,我们使用 API 创建了一个工作区。为此,我们创建了一个名为workspace.json的文件,其中包含将作为参数发送到 API 的主体(payload)。在此文件中,我们定义了以下属性:
-
name:要创建的工作区名称。 -
working-directory:包含 Terraform 配置的仓库目录。 -
auto-apply:这表示在计划之后,运行将自动执行,而无需用户手动审查该计划(可选属性)。 -
vcs-repo:该块包含我们在组织中配置的 VCS 提供者的信息,详情请见:www.terraform.io/docs/cloud/vcs/index.html。oauth-token-id属性可以通过 VCS 提供者的界面或通过 API 获取,具体细节请见:www.terraform.io/docs/cloud/api/oauth-clients.html。
然后,我们编写并执行了调用工作区创建 API 的 PowerShell 脚本。在这个脚本中,我们定义了用户的 API 令牌和组织的名称作为变量,然后调用了工作区创建 API。
工作区 API 的文档可以在此查看:www.terraform.io/docs/cloud/api/workspaces.html。
在执行结束时,该脚本会显示创建的工作区 ID,必须保留此 ID 以便我们可以继续进行 API 调用。在 Terraform Cloud 的 Web 界面中,我们将能够查看到这个新的工作区:

接着,从步骤 4到步骤 6,我们使用 API 在这个新工作区中创建了环境变量。为此,我们创建了一个variables.json文件,该文件包含了 Azure Service Principal 的四个变量以及它们的主体内容,这些内容将被处理,以便 API 的有效载荷参数被发送到 API。在该文件中,我们为每个变量定义了以下属性:
-
name -
value -
category:env(定义为环境变量)
接着,我们编写并执行了 PowerShell 脚本,调用了每个变量的工作区创建 API。
在本教程中,我们创建了这四个环境变量,因为我们作为示例使用的 Terraform 配置管理的是 Azure 基础设施。如果不需要环境变量,这一步是可选的。
此外,变量 API 的文档可以在此查看:www.terraform.io/docs/cloud/api/workspace-variables.html。
在执行结束时,在 Terraform Cloud UI 中,我们将能够看到新的变量环境:

最后,在步骤 7到步骤 9,我们使用 API 触发了一个运行——即在工作区(在 VCS 中)定义的 Terraform 配置的执行。为此,我们创建了一个名为run.json的文件,该文件也将作为 API 的有效载荷使用。它包含了以下属性:
-
message: 关于运行的消息 -
workspace.id: 工作区 ID
然后,我们编写并执行了调用 run 触发器 API 的 PowerShell 脚本。在其执行结束时,我们看到 Terraform 在 Terraform Cloud 中被触发执行,如下图所示:

通过使用工作区的 auto-apply 属性,terraform apply 将自动完成,无需用户手动配置。
还有更多…
在本教程中,我们查看了 Terraform Cloud API 的简单基本使用。如同往常一样,还有许多其他场景可以使用它们。
请记住,通过不将 API 令牌和任何 Azure 或类似的令牌以明文形式放入脚本中,来保护它们。
此外,为了保持在 IaC 的上下文中,您可以使用带有 Terraform Enterprise 提供程序 的 Terraform 配置,而不是直接使用 API,该文档在这里:www.terraform.io/docs/providers/tfe/index.html。这也是 HashiCorp 推荐用于 Terraform Cloud 管理的方式。您可以在www.terraform.io/docs/cloud/api/index.html(第一条注释)中找到更多信息。
另见
-
关于各种 Terraform Cloud API 的文档可以在这里查看:
www.terraform.io/docs/cloud/api/index.html。 -
以下是关于使用 Terraform Cloud/Enterprise API 的视频演示:
www.hashicorp.com/resources/demystifying-the-terraform-enterprise-api/。
使用 Sentinel 测试 Terraform 配置的合规性
本书中已多次讨论过 Terraform 配置测试的相关内容,例如使用 terratest(在第五章的 使用 Terratest 测试 Terraform 模块代码 配方和 使用 Terraform 配置 Azure 基础设施 配方中)和 kitchen-terraform(在第七章的 使用 kitchen-terraform 测试 Terraform 配置 配方和 深入探索 Terraform 配方中)。这两种工具的共同点是,测试的目的是编写并测试 Terraform 已经应用的更改。
所有这些测试都非常有益,但它们是在做出更改后进行的。回滚也需要所有团队的协作。为了更好地与公司的业务和财务需求对接,另有一层测试,用于在应用到目标基础设施之前验证 Terraform 配置的合规性。
在 Terraform 中,这些合规性测试在执行terraform plan命令后进行。它们验证plan命令的结果是否符合测试中描述的规则。只有这些测试通过后,才能执行terraform apply命令。
在合规性测试的工具和框架中,Terraform Cloud 在其付费计划中提供了栈功能,允许我们使用Sentinel框架编写测试,并通过在plan和apply之间使用run命令直接在 Terraform Cloud 中执行它们。
在本教程中,我们将研究一个简单的案例,编写合规性测试并在 Terraform Cloud 中执行它们。
准备工作
本教程的基本要求是拥有 Terraform Cloud 的付费计划。我们将使用 Team & Governance 功能:

如果您使用的是免费计划,您可以通过激活免费试用,体验付费计划的所有功能 30 天:

关于计划、价格和功能的文档可以在这里找到:www.terraform.io/docs/cloud/paid.html。
我们将使用的 Terraform 配置已在之前讨论过,并可以在github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP08/remote找到。此配置将创建一个资源组,一个服务计划和一个 Azure 应用服务。
此外,要在您的组织中使用本教程中编写的代码,您需要创建该书原始仓库的一个分支(github.com/PacktPublishing/Terraform-Cookbook)。
本教程的目标是编写作为策略集一部分的规则,用来测试以下内容:
-
要求应用服务的 FTP 模式仅配置为 FTPS 模式。
-
要提供给 SKU 层级类型的服务计划必须是 Basic 或 Standard。此规则禁止提供 Premium 或 Premium v2 服务计划。
接下来,我们将学习如何在 Terraform Cloud 执行时应用这些策略集。
为了编写这些策略,我们将使用Sentinel,这是 HashiCorp 提供的一个测试框架。其文档可以在www.hashicorp.com/resources/writing-and-testing-sentinel-policies-for-terraform/找到。
本教程的目的不是研究编写策略的所有元素(前面的指南可以用于此)。在这里,我们将编写一些简单的代码,您可以轻松地复现。
最后,必须在 Terraform Cloud 中创建并配置demo-app-remote工作区,具体操作可以参考本章中的在 Terraform Cloud 中远程执行 Terraform 配置教程。
该配方的源代码可以在此处找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP08/sentinel-policies.
如何操作…
要完成这个步骤,我们需要完成三件事情:
-
编写策略规则
-
配置组织以便集成这些策略
-
使用这些策略运行 Terraform 配置
我们首先编写合规性策略,如下所示:
我们将在此部分编写的代码可以与要测试的配置位于同一个仓库中。或者,如果其策略是共享的,则可以将此代码放入另一个仓库中。
将这些策略放在一个单独的仓库中仍然是一个好的实践,这样你就不会混合策略的 Terraform 配置提交。另一个做法是,这个独立的仓库可以由另一个团队(如运维或安全团队)管理。
在这个配方中,为了简单起见,我们将代码写入包含 Terraform 配置的仓库中,该仓库已经集成到我们组织的 VCS 提供者中。
- 在一个新文件夹
sentinel-policies内,创建一个名为restrict-app-service-to-ftps.sentinel的新文件,以测试 App Service 实例的 FTP 模式。使用以下代码完成:
import "tfplan-functions" as plan
allAzureAppServices = plan.find_resources("azurerm_app_service")
violatingAzureAppServices = plan.filter_attribute_is_not_value(allAzureAppServices,
"site_config.0.ftps_state", "FtpsOnly" , true)
main = rule {
length(violatingAzureAppServices["messages"]) is 0
}
此文件的完整源代码可以在此处找到:github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP08/sentinel-policies/restrict-app-service-to-ftps.sentinel.
- 创建一个名为
allowed-app-service-plan-tiers.sentinel的新文件来测试服务计划 SKU 的类型。确保它包含以下内容:
import "tfplan-functions" as plan
allowed_tiers = ["Basic", "Standard"]
allAzureServicePlan = plan.find_resources("azurerm_app_service_plan")
violatingAzureServicePlan = plan.filter_attribute_not_in_list(allAzureServicePlan,
"sku.0.tier", allowed_tiers, true)
main = rule {
length(violatingAzureServicePlan["messages"]) is 0
}
该文件的完整源代码(带有注释)可以在此处找到:github.com/PacktPublishing/Terraform-Cookbook/blob/master/CHAP08/sentinel-policies/allowed-app-service-plan-tiers.sentinel.
- 创建一个名为
sentinel.hcl的新文件。这将是测试和引用前述两个策略的入口点。添加以下内容:
module "tfplan-functions" {
source = "https://raw.githubusercontent.com/hashicorp/terraform-guides/master/governance/third-generation/common-functions/tfplan-functions/tfplan-functions.sentinel"
}
policy "restrict-app-service-to-ftps"{
source ="./restrict-app-service-to-ftps.sentinel"
enforcement_level = "hard-mandatory"
}
policy "allowed-app-service-plan-tiers"{
source ="./allowed-app-service-plan-tiers.sentinel"
enforcement_level = "hard-mandatory"
}
- 将所有文件提交并推送到远程仓库。
在第二阶段,我们需要在我们的 Terraform Cloud 组织中配置策略集:
- 在你的组织设置中,转到策略集选项卡,然后点击“连接新策略集”按钮:

- 在向导的第一步中,选择包含我们刚编写的策略代码的 VCS 提供者:

- 在第二步中,选择包含策略代码的仓库:

- 在向导的最后一步,配置策略集,添加名称和描述详情,如下截图所示:

在“附加选项”下,指定包含代码策略的文件夹以及将使用这些策略的目标工作区:

以下截图显示了您需要选择的工作区,以便使用该策略集:

- 点击“连接策略集”按钮来创建策略集。
现在,我们可以在 Terraform Cloud 中运行 Terraform 配置并检查策略:
-
在本章中我们在在 Terraform Cloud 中远程执行 Terraform 配置配方中创建的
demo-app-remote工作区中,我们将排队执行一个新的 Terraformrun。 -
在此
run的结果中,我们将能够看到策略检查的结果:

- 如果策略检查成功,请通过点击“确认并应用”按钮来应用更改:

它是如何工作的…
在本教程的第一部分,我们编写了合规性测试,以便通过Sentinel测试我们的 Terraform 配置。
在restrict-app-service-to-ftps.sentinel文件中,通过编写import "tfplan-functions" as plan这一行,我们导入了一个 Sentinel 函数库,该库可以在github.com/hashicorp/terraform-guides/tree/master/governance/third-generation/common-functions/tfplan-functions找到。此行之后的代码会在terraform plan命令中搜索所有azurerm_app_service资源,并检查 FTPS 状态是否仅配置为 FTPS:
allAzureAppServices = plan.find_resources("azurerm_app_service")
violatingAzureAppServices = plan.filter_attribute_is_not_value(
allAzureAppServices, "site_config.0.ftps_state",
"FtpsOnly" , true)
这是测试的调用点,当 FTPS 状态不合规时会发送一条错误信息。
在第二个文件allowed-app-service-plan-tiers.sentinel中,我们编写了allowed_tiers = ["Basic", "Standard"]这一行,以创建一个允许用于服务计划的 SKU 列表。这些行会在terraform plan中搜索所有azurerm_service_plan资源,并检查 SKU 是否在我们之前声明的列表中:
allAzureServicePlan = plan.find_resources("azurerm_app_service_plan")
violatingAzureServicePlan = plan.filter_attribute_not_in_list(allAzureServicePlan,
"sku.0.tier", allowed_tiers, true)
这是测试的调用点,当 SKU 不合规时会发送一条错误信息。
我们编写的第三个文件sentinel.hcl是测试文件的入口点。我们使用了一个模块来导入自定义库,并声明了两个策略,分别引用我们之前编写的两个文件。
Sentinel 测试的其他示例可以在此处找到:github.com/hashicorp/terraform-guides/tree/master/governance/third-generation。
然后,在本配方的第二部分,我们通过选择包含 Sentinel 代码的存储库,并选择这些策略将应用于的工作区,在我们的 Terraform Cloud 组织中配置了策略集。
最后,一旦编写了测试并完成了策略集的配置,我们在策略集中选择的工作区上触发了 Terraform run(远程模式),并且合规性测试成功的结果展示出来。
还有更多……
完成此配方后,所有测试都成功通过,但有趣的是通过测试一个失败的案例来测试它们的功能。为此,我们需要通过修改服务计划的 SKU 类型,充分修改在工作区中使用的 Terraform 配置,如下所示:
module "webapp" {
source = "app.terraform.io/demoBook/webapp/azurerm"
version = "1.0.4"
...
sp_sku = "Premium"
}
此 SKU 采用Premium值,而该值在测试中允许的 SKU 列表中是被禁止的。然后,我们执行run。在执行过程中,我们得到以下结果:

如我们所见,在运行terraform plan命令后,合规性测试失败,并且更改的实施被拒绝。
就阻止应用而言,这是在sentinel.hcl文件中为每个策略配置的,使用enforcement_level = "hard-mandatory"属性。要了解此属性的更多值及其含义,请阅读docs.hashicorp.com/sentinel/concepts/enforcement-levels/文档以及这里 www.terraform.io/docs/cloud/sentinel/manage-policies.html。
另请参阅
-
Sentinel 函数的代码可以在这里找到:
github.com/hashicorp/terraform-guides/tree/master/governance/third-generation。 -
安装 Sentinel CLI 的文档可以在这里找到:
docs.hashicorp.com/sentinel/intro/getting-started/install/。 -
编写和安装策略的指南可以在这里找到:
www.hashicorp.com/resources/writing-and-testing-sentinel-policies-for-terraform/。 -
策略的基本学习指南可以在这里找到:
learn.hashicorp.com/terraform/cloud-getting-started/enforce-policies。 -
阅读这篇文章以了解更多关于 Sentinel 的使用:
medium.com/hashicorp-engineering/using-new-sentinel-features-in-terraform-cloud-c1ade728cbb0。 -
以下是一个示范策略测试的视频:
www.hashicorp.com/resources/testing-terraform-sentinel-policies-using-mocks/。 -
我们还可以使用其他工具来编写和执行 Terraform 合规性配置,例如 terraform-compliance (
github.com/eerkunt/terraform-compliance) 和 Open Policy Agent (www.openpolicyagent.org/docs/latest/terraform/)。它们都是免费且开源的,但请注意:它们不能在 Terraform Cloud 执行中使用。
使用成本估算进行云资源的成本治理
当我们在云架构中创建资源时,往往会忘记这些操作会带来财务成本,而这个成本取决于创建的资源类型。使用自动化和基础设施即代码(IaC)时尤为如此,因为它们允许我们通过少量命令创建大量资源。
在付费版的 Terraform Cloud 中集成的一个有趣功能是成本估算,它使我们能够在运行 Terraform 配置时,实时可视化所管理资源的成本。
在本教程中,我们将学习如何在 Terraform Cloud 中使用成本估算。
准备工作
在开始本教程之前,你必须拥有一个付费的 Terraform Cloud 计划,或激活免费的 30 天试用:

完成此操作后,您需要了解云提供商以及成本管理功能支持的资源。此列表可在www.terraform.io/docs/cloud/cost-estimation/index.html#supported-resources查看。
本教程的目的是通过 Terraform 在 Azure 上配置一个虚拟机,并在 Terraform Cloud 界面中可视化该资源的成本估算。
本教程中将执行的 Terraform 配置的源代码可以在这里找到:github.com/PacktPublishing/Terraform-Cookbook/tree/master/CHAP08/cost。
如何操作…
要查看我们资源的成本估算,请执行以下步骤:
- 在 Terraform Cloud 组织的设置部分,进入成本估算标签页,勾选“启用所有工作空间的成本估算”复选框:

-
在已创建的工作空间中,通过我们的 Terraform 配置来配置 Azure 虚拟机,然后排队执行一个新的
run。 -
在执行我们的
plan之后,我们可以查看资源的评估成本:

它是如何工作的…
一旦启用了成本估算选项,Terraform Cloud 将使用不同云服务提供商的 API 来评估并显示将要配置的资源的成本。
还有更多内容…
需要注意的是,这只是一个估算值,仍然需要参考各云服务提供商的不同定价文档。
你还可以使用 Sentinel(我们在之前的配方中学习过)编写策略,以集成符合成本估算的合规规则。有关更多信息,请阅读文档:www.terraform.io/docs/cloud/cost-estimation/index.html#verifying-costs-in-policies。
另见
关于成本估算功能的文档可以在此查看:www.terraform.io/docs/cloud/cost-estimation/index.html。















浙公网安备 33010602011771号