Bitbucket PipelinesでGCPに対してTerraformでインフラCI/CDする


BitbucketにはBitbucket Pipelinesというパイプラインツールが付属しており、CI/CDなど処理の自動化に利用できます。
無料プランでも、ビルド実行時間にして月間500分までは使うことができます。1

本稿では、Bitbucket PipelinesでTerraformを実行し、Google Cloud Platformの構成管理を行う方法を紹介します。
また、GitOpsによるインフラCI/CDの作業フローも紹介します。

更新履歴

  • 2020-05-03 Bitbucketのリポジトリ変数設定を変更し、bitbucket-pipelines.ymlを簡素化
  • 2020-05-02 初稿投稿

動作確認環境

準備するもの

  • Bitbucketアカウント
  • Bitbucket上のリポジトリ ... Terraformのコードとbitbucket-pipelines.ymlを入れる
  • GCPプロジェクト
    • Service Account
      • Bitbucke Pipelines内で実行するTerraformで利用する
      • Terraformで行う操作に必要なCloud IAMの権限をつける
      • 今回の構成では、少なくともGCSへの読み書き権限が必要
    • GCSバケット ... TerraformのBackendとして利用し、tfstateを保存する
    • 必要なAPIを有効化する
      • 例)GCEインスタンスの操作には、Compute Engine APIの有効化が必要

推奨開発環境

ローカルで terraform コマンドを実行して動作確認を行うため、以下のツールを使えるようにしておくことをお薦めします:

セットアップ手順

  1. Terraform用のService AccoutのService Account KeyをJSON形式で生成し、保存する
  2. 1. をBitbucketのリポジトリ変数に設定する
    • 「Repository Settings > PIPELINES > Repository variables」
    • キー名は GOOGLE_CREDENTIALS とする(※5/3 GOOGLE_CREDENTIALS_DATA から変更)
    • 値のBase64化は不要
    • ※「Secured」にチェックを入れる
  3. リポジトリのルート直下に、下のようなbitbucket-pipelines.ymlを用意する

bitbucket-pipelines.yml の設定

bitbucket-pipelines.ymlはBitbucket Pipelinesのビルド構成ファイルです。

下は、TerraformでインフラCI/CDを行うための、サンプル設定です。

bitbucket-pipelines.yml
image: hashicorp/terraform:0.12.24

# パイプライン設定で重複するステップの記述をまとめるため、YAMLアンカーを定義
definitions:
  steps:
    - step: &terraform-plan
        name: terraform plan
        script:
          - terraform init -input=false
          - terraform plan -input=false
    - step: &terraform-apply
        name: terraform apply
        script:
          - terraform init -input=false
          - terraform apply -input=false --auto-approve

# パイプライン設定
pipelines:
  # プルリクエストによって実行されるパイプライン
  pull-requests:
    '**': # 任意のブランチからのPRが対象
      - step: *terraform-plan

  # ブランチの更新によって実行されるパイプライン
  branches:
    master:
      - step: *terraform-plan
      - step:
          <<: *terraform-apply
          # terraform applyは手動で実行する
          trigger: manual

※2020-05-03 追記: 環境変数をファイルに書き出す処理を削除しました。(後述)

あまり解説の必要もないかもしれませんが、上のパイプライン設定により、以下が実現できます:

  • プルリクエストによってトリガーされるパイプラインによって、 terraform plan を実行する
    • マージ元ブランチの更新によってもトリガーされる
  • masterブランチの更新によってトリガーされるパイプラインによって、 terraform plan 及び terraform apply を順番に実行する
    • terraform apply のステップには trigger: manual を指定しているので、パイプラインの画面から手動で「Run」ボタンを押して実行します

これにより、インフラコードをGitで管理し、プルリクエストで変更をレビューしてCI/CDで適用する、といった流れのGitOpsが実現できます。
実際にどのような挙動になるかは、作業フローと合わせて後述の「デモ」の節で見ていきます。

GCPへの認証設定について(5/3編集)

作成したService Account Keyを次の2つの認証で用います:

  • BackendのGCSに対する認証
  • terraform-provider-googleによる認証

それぞれの仕様では、Service Account Keyファイルのパスを指定する環境変数が微妙に違いますが、共通して GOOGLE_CREDENTIALS を使うことができます。

(5/3追記)
Backendの方のリファレンスを見て、パス名しか指定できないと思い込んでいましたが、試したところ、Providerでの設定と同様に、JSONファイルの内容そのものを値に設定しても問題ありませんでした。
従って、上のリポジトリ変数設定を変更し、bitbucket-pipelines.ymlのパイプライン設定を簡素化することができました。

参考:

任意のブランチの更新でterraform planを実行する場合

例えば、上記のYAMLで pipelines: 以下の行を次のように書き換えると、master以外のブランチで terraform plan を実行することができます。

bitbucket-pipelines.yml
# :
#(ここまでは上のYAMLと同じ)

# パイプライン設定
pipelines:
  branches:
    '**':
      - step: *terraform-plan
    master:
      - step: *terraform-plan
      - step:
          <<: *terraform-apply
          trigger: manual

masterブランチ更新時の挙動は、先ほどと同様です。

ブランチ運用のスタイルによっては、この方式も便利だと思います。

参考ドキュメント

デモ: GitOpsによるインフラCI/CD

