はじめに
このブログは随分と昔に Cloud Storage の静的ウェブサイトホスティング機能を用いて作ったきりで, HTTPS に対応していなかったり IaC 化できていなかったりと粗が目立っていた.この度これらの問題を解決するためにインフラを構築し直したため,対応内容を本記事にメモしておく.
前提,要望
- ウェブサイト自体は静的サイトジェネレータで作成している
- 静的ファイルが Cloud Storage にアップロードされることでサイトが更新される
- サイトの更新方法は変えない
- HTTPS に対応したい
- HTTP アクセスは HTTPS にリダイレクトしたい
- IaC したい
- ネイキッドドメインとサブドメインでサイトを構築したい
- example.com
- blog.example.com
資料
- 静的ウェブサイトをホストする | Cloud Storage | Google Cloud
- 静的ウェブサイトの例とヒント | Cloud Storage | Google Cloud
- 転送ルールの概要 | 負荷分散 | Google Cloud
- Cloud Storage バケットを使用してグローバル外部アプリケーション ロードバランサを設定する | 負荷分散 | Google Cloud
- 外部アプリケーション ロードバランサの Terraform の例 | 負荷分散 | Google Cloud
- Google マネージド SSL 証明書を使用する | 負荷分散 | Google Cloud
実装
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 のロードバランサ周辺の理解を深めることができてよかった.