在GitHub Actions上搭建Terraform的CI/CD环境
首先
Github Actions正式版已经发布大约一个月了。虽然之前没有机会尝试过,但最近在研究使用Terraform进行CI/CD环境构建时,进行了一次尝试,以下是此次的总结。
示例项目
如果只是想先试一试的话,你可以直接使用Terraform官方提供的Terraform GitHub Actions,很快就能完成。
但仅使用这一点可能不够有趣,所以尽可能模拟实际运营并继续进行测试。然而,由于尝试所有可能性较为困难,我们假设以下示例项目。
-
- providerはAWS
-
- moduleを使う
-
- workspaceを使う
- リポジトリ内に複数のmain.tfがある
目录结构如下。(文件内容不是重点,故省略)
├── modules
│ ├── ec2
│ │ ├── main.tf
│ │ └── variables.tf
│ └── iam-role
│ ├── main.tf
│ └── variables.tf
├── service1
│ ├── ec2
│ │ ├── main.tf
│ │ └── variable.tf
│ └── iam-role
│ ├── main.tf
│ └── variable.tf
└── service2
├── ec2
│ ├── main.tf
│ └── variable.tf
└── iam-role
├── main.tf
└── variable.tf
进球
对上述所写的结构样本执行以下1-4步骤。
-
- 当创建向主分支的拉取请求时,触发以下三个操作(以下称为自动测试):
terraform fmt
terraform validate
terraform plan
在一次拉取请求中,对多个工作区执行自动测试。
在向主分支进行推送(合并)时,触发自动测试和terraform apply操作。仅在自动测试工作流成功的情况下才执行apply操作。
自动测试和apply操作不会对代码库中的所有main.tf文件进行操作,而是只对与已更改的文件相关的main.tf文件进行操作。
1. 当创建向主分支的PR时,触发自动化测试执行。
创建工作流程
我們將立即開始創建workflow,我們將使用Terraform官方提供的Terraform GitHub Actions。我們將根據以下官方示例進行修改。
name: 'Terraform GitHub Actions'
on:
- pull_request
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
steps:
- name: 'Checkout'
uses: actions/checkout@master
- name: 'Terraform Format'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.17
tf_actions_subcommand: 'fmt'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: 'Terraform Init'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.17
tf_actions_subcommand: 'init'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: 'Terraform Validate'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.17
tf_actions_subcommand: 'validate'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: 'Terraform Plan'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.17
tf_actions_subcommand: 'plan'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AWS 的凭证

把步骤中的env移到上一级,并在那里设置AWS凭据信息。虽然Checkout步骤不使用它,但为了避免重复写入,我们将其移到上方。
name: 'Terraform GitHub Actions'
on:
- pull_request
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
env: # 追加
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # 移動
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} # 追加
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # 追加
steps:
- name: 'Checkout'
uses: actions/checkout@master
- name: 'Terraform Format'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.17
tf_actions_subcommand: 'fmt'
- name: 'Terraform Init'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.17
tf_actions_subcommand: 'init'
- name: 'Terraform Validate'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.17
tf_actions_subcommand: 'validate'
- name: 'Terraform Plan'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.17
tf_actions_subcommand: 'plan'
设定工作区
首先,我将尝试在名为dev的工作空间中运行。由于Terraform GitHub Actions中可以使用名为TF_WORKSPACE的环境变量来指定工作空间,因此我将它添加到env中。
env:
TF_WORKSPACE: dev # 追加
指定执行目录
由于main.tf分为多个部分,需要指定在哪个包含main.tf的目录中执行。
通过指定在Github Actions中提供的strategy.matrix,可以轻松地对多个配置执行作业。
在本例中,有4个main.tf文件,因此需要分别指定strategy.matrix,并将其通过tf_actions_working_dir传递。
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
strategy: # 追加 strategyのmatrixでworkspace設定。この組み合わせの数だけjobが実行される
matrix:
workdir: [./service1/ec2, ./service2/ec2, ./service1/iam-role, ./service2/iam-role]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
TF_WORKSPACE: dev
steps:
- name: 'Checkout'
uses: actions/checkout@master
- name: 'Terraform Format'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.17
tf_actions_subcommand: 'fmt'
tf_actions_working_dir: ${{ matrix.workdir }} # 追加
- name: 'Terraform Init'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.17
tf_actions_subcommand: 'init'
tf_actions_working_dir: ${{ matrix.workdir }} # 追加
- name: 'Terraform Validate'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.17
tf_actions_subcommand: 'validate'
tf_actions_working_dir: ${{ matrix.workdir }} # 追加
- name: 'Terraform Plan'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.17
tf_actions_subcommand: 'plan'
tf_actions_working_dir: ${{ matrix.workdir }} # 追加
当触发对主机的pull request
只需根据以下方式设置分支过滤器即可。(在以下设置的情况下,除了在创建PR时触发外,还将在将推送到创建了Pull Request的分支上时以及其他几个事件中触发。)
name: 'Terraform GitHub Actions'
on:
pull_request:
branches:
- master
确认动作
我会在当前设置下进行操作确认。当针对主分支创建拉取请求时,Actions应该能够成功执行。你会看到根据strategy.matrix指定的目录数量执行相应的作业。

在一次pull request中,对多个工作空间进行自动化测试。
假设有两个名为dev和prod的工作空间,只需像指定执行目录一样使用strategy.matrix,就能轻松实现。
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
strategy:
matrix:
env: [dev, prod] # 追加
workdir: [./service1/ec2, ./service2/ec2, ./service1/iam-role, ./service2/iam-role]
env:
TF_WORKSPACE: ${{ matrix.env }} # matrixから値を取得するように修正
确认动作
在此设置下进行操作确认。如果为master分支创建拉取请求,将会有8个作业运行,即4个workdir和2个env。

