はじめに
記事を書こう思いつつも、なかなかかけていなかったので、初のアドベントカレンダー参加&久々に投稿したいと思いいます
ということで、
前回かその前ぐらいに、 terraform を使った AWS 環境の構築をしました。
今回は、 GithubActions を使って terraform の CI/CD をやってみようと思います。
今回のゴール
流れとしてはこんな感じ。
- main ブランチから feature ブランチを作成
- ローカルに clone
- コードの修正
- feature ブランチへ push
- CI Workflow 実行
- PR 作成
- CI Workflow 実行
- main へマージ
- CD Workflow 実行
- AWS 環境へ terraform apply
- CD Workflow 実行
やること
前提
AWS 環境を構築する terraform のコードは以下を使用します。
今回の作業は、上記のリポジトリをコピーして別のリポジトリを作成し行います。
事前準備
OIDC と IAM Role の作成と設定
Github Actions から terraform で AWS のリソース操作をするための認証設定と IAM Role を作成します。
作業の流れ
- AWS での OIDC の設定
- AWS での IAM ロールの作成
- Github リポジトリへの設定
1. AWS での OIDC の設定
AWS のコンソール画面から OIDC プロバイダの作成をします。
IAM コンソールから ID プロバイダ
-> プロバイダを作成
を選択します。
プロバイダのタイプ | OpenID Connect |
プロバイダの URL | https://token.actions.githubusercontent.com |
対象者 | sts.amazonaws.com |
2. IAM ロールの作成
続いて Github Actions に渡す IAM ロールを作成します。
IAM コンソールから ロール
-> ロールの作成
を選択します。
ここでは、信頼されたエンティティタイプ
ではカスタム信頼ポリシー
を選択し、
カスタム信頼ポリシー
には以下設定を行います。
※あくまで一例なので、自身の環境にあわせ適切な権限を設定してください
参考:Amazon Web Services での OpenID Connect の設定
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::<AWSアカウントID>:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
}
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:Ixy-194/go-api-sample-todo-terraform-cicd:*"
}
}
}
]
}
Condition は必ず設定しましょう
Condition
の StringLike
で リポジトリの制限をかけています。
"token.actions.githubusercontent.com:sub": "repo:Ixy-194/go-api-sample-todo-terraform-cicd:*"
これをしないと、 別のリポジトリからも IAM ロールが使えるようになり、悪用されるリスクになります。必ず設定しましょう。
ここでは、リポジトリの制限しかかけていないですが、他にも特定のブランチ
、特定の操作のみ(push, pr)
といった制限をかけることもできます。参考
入力したら次へ
を選択します。
続いて、 IAM ロールにポリシーをアタッチします。
ここでは、AdministratorAccess
を設定しますが、適宜環境に合わせ必要な権限のみ設定するようにしてください。
最後にロール名を設定します。
ここではgithubactions-oidc
としました。
ロールが作成されました。
このあとに、 ARN の値を Github リポジトリに登録するので値をメモっておきましょう。
3.Github リポジトリへの設定
作成した IAM ロールの ARN を GithubActions のワークフローから参照できるようにリポジトリに環境変数として登録をします。
リポジトリ画面を開いて、Settings
タブから Secres and variables
-> Actions
を選択し、New repository secret
から登録します。
これで、事前準備は終了です。
これもコード化したいなー(´・ω・)
CI/CD ワークフローの作成
では、ここから実際に GithubActions での CI/CD ワークフローを作成します。
作成するワークフローファイル(yaml)は.github/workflows
配下に作成します。
CI/CD の作成にあたり、以下のファイルを追加しています
├── .github
│ └── workflows
│ ├── dev-apply.yml # CD Workflow
│ └── validation.yml # CI Workflow
├── .tflint.hcl # CI tflint setting file
CI ワークフローの作成
まずは CI の実装です。
CI のワークフローでは、リポジトリへのソースコードの PUSH、 PR をトリガーに以下を行います。
- terraform コマンドによる静的チェック
- terraform fmt
- terraform validate
- review dog を使った tflint による静的チェック
- terraform plan による実行計画の書き出し(PRにコメントとして記載)
実際に作成したワークフローはこんな感じ。
name: Terraform Validation
permissions:
id-token: write
contents: read
pull-requests: write
on:
pull_request:
push:
jobs:
terraform:
name: Terraform Validation
runs-on: ubuntu-latest
defaults:
run:
shell: bash
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ap-northeast-1
role-to-assume: ${{ secrets.GITHUBACTIONS_OIDC_ARN }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.8
- name: Check Terraform Format
run: terraform fmt -recursive -check
- name: Initialize Terraform (Dev)
working-directory: ./env/dev
run: terraform init
- name: Validate Terraform (Dev)
working-directory: ./env/dev
run: terraform validate
- name: Run TFLint
uses: reviewdog/action-tflint@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
tflint_init: "true"
tflint_config: "${{ github.workspace }}/.tflint.hcl"
level: "error"
fail_on_error: "true"
filter_mode: "added"
flags: "--recursive"
- name: Create Terraform Plan (Dev)
working-directory: ./env/dev
id: dev_plan
run: terraform plan -no-color
- name: Update Pull Request with Terraform Plan
uses: actions/github-script@v7
if: github.event_name == 'pull_request'
env:
Dev_PLAN: "terraform\n${{ steps.dev_plan.outputs.stdout }}"
with:
result-encoding: string
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `#### Check! Terraform Show Plans 🖌\`
<details><summary>Dev Show Plan</summary>
\`\`\`\n
${process.env.Dev_PLAN}
\`\`\`
</details>
*Pusher: @${{ 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
});
主な設定ポイント
ワークフロー内での権限設定
OIDC、Workflow、reviewdog に対して必要な権限の付与をしています。
permissions:
id-token: write # OIDC を利用するのに必要な権限
contents: read # actions/checkout@v4 を利用するのに必要な権限
pull-requests: write # reciewdog によって PR にコメントするのに必要な権限
詳細は公式ドキュメントを確認してください。
ワークフローの実行トリガー
以下の記述により、PR 作成時、リポジトリへの PUSH 時にワークフローが起動するようにしています。
on:
pull_request:
push:
OIDC による認証
ここで、事前準備で作成した IAM Role を使って認証を行います。
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ap-northeast-1
role-to-assume: ${{ secrets.GITHUBACTIONS_OIDC_ARN }}
reviewdog(tflint)による静的チェック
reviewdog による静的チェックの定義です。
- name: Run TFLint
uses: reviewdog/action-tflint@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review
tflint_init: "true"
tflint_config: "${{ github.workspace }}/.tflint.hcl"
level: "error"
fail_on_error: "true"
filter_mode: "added"
flags: "--recursive"
with
句内で、 reviewdog の動作設定や設定ファイルの指定を行なっています。
設定ファイルの内容はこんな感じです。
plugin "aws" {
enabled = true
deep_check = true
version = "0.34.0"
source = "github.com/terraform-linters/tflint-ruleset-aws"
}
ここでは、AWS のプラグインを使用してAWS に特化した
内容でチェックをするようにしています。
また、deep_check = true
することにより、単なる静的チェックだけではなく、
実際のリソース確認、例えば 指定した EC2 インスタンスの存在チェックまで行ってくれるようになります。
※こちらは後述します
reviewdog の詳細は公式ドキュメントを確認してください。
PR への terraform plan 結果の書き込み
reate Terraform Plan (Dev)
の実行結果(標準出力)をactions/github-script@v7
を使って PR に書き出すようにしています。
terraform の レビューをする時、実行計画も見ないとなんともいえないですからね。
- name: Create Terraform Plan (Dev)
working-directory: ./env/dev
id: dev_plan
run: terraform plan -no-color
- name: Update Pull Request with Terraform Plan
uses: actions/github-script@v7
if: github.event_name == 'pull_request'
env:
Dev_PLAN: "terraform\n${{ steps.dev_plan.outputs.stdout }}"
with:
result-encoding: string
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `#### Check! Terraform Show Plans 🖌\`
<details><summary>Dev Show Plan</summary>
\`\`\`\n
${process.env.Dev_PLAN}
\`\`\`
</details>
*Pusher: @${{ 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
});
実際に動かしてみる
正常系
これが実際の PR です。
https://github.com/Ixy-194/go-api-sample-todo-terraform-cicd/pull/7
PR を作成するとこんな感じに CI の結果が表示されるのと、
terraform plan の結果がコメントされます。
https://github.com/Ixy-194/go-api-sample-todo-terraform-cicd/pull/7#issuecomment-2424148355
レビューをする際には、実際に変更されたファイルに加え、実行プランの確認ができていいですね。
異常系
続いて、異常系のパターンです。
わざとエラーを出すために、 tf ファイルの一部を書き換えました。
EC2 インスタンス作成時の instance_type
を実際には存在しないインスタンスタイプを指定するようにしました。
さて、どうなるでしょうか?
resource "aws_instance" "this" {
ami = data.aws_ssm_parameter.this.value
instance_type = "t2.nanoex" # 存在しない インスタンスタイプ
subnet_id = var.subnet_id
iam_instance_profile = aws_iam_instance_profile.this.name
vpc_security_group_ids = [aws_security_group.this.id]
tags = {
Name = "${var.env}-bastion"
Terraform = "true"
Environment = var.env
}
}
ちゃんと PR にコメントも書き込まれていますね、いいですね。
ただ、これ、 terraform の plan では検知してくれないんです。
reviedog で検知した内容になっています。
Githubactions のログから terraform plan の結果を確認するとこんな感じになっていて、エラーとして検知されず通っているんですよね。
+ resource "aws_iam_role" "this" {
~~~~~~ 一部省略
+ instance_type = "t2.nanoex"
# module.bastion.aws_security_group.this will be created
~~~~~~ 一部省略
Plan: 5 to add, 0 to change, 0 to destroy.
─────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't
guarantee to take exactly these actions if you run "terraform apply" now.
なので、 terraform のチェックだけでは心許ないなということで、今回 reviewdog(tflint)を入れてみました。
reviewdog(tflint)の設定ファイル「.tflint.hcl」で、deep_check = true
にすることによって、実際に API call して ami やインスタンスタイプの有効性をチェックしてくれんです。素晴らしいですね。
これで、CI は OK だったのに、実際に apply したら爆死する、という不幸を減らせそうです。
CD ワークフローの作成
続いて、CD のワークフローです。
CD のワークフローでは、main ブランチへのマージをトリガーに CI が実行
実行された CI が成功した時のみ、 CD が走り、terraform apply
が行われるようにしています。
実際のコードはこんな感じ。
name: Terraform Apply
permissions:
id-token: write
contents: read
on:
workflow_run:
workflows:
- Terraform Validation
branches:
- main
types:
- completed
jobs:
terraform:
name: Terraform Apply
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
defaults:
run:
shell: bash
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-region: ap-northeast-1
role-to-assume: ${{ secrets.GITHUBACTIONS_OIDC_ARN }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v3
with:
terraform_version: 1.9.8
- name: Initialize Terraform
working-directory: ./env/dev
run: terraform init
- name: Apply Terraform
working-directory: ./env/dev
run: terraform apply -auto-approve
主な設定ポイント
ワークフローの実行トリガー
以下の記述により、main ブランチで CI が実行され、それが完了・正常終了した時のみ CD が実行されるようになっています。
on:
workflow_run:
workflows:
- Terraform Validation
branches:
- main
types:
- completed
jobs:
terraform:
name: Terraform Apply
runs-on: ubuntu-latest
# 前のワークフローが成功した時のみ実行
if: ${{ github.event.workflow_run.conclusion == 'success' }}
terraform apply -auto-approve
オプションによって、作成予定のリソース表示や実行確認 yes をスキップするように、Githubactions のワークフローで apply が自動で行えるようにしています。
- name: Apply Terraform
working-directory: ./env/dev
run: terraform apply -auto-approve
実際に動かしてみる
PR を作成してマージをするとこんな感じにジョブが実行されます。
ちゃんと terraform apply
されて、リソースも作られています。良かった。
最後に
こんな感じで、 terraform での AWS インフラの CI/CD を実装してみましたがいかがでしたでしょうか。
IaC でインフラもコード化することによって、再現性、再利用性の高いものが作れるようになり、
設定内容や利用しているリソースが簡単に可視化できるようになりました。
また、CI によって事前検証もできるので、品質向上・事故防止にも役立つのではないでしょうか。
reviewdog(tflint)、初めて使ったのですが思っていたよりすごいですね。
もっと深掘りできれば良かったのですが、今回は CI/CD がメインだったのでまたいずれ。
今回のソースコードはこちらに公開しています。
ぜひご参考までに。
PR 正常系
PR 異常系
最後までありがとうございました。