はじめに

このブログは随分と昔に Cloud Storage の静的ウェブサイトホスティング機能を用いて作ったきりで, HTTPS に対応していなかったり IaC 化できていなかったりと粗が目立っていた.この度これらの問題を解決するためにインフラを構築し直したため,対応内容を本記事にメモしておく.

前提,要望

  • ウェブサイト自体は静的サイトジェネレータで作成している
    • 静的ファイルが Cloud Storage にアップロードされることでサイトが更新される
  • サイトの更新方法は変えない
  • HTTPS に対応したい
    • HTTP アクセスは HTTPS にリダイレクトしたい
  • IaC したい
  • ネイキッドドメインとサブドメインでサイトを構築したい
    • example.com
    • blog.example.com

資料

実装

IaC には Terraform を用いた.実際の設定は github.com/aoimotoya/terraform にまとめてある.

アーキテクチャ

アーキテクチャ

  • サイトごとに (ドメインごとに) Cloud Storage バケットを用意する
  • フロントは一つのロードバランサに任せる
    • 指定されたドメインに応じてリクエストを適切なバックエンドに振り分ける
  • DNS 設定は Google Cloud の外で別途設定する

locals

locals {
  region  = "asia-northeast1"
  project = "your-project"
  domain_names = {
    top  = "example.com"
    blog = "blog.example.com"
  }
}

バックエンド

サイトごとに Cloud Storage バケットを作成し,バケットを静的ウェブサイト用に設定する.

/**
 * バケット名を一意に定めるためのサフィックスを用意
 */

resource "random_id" "bucket_suffix" {
  byte_length = 8
}

/**
 * google_storage_bucket
 * バケットを定義する
 * 静的ウェブサイト用の設定も行う
 */

# example.com
resource "google_storage_bucket" "top" {
  name          = "web-top-${random_id.bucket_suffix.hex}"
  location      = "asia-northeast1"
  storage_class = "STANDARD"
  force_destroy = true

  uniform_bucket_level_access = true

  website {
    main_page_suffix = "index.html"
    not_found_page   = "404.html"
  }
}

# blog.example.com
resource "google_storage_bucket" "blog" {
  name          = "web-blog-${random_id.bucket_suffix.hex}"
  location      = "asia-northeast1"
  storage_class = "STANDARD"
  force_destroy = true

  uniform_bucket_level_access = true

  website {
    main_page_suffix = "index.html"
    not_found_page   = "404.html"
  }
}

/**
 * google_storage_bucket_iam_member
 * バケットのアクセス権を設定する
 */

# example.com
resource "google_storage_bucket_iam_member" "top" {
  bucket = google_storage_bucket.top.name
  role   = "roles/storage.objectViewer"
  member = "allUsers"
}

# blog.example.com
resource "google_storage_bucket_iam_member" "blog" {
  bucket = google_storage_bucket.blog.name
  role   = "roles/storage.objectViewer"
  member = "allUsers"
}

SSL 証明書

サイト (ドメイン) ごとに SSL 証明書を用意する.

/**
 * google_compute_managed_ssl_certificate
 * SSL 証明書を定義する
 */

# example.com
resource "google_compute_managed_ssl_certificate" "top" {
  name = "top-ssl-cert"
  managed {
    domains = [local.domain_names.top]
  }
}

# blog.example.com
resource "google_compute_managed_ssl_certificate" "blog" {
  name = "blog-ssl-cert"
  managed {
    domains = [local.domain_names.blog]
  }
}

ロードバランサ

今回の実装ではロードバランサ周辺の仕事がもっとも多い.クライアントからバケットへの経路を設定したり SSL 証明書を組み込みつつリダイレクトを設定する.

/**
 * google_compute_global_address
 * ロードバランサ用のグローバル IP アドレスを用意する
 */

resource "google_compute_global_address" "default" {
  name = "web-lb-ip"
}

/**
 * google_compute_backend_bucket
 * ロードバランサのバックエンドに置くバケットを指定する
 */

# example.com
resource "google_compute_backend_bucket" "top" {
  name        = "top-backend-bucket"
  bucket_name = google_storage_bucket.top.name
}

# blog.example.com
resource "google_compute_backend_bucket" "blog" {
  name        = "blog-backend-bucket"
  bucket_name = google_storage_bucket.blog.name
}

/**
 * google_compute_url_map
 * URL とバックエンドを紐付ける
 */

resource "google_compute_url_map" "default" {
  name            = "web-url-map"
  # デフォルトで example.com 用のバケットに紐付ける
  default_service = google_compute_backend_bucket.top.id

  # 例外として blog.example.com 用の挙動を追加する
  host_rule {
    hosts        = [local.domain_names.blog]
    path_matcher = "blog"
  }

  path_matcher {
    name            = "blog"
    default_service = google_compute_backend_bucket.blog.id
  }
}

# HTTP アクセスを HTTPS にリダイレクトする
resource "google_compute_url_map" "redirect" {
  name = "web-url-map-redirect-http-to-https"
  default_url_redirect {
    https_redirect = true
    strip_query    = false
  }
}

/**
 * google_compute_target_http_proxy
 * HTTP リクエストを URL マップにルーティングする
 */

resource "google_compute_target_http_proxy" "default" {
  name    = "http-proxy"
  url_map = google_compute_url_map.redirect.id
}

/**
 * google_compute_target_https_proxy
 * HTTPS リクエストを URL マップにルーティングする
 */

resource "google_compute_target_https_proxy" "default" {
  name    = "https-proxy"
  url_map = google_compute_url_map.default.id
  # HTTPS では HTTP の設定に追加して SSL 証明書の設定を行う
  ssl_certificates = [
    google_compute_managed_ssl_certificate.top.name,
    google_compute_managed_ssl_certificate.blog.name
  ]
  depends_on = [
    google_compute_managed_ssl_certificate.top,
    google_compute_managed_ssl_certificate.blog
  ]
}

/**
 * google_compute_global_forwarding_rule
 * トラフィックをロードバランサに転送するルールを設定する
 */

resource "google_compute_global_forwarding_rule" "redirect" {
  name                  = "http-lb-forwarding-rule"
  ip_protocol           = "TCP"
  load_balancing_scheme = "EXTERNAL_MANAGED"
  port_range            = "80"
  target                = google_compute_target_http_proxy.default.id
  ip_address            = google_compute_global_address.default.id
}

resource "google_compute_global_forwarding_rule" "default" {
  name                  = "https-lb-forwarding-rule"
  ip_protocol           = "TCP"
  load_balancing_scheme = "EXTERNAL_MANAGED"
  port_range            = "443"
  target                = google_compute_target_https_proxy.default.id
  ip_address            = google_compute_global_address.default.id
}

DNS

SSL 証明書の有効化を兼ねて DNS を設定する. example.com および blog.example.com の A レコードとして google_compute_global_address.default.address が示す IP アドレスを設定する. Google Cloud にてドメインと IP アドレスの紐付けが確認されたら当該ドメインの SSL 証明書が有効化される.

おわりに

以上で静的ウェブサイト example.com および blog.example.com のインフラを構築できた.まさに本ブログがこのインフラ上で動作している.実装においては Google Cloud のロードバランサ周辺の理解を深めることができてよかった.