👋

githubのCIとterraformを使ってGCPにデプロイする

2023/05/19に公開

はじめに

業務でよくterraformを使っている。
0から構築する場合、どうしているのかが気になったので、整理しつつ実装した。

流れ

  1. GithubActionの設定
  2. tfstate用のバケットを準備
  3. tfstateファイルの場所の設定
  4. デプロイとロックについて

ディレクトリ構成は下記のようになる

├── .github
│   └── workflows
│       └── run.yaml
├── .gitignore
├── accounts.tf
└── backend.tf

GithubActionの設定

GCPのIAMからサービスアカウントの作成・サービスアカウントキーの発行を行う。

その後、サービスアカウントのクレデンシャルをgithubのsecretに登録する。

GithubActionの設定を書く。

.github/workflows/run.yaml
name: 'Deploy'

on:
  push:
    branches:
    - main
  pull_request:

jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    defaults:
      run:
        shell: bash
    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v1

    - name: Terraform Init
      run: terraform init
      env:
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}

    - name: Terraform Format
      run: terraform fmt -check

    - name: Terraform Plan
      run: terraform plan
      env:
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
    
    - name: Terraform Apply
      if: github.ref == 'refs/heads/main' && github.event_name == 'push'
      run: terraform apply -auto-approve
      env:
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}

tfstate用のバケットを準備

tfstateファイルはterraformが管理しているリソースの現在の状態を表すファイルになる。

GCSの情報を設定する。

account.tf
provider "google" {
  project = "PROJECT_ID"
}

terraform の.tfstate用のバケットを作成する。

backend.tf
resource "google_storage_bucket" "RESOURCE_NAME" {
  name     = "BUCKET-NAME"
  location = "us-central1"
  storage_class = "REGIONAL"

  versioning {
    enabled = true
  }

  lifecycle_rule {
    action {
      type = "Delete"
    }
    condition {
      num_newer_versions = 5
    }
  }
}

そして、mainにpushを行うと、CIが走る。

CIによって、バケットが作成されている

tfstateファイルの場所の設定

terraformのstateファイルの保存場所を指定する。

account.tf
provider "google" {
  project = "PROJECT_ID"
}

+ terraform {
+   backend "gcs" {
+    bucket = "BUCKET-NAME"
+    prefix = "terraform/state"
+   }
+ }

terraform importを使って、既存リソースをterraformに取り込むために、githubActionに追記する・

バケットの作成とtfstateファイルの指定は同時にはできず、まずバケットを作成する必要がある。
その際に、作成されたtfstateファイルは破棄されている。terraform importを使わないと、GCSバケットは存在するが、tfstate的には存在しないことになっているので、無理やり作成しようとしてコンフリクトが発生する。そのため、既存のリソースを取り込むための一時的なコマンドを書いておく。

(多分手動でローカルのtfstateファイルをコピーするなど回避方法はありそうなので、一つの方法くらいに思っておいてもらえますと)

.github/workflows/run.yaml
    - name: Terraform Plan
      run: terraform plan
      env:
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}

+    - name: Terraform Import
+      run: terraform import google_storage_bucket.RESOURCE_NAME BUCKET_NAME
+      env:
+        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}

無事stateファイルが作成されている。

デプロイが終わったら、一時的なimport系の処理は削除しておく

.github/workflows/run.yaml
    - name: Terraform Plan
      run: terraform plan
      env:
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}

-    - name: Terraform Import
-      run: terraform import google_storage_bucket.RESOURCE_NAME BUCKET_NAME
-      env:
-        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}

この後、自由にリソース作成ができる。

デプロイとロックについて

どうやら、terraformはGCSと連携させると、自動でロックをとってくれるらしい。

デプロイがされている間は、tf.lockファイルがGCSに存在し、デプロイが終わると自動で削除される。

このファイルが存在する限り、他にCIが走ってもデプロイができないみたい。便利。

Discussion