🔥

Terraform で Firebase プロジェクトを構築してみる

2023/07/13に公開

こんにちは、SRE ディビジョンの小堀内です。
今回も 前回に引き続き Google I/O 2023 で発表された Firebase の新機能 の中から紹介していきます。

本記事のテーマ

Terraform integration to automate provisioning and configuration workflows
(Terraform の統合によるプロビジョニングと構成のワークフローの自動化)

対象読者

  • Terraform に興味はあるが触ったことがない方
  • Terraform を通して IaC を学んでみたい方
  • コンソール操作によって Firebase プロジェクトを構築している方

Terraform とは

Terraform とは、HashiCorp 社が開発した IaC(Infrastructure as Code)ツールの一種です。
異なるクラウドプロバイダーまたはオンプレミス環境上のリソースを統一的な形式で記述し、管理できるように設計されています。

ちなみに IaC(Infrastructure as Code)とは、インフラストラクチャをコードとして管理する手法のことです。
IaC を使用すると、コンソール上から手動でネットワーク、仮想マシン、負荷分散などのインフラリソースを設定する代わりに、コードでこれらを定義し、自動的に適用することが可能となります。
IaC 化により、環境構築が迅速かつ一貫したものになり、エラーや非効率な手順を大幅に削減することができます。

Terraform を使用することで、環境のプロビジョニングと構成をコードで記述し、自動的にデプロイすることができます。

基本的な Terraform コマンド

まず「これだけは押さえておきたい」という基本的な Terraform コマンドを紹介します。

コマンド 役割
terraform init - Terraform 初期化時に使用する
- 作業ディレクトリの初期化が行われる
terraform fmt - Terraform コードのフォーマットを整える
- コードの一貫性が保たれる
terraform plan - 追加、更新、削除される予定のリソースを表示する
- 実際の環境に適用する前に変更内容をレビューすることができる
terraform apply - terraform plan で確認した変更内容を実行し、インフラストラクチャに変更を適用する

コマンドの実行結果に関しては、Firebase プロジェクトを構築する際に確認していきましょう。

Terraform を用いて Firebase プロジェクトを構築

それでは実際に、Terraform を用いて Firebase プロジェクトを構築していきたいと思います。

構成

イメージ

次の構成図をもとに Terraform を使用して構築を行います。
firebase-image

ディレクトリ構成

次に示すディレクトリ構成をもとにして以降の説明を行います。

.
├── README.md
├── locals.tf
├── main.tf
├── modules
│   ├── authentication
│   │   ├── main.tf
│   │   └── variables.tf
│   ├── firestore
│   │   ├── main.tf
│   │   ├── output.tf
│   │   └── variables.tf
│   └── storage
│       ├── main.tf
│       └── variables.tf
├── provider.tf
└── variables.tf

※ Firebase プロジェクト構築時の Terraform ディレクトリ構成の一例となります。

構築

1. 変数定義

まず、外部(ターミナル、Cloud Build など)から値を渡してもらい Terraform コードを実行するための変数を定義します。

variables.tf
variable "billing_account" {
  description = "Firebase プロジェクトに紐づける Google Cloud Billing Account の ID"
  type        = string
}

variable "project_name" {
  description = "Firebase プロジェクトの名前"
  type        = string
}

variable "project_id" {
  description = "Firebase プロジェクトの ID(世界で一意となるコード)"
  type        = string
}

variable "android_package_name" {
  description = "パッケージ名(Android)"
  type        = string
}

variable "ios_bundle_id" {
  description = "バンドルID(iOS)"
  type        = string
}

2. 使用するプロバイダーの用意

次に required_providers ブロックを使用して 4.x バージョンgoogle-beta プロバイダーを使用することを宣言します。

provider.tf
terraform {
  required_providers {
    google-beta = {
      source  = "hashicorp/google-beta"
      version = "~> 4.0"
    }
  }
}

provider "google-beta" {
  # プロジェクトに関連するコストを追跡する
  user_project_override = true
  billing_project       = var.project_id
}

provider "google-beta" {
  alias                 = "no_user_project_override"
  user_project_override = false
}

3. Firebase プロジェクト用の Google Cloud プロジェクト構築

Firebase プロジェクトは Google Cloud プロジェクトの一部として構築されます。
そのため、Firebase プロジェクト構築前に Google Cloud プロジェクトを構築する必要があります。

