05. Cloud Storage
GCP のオブジェクトストレージ。AWS S3 相当ですが、命名規約・storage class・IAM 権限 に独特な点があります。uniform_bucket_level_access 必須が今のベストプラクティス。
バケットの基本
resource "google_storage_bucket" "data" {
name = "myapp-prd-data-${random_id.suffix.hex}" # 全世界一意
location = "ASIA-NORTHEAST1" # リージョン or マルチリージョン (例: "ASIA")
storage_class = "STANDARD"
uniform_bucket_level_access = true # ACL を無効化、IAM のみで制御(推奨)
force_destroy = false # 中身があると destroy 拒否
labels = local.common_labels
}
命名の制約
バケット名は全世界一意、3-63 文字、英小文字/数字/ハイフン/ピリオド/アンダースコア。
goog や google は使えない。一度付けると変更不可。
Storage Class
| class | 用途 | 最低保管 | 取得料金 |
|---|---|---|---|
| STANDARD | 頻繁アクセス(汎用) | なし | 無料 |
| NEARLINE | 月 1 回程度 | 30 日 | あり |
| COLDLINE | 四半期 1 回 | 90 日 | 大 |
| ARCHIVE | 年 1 回 | 365 日 | 最大 |
S3 と違って 同一バケット内で複数 class を持てる。ライフサイクルで自動 transition すれば、ホット→コールド の自動降格が可能。
セキュリティ推奨設定
resource "google_storage_bucket" "data" {
# ... 基本項目 ...
# パブリックアクセスを完全遮断
public_access_prevention = "enforced"
# ACL 廃止、IAM のみに統一
uniform_bucket_level_access = true
# バージョニング
versioning {
enabled = true
}
# CMEK 暗号化(次章で KMS キーを作る)
encryption {
default_kms_key_name = google_kms_crypto_key.bucket.id
}
# ログ
logging {
log_bucket = google_storage_bucket.logs.name
log_object_prefix = "data/"
}
# CORS(Web アプリから直接アクセスする時)
cors {
origin = ["https://myapp.com"]
method = ["GET", "HEAD"]
response_header = ["*"]
max_age_seconds = 3600
}
}
ライフサイクル
resource "google_storage_bucket" "data" {
# ...
lifecycle_rule {
condition {
age = 30 # 30 日経過
}
action {
type = "SetStorageClass"
storage_class = "NEARLINE"
}
}
lifecycle_rule {
condition {
age = 365
}
action {
type = "Delete" # 1 年経ったら削除
}
}
lifecycle_rule {
condition {
with_state = "ARCHIVED" # 古いバージョン
num_newer_versions = 3 # 3 世代より古いもの
}
action {
type = "Delete"
}
}
}
IAM
GCP IAM の 3 リソースの違い(06 章参照)が Cloud Storage でも同様に重要。
# ★ 推奨: member(個別追加、最も安全)
resource "google_storage_bucket_iam_member" "vm_read" {
bucket = google_storage_bucket.data.name
role = "roles/storage.objectViewer"
member = "serviceAccount:${google_service_account.vm.email}"
}
# binding(ロールごとに複数 member を一括管理。他で追加した member は消える)
resource "google_storage_bucket_iam_binding" "viewers" {
bucket = google_storage_bucket.data.name
role = "roles/storage.objectViewer"
members = [
"serviceAccount:${google_service_account.vm.email}",
"group:viewers@example.com",
]
}
# policy は使わない(バケット全 IAM を上書きする超危険な操作)
よく使うロール
| ロール | 権限 |
|---|---|
roles/storage.objectViewer | オブジェクト読み取り |
roles/storage.objectCreator | 書き込みのみ(読まない) |
roles/storage.objectAdmin | 読み書き削除 |
roles/storage.admin | バケット管理含む全権 |
オブジェクトアップロード
resource "google_storage_bucket_object" "logo" {
name = "images/logo.png"
bucket = google_storage_bucket.data.name
source = "${path.module}/assets/logo.png"
content_type = "image/png"
metadata = {
uploaded_by = "terraform"
}
}
# ディレクトリ一括(fileset で)
resource "google_storage_bucket_object" "site_files" {
for_each = fileset("${path.module}/../public", "**/*")
name = each.value
bucket = google_storage_bucket.site.name
source = "${path.module}/../public/${each.value}"
content_type = lookup({
"html" = "text/html"
"css" = "text/css"
"js" = "application/javascript"
"png" = "image/png"
}, lower(reverse(split(".", each.value))[0]), "application/octet-stream")
}
静的サイトホスティング
resource "google_storage_bucket" "site" {
name = "site.myapp.com"
location = "ASIA"
website {
main_page_suffix = "index.html"
not_found_page = "404.html"
}
uniform_bucket_level_access = true
}
# 全ユーザーに read 権限(公開)
resource "google_storage_bucket_iam_member" "public" {
bucket = google_storage_bucket.site.name
role = "roles/storage.objectViewer"
member = "allUsers"
}
独自ドメイン + HTTPS で公開するなら Cloud Load Balancer を前段に置きます(10 章)。