CircleCIでTerraformを実行してAWSにリソースを作成する。

July 29, 2020

CircleCIで $ terraform [fmt or plan or apply or destroy] を自動化したのでまとめていきたいと思います。

目次
1.Terraformが実行できるDockerイメージを作成する。
2.【CircleCI】プロジェクトを作成する。
3.【CircleCI】AWS認証情報を環境変数に設定する。
4.【CircleCI】Slack通知を設定する。
5.【CircleCI】Terraformが実行できるように設定ファイルを編集する。
6.【CircleCI】実行結果を確認する。
7.まとめ

Terraformが実行できるDockerイメージを作成する。

今回、TerraformはDokcerイメージから実行します。そのイメージを作成するのに必要な Dockerfiledocker-compose.yml を解説していきます。

ファイル構成

.circleci
Dockerfile
docker-compose.yml
src/
   |-- terraform/   # ⇐ここにTerraformコードを入れていく。
                |-- iam/
                       |-- env/dev/main.tf
                       |-- modules/main.tf

Dockerfile

FROM ubuntu:latest

# AWS認証情報を環境変数。(docker-compose.yml経由で渡す。)
ARG REGION
ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY

# 作業用ディレクトリ
WORKDIR /var/tmp

# 各種ツールをインストール。
RUN apt-get update && \
    apt-get install -y wget \
                       unzip \
                       less \
                       jq \
                       ssh &&\
    # Terraformのセットアップ
    wget "https://releases.hashicorp.com/terraform/0.12.24/terraform_0.12.24_linux_amd64.zip"  && \
    unzip terraform_0.12.24_linux_amd64.zip && \
    mv terraform /usr/local/bin/ && \

    # AWS CLIのセットアップ。
    wget "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"  && \
    unzip awscli-exe-linux-x86_64.zip && \
    ./aws/install  && \

    # 不要なZIPファイルを削除。
    rm -f terraform_0.12.24_linux_amd64.zip \
          awscli-exe-linux-x86_64.zip

# AWS認証ファイルの準備。
COPY config /root/.aws/config
COPY credentials /root/.aws/credentials

# AWS認証ファイルの値を正式なキーに変換。
RUN sed -i "s/REGION/${REGION}/g" /root/.aws/config && \
    sed -i "s/AWS_ACCESS_KEY_ID/${AWS_ACCESS_KEY_ID}/g" /root/.aws/credentials && \
    sed -i "s/AWS_SECRET_ACCESS_KEY/${AWS_SECRET_ACCESS_KEY}/g" /root/.aws/credentials

docker-compose.yml

version: '2'
services:
  app:
    build:
      context: .
      args:
        # AWS認証情報をCircleCIに設定した環境変数から設定する。
        - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
        - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
        - REGION=${REGION}
    # 作成されるイメージ名を設定する。
    image: terraform_for_aws
    volumes:
      - ./src:/infra

【CircleCI】プロジェクトを作成する。

それでは、CircleCIの設定を行っていきます。まずは、Terraformを実行するためのプロジェクトを作成からです。

CircleCI にログインして下記を行う。

① 実行するリポジトリの「Set Up Project」をクリックして、プロジェクトを作成する。 circleci terraform 001

② 「Add Config」をクリックして、CircleCIの設定ファイルを自動で作成する。 circleci terraform 002

③ パイプラインが自動実行される。(特に気にしなくて大丈夫です。) circleci terraform 003

以上でプロジェクトの作成は完了です。


【CircleCI】AWS認証情報を環境変数に設定する。

TerraformでAWSリソースを作成するために、AWS認証情報をDockerイメージに埋め込む必要があります。
今回は、CircleCIの環境変数から設定するため、認証情報を登録していきます。

① プロジェクトの設定を開く。 circleci terraform 004

② AWS認証情報を環境変数に設定する。
設定する変数は全部で3つ。

- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- REGION

circleci terraform 005

以上環境変数の設定が完了です。ここで設定した値が、Dockerイメージのビルド時に自動的に組み込まれるようになります。


【CircleCI】Terraformが実行できるように設定ファイルを編集する。

以下が実行する一連の処理になります。

  1. Dockerイメージをビルドする。
  2. terraform fmt & plan を実行する。(全てのブランチで実行する。)
  3. terraform apply を実行する。(masterブランチだけで実行する。)
  4. terraform destroy を実行する。(masterブランチだけで実行する。また、承認しないと実行できないようにする。)

それではコードを解説していきます。

.circleci/config.yml

version: 2.1

orbs:
  # ジョブの結果をSlackに通知したいため。
  slack: circleci/slack@3.4.2