main.tf
# Firebase プロジェクト用の Google Cloud プロジェクトを立ち上げる
resource "google_project" "default" {
  provider = google-beta

  # project_id は一意である必要がある
  project_id      = var.project_id
  name            = var.project_name
  billing_account = var.billing_account

  # Firebase のプロジェクトとして表示するために必要
  labels = {
    "firebase" = "enabled"
  }
}

4. 各種 API 有効化

Google Cloud プロジェクト内のいくつかの API を有効化して Firebase プロジェクト内のプロダクト(Cloud Firestore, Cloud Storage, Firebase Authentication 等)を使用可能な状態にします。

main.tf
# Firebase 内のプロダクトを利用可能にするために Google Cloud 内の API を有効化する
resource "google_project_service" "default" {
  provider = google-beta.no_user_project_override
  project  = google_project.default.project_id
  for_each = toset([
    "firestore.googleapis.com",
    "cloudbilling.googleapis.com",
    "cloudresourcemanager.googleapis.com",
    "serviceusage.googleapis.com",
    "identitytoolkit.googleapis.com",
    "firebase.googleapis.com",
    "firebaserules.googleapis.com",
    "firebasestorage.googleapis.com",
    "storage.googleapis.com",
    
    # 後々 Cloud Build 経由で Terraform コマンドを実行するようにしたい場合はこちらも有効化しておくとよい
    "cloudbuild.googleapis.com", 
  ])
  service            = each.key
  disable_on_destroy = false
}

5. Firebase プロジェクトの立ち上げ

これで Google Cloud プロジェクト側での事前準備が整いました。
次に Firebase プロジェクトを構築するコードを書いていきます。

main.tf
# Firebase のプロジェクトを立ち上げる
resource "google_firebase_project" "default" {
  provider = google-beta
  project  = google_project.default.project_id

  # Google Cloud プロジェクトの各種 API が有効化されるのを待ってから本リソースを実行する
  depends_on = [
    google_project_service.default,
  ]
}

6. Firebase プロジェクトのリージョン設定

東京リージョンに配置するように指定します。
もし、別リージョンに Firebase プロジェクトを配置したい場合は、使用可能なリージョンとゾーン に記載のリージョン名を使用してください。

locals.tf
# 各種リソース共通の定数群
locals {
  # リージョン名:東京
  region = "asia-northeast1"
}
main.tf
# Firebase プロジェクトを東京リージョンに配置する
resource "google_firebase_project_location" "default" {
  provider = google-beta
  project  = google_firebase_project.default.project

  location_id = local.region
}

7. アプリ設定

Web, Android, iOS のアプリ設定をそれぞれ行います。

設定後のイメージ

firebase-app

Web
main.tf
# Firebase Web App
resource "google_firebase_web_app" "default" {
  provider     = google-beta
  project      = var.project_id
  display_name = "My Web App"

  depends_on = [
    google_firebase_project.default,
  ]
}
Android
main.tf
# Firebase Android App
resource "google_firebase_android_app" "default" {
  provider     = google-beta
  project      = var.project_id
  display_name = "My Android App"
  package_name = var.android_package_name

  depends_on = [
    google_firebase_project.default,
  ]
}
iOS
main.tf
# Firebase iOS App
resource "google_firebase_apple_app" "default" {
  provider     = google-beta
  project      = var.project_id
  display_name = "My iOS app"
  bundle_id    = var.ios_bundle_id

  depends_on = [
    google_firebase_project.default,
  ]
}

8. Firebase の各種プロダクトを有効化

ここでは Cloud Firestore, Cloud Storage, Firebase Authentication の有効化をしてみます。

Cloud Firestore

ここでは 公式ドキュメント および Terraform サンプル の例を参考に somenewcollection というコレクションを作成して、さらにその中に sub_docs というサブコレクションを作成しています。

./modules/firestore/main.tf
# Firebase Firestore
resource "google_firestore_database" "default" {
  project                     = var.project_id
  name                        = "(default)"
  location_id                 = var.location
  type                        = "FIRESTORE_NATIVE"
  concurrency_mode            = "OPTIMISTIC"
  app_engine_integration_mode = "DISABLED"

  depends_on = [
    var.services_ready
  ]
}

