0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[AWS]GithubActionsで terraform のCI/CDやってみた

Posted at

はじめに

「良いコードは良い筋肉から」、「筋肉は全てを解決する」って誰かが言ってました。
最近コードは全く書かなくなって、代わりに毎日パワポを書いているのですが、
どんなに筋トレ頑張ってもいいパワポはいっこうに書けるようになりません。

筋肉じゃパワポは書けないのかしら(´・ω・`)
いや、筋肉が足りていないのか???

ということで、
前回かその前ぐらいに、 terraform を使った AWS 環境の構築をしました。

今回は、 GithubActions を使って terraform の CI/CD をやってみようと思います。

今回のゴール

こんな感じの CI/CD フローを作成します。
image.png

流れとしてはこんな感じ。

  1. main ブランチから feature ブランチを作成
  2. ローカルに clone
  3. コードの修正
  4. feature ブランチへ push
    1. CI Workflow 実行
  5. PR 作成
    1. CI Workflow 実行
  6. main へマージ
    1. CD Workflow 実行
      1. AWS 環境へ terraform apply

やること

前提

AWS 環境を高築する terraform のコードは以下を使用します。

今回の作業は、上記のリポジトリをコピーして別のリポジトリを作成し行います。

事前準備

OIDC と IAM Role の作成と設定

Github Actions から terraform で AWS のリソース操作をするための認証設定と IAM Role を作成します。

作業の流れ

  1. AWS での OIDC の設定
  2. AWS での IAM ロールの作成
  3. Github リポジトリへの設定

1. AWS での OIDC の設定

AWS のコンソール画面から OIDC プロバイダの作成をします。

IAM コンソールから ID プロバイダ -> プロバイダを作成を選択します。
image.png

以下内容を入力でプロバイダを追加します。
image.png

プロバイダのタイプ OpenID Connect
プロバイダの URL https://token.actions.githubusercontent.com
対象者 sts.amazonaws.com

こんな感じに作成されました。
image.png

2. IAM ロールの作成

続いて Github Actions に渡す IAM ロールを作成します。

IAM コンソールから ロール -> ロールの作成を選択します。
image.png

以下の画面が表示されるので、
image.png

ここでは、信頼されたエンティティタイプではカスタム信頼ポリシーを選択し、
カスタム信頼ポリシーには以下設定を行います。
※あくまで一例なので、自身の環境にあわせ適切な権限を設定してください
参考: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 は必ず設定しましょう
ConditionStringLikeで リポジトリの制限をかけています。
"token.actions.githubusercontent.com:sub": "repo:Ixy-194/go-api-sample-todo-terraform-cicd:*"
これをしないと、 別のリポジトリからも IAM ロールが使えるようになり、悪用されるリスクになります。必ず設定しましょう。

ここでは、リポジトリの制限しかかけていないですが、他にも特定のブランチ特定の操作のみ(push, pr)といった制限をかけることもできます。参考

入力したら次へを選択します。
続いて、 IAM ロールにポリシーをアタッチします。
ここでは、AdministratorAccessを設定しますが、適宜環境に合わせ必要な権限のみ設定するようにしてください。
image.png

最後にロール名を設定します。
ここではgithubactions-oidcとしました。
image.png

ロールが作成されました。
このあとに、 ARN の値を Github リポジトリに登録するので値をメモっておきましょう。
image.png

3.Github リポジトリへの設定

作成した IAM ロールの ARN を GithubActions のワークフローから参照できるようにリポジトリに環境変数として登録をします。
リポジトリ画面を開いて、Settings タブから Secres and variables -> Actions を選択し、New repository secret から登録します。

image.png

これで、事前準備は終了です。
これもコード化したいなー(´・ω・)

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にコメントとして記載)

実際に作成したワークフローはこんな感じ。

validation.yml
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 の動作設定や設定ファイルの指定を行なっています。
設定ファイルの内容はこんな感じです。

.tflint.hcl
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

image.png
image.png

レビューをする際には、実際に変更されたファイルに加え、実行プランの確認ができていいですね。

異常系

続いて、異常系のパターンです。
わざとエラーを出すために、 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
  }
}


結果、このようにエラーとしてしっかり検知してくれています。
image.png

ちゃんと 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が行われるようにしています。
実際のコードはこんな感じ。

dev-apply.yml
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 を作成してマージをするとこんな感じにジョブが実行されます。
image.png

image.png
image.png

ちゃんと terraform applyされて、リソースも作られています。良かった。

最後に

こんな感じで、 terraform での AWS インフラの CI/CD を実装してみましたがいかがでしたでしょうか。
IaC でインフラもコード化することによって、再現性、再利用性の高いものが作れるようになり、
設定内容や利用しているリソースが簡単に可視化できるようになりました。
また、CI によって事前検証もできるので、品質向上・事故防止にも役立つのではないでしょうか。

reviewdog(tflint)、初めて使ったのですが思っていたよりすごいですね。
もっと深掘りできれば良かったのですが、今回は CI/CD がメインだったのでまたいずれ。

今回のソースコードはこちらに公開しています。
ぜひご参考までに。

PR 正常系

PR 異常系

最後までありがとうございました。

p.s いいパワポがかけるように引き続き筋トレはがんばろーと思います(´・ω・)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?