上で示したbitbucket-pipelines.ymlの最初の例(プルリクエスト駆動)を設定した状態で、インフラCI/CDのワークフローがどのようになるかを見ていきます。

0. 下準備

先に示した事前準備とセットアップ手順に加えて、以下を行っています:

  • Terraformでテスト用のService Accountを作成するため、GCPプロジェクトでCloud Resource Manager APIを有効化しています
  • リポジトリの「CHAT NOTIFICATIONS」設定により、Slack通知を有効にしています

5/3追記: デモでのパイプライン設定の差異について

流れは変わりませんが、念のために注記しておきます。

5/3にリポジトリ変数を変更してbitbucket-pipelines.ymlを簡素化しました。
以下のデモでは、次の2点が異なっています:

  • リポジトリ変数に設定したService Account Keyのキー名が GOOGLE_CREDENTIALS ではなく、 GOOGLE_CREDENTIALS_DATA となっている -bitbucket-pipelines.ymlのアンカー &terraform-plan, &terraform-apply それぞれで定義したscriptの先頭に以下の2行がある
         script:
+          - echo "${GOOGLE_CREDENTIALS_DATA}" > service-account-key.json
+          - export GOOGLE_APPLICATION_CREDENTIALS=service-account-key.json
           - terraform init -input=false
           :

1. topicブランチを作成し、コード変更をプッシュ

  1. topic/add-test-sa というブランチを作成します
  2. .tfファイルに google_service_account リソースを定義します
    • 実はこのコードには誤りがあるので、この後のパイプラインは失敗します
  3. Bitbucketにブランチをプッシュします。
% git checkout -b topic/add-test-sa
% $EDITOR main.tf
% git diff
diff --git a/main.tf b/main.tf
index b6bb61a..d5c0613 100644
--- a/main.tf
+++ b/main.tf
@@ -31,3 +31,7 @@ resource "google_project_iam_member" "sa_terraform" {
   role    = "roles/owner"
   member  = "serviceAccount:${google_service_account.terraform.email}"
 }
+
+resource "google_service_account" "test" {
+  account_id = "test"
+}
% git add .
% git commit -m "Add test service account"
% git push origin topic/add-test-sa

master以外のブランチにはトリガーを設定していないので、ここではパイプラインは実行されません。

2. プルリクエストの作成

Bitbucket画面上でプルリクエストを作成します。

bitbucket-pipelines.ymlの pull-requests で設定したトリガーにより、 terraform plan のパイプラインが実行されます。

3. ビルド結果(失敗)の確認

Slackにビルド結果が通知されました。

「Pipeline #37」のリンクをクリックして、ビルドログを確認します。

google_service_account.test.account_id のバリデーションでエラーになっていることがわかりました。

4. コードを修正して再プッシュ

google_service_account.test.account_id を修正して、ブランチを再プッシュします。

% $EDITOR main.tf
% git diff
diff --git a/main.tf b/main.tf
index d5c0613..fd9a3ec 100644
--- a/main.tf
+++ b/main.tf
@@ -33,5 +33,5 @@ resource "google_project_iam_member" "sa_terraform" {
 }

 resource "google_service_account" "test" {
-  account_id = "test"
+  account_id = "test01"
 }
% git add .
% git commit -m "Fix test service account id"
% git push origin topic/add-test-sa

先ほどと同様に、bitbucket-pipelines.ymlの pull-requests で設定したトリガーにより、 terraform plan のパイプラインが実行されます。

5. ビルド結果(成功)の確認

Slackにビルド結果が通知されました。

「fixed」となっており、成功したようです。
パイプラインのログも確認しておきましょう。

terraform plan の出力結果が意図通りになっていることが確認できました。

6. PR画面の確認とマージ

プルリクエストの「活動履歴」タブで、ビルドを含む履歴を確認することができます。

問題ないので、マージします。

7. masterブランチ上でのパイプライン実行

bitbucket-pipelines.ymlの branches.master で設定したトリガーにより、 terraform plan + terraform apply のパイプラインが実行されます。

Slack通知によってmasterブランチでパイプラインが実行されたことがわかります。

「Pipeline #41」のリンクをクリックして、パイプライン画面に遷移します。2

今回のパイプライン設定では、ここでもう一度 terraform plan の結果を確認することができます。

terraform apply のステップには trigger: manual を設定しているので、人間が手動で確認して実行します。
問題がなければ、「Run」ボタンを押して、 terraform apply を実行します。

terraform apply も問題なく成功しました

まとめと所感

Bitbucket PipelinesでTerraformを実行し、GCP環境のインフラCI/CDを行う方法と、GitOpsによる作業フローのイメージを紹介しました。

今回、Bitbucket Pipelinesについて全く知らない状態から色々と調べて構築してみたのですが、普通に使えるCIサービスだなと思いました。
最初、なかなか求める情報が見つけられず、「機能が乏しいのではないか」などと思ったりしてしまったのですが、振り返ってみれば、必要十分な機能は揃っているように思います。また、必要な情報はだいたい公式ドキュメントにまとまっていました。

ここ1〜2年で、GitHubでもプライベートリポジトリの作成が無料化されたり、GitHub Actionsというパイプラインツールが登場したので、敢えてBitbucketを使う理由は減っていると思いますが、Bitbucketでも特に困ることはないなと思いました。

脚注


  1. https://bitbucket.org/product/ja/pricing 

  2. ※別のパイプライン実行が間に挟まったので、直前から番号が飛んでいます。