使用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版相同。

安装步骤

    1. 为了Terraform,生成并保存Service Account Key以JSON格式保存。

 

    1. 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”的分支,并推送代码更改。

    1. 创建一个名为topic/add-test-sa的分支。

 

    1. 在.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界面上创建一个拉取请求。

Screenshot from 2020-05-03 20-21-37.png

通过创建pull request,将触发在.github/workflows/terraform-plan.yml中设置的工作流程。

3. 确认构建结果(失败)①

在 pull request 页面上,我们可以确认 terraform fmt 失败了。

Screenshot from 2020-05-03 19-19-50_crop.png

当展开./main.tf文件时,可以确认在应用fmt之后的差异。

Screenshot from 2020-05-03 19-20-31.png

点击“×”图标或“细节”链接可以查看工作流的详细执行日志。

Screenshot from 2020-05-03 19-22-41.png

由于 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验证失败了。

Screenshot from 2020-05-03 19-26-13.png

与 Bitbucket Pipelines 版本的演示类似,但在 google_service_account.test.account_id 的验证中出现了错误。

这次使用 terraform fmt 没有差异,所以没有添加评论。

刚才我错过了截屏,但在Slack上也收到了通知。

Screenshot from 2020-05-03 19-26-48.png

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。您可以通过收到的拉取请求评论来确认差异。

Screenshot from 2020-05-03 19-31-35.png

在使用 terraform fmt|validate 进行格式化和验证时,没有出现错误,因此此次没有添加这些注释。

8. 合并拉取请求

在上面检查差异,确认没有问题后执行合并。

通过将更改合并到主分支,将触发在 .github/workflows/terraform-apply.yml 中设置的工作流程。

9. 验证部署结果

在部署开始和完成时,将会收到Slack的通知。(以下是完成后)

Screenshot from 2020-05-03 19-36-08.png

另外,terraform {plan,apply} 的结果也会作为评论添加到合并请求中。

Screenshot from 2020-05-03 19-35-02.png

最后,我们看一下部署仪表板。您可以通过仓库菜单中的“环境”链接查看。

Screenshot from 2020-05-03 19-38-25_mark.png

点击后,您可以看到以下画面。

Screenshot from 2020-05-03 19-38-45.png

在以上情况下,我可以确认部署已经成功。

总结和感想

我介绍了使用 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添加评论似乎特别困难。 ↩

广告
将在 10 秒后关闭
bannerAds