なになれ

IT系のことを記録していきます。

AWSでTerraformの実行を自動化する方法

AWSでTerraformの実行を自動化する環境を作成しました。

github.com

AWS内で閉じた形で、Terraformを使い、自動的にAWSリソースを作成する前提です。
Terraformを実行する環境はCodeBuildで用意しました。
その実現方法を紹介します。

環境作成

ディレクトリ構成

terraform-auto-apply-example直下には、自動化の環境を作成するためのtfファイル(main.tf、codebuild.tf)があります。
exampleディレクトリ内のtfファイルが自動実行の対象になります。

terraform-auto-apply-example
├── Dockerfile
├── README.md
├── buildspec.yml
├── codebuild.tf
├── docker-build.sh
├── example
│   └── main.tf
├── main.tf
├── scripts
│   ├── apply.sh
│   ├── build.sh
│   └── plan.sh
├── tfnotify-apply.yml
└── tfnotify-plan.yml

Terraformイメージの用意

CodeBuildでTerraformを実行するためにTerraformを実行可能なコンテナイメージを用意します。
Terraformだけであれば公式のイメージがあります。 今回は、GitHubやSlackにTerraformの実行結果を通知したいため、用意するコンテナイメージにtfnotifyもインストールします。

GitHub - mercari/tfnotify: A CLI command to parse Terraform execution result and notify it to GitHub

tfnotifyはTerraformの実行結果を通知するプログラムです。

以下のように、terraformとtfnotifyをインストールするDockerfileを準備します。

Dockerfile

FROM hashicorp/terraform:0.14.7

RUN wget https://github.com/mercari/tfnotify/releases/download/v0.7.0/tfnotify_linux_amd64.tar.gz -P /tmp
RUN tar zxvf /tmp/tfnotify_linux_amd64.tar.gz -C /tmp
RUN mv /tmp/tfnotify /bin/tfnotify

作成したDockerイメージをCodeBuildで利用できるようにECRにPushします。
以下のようにPushのためのシェルスクリプトを用意しました。

docker-build.sh

#!/bin/bash -ex

REGISTRY_URI="012345678912.dkr.ecr.ap-northeast-1.amazonaws.com"
CONTAINER_NAME="terraform"
IMAGE_TAG="0.0.1"
REPOSITORY_URI=${REGISTRY_URI}/${CONTAINER_NAME}

aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin $REGISTRY_URI
aws ecr create-repository --repository-name ${CONTAINER_NAME} --region ap-northeast-1 || true
docker build -t $REPOSITORY_URI:$IMAGE_TAG .
docker tag $REPOSITORY_URI:$IMAGE_TAG $REPOSITORY_URI:latest
docker push $REPOSITORY_URI:$IMAGE_TAG
docker push $REPOSITORY_URI:latest

CodeBuildの作成

Terraformを実行するCodeBuildの環境をTerraformで作成します。
まずは、CodeBuildのプロジェクト部分です。

codebuild.tf

resource "aws_codebuild_project" "terraform_auto_apply_example" {
  name         = "terraform-auto-apply-example"
  service_role = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/service-role/codebuild-terraform-auto-apply-example-service-role"

  artifacts {
    type = "NO_ARTIFACTS"
  }

  environment {
    compute_type                = "BUILD_GENERAL1_SMALL"
    image                       = "${data.aws_caller_identity.current.account_id}.dkr.ecr.ap-northeast-1.amazonaws.com/terraform:0.0.1"
    type                        = "LINUX_CONTAINER"
    image_pull_credentials_type = "SERVICE_ROLE"
  }

  source {
    type            = "GITHUB"
    location        = "https://github.com/hi1280/terraform-auto-apply-example.git"
    git_clone_depth = 1
    git_submodules_config {
      fetch_submodules = false
    }
  }
}

CodeBuildに紐付けるIAMロール(service_role)にはAdministratorAccessのIAMポリシーが付与されていることを想定しています。
TerraformでAWSリソースを作る場合には、強い権限が必要です。
environment.imageで先程の手順で作成したterraformのイメージを利用します。

GitHubからのWebhookを処理するために以下のように設定します。

codebuild.tf

resource "aws_codebuild_webhook" "terraform_auto_apply_example" {
  project_name = aws_codebuild_project.terraform_auto_apply_example.name

  filter_group {
    filter {
      pattern                 = "PULL_REQUEST_CREATED"
      type                    = "EVENT"
    }
  }

  filter_group {
    filter {
      pattern                 = "PULL_REQUEST_UPDATED"
      type                    = "EVENT"
    }
  }

  filter_group {
    filter {
      pattern                 = "PULL_REQUEST_REOPENED"
      type                    = "EVENT"
    }
  }

  filter_group {
    filter {
      exclude_matched_pattern = false
      pattern                 = "PUSH"
      type                    = "EVENT"
    }
    filter {
      exclude_matched_pattern = false
      pattern                 = "master"
      type                    = "HEAD_REF"
    }
  }
}

