Google Cloud Platform
Google Cloud Platform
Self-hosting on Google Cloud Platform
To self-host Membrane on Google Cloud Platform, you need to set up Google Cloud Storage.
Everything else is the same as in the main guide.
Setting up Google Cloud Storage
This guide uses terraform to manage the infrastructure.
Create a service account:
# Create a Google Service Account for the API service
resource "google_service_account" "api_service" {
account_id = "${var.project}-${var.environment}-api"
display_name = "API Service Account for ${var.environment}"
project = var.gcp_project_id
}
# Grant necessary permissions to the service account
resource "google_project_iam_member" "api_storage_admin" {
project = var.gcp_project_id
role = "roles/storage.admin"
member = "serviceAccount:${google_service_account.api_service.email}"
}
# Grant token creator permission for storage signed URLs
resource "google_project_iam_member" "api_token_creator" {
project = var.gcp_project_id
role = "roles/iam.serviceAccountTokenCreator"
member = "serviceAccount:${google_service_account.api_service.email}"
}
# Allow the Kubernetes service account to impersonate the Google service account
resource "google_service_account_iam_binding" "api_workload_identity" {
service_account_id = google_service_account.api_service.name
role = "roles/iam.workloadIdentityUser"
members = [
"serviceAccount:${var.gcp_project_id}.svc.id.goog[${var.kubernetes_namespace}/integration-app]"
]
}
- Create your buckets
# Temporary files bucket
resource "google_storage_bucket" "tmp" {
name = "${var.project}-${var.environment}-tmp"
location = var.gcp_region
project = var.gcp_project_id
# Force destroy even if bucket contains objects
force_destroy = true
# Uniform bucket-level access
uniform_bucket_level_access = true
# Lifecycle rule to delete objects after 7 days
lifecycle_rule {
condition {
age = 7
}
action {
type = "Delete"
}
}
# Versioning
versioning {
enabled = false
}
# Labels
labels = local.common_labels
}
# Connectors bucket
resource "google_storage_bucket" "connectors" {
name = "${var.project}-${var.environment}-connectors"
location = var.gcp_region
project = var.gcp_project_id
# Prevent accidental deletion
force_destroy = false
# Uniform bucket-level access
uniform_bucket_level_access = true
# Versioning for code safety
versioning {
enabled = true
}
# Lifecycle rule to delete old versions after 30 days
lifecycle_rule {
condition {
num_newer_versions = 3
with_state = "ARCHIVED"
}
action {
type = "Delete"
}
}
# Labels
labels = local.common_labels
}
# Static content bucket
resource "google_storage_bucket" "static" {
name = "${var.project}-${var.environment}-static"
location = var.gcp_region
project = var.gcp_project_id
# Force destroy for development environments
force_destroy = var.environment != "prod"
# Uniform bucket-level access
uniform_bucket_level_access = true
# Website configuration
website {
main_page_suffix = "index.html"
not_found_page = "404.html"
}
# CORS configuration
cors {
origin = ["*"]
method = ["GET", "HEAD", "OPTIONS"]
response_header = ["*"]
max_age_seconds = 3600
}
# Versioning
versioning {
enabled = false
}
# Labels
labels = local.common_labels
}
# Make static bucket publicly accessible for CDN (Optional)
resource "google_storage_bucket_iam_member" "static_public_access" {
bucket = google_storage_bucket.static.name
role = "roles/storage.objectViewer"
member = "allUsers"
}
- Configure CDN for serving static files (Optional)
# Reserve a global static IP for the load balancer
resource "google_compute_global_address" "cdn_ip" {
count = var.enable_cdn ? 1 : 0
name = "${var.project}-${var.environment}-cdn-ip"
project = var.gcp_project_id
}
# Backend bucket for serving static content
resource "google_compute_backend_bucket" "static_backend" {
count = var.enable_cdn ? 1 : 0
name = "${var.project}-${var.environment}-static-backend"
description = "Backend bucket for static content"
bucket_name = google_storage_bucket.static.name
project = var.gcp_project_id
# Enable CDN
enable_cdn = true
# CDN policy
cdn_policy {
cache_mode = "CACHE_ALL_STATIC"
# Cache TTLs
client_ttl = 3600
default_ttl = 3600
max_ttl = 86400
# Negative caching
negative_caching = true
negative_caching_policy {
code = 404
ttl = 120
}
# Cache key policy
cache_key_policy {
query_string_whitelist = []
}
}
# Compression
compression_mode = "AUTOMATIC"
}
# URL map for routing
resource "google_compute_url_map" "cdn_url_map" {
count = var.enable_cdn ? 1 : 0
name = "${var.project}-${var.environment}-cdn-url-map"
default_service = google_compute_backend_bucket.static_backend[0].id
project = var.gcp_project_id
# Host rules for custom domains
host_rule {
hosts = ["static.${var.domain_name}"]
path_matcher = "static-paths"
}
# Path matchers
path_matcher {
name = "static-paths"
default_service = google_compute_backend_bucket.static_backend[0].id
path_rule {
paths = ["/api/*"]
service = google_compute_backend_bucket.static_backend[0].id
route_action {
url_rewrite {
path_prefix_rewrite = "/"
}
}
}
}
}
# Managed SSL certificate
resource "google_compute_managed_ssl_certificate" "cdn_cert" {
count = var.enable_cdn ? 1 : 0
name = "${var.project}-${var.environment}-cdn-cert"
project = var.gcp_project_id
managed {
domains = ["static.${var.domain_name}"]
}
}
# HTTPS proxy
resource "google_compute_target_https_proxy" "cdn_https_proxy" {
count = var.enable_cdn ? 1 : 0
name = "${var.project}-${var.environment}-cdn-https-proxy"
url_map = google_compute_url_map.cdn_url_map[0].id
ssl_certificates = [google_compute_managed_ssl_certificate.cdn_cert[0].id]
project = var.gcp_project_id
}
# HTTP proxy (for redirect to HTTPS)
resource "google_compute_target_http_proxy" "cdn_http_proxy" {
count = var.enable_cdn ? 1 : 0
name = "${var.project}-${var.environment}-cdn-http-proxy"
url_map = google_compute_url_map.cdn_http_redirect[0].id
project = var.gcp_project_id
}
# URL map for HTTP to HTTPS redirect
resource "google_compute_url_map" "cdn_http_redirect" {
count = var.enable_cdn ? 1 : 0
name = "${var.project}-${var.environment}-cdn-http-redirect"
project = var.gcp_project_id
default_url_redirect {
https_redirect = true
redirect_response_code = "MOVED_PERMANENTLY_DEFAULT"
strip_query = false
}
}
# Global forwarding rule for HTTPS
resource "google_compute_global_forwarding_rule" "cdn_https_forwarding" {
count = var.enable_cdn ? 1 : 0
name = "${var.project}-${var.environment}-cdn-https-forwarding"
ip_protocol = "TCP"
load_balancing_scheme = "EXTERNAL_MANAGED"
port_range = "443"
target = google_compute_target_https_proxy.cdn_https_proxy[0].id
ip_address = google_compute_global_address.cdn_ip[0].id
project = var.gcp_project_id
}
# Global forwarding rule for HTTP
resource "google_compute_global_forwarding_rule" "cdn_http_forwarding" {
count = var.enable_cdn ? 1 : 0
name = "${var.project}-${var.environment}-cdn-http-forwarding"
ip_protocol = "TCP"
load_balancing_scheme = "EXTERNAL_MANAGED"
port_range = "80"
target = google_compute_target_http_proxy.cdn_http_proxy[0].id
ip_address = google_compute_global_address.cdn_ip[0].id
project = var.gcp_project_id
}
# Cloud Armor security policy (optional, for DDoS protection)
resource "google_compute_security_policy" "cdn_security_policy" {
count = var.enable_cdn && var.environment == "prod" ? 1 : 0
name = "${var.project}-${var.environment}-cdn-security-policy"
project = var.gcp_project_id
# Default rule
rule {
action = "allow"
priority = "2147483647"
match {
versioned_expr = "SRC_IPS_V1"
config {
src_ip_ranges = ["*"]
}
}
description = "Default rule, allow all traffic"
}
# Rate limiting rule
rule {
action = "rate_based_ban"
priority = "1000"
match {
versioned_expr = "SRC_IPS_V1"
config {
src_ip_ranges = ["*"]
}
}
rate_limit_options {
conform_action = "allow"
exceed_action = "deny(429)"
enforce_on_key = "IP"
rate_limit_threshold {
count = 1000
interval_sec = 60
}
}
description = "Rate limit rule"
}
}
- Configure custom domain DNS managed zone for BASE_STATIC_URI (Optional)
# Create DNS managed zone
resource "google_dns_managed_zone" "main" {
name = var.dns_zone_name
dns_name = "${var.domain_name}."
description = "${var.project} ${var.environment} DNS zone"
project = var.gcp_project_id
labels = local.common_labels
dnssec_config {
state = "on"
}
}
# DNS record for static content CDN
resource "google_dns_record_set" "static_cdn" {
count = var.enable_cdn ? 1 : 0
name = "static.${google_dns_managed_zone.main.dns_name}"
managed_zone = google_dns_managed_zone.main.name
type = "A"
ttl = 300
project = var.gcp_project_id
rrdatas = [google_compute_global_address.cdn_ip[0].address]
}
# TXT record for domain verification
resource "google_dns_record_set" "domain_verification" {
name = google_dns_managed_zone.main.dns_name
managed_zone = google_dns_managed_zone.main.name
type = "TXT"
ttl = 300
project = var.gcp_project_id
rrdatas = [
"\"v=spf1 -all\"",
"\"google-site-verification=${var.google_site_verification_code}\""
]
}
# A record for the root domain (optional)
resource "google_dns_record_set" "root" {
count = var.enable_cdn ? 1 : 0
name = google_dns_managed_zone.main.dns_name
managed_zone = google_dns_managed_zone.main.name
type = "A"
ttl = 300
project = var.gcp_project_id
rrdatas = [google_compute_global_address.cdn_ip[0].address]
}
# CAA record for SSL certificate issuance
resource "google_dns_record_set" "caa" {
name = google_dns_managed_zone.main.dns_name
managed_zone = google_dns_managed_zone.main.name
type = "CAA"
ttl = 300
project = var.gcp_project_id
rrdatas = [
"0 issue \"pki.goog\"",
"0 issue \"letsencrypt.org\""
]
}
Updated 12 days ago