使用GitHub Actions和Terraform在GCP上建立基础设施的持续集成/持续部署
关于本稿
2019年11月,GitHub正式发布了可在其平台上免费使用的工作流工具——GitHub Actions。通过使用该工具,可以自动化处理CI/CD等操作。
本文将介绍如何使用GitHub Actions 在Terraform上执行操作,并进行Google Cloud Platform的配置管理。
同时也会介绍基于GitOps的基础设施CI/CD工作流程。
昨天,我写了一篇名为”通过Bitbucket Pipelines和Terraform在GCP上进行基础设施CI/CD”的文章,现在我要写一篇关于GitHub Actions版本的文章。
由于两篇文章有很多相似内容,因此在后文中我将参考那篇文章并称其为”Bitbucket Pipelines版”。
更新历史
-
- 20200504 .github/workflows/terraform-apply.yml で、 terraform apply 失敗時にDeployment失敗通知が飛ばなかった問題を修正
- 20200503 初稿投稿
确认操作环境
-
- terraform v0.12.24
terraform-provider-google v3.19.0
需要准备的事项
-
- GitHubアカウント
-
- GitHub上のリポジトリ … TerraformのコードとGitHub Actionsのワークフロー設定ファイルを入れる
-
- GCPプロジェクト
Service Account
GitHub Actions内で実行するTerraformで利用する
Terraformで行う操作に必要なCloud IAMの権限をつける
今回の構成では、少なくともGCSへの読み書き権限が必要
GCSバケット … TerraformのBackendとして利用し、tfstateを保存する
必要なAPIを有効化する
例)GCEインスタンスの操作には、Compute Engine APIの有効化が必要
只有Bitbucket Pipelines版本变成了Bitbucket→GitHub。
建议使用的开发环境
与Bitbucket Pipelines版相同。
安装步骤
-
- 为了Terraform,生成并保存Service Account Key以JSON格式保存。
-
- 1. 将其设置为GitHub存储库的Secrets。
密钥名称可以任意,但在这里我们将其命名为GOOGLE_CREDENTIALS。
无需对值进行Base64编码。
在存储库的.github/workflows/下,准备两个如下所示的YAML文件。
PR时进行terraform的格式化/验证/计划工作流程
我们将使用HashiCorp提供的hashicorp/terraform-github-actions操作。
name: terraform plan
# プルリクエストのOpen、更新、Reopen時にトリガー
on: pull_request
env:
tf_version: '0.12.24'
tf_work_dir: '.'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: terraform fmt
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: ${{ env.tf_version }}
tf_actions_subcommand: fmt
tf_actions_working_dir: ${{ env.tf_work_dir }}
- name: terraform init
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: ${{ env.tf_version }}
tf_actions_subcommand: init
tf_actions_working_dir: ${{ env.tf_work_dir }}
- name: terraform validate
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: ${{ env.tf_version }}
tf_actions_subcommand: validate
tf_actions_working_dir: ${{ env.tf_work_dir }}
- name: terraform plan
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: ${{ env.tf_version }}
tf_actions_subcommand: plan
tf_actions_working_dir: ${{ env.tf_work_dir }}
通过这个工作流设置,当创建pull request、更新源分支2以及重新打开pull request时,按顺序执行 terraform fmt → terrafom init → terrafom validate → terrafom plan。
在PR合并时进行terraform plan/apply的工作流程。
与上面相同,使用hashicorp/terraform-github-actions的action。
此外,还会使用bobheadxi/deployments操作,同时创建与执行terraform apply相适应的Deployment。
name: terraform apply
on:
pull_request:
branches:
- master
types: [closed]
env:
tf_version: '0.12.24'
tf_work_dir: '.'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
jobs:
apply:
runs-on: ubuntu-latest
if: ${{ github.event.pull_request.merged == true }}
steps:
- uses: actions/checkout@v2
- name: terraform init
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: ${{ env.tf_version }}
tf_actions_subcommand: init
tf_actions_working_dir: ${{ env.tf_work_dir }}
- name: terraform plan
id: plan
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: ${{ env.tf_version }}
tf_actions_subcommand: plan
tf_actions_working_dir: ${{ env.tf_work_dir }}
# Deploymentを開始する
- name: Start Deployment
# terraform planで差分があるときのみ実行
if: ${{ steps.plan.outputs.tf_actions_plan_has_changes == 'true' }}
uses: bobheadxi/deployments@master
id: deployment
with:
step: start
token: ${{ secrets.GITHUB_TOKEN }}
env: production
- name: terraform apply
# terraform planで差分があるときのみ実行
if: ${{ steps.plan.outputs.tf_actions_plan_has_changes == 'true' }}
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: ${{ env.tf_version }}
tf_actions_subcommand: apply
tf_actions_working_dir: ${{ env.tf_work_dir }}
# Deploymentを終了する
- name: Finish Deployment
uses: bobheadxi/deployments@master
# terraform planで差分があるときのみ実行
if: ${{ always() && steps.plan.outputs.tf_actions_plan_has_changes == 'true' }}
with:
step: finish
token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status }}
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
通过这个工作流设置,您可以在将拉取请求合并到主分支时触发顺序执行 terrafom init → terraform plan → terraform apply。
此外,如果执行 terraform plan 没有差异,将不会执行 terraform apply 和创建部署。
部署任务在基础设施CI/CD的工作流程中并不是关键部分,因此可以省略。然而,考虑到GitHub与Slack的集成可以进行通知,并且可以查看部署历史记录,我认为这一功能是很方便的。
通过上述工作流设置,可以使用Git管理基础设施代码,并通过拉取请求进行更改审查,并在CI/CD中应用,实现GitOps流程。在接下来的“演示”部分中,将结合工作流程来展示其具体行为。
5/4补记:已修复无法发送部署失败通知的问题。
我将上述的 .github/workflows/terraform-apply.yml 文件中的“Finish Deployment”任务进行了以下修改。
:
# Deploymentを終了する
- name: Finish Deployment
uses: bobheadxi/deployments@master
# terraform planで差分があるときのみ実行
- if: ${{ steps.plan.outputs.tf_actions_plan_has_changes == 'true' }}
+ if: ${{ always() && steps.plan.outputs.tf_actions_plan_has_changes == 'true' }}
with:
step: finish
token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status }}
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
当工作流中的任务失败时,默认情况下后续任务将被跳过。
根据此规范,以前的设置在 terraform apply 失败时会跳过 Finish Deployment 任务。
如果我们在的条件中始终加入always(),就可以始终对该步骤进行评估。
在Github帮助页面的文档”GitHub Actions的上下文和表达式语法 – GitHub帮助#always”中提到:
当关键的故障阻止任务运行时,工作或步骤将无法运行。例如,如果获取资源失败。
关于对GCP的认证设定,补充如下:
请查看Bitbucket管道版本。
使用第二个分支更新来执行工作流程。
如果您将上述的YAML中的”on:”块修改为以下方式,您就可以在所需的分支上执行工作流程。
# masterブランチへの更新をトリガーとする場合
on:
push:
branches:
- master
---
# masterブランチ以外の更新をトリガーとする場合
on:
push:
branches-ignore:
- master
即使使用这样的设置,也可以实现与基于原始YAML的拉取请求触发的场景相似的基础设施CI/CD工作流程。不过,如果使用hashicorp/terraform-github-actions动作,它会通过评论通知拉取请求中terraform fmt|validate|plan|apply的结果,因此我认为构建拉取请求驱动的流程更方便。
参考文件和其他相关链接
-
- https://www.terraform.io/docs/github-actions/
https://help.github.com/en/actions
Workflow syntax for GitHub Actions – GitHub Help
Using variables and secrets in a workflow – GitHub Help
GitHub Actions覚え書き – Qiita#プルリクエストで実行する
演示:通过GitOps实现基础架构的持续集成/持续交付。
让我们来看一下,在设置了基于Pull Request的工作流程之后,基础设施CI/CD的工作流程会变成什么样子。
0. 准备好
除了之前所提到的事前准备和设置步骤外,还进行了以下操作:
-
- Terraformでテスト用のService Accountを作成するため、GCPプロジェクトでCloud Resource Manager APIを有効化しています
GitHubのSlackインテグレーションをリポジトリに設定し、Slack通知を有効にしています
featureとして、 issues, pulls, deployments, statuses, public, releases をsubscribeしました(デフォルトから commits のみunsubscribe)
创建一个名为”topic”的分支,并推送代码更改。
-
- 创建一个名为topic/add-test-sa的分支。
-
- 在.tf文件中定义google_service_account资源。
实际上,这段代码故意包含错误,以便terraform fmt也会产生差异。
将分支推送到GitHub。
% git checkout -b topic/add-test-sa
% $EDITOR main.tf
% git diff
diff --git a/main.tf b/main.tf
index b6bb61a..d5c0613 100644
--- a/main.tf
+++ b/main.tf
@@ -31,3 +31,7 @@ resource "google_project_iam_member" "sa_terraform" {
role = "roles/owner"
member = "serviceAccount:${google_service_account.terraform.email}"
}
+
+resource "google_service_account" "test" {
+ account_id = "test"
+}
% git add .
% git commit -m "Add test service account"
% git push origin topic/add-test-sa
2. 创建拉取请求
在GitHub界面上创建一个拉取请求。
通过创建pull request,将触发在.github/workflows/terraform-plan.yml中设置的工作流程。
3. 确认构建结果(失败)①
在 pull request 页面上,我们可以确认 terraform fmt 失败了。
当展开./main.tf文件时,可以确认在应用fmt之后的差异。
点击“×”图标或“细节”链接可以查看工作流的详细执行日志。
由于 terraform fmt 失败,可以看出后续的 terraform {init,validate,plan} 任务已被跳过。
4. 修正代码并重新推送①。
在本地应用 “terraform fmt”,然后重新推送分支。
% terraform fmt
main.tf
% git diff
diff --git a/main.tf b/main.tf
index 7ee15f7..d5c0613 100644
--- a/main.tf
+++ b/main.tf
@@ -33,5 +33,5 @@ resource "google_project_iam_member" "sa_terraform" {
}
resource "google_service_account" "test" {
- account_id = "test"
+ account_id = "test"
}
% git add .
% git commit -m "terraform fmt"
% git push origin topic/add-test-sa
通过对拉取请求的源分支进行更新,会触发在 .github/workflows/terraform-plan.yml 中设置的工作流。
5. 确认构建结果(失败)②
下一个是terraform验证失败了。
与 Bitbucket Pipelines 版本的演示类似,但在 google_service_account.test.account_id 的验证中出现了错误。
这次使用 terraform fmt 没有差异,所以没有添加评论。
刚才我错过了截屏,但在Slack上也收到了通知。
6. 修复代码并重新推送②
为了使 google_service_account.test.account_id 能够通过验证,我会修改并重新推送分支。
% $EDITOR main.tf
% git diff
diff --git a/main.tf b/main.tf
index d5c0613..fd9a3ec 100644
--- a/main.tf
+++ b/main.tf
@@ -33,5 +33,5 @@ resource "google_project_iam_member" "sa_terraform" {
}
resource "google_service_account" "test" {
- account_id = "test"
+ account_id = "test01"
}
% git add .
% git commit -m "Fix test service account id"
% git push origin topic/add-test-sa
7. 确认构建结果(成功)。
终于成功执行了 terraform plan。您可以通过收到的拉取请求评论来确认差异。
在使用 terraform fmt|validate 进行格式化和验证时,没有出现错误,因此此次没有添加这些注释。
8. 合并拉取请求
在上面检查差异,确认没有问题后执行合并。
通过将更改合并到主分支,将触发在 .github/workflows/terraform-apply.yml 中设置的工作流程。
9. 验证部署结果
在部署开始和完成时,将会收到Slack的通知。(以下是完成后)
另外,terraform {plan,apply} 的结果也会作为评论添加到合并请求中。
最后,我们看一下部署仪表板。您可以通过仓库菜单中的“环境”链接查看。
点击后,您可以看到以下画面。
在以上情况下,我可以确认部署已经成功。
总结和感想
我介绍了使用 GitHub Actions 执行 Terraform 并进行基于 GCP 环境的基础设施 CI/CD 的方法,以及通过 GitOps 进行工作流程的图示。
这次我们从Bitbucket Pipelines版本中迁移了设置,所以基本部分很快就完成了。但在打造CI/CD流程的过程中,意外地发现GitHub Actions缺少Bitbucket Pipelines中的一些功能。
虽然如此,我认为Bitbucket Pipelines还有一些功能无法实现,所以有优劣之分。5
脚注是指在文中给出的注释或参考资料的标记。
以下是对原文的本地化翻译(只提供一种选项):
https://github.blog/jp/2019-11-14-universe-day-one/ ↩
确切来说,这是一个名为synchronize的事件类型,但我还没有仔细调查过这个事件是什么(汗)。参考链接:https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-event-pull_request ↩
根据GitHub帮助中的《查看存储库的部署活动》所述,据2020年5月3日,该仪表盘还处于测试阶段。 ↩
在设置本文时注意到的一些要点是YAML锚点、手动触发构建、构建结果的Slack通知(可以使用第三方的操作实现)。参考链接:GitHub Actions备忘录-Qiita-已知的限制事项 ↩
给Pull Request添加评论似乎特别困难。 ↩