jobs:
  # Dockerイメージをビルドするジョブ。
  build:
    # CircleCIのデフォルトイメージを使用するため、「true」に設定する。
    # 今回は、デフォルトイメージの中でDockerイメージを作成していく。
    machine: true
    steps:
      # ソースのチェックアウト。
      - checkout

      # Dockerイメージをキャッシュからリストアする。
      - restore_cache:
          # キャッシュは、「docker-compose.yml」、「Dockerfile」のそれぞれのチェックサム値で保存されている。そのため、もしそれぞれのファイルが変更されているとチェックサム値も変更されてしまい、リストアされず後続のビルド処理が走ることになる。
          key: docker-{{checksum "docker-compose.yml"}}-{{checksum "Dockerfile"}}
          paths: ~/images.tar

      # キャッシュからリストアされていない場合に、Dockerのイメージファイルを作成する。
      - run:
          name: docker image build & save
          # 1.AWS認証情報をイメージ内で使用できる環境変数に設定する。
          # 2.イメージファイルがキャッシュからリストアチェックされていないか確認する。存在していない場合はイメージを作成して、それをファイルに保存する。
          command: |
            echo "export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" >> $BASH_ENV
            echo "export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" >> $BASH_ENV
            if [ ! -f ~/images.tar ];then
              docker-compose build
              docker save terraform_for_aws -o ~/images.tar terraform_for_aws:latest
            fi

      # DockerのイメージファイルをCircleCIにキャッシュする。
      - save_cache:
          key: docker-{{checksum "docker-compose.yml"}}-{{checksum "Dockerfile"}}
          paths: ~/images.tar

  # terraform fmt & plan を実行するジョブ。
  test:
    machine: true
    steps:
      - checkout

      - restore_cache:
          key: docker-{{checksum "docker-compose.yml"}}-{{checksum "Dockerfile"}}
          paths: ~/images.tar

      # Dockerイメージファイルからイメージをロードする。
      - run: docker load -i ~/images.tar

      # AWS認証情報を環境変数に設定する。
      - run:
          name: set aws credential to BASH_ENV
          command: |
            echo "export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" >> $BASH_ENV
            echo "export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" >> $BASH_ENV

      - run:
          name: terraform fmt
          command: docker-compose run --rm app /bin/bash -c "terraform fmt -recursive /infra"

      - run:
          name: terraform init
          command: |
            docker-compose run --rm app /bin/bash -c "cd /infra/terraform/iam/env/dev && terraform init"

      - run:
          name: terraform plan iam
          command: docker-compose run --rm app /bin/bash -c "cd /infra/terraform/iam/env/dev && terraform plan"

      # Slackへの通知。
      - slack/status:
          success_message: "[SUCCESS]: ${CIRCLE_PROJECT_REPONAME}/${CIRCLE_BRANCH} terraform plan が成功しました。"
          failure_message: "[ERROR]: ${CIRCLE_PROJECT_REPONAME}/${CIRCLE_BRANCH} terraform plan が失敗しました。"
          # SlackのWebHookのURLを環境変数から設定する。
          webhook: ${SLACK_WEBHOOK}

  # terraform apply を実行するジョブ。
  apply:
    machine: true
    steps:
      - checkout
      - restore_cache:
          key: docker-{{checksum "docker-compose.yml"}}-{{checksum "Dockerfile"}}
          paths: ~/images.tar

      - run: docker load -i ~/images.tar

      - run:
          name: set aws credential to BASH_ENV
          command: |
            echo "export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" >> $BASH_ENV
            echo "export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" >> $BASH_ENV
      - run:
          name: terraform init
          command: |
            docker-compose run --rm app /bin/bash -c "cd /infra/terraform/iam/env/dev && terraform init"
      - run:
          name: terraform apply
          command: docker-compose run --rm app /bin/bash -c "cd /infra/terraform/iam/env/dev && terraform apply -auto-approve"

      - slack/status:
          success_message: "[SUCCESS]: ${CIRCLE_PROJECT_REPONAME}/${CIRCLE_BRANCH} terraform apply が成功しました。"
          failure_message: "[ERROR]: ${CIRCLE_PROJECT_REPONAME}/${CIRCLE_BRANCH} terraform apply が失敗しました。"
          webhook: ${SLACK_WEBHOOK}

  # terraform apply を実行するジョブ。
  destroy:
    machine: true
    steps:
      - checkout
      - restore_cache:
          key: docker-{{checksum "docker-compose.yml"}}-{{checksum "Dockerfile"}}
          paths: ~/images.tar

      - run: docker load -i ~/images.tar

      - run:
          name: set aws credential to BASH_ENV
          command: |
            echo "export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}" >> $BASH_ENV
            echo "export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}" >> $BASH_ENV
      - run:
          name: terraform init
          command: |
            docker-compose run --rm app /bin/bash -c "cd /infra/terraform/iam/env/dev && terraform init"
      - run:
          name: terraform destroy
          command: docker-compose run --rm app /bin/bash -c "cd /infra/terraform/iam/env/dev && terraform destroy -auto-approve"

      - slack/status:
          success_message: "[SUCCESS]: ${CIRCLE_PROJECT_REPONAME}/${CIRCLE_BRANCH} terraform destroy が成功しました。"
          failure_message: "[ERROR]: ${CIRCLE_PROJECT_REPONAME}/${CIRCLE_BRANCH} terraform destroy が失敗しました。"
          webhook: ${SLACK_WEBHOOK}

# 上記で設定してきたジョブの順番を設定していく。
workflows:
  terraform:
    jobs:
      - build
      - test:
          requires:
            - build
      - apply:
          requires:
            - test
          # masterブランチのみで実行する。
          filters:
            branches:
              only:
                - master
      # destroyジョブは承認された場合のみ実行するための設定。(※1)
      - hold:
          type: approval
          requires:
            - apply
          # masterブランチのみで実行する。
          filters:
            branches:
              only:
                - master
      - destroy:
          requires:
            - hold
          # masterブランチのみで実行する。
          filters:
            branches:
              only:
                - master

※1 一時的にジョブを停止する方法をちょっと解説

① ワークフローがholdジョブで一時停止する。 circleci terraform 007

② 「Approve」することで後続ジョブを実行する。 circleci terraform 008

③ 後続ジョブを実行される。 circleci terraform 009


【CircleCI】実行結果を確認する。

下記がブランチ(画像はmasterブランチ)にpushされ、CircleCIのジョブが実行された時の画像です。

circleci terraform 006


まとめ

まだまだ改善できますが、最低限の実装はできたかなーと思います。これをベースに色々カスタマイズしていきたいと思います。