# Firebase Cloud Firestore コレクション/ドキュメント定義
resource "google_firestore_document" "mydoc" {
  project     = var.project_id
  collection  = "somenewcollection"
  document_id = "my-doc-id"
  fields      = "{\"something\":{\"mapValue\":{\"fields\":{\"akey\":{\"stringValue\":\"avalue\"}}}}}"
  depends_on  = [google_firestore_database.default]
}
resource "google_firestore_document" "sub_document" {
  project     = var.project_id
  collection  = "${google_firestore_document.mydoc.path}/subdocs"
  document_id = "bitcoinkey"
  fields      = "{\"something\":{\"mapValue\":{\"fields\":{\"ayo\":{\"stringValue\":\"val2\"}}}}}"
  depends_on  = [google_firestore_database.default]
}
Cloud Storage
./modules/storage/main.tf
# Firebase Cloud Storage
# Firebase の Cloud Storage を使用するには 先に App Engine の有効化が必要
resource "google_app_engine_application" "default" {
  project     = var.project_id
  location_id = var.location

  # Cloud Firestore DB を作成する場合は、その作成を待つ必要がある
  depends_on = [
    var.services_ready
  ]
}

# Storage バケット
resource "google_firebase_storage_bucket" "default" {
  provider  = google-beta
  project   = var.project_id
  bucket_id = google_app_engine_application.default.default_bucket
}
Firebase Authentication

ここでは Terraform ドキュメント を参考に Firebase Authentication の匿名認証、メール認証、電話番号認証(テスト用の電話番号の登録を含む)を有効化してみます。

./modules/authentication/main.tf
# Firebase Authentication
resource "google_identity_platform_config" "default" {
  provider = google-beta
  project  = var.project_id
  depends_on = [
    var.services_ready,
  ]
}

resource "google_identity_platform_project_default_config" "default" {
  provider = google-beta
  project  = var.project_id
  sign_in {
    allow_duplicate_emails = true
    anonymous {
      enabled = true
    }
    email {
      enabled           = true
      password_required = false
    }
    phone_number {
      enabled = true
      test_phone_numbers = {
        "+11231231234" = "000000"
      }
    }
  }
  depends_on = [google_identity_platform_config.default]
}

9. Terraform コマンド実行

ここまでで Firebase プロジェクトを管理するためのコードが作成できたので、次は実際の環境に対して適用していきます。

terraform init

まずは terraform init コマンドを実行して作業ディレクトリを初期化します。
実行例は下記のようになります。
terraform-init

terraform plan

ディレクトリ初期化が完了したら terraform plan コマンドを実行して、実際の環境に適用されるリソースを確認していきます。
下記の例では、実際の環境に対してリソースが 22 個追加されることを示しています。
terraform-plan-1
terraform-plan-2

terraform apply

上記で変更が加えられるリソースが確認できたら terraform apply コマンドを実行して、コードの状態を実際の環境に適用します。
「これらのアクションを実行しますか?」と質問されるので yes を入力して承認することで適用が行われます。
terraform-apply

terraform apply 後の確認

上記 terraform apply コマンドの実行によりコードの状態が実際の環境に適用されました。
適用されたことを Firebase プロジェクトのコンソール上からも確認してみましょう。

Firebase プロジェクト

firebase-project-top

Firebase プロジェクトのリージョン

firebase-region

Firebase アプリ

firebase-project-summary

Firebase Cloud Firestore

firebase-firestore

Firebase Cloud Storage

firebase-storage

Firebase Authentication

firebase-authentication
firebase-authentication-phone

サンプルコード

https://github.com/cloud-ace/zenn-firebase-terraform

まとめ

個人的に興味のある Firebase で Terraform がサポートされた
+
最近アサインされた案件で Terraform を使用することになった

というとても良いタイミングで Terraform を学習することができたと感じています。
Terraform の基本的な使い方を理解することができたので案件内ではこの学習を生かしていきます。
今後 Terraform をはじめとして IaC についてさらに深く学習して要件に沿ったインフラ設計をできるよう努力します。

参考

https://zenn.dev/aldagram_tech/articles/54729ac3954484
https://firebase.google.com/docs/projects/terraform/get-started?hl=ja
https://registry.terraform.io/providers/hashicorp/google-beta/latest/docs

Discussion