TerraformのコードをCircleCI経由でデプロイさせる

2016.08.08

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

こんにちは、中山です。

何番煎じか微妙なところではあるのですが、今回TerraformとCircleCIを連携させてみたので一本エントリを書きます。TerraformのコードをGitHubで管理しておき、プッシュしたらCircleCIでTerraform実行というパターンです。

構成図

今回のエントリには直接関係しないのですが、Terraformで作成されるシステムは以下の通りです。一般的なWebシステムによくある構成にしてみました。

aws

コード

GitHubに上げておきました。ご自由にお使いください。

ディレクトリ構造

以下の通りです。

terraform-circleci-demo/
├── Makefile
├── README.md
├── app.tf
├── bastion.tf
├── circle.yml
├── cloudfront.tf
├── db.tf
├── elasticache.tf
├── elb.tf
├── envs
│   ├── dev
│   │   ├── main.tf
│   │   └── variables.tf
│   ├── prd
│   │   ├── main.tf
│   │   └── variables.tf
│   └── stg
│       ├── main.tf
│       └── variables.tf
├── key_pair.tf
├── keys
│   └── site_key.pub
├── modules
│   ├── dns
│   │   ├── dns.tf
│   │   └── variables.tf
│   └── iam
│       ├── iam.tf
│       ├── outputs.tf
│       ├── policies
│       │   └── ec2_assume_role_policy.json
│       └── variables.tf
├── network.tf
├── outputs.tf
├── s3.tf
├── security_group.tf
├── sns.tf
├── user_data
│   ├── app_cloud_config.yml
│   └── bastion_cloud_config.yml
└── variables.tf

ディレクトリ構造の解説については以下のエントリをご覧ください。

コードの解説

circle.yml

machine:
  environment:
    S3_BUCKET: terraform-circleci-demo
    AWS_DEFAULT_REGION: ap-northeast-1
    TERRAFORM_VER: 0.7.0

dependencies:
  override:
    - |
      if [[ ! -f ~/.local/bin/terraform ]]; then
        mkdir -p ~/.local/bin
        cd ~/.local/bin
        wget "https://releases.hashicorp.com/terraform/${TERRAFORM_VER}/terraform_${TERRAFORM_VER}_linux_amd64.zip"
        unzip *.zip
        rm *.zip
      fi
  cache_directories:
    - ~/.local/bin


test:
  override:
    - |
      if [[ "${CIRCLE_BRANCH}" =~ (release/)?dev/? ]]; then
        make remote-enable ENV=dev
        make terraform ENV=dev ARGS="get -update"
        make terraform ENV=dev ARGS=plan
      elif [[ "${CIRCLE_BRANCH}" =~ (release/)?stg/? ]]; then
        make remote-enable ENV=stg
        make terraform ENV=stg ARGS="get -update"
        make terraform ENV=stg ARGS=plan
      elif [[ "${CIRCLE_BRANCH}" =~ (release/)?prd/?|master ]]; then
        make remote-enable ENV=prd
        make terraform ENV=prd ARGS="get -update"
        make terraform ENV=prd ARGS=plan
      fi

deployment:
  development:
    branch: release/dev
    commands:
      - make terraform ENV=dev ARGS="get -update"
      - make terraform ENV=dev ARGS=apply
      - make terraform ENV=dev ARGS="remote push"
  staging:
    branch: release/stg
    commands:
      - make terraform ENV=stg ARGS="get -update"
      - make terraform ENV=stg ARGS=apply
      - make terraform ENV=stg ARGS="remote push"
  production:
    branch: release/prd
    commands:
      - make terraform ENV=prd ARGS="get -update"
      - make terraform ENV=prd ARGS=apply
      - make terraform ENV=prd ARGS="remote push"

circle.yml の詳細についてはドキュメントを参照してください。以下では利用している設定内容について解説します。

  • machine

環境変数の設定をしています。 circle.yml 内で多用する文字列などをここで定義しておきます。

  • dependencies

テスト前に必要なソフトウェアをインストールさせる処理を書きます。Terraformが必要なのでそのインストールをしています。

  • test

テストの処理を記述します。今回は plan サブコマンドが正常に実行されるかどうかをもとにテストの正常性を判断しています。Remote Stateを有効化しておき、 get -update でモジュールを最新の状態にします。最後に plan サブコマンドでテストを実行します。

また、CircleCIは $CIRCLE_BRANCH でプッシュされたブランチ名を参照可能です。この変数を利用して環境毎にテスト方法を分けています。

  • deployment

