Terraform どこで実行していますか?

Isao Shimizu
MIXI DEVELOPERS
Published in
11 min readDec 22, 2018

--

ミクシィでの仕事も気づいたら8年目を迎えていました。キャンプとクラフトビールが大好きです。普段はSREをやっている清水と申します。本記事は、ミクシィグループ Advent Calendar 2018 23日目の記事です。

私は、2018年2月から「家族アルバム みてね」というサービスでSREを担当しています。それまでは、約3年ほど「モンスターストライク」のSREを担当していましたが、みてねのSREに変わったことで本当に大きな変化がありました。SREというロール自体は変わりませんが、サービスの性質、規模、組織、インフラ、アーキテクチャ、トラフィックのパターンなどにおいて大きな違いがありました。それぞれの特性を理解、把握し、日々多くの課題解決に取り組んでいます。

9月のMeetupでは「みてねSREチームの取り組み」と、約半年間みてねのSREとしてやってきたことを発表しました。また、10月のAWS Dev Day Tokyo 2018 Lightning Talk大会では「GitHub Flowで作るAWSインフラストラクチャ」という内容で、聴衆の皆さまの投票により、幸運にもベストスピーカー賞を頂くことができました。本当にありがたいことでした。

AWS Dev Day Tokyo 2018 Lightning Talk大会

本記事では、この「GitHub Flowで作るAWSインフラストラクチャ」で発表した内容の一部(特に重要だと考えている箇所)と、スライドに書ききれなかった内容について触れてみたいと思います。

Terraformはどの環境で実行すべきか

Terraformの事例は今や数多くあり、ユーザーも世界規模で相当数となっていることが想像できますが、Terraformの実行環境についても様々なパターンが存在するようです。私が見聞きした範囲では、

  • 手元のPC環境
  • EC2インスタンス上
  • Jenkins
  • CircleCI

というのがよく聞く実行環境でした。また、手動による実行環境においては、Terraformの実行者が複数存在するケースもよくあるパターンです。

手元のPC環境での実行の場合、強力な権限を持った認証情報をPC内に持って実行するといったケースもあり、これではセキュリティ上安全とは言い難い状況と言えるでしょう。また、手作業でTerraformのコマンドを実行することは、作業者の手順を増やし、人為的なミスを起こしやすくなります。

対して、JenkinsやCircleCIでのTerraformの実行は、GitHubと連携して実行を自動化できるため、手間という面では大きく改善ができます。ただし、Jenkinsは構築や運用の手間が大きいという課題があります(SPOFな構成や、Pluginのアップデートが放置されたりといった問題も多く見てきました)。またJenkinsやCircleCIでは、AWSを操作するための強力な権限を持った認証情報(アクセスキー、シークレットキー)を発行して、設定をしておく必要があります。

AWS Well-Architected FrameworkのIAMのセクションでは、認証情報のローテーションがベストプラクティスであると謳われていることもあり、ローテーションの手間を考えると、できればアクセスキーは発行したくないのが本音と言えるでしょう。

これらのすべての課題を100%解決することは難しいかもしれませんが、バランスが取れた解決方法の1つとしてAWS CodeBuildを使う方法があります。AWS CodeBuildの基本的な話はここでは触れませんが、どのように連携させるかを解説します。

Terraform on AWS CodeBuild

Terraform実行用に、AWS CodeBuildのプロジェクトを作る際、以下の点が重要なポイントとなります。

  • イメージは hashicorp/terraform:0.11.10 といったように、Terraform公式のDockerイメージをバージョンタグをつけて使います。 latest タグの利用は、不意にアップデートされた際に互換性の問題が起きる可能性があるために避けています。
  • Terraformの実行に必要なIAMロールを作っておき、プロジェクトのサービスロールに指定します。これによりアクセスキーを発行する必要がありません。

Terraform自体が持つworkspaceの機能は使わずに、環境はディレクトリごとに分けて、tfファイルを管理します。BackendはAmazon S3を利用しています。

ディレクトリの例(簡略化しています)

terraform (GitHubレポジトリ)
|-- common (IAMやRoute 53など共通の設定)
|-- service_dev
|-- service_prod
|-- service_stg
|-- scripts (Terraform実行用のスクリプト格納用)
|-- bin (tfnotifyなどのツール格納用)
|-- buildspec.yml
|-- .tfnotify.yml

tfnotifyについては、私のスライド公式レポジトリを参照して下さい。レポジトリ直下に置く.tfnotify.ymlはtfnotify向けの設定ファイルで、GitHubのトークンはAWS CodeBuildプロジェクト側の環境変数から拾うように設定しています。

tfnotifyのAWS CodeBuild対応は、

のとおり、先日Pull Requestを作り、9無事マージ、対応されました。

AWS CodeBuildプロジェクト向けのbuildspec.ymlはシンプルにシェルスクリプトに渡す形で記述します。

