AWSでTerraformの実行を自動化する方法
AWSでTerraformの実行を自動化する環境を作成しました。
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になります。
Slackの通知設定
tfnotify用のSlackアプリを用意します。
BotsとPermissionsを設定します。
Permissionsでは、Bot User OAuth TokenというトークンがSlack用のトークンになります。
スコープには、chat:writeとchat:write.publicを設定します。
あとは、このSlackアプリを該当のワークスペースにインストールします。
該当のワークスペース内にある通知先のSlackチャンネルも用意する必要があります。
実行
該当のGitHubリポジトリでPRを作成すると、Planの結果が投稿されます。
PRをマージすると、SlackにApplyの結果が投稿されます。
まとめ
Terraformの実行を自動化する方法を紹介しました。
通知先など細かい点で工夫のしようがあると思うので、うまく活用してもらえたらと思います。
一方で、Terraform Cloudを使うとそもそも今回のようなセットアップは不要で自動化してくれるようなので、Terraform Cloudを使うのもアリだと思います。