デプロイの処理を記述します。 branch でブランチ名を、 commands で実行内容を記述します。 get -update でTerraformのモジュールを最新の状態にし、 apply サブコマンドでデプロイ、 remote push コマンドで変更されたtfstateをS3にアップロードしています。

実行方法

以下では本番環境(prd)を利用する際の手順です。すでにGitHub上に今回のコードが存在し、CircleCIのアカウントがあることを前提としています。

S3バックエンドの有効化

Terraformのtfstateはローカルとリモート(CircleCI)で共有しておく必要があります。このファイルと実際の状態を一致させることで差分を検知できるからです。TerraformにはRemote Stateという方法でリモートのストレージサービスにtfstateを保管することが可能です。今回はS3バックエンドを利用します。

まず、tfstate保存用のバケットを作成します。

# 作成
$ aws s3 mb s3://<bucket-name>
# 確認
$ aws s3 ls

続いてMakefileのバケット名の箇所を修正します。 <bucket-name> を任意のものに変更してください。

BUCKET_NAME = <bucket-name>
REGION = ap-northeast-1
CD = [ -d envs/${ENV} ] && cd envs/${ENV}
ENV = $1
ARGS = $2
<snip>

また、 circle.yml でもバケット名を定義しているので、修正する必要があります。

machine:
  environment:
    S3_BUCKET: <bucket-name>
    AWS_DEFAULT_REGION: ap-northeast-1
    TERRAFORM_VER: 0.7.0

最後に remote サブコマンドでRemote Stateを有効化します。コマンドが長いのでMakefileでタスク化しています。

$ make remote-enable ENV=prd
Initialized blank state with remote state enabled!
Remote state configured and pulled.

上記コマンドを実行すると envs/prd/.terraform というディレクリが作成され、以降 terraform.tfstate はそこに移動します。

CircleCIとGitHubを紐付ける

CircleCIにログイン後、GitHub上のリポジトリを紐付けます。

「ADD PROJECTS」をクリック後、自分のGitHubアカウント名をクリックし、該当のリポジトリの「Build project」を選択します。

circleci-1

続いてAWSのクレデンシャルを登録します。「Project Settings」をクリックしてください。

circleci-2

「AWS Permissions」を選択後、「Access Key ID」と「Secret Access Key」を埋めてください。最後に「Save AWS Keys」で保存します。

circleci-3

ブランチの作成

本番環境についてはmasterブランチがテスト用、release/prdブランチがデプロイ用です。事前に両方のブランチを作成しておきます。masterはできていると思うのでリリース用ブランチを作成してください。

$ git branch release/prd
$ git branch
* master
  release/prd

コードの修正とプッシュ

準備が整ったので実際にコードを修正してみます。今回本番環境ではアプリサーバを2台ASGで立てていますが、お金がないという体で1台だけにしましょう。該当のファイル envs/prd/variables.tf を以下のように修正してください。

diff --git a/envs/prd/variables.tf b/envs/prd/variables.tf
index b38bf6b..5741df0 100644
--- a/envs/prd/variables.tf
+++ b/envs/prd/variables.tf
@@ -23,9 +23,9 @@ variable "instance_types" {

 variable "asg_config" {
   default = {
-    "min"     = 2
-    "max"     = 2
-    "desired" = 2
+    "min"     = 1
+    "max"     = 1
+    "desired" = 1
   }
 }

ローカルのmasterブランチでコミット後、リモートのmasterにプッシュします。

$ git add envs/prd/variables.tf
$ git commit -m 'Change asg num'
[master 4b20190] Change asg num
 1 file changed, 3 insertions(+), 3 deletions(-)
$ git push origin master

するとGitHubからCircleCIに通知がいき、テストが開始されます。関係ないところでも差分出ていますが、無視してください。。。

circleci-4

masterへのプッシュなのでこの時点ではテストのみ、つまり plan サブコマンドの実行しかされていません。

デプロイの実施

テストが通ったので最後にデプロイをしてみましょう。ブランチを切り替えてプッシュします。

$ git checkout -b release/prd
Switched to a new branch 'release/prd'
$ git merge --no-ff master
Merge made by the 'recursive' strategy.
 envs/prd/variables.tf | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
$ git push origin release/prd

デプロイに成功するとCircleCI上で以下のような画面が表示されると思います。

circleci-5

まとめ

いかがでしょうか。

複数人のチームでTerraformのコードを管理する場合はCI経由で実行しないと運用が破綻しそうだなと思いました。今までローカルでの実行が多かったのですが、今後はこういったCIと連携させていこうと思います。

本エントリがみなさんの参考になれば幸いです。

参考リンク

本エントリを書く際に、下記ブログを大変参考にさせていただきました。ありがとうございました。