概要
静的サイトをGCPで簡単に構築する方法を共有します!
今回はTerraformを使用し、下記のような構成でサイトを構築しようと思います。
- GCS
- GLBからのリクエストを受付、HTML, CSS, JSを表示する
- GLB
- http→httpsリダイレクトを行う
- GCSにリクエストを転送
- 証明書
- GCPのマネージド証明書を利用
GCSではhtaccessなどが動作しないので、GLBでhttpsリダイレクトの役割を持たせることにします。
リダイレクトの主な仕組みについては、公式ドキュメントがわかりやすく解説してくれてますので、興味があればご覧ください!
また、証明書の更新もGCPのマネージド証明書を利用すると自動でやってくれるので、マネージド証明書を利用します。
Terraformによる構築
共通設定
provider.tf
provider "google" {
credentials = file(var.credentials)
project = var.project
}
provider "google-beta" {
credentials = file(var.credentials)
project = var.project
}
GCSバケット
下記の内容でバケットを構築します。
- バージョニングを有効化
- 特殊ページを設定
- index.html
- 404.html
- アクセス権を均一化
main.tf
resource "google_storage_bucket" "bucket" {
name = var.advent_calendar_bucket.bucket_name
location = var.advent_calendar_bucket.region
storage_class = var.advent_calendar_bucket.storage_class
force_destroy = true
labels = {
project = "advent-calendar"
}
# バージョニングの有効化
versioning {
enabled = true
}
# 特殊ページの割り当て
website {
main_page_suffix = "index.html"
not_found_page = "404.html"
}
# アクセスレベルを均一化
uniform_bucket_level_access = true
}
# 誰でも見れるように一般公開(allUsers)にする
resource "google_storage_bucket_iam_member" "advent_calendar_bucket_iam_member_object_viewer" {
bucket = google_storage_bucket.bucket.name
role = "roles/storage.legacyObjectReader"
member = "allUsers"
}
variables.tf
variable "advent_calendar_bucket" {
default = {
bucket_name = "バケット名"
region = "asia"
storage_class = "MULTI_REGIONAL"
}
}
GCSのアクセス制御はGCPの推奨アーキテクチャ通り、アクセスレベルを均一にしています。公開するバケットにてオブジェクト単位でアクセス制御を行っていると、オブジェクトを新しく作成するたびにアクセス権を設定しないといけません。こういった作業は事故の元なので、アクセスレベルは均一にしています。
また、指定されたページがない時やhttps://ドメイン名/
にアクセスされた時にデフォルトのページを表示したいので特殊ページを設定しています。
そのほかの特徴として、google_storage_bucket_iam_member
を使用してバケットに紐づくIAMを設定しています。今回はバケットを公開するため、allUsers
にroles/storage.legacyObjectReader
を付与しています。
バケット公開をするにあたってroles/storage.objectViewer
でも良いですが、こちらの権限はバケットのオブジェクト一覧を表示する権限があります。権限を最小化するため今回はroles/storage.legacyObjectReader
を付与しています。
参考公式リンク
GLB
main.tf(GCSのリソースと同じファイルに書く想定で書いてます。)
resource "google_compute_managed_ssl_certificate" "cert" {
provider = google-beta
name = var.certificate_name
managed {
domains = [var.certificate_domain]
}
}
resource "google_compute_global_address" "lb_ip_address" {
name = var.global_address_name
}
resource "google_compute_backend_bucket" "lb_backend_bucket" {
name = var.backend_name
bucket_name = google_storage_bucket.bucket.name
enable_cdn = var.enable_cdn
}
# http→httpsリダイレクトを行うためにLBを二つに分ける
resource "google_compute_url_map" "lb_http_url_map" {
name = var.http_lb_name
# default_url_redirectを指定する場合は、default_servicは指定してはいけない
default_url_redirect {
strip_query = false
https_redirect = true
}
}
resource "google_compute_url_map" "lb_https_url_map" {
name = var.https_lb_name
default_service = google_compute_backend_bucket.lb_backend_bucket.self_link
}
resource "google_compute_target_http_proxy" "lb_http_proxy" {
name = var.http_proxy_name
url_map = google_compute_url_map.lb_http_url_map.self_link
}
resource "google_compute_target_https_proxy" "lb_https_proxy" {
name = var.https_proxy_name
url_map = google_compute_url_map.lb_https_url_map.self_link
ssl_certificates = [google_compute_managed_ssl_certificate.cert.id]
}
resource "google_compute_global_forwarding_rule" "lb_http_forwarding_rule" {
name = var.http_forwarding_rule_name
target = google_compute_target_http_proxy.lb_http_proxy.self_link
ip_address = google_compute_global_address.lb_ip_address.address
port_range = var.http_forwarding_rule_port
}
resource "google_compute_global_forwarding_rule" "lb_https_forwarding_rule" {
name = var.https_forwarding_rule_name
target = google_compute_target_https_proxy.lb_https_proxy.self_link
ip_address = google_compute_global_address.lb_ip_address.address
port_range = var.https_forwarding_rule_port
}
variables.tf
variable "certificate_name" {
default = "advent-calendar2020"
}
variable "certificate_domain" {
default = "example.com"
}
variable "backend_name" {
default = "advent-calendaer2020-backend"
}
variable "http_lb_name" {
default = "advent-calendar2020-http-lb"
}
variable "https_lb_name" {
default = "advent-calendar2020-https-lb"
}
variable "http_proxy_name" {
default = "advent-calendar2020-http-proxy"
}
variable "https_proxy_name" {
default = "advent-calendar2020-https-proxy"
}
variable "http_forwarding_rule_name" {
default = "advent-calendar2020-http-forwarding-rule"
}
variable "https_forwarding_rule_name" {
default = "advent-calendar2020-https-forwarding-rule"
}
variable "http_forwarding_rule_port" {
default = "80"
}
variable "https_forwarding_rule_port" {
default = "443"
}
variable "enable_cdn" {
default = true
}
variable "global_address_name" {
default = "advent-calendar2020-address"
}
Googleのロードバランサーは複数のリソースから成り立っています。
そのため、必要なリソースをterraformで定義しています。特徴としては、今回HTTPSリダイレクトを行っているため、 default_url_redirect
をurl_map
で定義しています。
この時、クエリストリングを削ることもできるようですが、特に削る必要がないので削らないように設定をしています。
各リソースがどう関係しているかは公式の下記画像を見ていただくとわかりやすいと思います。構築したサイトと異なる点としてはCompute HTTPS load balancer
のBackend Service
の部分がBackend Bucket
になっているだけですね。
https://cloud.google.com/load-balancing/docs/https/setting-up-http-https-redirect
特殊ページのアップロード
下記のような簡単なファイルをGCSバケットにアップロードします。
index.html
<h1>Hello world.</h1>
404.html
<h1>Not Found.</h1>
構築したページ
TOP
指定したパスがない時
ちょっと困ったところ
GCSを用いたGLBでは、IPアドレス制御などによるアクセス制限が行えない
一般的にGLBでアクセス制御を行うには下記の方法があります。
しかし、両方とも対応しているのはバックエンドサービスのみという事実。。。バックエンドバケットは対応していませんでした。。。
すでに機能追加リクエストがGCPにあがっているものの、まだ対応されていません。
こちら、ステージング環境を社内のみ・関連会社のみに共有したいという場合などにとても困ります。
また、Cloud Armorを設定したとしてもGCSのオブジェクト自体は公開されているので、公開した際に発行されるGCS専用URLがわかると誰でもアクセスできてしまいます。
対応方法
バケットの公開範囲を絞る方法としてVPC Service Controlがあります。
こちらを用いることで、BigQueryやGCSといった非VPCリソースに対してIP制限を行うことが可能だと聞いています。
ただ、こちらの設定はGCPの組織に紐づく設定のようで、十分に検討・検証を行わないと既存のトラフィックにも影響が出てしまう可能性があります。
良かったところ
- HTTPSリダイレクトをGLBに持たせることができる
- Cloud CDNで簡単にキャッシュできる
- マネージド証明書による証明書管理
- コストなし・自動更新
証明書の更新作業やリダイレクト処理をGCPが賄ってくれるのが非常にありがたいと感じました!
また、terraformのコードさえ書いて仕舞えば、同じ構成のリソースを簡単に作れるので一時的なサイトなどを構築する際は非常に便利だと感じています。
まとめ
GCS・GLBを利用することで簡単に静的サイトを構築することができました。
しかし、IPアドレスの制限などを行いたい場合はひと工夫ふた工夫必要になってきます🤥
皆さんにおすすめしたいこととしては、「バックエンドサービス(GCE)でできるのだから、バックエンドバケット(GCS)でもできるでしょ〜🤪」などといった思い込みはもたずに、案件が来たらちゃんと実現できるか公式ドキュメントを漁りましょう!(こんな思い込みをしてて痛い目にあいました。)
それでは、良いお年を!