Pull Requestに対して変更があった場合とmasterブランチにPushされた場合にCodeBuildが実行されます。

CodeBuildのビルドスクリプト

CodeBuildのビルドスクリプト内で、Terraformを実行します。
Pull Requestを作成した時点では、Terraformのplanを実行し、masterにマージ(Push)された時にTerraformのapplyを実行するようにします。

buildspec.yml

version: 0.2
env:
  parameter-store:
    SLACK_CHANNEL_ID: /slack/channel
    SLACK_TOKEN: /slack/token
    GITHUB_TOKEN: /github_token
phases:
  build:
    commands:
      - ${CODEBUILD_SRC_DIR}/scripts/build.sh

SSMパラメータストアを利用するenv.parameter-storeで、通知先の各サービスの情報を環境変数に設定します。

以下のAWS CLIコマンドでSSMパラメータストアにパラメータを登録できます。

$ aws ssm put-parameter --name "/github_token" --type "SecureString" --value "xxxx"

tfnotifyでSlackに通知する場合には、通知先のチャンネルとトークンの情報が必要です。
GitHubに通知する場合には、トークンの情報が必要です。
各サービス向けの設定は後述します。

buildspec.ymlでは、scripts/build.shに処理を委譲します。

scripts/build.sh

#!/bin/sh -ex

if [[ ${CODEBUILD_WEBHOOK_TRIGGER} = 'branch/master' ]]; then
  ${CODEBUILD_SRC_DIR}/scripts/apply.sh
else
  ${CODEBUILD_SRC_DIR}/scripts/plan.sh
fi

ここで、planを実行するのか、applyを実行するのかを切り替えています。

applyを実行するシェルスクリプトです。

scripts/apply.sh

#!/bin/sh -ex

cd ${CODEBUILD_SRC_DIR}/example
terraform init -input=false -no-color
terraform apply -input=false -no-color -auto-approve | tfnotify --config ${CODEBUILD_SRC_DIR}/tfnotify-apply.yml apply

tfnotify --config ${CODEBUILD_SRC_DIR}/tfnotify-apply.yml applyでapply用の通知を行います。

tfnotify-apply.yml

ci: codebuild
notifier:
  slack:
    token: $SLACK_TOKEN
    channel: $SLACK_CHANNEL_ID
    bot: tfnotify
terraform:
  apply:
    template: |
      {{ .Message }}
      {{if .Result}}
      ```
      {{ .Result }}
      ```
      {{end}}
      ```
      {{ .Body }}
      ```

applyの時には、Slackにapply結果を通知するようにしています。

planを実行するシェルスクリプトです。

scripts/plan.sh

#!/bin/sh -ex

cd ${CODEBUILD_SRC_DIR}/example
terraform init -input=false -no-color
terraform plan -input=false -no-color | tfnotify --config ${CODEBUILD_SRC_DIR}/tfnotify-plan.yml plan

tfnotify --config ${CODEBUILD_SRC_DIR}/tfnotify-plan.yml planでplan用の通知を行います。

tfnotify-plan.yml

ci: codebuild
notifier:
  github:
    token: $GITHUB_TOKEN
    repository:
      owner: "hi1280"
      name: "terraform-auto-apply-example"
terraform:
  plan:
    template: |
      {{ .Title }} <sup>[CI link]( {{ .Link }} )</sup>
      {{ .Message }}
      {{if .Result}}
      <pre><code>{{ .Result }}
      </pre></code>
      {{end}}
      <details><summary>Details (Click me)</summary>

      <pre><code>{{ .Body }}
      </pre></code></details>

planの結果内容は、GitHubのPull Requestのコメントに投稿するようにしています。

GitHubの通知設定

GITHUBトークンは、アカウントのSettings -> Developer settings -> Personal access tokensの画面で発行できます。
トークンに設定するスコープは、repoとadmin:repo_hookになります。
f:id:hi1280:20210310201822p:plain:w600

Slackの通知設定

tfnotify用のSlackアプリを用意します。
f:id:hi1280:20210309225352p:plain:w600
BotsとPermissionsを設定します。
Permissionsでは、Bot User OAuth TokenというトークンがSlack用のトークンになります。
スコープには、chat:writeとchat:write.publicを設定します。
あとは、このSlackアプリを該当のワークスペースにインストールします。
該当のワークスペース内にある通知先のSlackチャンネルも用意する必要があります。

実行

該当のGitHubリポジトリでPRを作成すると、Planの結果が投稿されます。
f:id:hi1280:20210310000448p:plain:w600

PRをマージすると、SlackにApplyの結果が投稿されます。
f:id:hi1280:20210310003004p:plain:w600

まとめ

Terraformの実行を自動化する方法を紹介しました。
通知先など細かい点で工夫のしようがあると思うので、うまく活用してもらえたらと思います。
一方で、Terraform Cloudを使うとそもそも今回のようなセットアップは不要で自動化してくれるようなので、Terraform Cloudを使うのもアリだと思います。

www.terraform.io

参考