当将更改推送(合并)到主分支时,会触发自动测试和应用程序执行。
在触发向主分支推送(合并)时执行任务。
在pull_request的同一级别添加一个push到branches,并指定为master分支。这样就可以使job在满足对master分支的拉取请求或推送的条件下执行。
name: 'Terraform GitHub Actions'
on:
pull_request:
branches:
- master
push: # 追加
branches:
- master
我觉得在这个案例中,虽然我们将定义放在一个yml文件中,但将yml分为多个文件而不是只在拉取请求的情况下进行,也是可行的。
添加适用步骤
因为Terraform GitHub Actions已经提供了apply的选项,所以我们将使用它。但是,我们希望apply只在修改被应用到master分支时才执行,所以我们在流程中添加了一个条件来控制。我们可以通过github context获取到触发作业执行的事件信息等,因此我们将只在针对master分支的事件时执行apply。
- name: 'Terraform Plan'
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.17
tf_actions_subcommand: 'plan'
tf_actions_working_dir: ${{ matrix.workdir }}
- name: 'Terraform Apply' # ここから下を追加
if: github.ref == 'refs/heads/master' # プルリクエストの作成では動かないように
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: 0.12.17
tf_actions_subcommand: 'apply'
tf_actions_working_dir: ${{ matrix.workdir }}
只有在完成了计划中的步骤后才执行申请程序。
如果前一步失败了,默认情况下,下一步不会执行,因此无需特别设置,在计划成功时才会执行apply。
确认动作 (Confirm/action)

只对目录内被修改的main.tf文件执行作业。
当考虑到实际操作时,完成创建Pull Request并执行plan后合并到master分支并应用的流程已经建立,但可能会出现以下问题。
-
- main.tfの数やworkspaceのが増えると一度の修正で実行されるjobの数が増え、jobの並列実行数が増加し続けGitHub Actionsの上限までいってしまう。
- 修正と関係ないmain.tfに対してもplan、applyが実行されるので、修正内容に関わらず一番実行時間が長いjobの分だけ時間がかかってしまう。
我们将设置一个配置,只执行与已修复文件相关的作业,以解决这些问题。
对于 paths 过滤器的设置。
通过使用路径过滤器,可以在特定的目录或文件发生更改时执行作业。例如,如果将其设置为PR请求并且在service1目录和modules目录中有更改,则会触发作业执行。
name: 'Terraform GitHub Actions'
on:
pull_request:
branches:
- master
paths: # 追加
- service1/**
- modules/**
顺便提一下,检测差异的方法似乎是通过拉取请求、对已存在分支的推送和对新建分支的推送完成以下操作。
关于
Pull请求:三个点的差异是将主题分支的最新版本与主题分支上次与基础分支同步的提交进行比较。
对现有分支的推送:两个点的差异直接将头部和基础SHAs进行比较。
对新分支的推送:与推送的最深提交的祖先父级进行两个点的差异比较。
使用路径过滤器
./service1ディレクトリ以下のファイルと./modulesディレクトリ以下のファイルに変更がある場合、./service1以下のmain.tfを実行する
./service2ディレクトリ以下のファイルと./modulesディレクトリ以下のファイルに変更がある場合、./service2以下のmain.tfを実行する
为了使其按照类似的方式运作,我们将原始的terrafrom.yml文件分为两个部分,如下所示。
name: 'Terraform Service1'
on:
pull_request:
branches:
- master
paths: # service1に関係する変更のみを検知
- service1/**
- modules/**
push:
branches:
- master
paths: # service1に関係する変更のみを検知
- service1/**
- modules/**
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
strategy:
matrix:
env: [dev, prod]
workdir: [./service1/ec2, ./service1/iam-role] # service1のディレクトリのみを指定
# env以下は元のterraform.ymlと同じ
name: 'Terraform Service2'
on:
pull_request:
branches:
- master
paths: # service2に関係する変更のみを検知
- service2/**
- modules/**
push:
branches:
- master
paths: # service2に関係する変更のみを検知
- service1/**
- modules/**
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
strategy:
matrix:
env: [dev, prod]
workdir: [./service2/ec2, ./service2/iam-role] # service1のディレクトリのみを指定
# env以下は元のterraform.ymlと同じ
考虑到各个main.tf文件的依赖关系,可能会变得复杂。然而,通过使用路径过滤器,可以执行与修正文件相对应的作业。
确认操作
我在修正(service1.yml、service2.yml)后,试着只修改了service1/ec2/variable.tf并创建了一个拉取请求,可以确认./service1目录中与main.tf相关的工作已被执行。

总结
我在GitHub Actions上创建了一个CI/CD流程,用于处理一个简单的Terraform仓库,并尝试了不同的配置。虽然这是我第一次使用GitHub Actions,但文档写得很清楚,几乎没有遇到问题,非常易于使用。以下还有其他一些优点。
-
- GitHub上のサービスだけあって、GitHubのコンテキストの情報を簡単に取得できる
-
- 無料で並列実行までできるのすごい
-
- maxrixによる並列実行がかなり便利
- pathsフィルタがかなり便利
个人认为在申请时,我希望有一个像Circle CI提供的手动批准执行的功能。虽然似乎有很多人提出了这个需求,但我认为它可能会在以后被添加进来。
因为这次定的Terraform仓库很简单,所以可能很难直接用于实际操作,但能够想象出可能做的事情还是很好的。
请提供更多的上下文来准确理解和引用。
GitHub Actions文档
Terraform GitHub Actions文档
Terraform GitHub Actions