version: 0.2phases:  pre_build:    commands:      - /bin/sh scripts/setup.sh  build:    commands:      - /bin/sh scripts/run.sh

run.shの中身は以下のとおりです。AWS CodeBuildとGitHubの連携時、Pull Requestが作られた際に pr/ で始まる文字列が CODEBUILD_SOURCE_VERSION 環境変数に渡されることを利用しています(Pull Requestに追加でPushされるときも同様)。Pull Requestがマージされた際は CODEBUILD_SOURCE_VERSION はコミットハッシュが渡されてきます。

#!/bin/sh -xeif echo $CODEBUILD_SOURCE_VERSION | grep -q 'pr/'; then  /bin/sh scripts/plan.shelse  /bin/sh scripts/apply.shfi

各スクリプトではgitコマンドで差分のあったディレクトリのみを抽出して、Terraformの実行対象とします。scriptsやbinディレクトリはTerraformの実行対象からは除外します。plan実行時は以下のとおりです。

#!/bin/sh -xeexport EXCLUDE_DIRS='^\.|scripts|bin'DIRS=$(git --no-pager diff origin/master..HEAD --name-only | xargs -I {} dirname {} | egrep -v "$EXCLUDE_DIRS" | uniq)if [ -z "$DIRS" ]; then  echo "No directories for plan."  exit 0fifor dir in $DIRSdo  (cd $dir && terraform init -input=false -no-color)  (cd $dir && terraform plan -input=false -no-color | ../bin/tfnotify --config ../.tfnotify.yml plan --message "$dir")done

apply実行時は以下の通り。若干力技ではありますが git log からPull Requestの番号を取得し CODEBUILD_SOURCE_VERSIONpr/番号 をセットしています。このあたりはAWS CodeBuild側でもう少し充実した環境変数のサポートを期待したいところです。

#!/bin/sh -xeexport EXCLUDE_DIRS='^\.|scripts|bin'DIRS=$(git --no-pager diff HEAD^..HEAD --name-only | xargs -I {} dirname {} | egrep -v "$EXCLUDE_DIRS" | uniq)if [ -z "$DIRS" ]; then  echo "No directories for apply."  exit 0fiexport CODEBUILD_SOURCE_VERSION=$(git log --merges --oneline --reverse --ancestry-path HEAD^..HEAD | grep 'Merge pull request #' | head -n 1 | cut -f5 -d' ' | sed 's/#/pr\//')for dir in $DIRSdo  (cd $dir && terraform init -input=false -no-color)  (cd $dir && terraform apply -input=false -no-color -auto-approve | ../bin/tfnotify --config ../.tfnotify.yml apply --message "$dir")done

これらのサンプルコードは、あくまでもお使いの環境に合わせて変更し、動作確認を十分におこなった上でご利用ください(もっと良いコード、コマンドがあればぜひご指摘ください!)

このあたりの実装にあたっては メルカリ Microservices Team による Terraform 運用とその中で開発したOSSの紹介 の記事を大変参考にさせていただきました。貴重なノウハウの共有に感謝です。

GitHubレポジトリ側の設定

TerraformのコードをGitHubでPull Requestでplan実行、マージでapply実行をする上で大事な設定があります。

Branch protection ruleの画面で以下のチェックをオンにする。

  • Require pull request reviews before merging: マージする前にレビューを必須とする。いわゆるセルフマージを防ぎ、コードの誤りによるトラブルを最小限にできます。
  • Require status checks to pass before merging: マージする前にステータスのチェックを必須とする。ここでいうステータスのチェックは、AWS CodeBuildでのplan実行が成功したかどうかです。エラーが発生した場合はマージできなくなります。
  • Require branches to be up to date before merging: マージする前にブランチが最新であること。例えば、他の開発メンバーが別のPull Requestを作ってそれが先にマージされた場合、自分のブランチが古くなり、Terraformが実行された時に、先にマージされた差分がdestroyされたり変更される問題が起きないようにすることできます。

まとめ

Terraformでインフラをコードで管理できるようになっただけではなく、AWS CodeBuildを使うことでセキュリティとレビューのフロー確保しながら、Terraformの実行を自動化することができるようになりました。AWSをお使いの場合の1つの選択肢となれば幸いです。

ただ、AWS以外のクラウドを併用している場合は、AWS CodeBuildを使ったとしても、結局他のクラウドの認証情報を持つ必要が出てくるため、CircleCIなどのCI環境でTerraformを実行するというのも有効な選択肢の1つだと思います。AWS STSを使って一時的な認証情報を使って実行するという手法も考えられます。このあたりは、今後もより適した手法をさらに模索していきたいと考えています。

本記事に書いたことが少しでもお役に立てたら幸いです。また、もっと良い実行環境やワークフローなどの事例があればぜひ教えてください(もしよければ美味しいビールを飲みながらでも話しましょう)。

それでは2018年も残すところ数日。良い年をお迎えください。

--

--