使用 Terraform 和 GitHub Actions 部署到 Azure
我正在构建自己的 Azure Kubernetes 集群,用于个人开发,并且我一直想提高我的 Terraform 技能,所以在过去的几天里,我花了一些时间让 Terraform 部署与 GitHub Actions 一起工作。
自从我上次认真使用 AzureRM 提供程序以来,它已经有了很大的改进,所以我了解了很多关于可用资源以及如何使用 GitHub Actions 将 Terraform 模板部署到 Azure 的知识。
在这篇文章中,我将介绍我目前为止使用 Terraform 模板所做的工作、我的代码结构以及我如何实现 GitHub Actions 工作流。我希望这篇文章能对每个人都有所帮助,无论你是刚刚开始使用 Terraform,想要了解如何通过 GitHub Actions 实现自动化部署,还是已经深入使用 Terraform 一段时间,只是想找些参考资料来解决特定问题。
如果您只想查看代码,可以点击这里。如果您想了解更多背景信息,请继续阅读!
我想要达到什么目标?
最初只是出于好奇,想看看如何搭建一个 AKS 集群来学习,结果却变成了一项更大的任务。我的目标是创建一个简单的 GitHub Actions 工作流,实现 AKS 集群的自动化部署。
随着时间的推移,我会向集群中添加监控、弹性、测试等功能,我想如果能自动化部署而不是在我的本地机器上运行,那就太好了。
一点建议——虽然确实需要付出更多努力,但绝对值得。我从中学习到了很多可以运用到日常生活中的知识。
基于以上考虑,我将我的操作步骤分为三步:
- 构建我的 Terraform 代码
- 创建 GitHub Actions 所需的资源
- 通过 GitHub Actions 部署我的 AKS 集群
步骤 1:构建我的 Terraform 代码
我把所有代码都放在一个代码仓库里了。我不想把它们分散到不同的代码仓库中,因为这些代码最终都会用于我的 AKS 集群部署。
截至撰写本文时,我的代码仓库中已实现了以下文件夹结构:
├── .github
│ ├── workflows
├── cluster-deployment
│ ├── tfvars
├── github-deployment
│ ├── tfvars
├── modules
│ ├── aks-cluster
│ ├── azure-container-registry
│ ├── etc. etc.
├── LICENSE
├── rEADME.md
└── .gitignore
这样做的主要目的是将与 GitHub 相关的 Terraform 代码与 AKS 的 Terraform 代码分开。目前,流水线配置为./cluster-deployment在运行时将工作目录设置为该文件夹(稍后会详细介绍),因此将它们拆分到两个文件夹中可以使其更易于管理。
我创建了一个单独的文件夹来存放一些 Terraform 模块。有些模块专门用于 AKS 部署,而另一些模块则只用于 GitHub Actions 相关功能。
步骤 2:创建 GitHub Actions 的资源。
对于我们的 Terraform 部署,在开始编写 GitHub Actions 工作流文件之前,我们需要完成以下几件事:
- 为 OIDC 身份验证创建用户分配的托管标识。
- 为托管身份创建联合凭据。
- 创建一个 Azure 存储帐户和容器来存储我们的状态文件。
- 通过 CLI 部署资源
使用用户分配的托管标识而不是服务主体进行联合凭据
过去,我曾使用带有联合身份验证的服务主体来为我的 GitHub Actions 部署使用 OIDC(我在这里写过一篇关于如何操作的博客)。
使用用户分配的托管标识 (User Assigned Managed Identitys) 不需要我们在 Microsoft Entra ID 中拥有提升的权限,并且令牌超时时间更长。两者都可以在 Terraform 中相对轻松地实现,区别在于 Terraform 提供程序。用户分配的标识使用 AzureRM 提供程序,而应用注册使用 Azure AD 提供程序。
您可以使用以下 Terraform 资源创建用户分配的身份:
resource "azurerm_user_assigned_identity" "msi" {
location = var.location
name = var.name
resource_group_name = var.rg_name
tags = var.tags
}
这里没什么太复杂的。因为我想把它模块化,所以我还会创建一个variables.tf文件,其中包含以下变量,以便在使用该模块时为其赋值:
variable "location" {
description = "The location where the user-assigned managed identity will be created."
type = string
}
variable "name" {
description = "The name of the user-assigned managed identity."
type = string
}
variable "rg_name" {
description = "The name of the resource group in which the user-assigned managed identity will be created."
type = string
}
variable "tags" {
description = "value of tags to assign to the user-assigned managed identity."
type = map(string)
}
我们还需要将一些输出结果保存到一个outputs.tf文件中,以便将其用作其他模块的输入。我们可以这样做:
output "user_assinged_identity_id" {
value = azurerm_user_assigned_identity.msi.id
description = "ID of the user-assigned managed identity."
}
output "user_assinged_identity_principal_id" {
value = azurerm_user_assigned_identity.msi.principal_id
description = "Principal ID of the user-assigned managed identity"
}
然后我们就可以像这样使用该模块了。我还创建了一个新的资源组来存储用户分配的身份(这是我个人的选择):
module "identity-resource-group" {
source = "../modules/resource-group"
name = var.identity_rg_name
location = var.location
tags = var.tags
}
module "gh_usi" {
source = "../modules/user-assigned-identity"
name = "${var.gh_uai_name}-${var.environment}"
location = var.location
rg_name = module.identity-resource-group.name
tags = var.tags
}
对于刚接触 Terraform 的朋友,我们来详细了解一下这段代码。
资源是 Terraform 中最重要的元素。我们使用的每个资源块都描述一个或多个基础架构对象。在我的示例中,我使用了一个资源块来描述我想要创建的用户分配托管身份。
模块是用于存放多个一起使用的资源的容器。一个模块由一组.tf文件组成,这些文件都保存在同一目录下。因此,在我们的../modules/user-assigned-identity目录中,我有以下结构:
├── modules
│ ├── user-assigned-identity
| |── main.tf
| |── variables.tf
| |── outputs.tf
所以所有这些 Terraform 文件现在都已成为模块的一部分。
最后,输入变量作为 Terraform 模块的参数,以便我们可以在不编辑源代码的情况下自定义资源的实现,而输出值则类似于 Terraform 模块的返回值。
要了解有关这些资源的更多信息,请查看 Terraform 文档中的以下资源:
作为部署的一部分,我需要将“所有者”角色分配给“用户分配身份”。这样我们才能创建我将在 GitHub 和 AKS 集群部署中使用的角色分配,所以我为此创建了另一个模块:
resource "azurerm_role_assignment" "role" {
principal_id = var.principal_id
principal_type = var.principal_type
role_definition_name = var.role_name
scope = var.scope_id
}
我们在main.tf文件中这样实现:
module "sub_owner_role_assignment" {
source = "../modules/role-assignment"
principal_id = module.gh_usi.user_assinged_identity_principal_id
role_name = var.owner_role_name
scope_id = data.azurerm_subscription.sub.id
}
为了能够授予我的托管身份对订阅的拥有者角色,我们需要能够检索订阅的 ID。要在我的main.tf文件中使用订阅 ID,我们可以像这样将订阅作为数据资源导入:
data "azurerm_subscription" "sub" {
}
数据源允许 Terraform 使用在 Terraform 外部定义、由其他独立的 Terraform 配置定义或由函数修改的信息。数据块请求 Terraform 从指定的数据源(在本例中为 `<datasource> azurerm_subscription`)读取数据,并将结果导出为指定的本地名称sub。然后,该名称将用于在同一 Terraform 文件中的其他位置引用此资源。
因此,在我们需要订阅 ID 的角色分配模块中,我们可以使用数据资源提供该 ID data.azurerm_subscription.sub.id。
要了解有关 Terraform 中数据源的更多信息,请查看以下文档。
创建联合凭证
要从 GitHub Actions 安全地对 Azure 服务进行身份验证,我们需要使用 OpenID Connect(或 OIDC)登录。
OpenID Connect 是一种身份验证协议,它是 OAuth 2.0 的扩展,用于规范用户登录访问服务时的身份验证和授权流程。要了解更多关于 OIDC 的信息,请查看这篇文章。
GitHub Actions 可以通过直接从 Azure 请求短期访问令牌,在工作流中使用 OIDC。这比使用凭据更好,因为我们无需为 GitHub 创建凭据,也无需将其作为密钥复制到 GitHub 中。此外,我们还可以更精细地控制工作流如何使用凭据,并且由于访问令牌仅对单个作业有效,因此我们可以享受凭据轮换带来的好处。
当我们为部署工作流创建联合凭据时,我们实际上是在告诉 Microsoft Entra ID 和 GitHub 彼此信任。当我们的 GitHub Actions 工作流尝试登录时,GitHub 会提供有关工作流运行的信息,以便 Microsoft Entra ID 可以决定是否允许登录。
对于我们的联合身份验证凭证,我们需要提供颁发者、主题和受众的信息。颁发者是外部身份提供商的 URL,必须与外部令牌的颁发者声明匹配。主题是外部软件工作负载的标识符,必须与所共享的外部令牌的子声明或主题声明匹配。每个身份提供商使用自己的主题。最后,受众列出了可以出现在外部令牌中的受众。
为了存储受众和发行方的值,我将它们添加到了一个local.tf文件中,如下所示:
locals {
default_audience_name = "api://AzureADTokenExchange"
github_issuer_url = "https://token.actions.githubusercontent.com"
}
在我的 GitHub Actions 工作流中,我希望能够通过拉取请求触发工作流,并且希望使用一个环境来暂存部署。为此,我需要创建两个联合身份凭证。因此,我将创建一个用于联合身份凭证的模块,如下所示:
resource "azurerm_federated_identity_credential" "cred" {
name = var.federated_identity_credential_name
resource_group_name = var.rg_name
audience = [ var.audience_name ]
issuer = var.issuer_url
parent_id = var.user_assigned_identity_id
subject = var.subject
}
main.tf要创建联合凭证身份,我可以像这样在我的文件中实现模块:
module "gh_federated_credential" {
source = "../modules/federated-identity-credential"
federated_identity_credential_name = "${var.github_organization_target}-${var.github_repository}-${var.environment}"
rg_name = module.identity-resource-group.name
user_assigned_identity_id = module.gh_usi.user_assinged_identity_id
subject = "repo:${var.github_organization_target}/${var.github_repository}:environment:${var.environment}"
audience_name = local.default_audience_name
issuer_url = local.github_issuer_url
}
module "gh_federated_credential-pr" {
source = "../modules/federated-identity-credential"
federated_identity_credential_name = "${var.github_organization_target}-${var.github_repository}-pr"
rg_name = module.identity-resource-group.name
user_assigned_identity_id = module.gh_usi.user_assinged_identity_id
subject = "repo:${var.github_organization_target}/${var.github_repository}:pull_request"
audience_name = local.default_audience_name
issuer_url = local.github_issuer_url
}
请注意两个联合身份凭据中主题值的区别。第一个凭据的主题repo:${var.github_organization_target}/${var.github_repository}:environment:${var.environment}将允许我的 GitHub Actions 工作流在 GitHub 开发环境中部署时向 Azure 进行身份验证。第二个凭据的主题repo:${var.github_organization_target}/${var.github_repository}:pull_request将允许我们的工作流在每次作为拉取请求的一部分触发工作流时向 Azure 进行身份验证。
使用 GitHub,您还可以使用分支名称作为主题,例如repo:my-github-user/my-repo:ref:refs/heads/main。
创建用于管理 .tfstate 文件的资源
Terraform 必须存储有关您托管的基础设施和配置的状态。Terraform 使用此状态将实际资源映射到您的配置、跟踪元数据并提升性能。然后,它使用状态文件来确定要对基础设施进行哪些更改。要了解有关 Terraform 中状态的更多信息,请参阅以下文档。
默认情况下,此状态存储在一个terraform.tfstate文件中。请勿将此文件提交到源代码控制系统!我们可以将其存储在 Azure 存储帐户中。
为了实现这个功能,我创建了以下模块。我只需要一个容器,所以这里不需要做任何复杂的操作:
resource "azurerm_storage_account" "account" {
name = var.storage_account_name
location = var.location
resource_group_name = var.resource_group_name
account_tier = var.account_tier
account_replication_type = var.account_replication_type
tags = var.tags
}
resource "azurerm_storage_container" "container" {
name = var.container_name
storage_account_name = azurerm_storage_account.account.name
container_access_type = "private"
}
回到我的main.tf文件中,我像这样实现该模块。同样,我为存储帐户创建了另一个资源组:
module "tf-resource-group" {
source = "../modules/resource-group"
name = var.tf_state_rg_name
location = var.location
tags = var.tags
}
module "tf-state-storage" {
source = "../modules/tfstate-storage"
storage_account_name = var.storage_account_name
resource_group_name = module.tf-resource-group.name
location = var.location
tags = var.tags
account_replication_type = var.account_replication_type
account_tier = var.account_tier
container_name = var.container_name
}
我还需要授予我用于 GitHub Actions 部署的用户分配身份“存储 Blob 数据贡献者”角色,以便它可以将.tfstate文件写入我的存储帐户。使用我之前编写的模块,我可以这样做:
module "tfstate_role_assignment" {
source = "../modules/role-assignment"
principal_id = module.gh_usi.user_assinged_identity_principal_id
role_name = "Storage Blob Data Contributor"
scope_id = module.tf-state-storage.id
}
部署我们的 GitHub 资源
为了简化操作,我决定直接在本地机器上运行用于 GitHub Actions 部署的资源,而不是使用 GitHub Actions 工作流。让我们借此机会学习一下部署 Terraform 代码需要运行哪些命令。
Terraform 的核心工作流程由三个主要步骤组成。
- 初始化- 此操作会准备您的工作区,以便 Terraform 可以应用您的配置。
- 计划- 此功能允许您在应用更改之前预览 Terraform 将要进行的更改。
- 应用- 这将根据您的计划定义进行更改,以创建、更新或销毁资源。
我们先从初始化阶段开始。打开终端,或者使用 IDE(例如 Visual Studio Code)中的集成终端,并导航到./github-deployment目标文件夹。在该文件夹中,运行以下命令:
$ terraform init
该terraform init命令会初始化一个包含 Terraform 配置文件的工作目录。运行此命令后,它会为 Terraform 准备当前工作目录。这包括初始化配置main.tf文件中使用的所有模块,以及配置所需的提供程序。
Terraform 初始化完成后,我们需要格式化并验证配置。要格式化代码,请运行以下命令:
$ terraform fmt
此命令将自动更新当前目录中的配置,以提高可读性和一致性。Terraform 会打印出在此过程中修改的所有文件名。
我们还需要使用以下命令确保配置在语法上有效且一致:
$ terraform validate
现在我们重点关注计划阶段。使用 Terraform 配置基础设施时,它会在应用任何更改之前创建一个执行计划。该计划的创建方式是将 Terraform 配置与基础设施的当前状态进行比较。
要制定计划,我们可以执行以下操作:
$ terraform plan -var-file=<FILENAME> -out tfplan
此命令会根据要进行的一系列更改创建一个计划,以使资源与您的配置相匹配。在上述命令中,您可以提供一个包含变量值的文件,并让系统将计划生成到该文件中。然后,我们可以在“应用”阶段应用该计划,如下所示:
$ terraform apply "tfplan"
您不必保存计划。如果您不保存计划,Terraform 将创建一个新计划,并在应用该计划之前提示您进行批准。
对于我们的流水线,我会保存一个计划,这样 Terraform 就只会执行我预期的更改。再次强调,请不要将计划提交到源代码控制系统!资源部署完成后,我们需要在 GitHub 上添加一些密钥,以便我们的流水线可以使用它们。
对于我们的用户分配的托管身份,我们需要以下密钥:
| GitHub 秘籍 | 用户分配的托管标识属性 |
|---|---|
| AZURE_CLIENT_ID | 客户ID |
| AZURE_SUBSCRIPTION_ID | 订阅 ID |
| AZURE_TENANT_ID | 目录(租户)ID |
对于我们的存储帐户,我们需要以下密钥:
| GitHub 秘籍 | 存储账户属性 |
|---|---|
| 后端 Azure 资源组名称 | 存储帐户部署到的资源组的名称。 |
| 后端 Azure 存储帐户名称 | 用于存储.tfstate文件的存储帐户名称。 |
| 后端 Azure 存储帐户容器名称 | 用于存储.tfstate文件的 Blob 容器的名称。 |
要了解有关如何在 GitHub Actions 中使用密钥的更多信息,请查看以下文档。
步骤 3:通过 GitHub Actions 部署我的 AKS 集群
完成所有设置后,我们现在可以着手使用 GitHub Actions 工作流文件部署 AKS 集群了。在这一步,我不会过多关注 Terraform 代码,而是将重点放在 GitHub Actions 工作流本身。
我们需要对 Terraform 进行一些小的更改,以确保它使用 OIDC 身份验证,因为我们将通过 GitHub Actions 而不是我们自己的本地机器来运行 Terraform 命令。
我们还可以使用 GitHub Actions 工作流文件中的不同任务来区分计划和应用阶段。
配置我们的 Azure 提供商以使用 OIDC
为了配置 Terraform 部署的 OIDC 身份验证,我创建了一个名为 `.terraform.terraform.conf` 的新文件providers.tf,并写入了以下内容:
terraform {
required_version = ">=1.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~>3.0"
}
azapi = {
source = "azure/azapi"
version = "~>1.5"
}
random = {
source = "hashicorp/random"
version = "~>3.0"
}
azuread = {
source = "hashicorp/azuread"
version = "2.30.0"
}
}
backend "azurerm" {
key = "terraform.tfstate"
use_oidc = true
}
}
provider "azurerm" {
features {}
use_oidc = true
}
provider "azapi" {
use_oidc = true
}
我的 Terraform 代码中将同时使用 AzureRM 提供程序和 AzAPI 提供程序。
对于 AzureRM 提供程序,我们需要正确配置后端,以便将状态文件存储在 Azure 存储中。在我的 Terraform 代码中,我提供了启用 OIDC 身份验证的值,以及 AzureRM 提供程序应使用的状态文件的名称。
使用 Terraform 初始化时terraform init,我们可以通过命令行参数传递一些后端配置信息-backend-config。对于 OIDC 身份验证,这包括存储帐户的详细信息(资源组、帐户名和容器名称)以及用户分配的托管标识的详细信息(客户端 ID、订阅 ID 和租户 ID)。我们还会传递一个参数,指示 Terraform 配置提供程序使用 Microsoft Entra ID 身份验证。
我们还需要use_oidc将 AzureRM 和 AzAPI 提供程序的标志都设置为 true。
设置我们的 GitHub Actions 工作流程
现在我们终于可以开始查看 GitHub Actions 工作流了!创建工作流身份并为其分配 Azure 环境访问权限后,我们就可以在工作流中使用它了。
为了允许我们的部署工作流请求令牌,我们需要添加permissions如下属性:
name: Deploy AKS Cluster Infra
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
permissions:
contents: read
id-token: write
pull-requests: write
env:
ARM_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
ARM_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
ARM_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
ARM_USE_AZUREAD: true
让我们逐一介绍这些权限:
contents: read- 此功能适用于代码仓库的内容。此特定权限允许在我们的工作流程中执行列出提交记录的操作。id-token: write- 这会获取 OpenID Connect (OIDC) 令牌。pull-requests: write- 这允许你在工作流程中执行操作,为拉取请求添加标签。稍后会详细介绍。
要了解有关如何在工作流程中控制 GitHub 令牌权限的更多信息,请查看以下文档。
生成我们的 Terraform 计划
GitHub Actions 工作流程的第一步是生成一个 Terraform 计划,我们可以在应用该计划之前手动验证它。我会将工作流程文件分成几个部分,并解释每个步骤。
我们来看一下默认设置:
jobs:
terraform-plan:
if: github.event_name == 'pull_request'
defaults:
run:
working-directory: ./cluster-deployment
本质上,这意味着对于每个拉取请求,都要运行这个作业。我所有用于 AKS 集群的 Terraform 代码都位于该./cluster-deployment文件夹中。因此,对于我的 GitHub Actions 工作流,我将该目录设置为我的工作目录。
下一步是运行我们的 Terraform 工作流:
name: Terraform Plan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Terraform Fmt
id: fmt
run: terraform fmt -check
- name: Terraform Init
id: init
run: terraform init -backend-config="resource_group_name=${{secrets.BACKEND_AZURE_RESOURCE_GROUP_NAME}}" -backend-config="storage_account_name=${{secrets.BACKEND_AZURE_STORAGE_ACCOUNT_NAME}}" -backend-config="container_name=${{secrets.BACKEND_AZURE_STORAGE_ACCOUNT_CONTAINER_NAME}}"
- name: Terraform Validate
id: validate
run: terraform validate -no-color
这里我唯一想强调的是,terraform init我们并非让它自动运行,而是通过向标志位提供一些参数,将 Azure 存储帐户配置为状态文件的后端。这些参数来自我们在 GitHub 中的密钥,以及我们之前在 GitHub Actions 工作流中定义的环境变量。
下一步是对我们的 Terraform 代码进行静态代码分析,以发现任何潜在的配置错误。我目前使用 tfsec 来完成这项工作,但tfsec 正在迁移到 Trivy——我会在以后的博客文章中介绍这一点,但现在我们可以使用以下方法添加此功能:
- name: tfsec
uses: aquasecurity/tfsec-pr-commenter-action@v1.2.0
with:
tfsec_args: --soft-fail
github_token: ${{ github.token }}
现在我们可以生成 Terraform 计划,将计划发布为工件,然后更新我们的 PR 以显示计划的状态以及计划的详细信息。
- name: Terraform Plan
id: plan
run: |
export exitcode=0
terraform plan -no-color -var-file="./tfvars/terraform.tfvars" -var="azure_object_id=${{ secrets.AZURE_OBJECT_ID }}" -out main.tfplan || export exitcode=$?
echo "exitcode=$exitcode" >> $GITHUB_OUTPUT
if [ $exitcode -eq 1 ]; then
echo "Error: Terraform plan failed"
exit 1
else
echo "Terraform plan was successful"
exit 0
fi
- name: Publish Terraform Plan
uses: actions/upload-artifact@v4
with:
name: tfplan
path: ./cluster-deployment/main.tfplan
- name: Update Pull Request
uses: actions/github-script@v6
env:
PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
#### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
<details><summary>Show Plan</summary>
\`\`\`\n
${process.env.PLAN}
\`\`\`
</details>
*Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
- name: Terraform Plan Status
if: steps.plan.outcome == 'failure'
run: exit 1
- name: Create String Output
id: tf-plan-string
run: |
TERRAFORM_PLAN=$(terraform show -no-color main.tfplan)
delimiter="$(openssl rand -hex 8)"
echo "summary<<${delimiter}" >> $GITHUB_OUTPUT
echo "## Terraform Plan Output" >> $GITHUB_OUTPUT
echo "<details><summary>Click to expand</summary>" >> $GITHUB_OUTPUT
echo "" >> $GITHUB_OUTPUT
echo '```
{% endraw %}
terraform' >> $GITHUB_OUTPUT
echo "$TERRAFORM_PLAN" >> $GITHUB_OUTPUT
echo '
{% raw %}
```' >> $GITHUB_OUTPUT
echo "</details>" >> $GITHUB_OUTPUT
echo "${delimiter}" >> $GITHUB_OUTPUT
- name: Publish Terraform Plan to Task Summary
env:
SUMMARY: ${{ steps.tf-plan-string.outputs.summary }}
run: |
echo "$SUMMARY" >> $GITHUB_STEP_SUMMARY
- name: Push Terraform Output to PR
if: github.ref != 'refs/heads/main'
uses: actions/github-script@v7
env:
SUMMARY: "${{ steps.tf-plan-string.outputs.summary }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const body = `${process.env.SUMMARY}`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: body
})
如果我们导航到我们的 PR,github-actions 机器人会在我们的 PR 上评论我们各种 Terraform 命令的执行结果,如下所示:
它还会添加一条注释,显示 Terraform 计划本身。
该计划还将作为工件上传到 GitHub,我们可以在 Actions UI 中看到它:
应用我们的 Terraform 计划
我们的计划已经生成并保存为 GitHub 上的一个工件,现在我们可以尝试在另一个 GitHub Actions 作业中应用该计划,从而部署我们的基础架构。接下来,我将把这个文件分解成各个组成部分。
首先,像之前一样,我将路径设置working-directory为 Terraform 代码所在的文件夹。然后,我使用与Plan作业相同的后端配置来初始化 Terraform 环境:
terraform-apply:
needs: terraform-plan
name: Terraform Apply
runs-on: ubuntu-latest
environment: dev
defaults:
run:
working-directory: ./cluster-deployment
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Terraform Init
id: init
run: terraform init -backend-config="resource_group_name=${{secrets.BACKEND_AZURE_RESOURCE_GROUP_NAME}}" -backend-config="storage_account_name=${{secrets.BACKEND_AZURE_STORAGE_ACCOUNT_NAME}}" -backend-config="container_name=${{secrets.BACKEND_AZURE_STORAGE_ACCOUNT_CONTAINER_NAME}}"
我现在想获取 Terraform 之前生成的计划并应用它。在我的工作流文件中,我们可以使用actions/download-artifact任务来实现这一点。
- name: Download Terraform Plan
uses: actions/download-artifact@v4
with:
name: tfplan
path: ./cluster-deployment
这对我来说真是太费劲了! ——也许是因为我年纪大了,记性越来越差了吧😁
让我困惑的是,由于我把文件保存working-directory到了另一个文件夹,所以工件被保存到了那里,但任务却在根目录下查找它。因此,当我尝试下载工件时,它会失败,因为它找不到文件。
要解决这个问题,你需要将path任务中的属性设置为你的工作目录,就像我上面所做的那样。
下载 Terraform 计划后,我们就可以使用 GitHub Actions 应用它terraform apply。在 GitHub Actions 工作流文件中,我们可以添加-auto-approve标志来自动批准应用操作。
- name: Terraform Apply
run: terraform apply -auto-approve "./main.tfplan"
结论
这篇文章涵盖了很多内容,希望对您有所帮助。我们还可以做更多的事情,尤其是在测试和安全方面,但这些内容我们改天再谈。
如果你正在学习云计算(无论是 Azure、AWS 还是 GCP),并且正在研究如何使用基础设施即代码 (IaC) 将资源部署到云端,我强烈建议你学习如何通过 CI/CD(例如 GitHub Actions)来实现。你会深入了解身份验证的工作原理、CI/CD 工具的工作原理以及 Terraform(或其他任何 IaC 工具)的工作原理。这需要投入大量精力,但从长远来看绝对值得。
如果您对此有任何疑问,请随时在推特上联系我@willvelida
下次再见,祝你编程愉快!🤓🖥️
文章来源:https://dev.to/willvelida/deploying-to-azure-with-terraform-and-github-actions-5191


