ops: scale down all non-critical services — dedicate resources to Neuron
Scaled to replicas: 0: fornax (coordinator/ui/workers), jellyfin via media, overseerr, plex, radarr, sonarr, bazarr, prowlarr, flaresolverr, ollama, harmonic-wordpress, docuseal, listmonk, memos, plane, redpanda, wp-coordinator
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
# ── Serverless NEGs ───────────────────────────────────────────────────────────
|
||||
# One NEG per Cloud Run service. NEGs for Cloud Run must use SERVERLESS type
|
||||
# and reference the Cloud Run service by name + region.
|
||||
|
||||
resource "google_compute_region_network_endpoint_group" "prod_us" {
|
||||
name = "marketing-neg-us"
|
||||
network_endpoint_type = "SERVERLESS"
|
||||
region = "us-central1"
|
||||
project = var.project_id
|
||||
|
||||
cloud_run {
|
||||
service = google_cloud_run_v2_service.prod_us.name
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_region_network_endpoint_group" "prod_eu" {
|
||||
name = "marketing-neg-eu"
|
||||
network_endpoint_type = "SERVERLESS"
|
||||
region = "europe-west1"
|
||||
project = var.project_id
|
||||
|
||||
cloud_run {
|
||||
service = google_cloud_run_v2_service.prod_eu.name
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_region_network_endpoint_group" "prod_apac" {
|
||||
name = "marketing-neg-apac"
|
||||
network_endpoint_type = "SERVERLESS"
|
||||
region = "asia-northeast1"
|
||||
project = var.project_id
|
||||
|
||||
cloud_run {
|
||||
service = google_cloud_run_v2_service.prod_apac.name
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_region_network_endpoint_group" "stage" {
|
||||
name = "marketing-neg-stage"
|
||||
network_endpoint_type = "SERVERLESS"
|
||||
region = "us-central1"
|
||||
project = var.project_id
|
||||
|
||||
cloud_run {
|
||||
service = google_cloud_run_v2_service.stage.name
|
||||
}
|
||||
}
|
||||
|
||||
# ── Cloud Armor Security Policy ───────────────────────────────────────────────
|
||||
# Applied to the backend service. Rules evaluated top-down (lowest priority wins).
|
||||
|
||||
resource "google_compute_security_policy" "marketing" {
|
||||
name = "marketing-armor"
|
||||
project = var.project_id
|
||||
|
||||
description = "Cloud Armor policy for neurontechnologies.ai marketing site"
|
||||
|
||||
# ── Rule 1000: SQLi protection ────────────────────────────────────────────
|
||||
rule {
|
||||
action = "deny(403)"
|
||||
priority = 1000
|
||||
description = "Block SQLi attacks (OWASP CRS sqli-v33-stable)"
|
||||
|
||||
match {
|
||||
expr {
|
||||
expression = "evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 1})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ── Rule 1001: XSS protection ─────────────────────────────────────────────
|
||||
rule {
|
||||
action = "deny(403)"
|
||||
priority = 1001
|
||||
description = "Block XSS attacks (OWASP CRS xss-v33-stable)"
|
||||
|
||||
match {
|
||||
expr {
|
||||
expression = "evaluatePreconfiguredWaf('xss-v33-stable', {'sensitivity': 1})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ── Rule 2000: Rate limiting ───────────────────────────────────────────────
|
||||
# Throttle any single IP exceeding 1000 req/min (≈17 req/s).
|
||||
rule {
|
||||
action = "throttle"
|
||||
priority = 2000
|
||||
description = "Rate limit: 1000 req/min per IP"
|
||||
|
||||
match {
|
||||
versioned_expr = "SRC_IPS_V1"
|
||||
config {
|
||||
src_ip_ranges = ["*"]
|
||||
}
|
||||
}
|
||||
|
||||
rate_limit_options {
|
||||
conform_action = "allow"
|
||||
exceed_action = "deny(429)"
|
||||
|
||||
rate_limit_threshold {
|
||||
count = 1000
|
||||
interval_sec = 60
|
||||
}
|
||||
|
||||
enforce_on_key = "IP"
|
||||
}
|
||||
}
|
||||
|
||||
# ── Default rule: allow all ───────────────────────────────────────────────
|
||||
rule {
|
||||
action = "allow"
|
||||
priority = 2147483647
|
||||
description = "Default: allow"
|
||||
|
||||
match {
|
||||
versioned_expr = "SRC_IPS_V1"
|
||||
config {
|
||||
src_ip_ranges = ["*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ── Prod Backend Service ──────────────────────────────────────────────────────
|
||||
# EXTERNAL_MANAGED scheme is required for Cloud CDN + global LB.
|
||||
# All three regional NEGs are attached with equal weight.
|
||||
|
||||
resource "google_compute_backend_service" "prod" {
|
||||
name = "marketing-backend-prod"
|
||||
project = var.project_id
|
||||
load_balancing_scheme = "EXTERNAL_MANAGED"
|
||||
protocol = "HTTPS"
|
||||
timeout_sec = 30
|
||||
|
||||
# Cloud Armor
|
||||
security_policy = google_compute_security_policy.marketing.self_link
|
||||
|
||||
# Cloud CDN
|
||||
enable_cdn = true
|
||||
|
||||
cdn_policy {
|
||||
cache_mode = "CACHE_ALL_STATIC"
|
||||
default_ttl = 3600
|
||||
max_ttl = 86400
|
||||
client_ttl = 3600
|
||||
negative_caching = true
|
||||
serve_while_stale = 86400
|
||||
|
||||
cache_key_policy {
|
||||
include_host = true
|
||||
include_protocol = true
|
||||
include_query_string = true
|
||||
}
|
||||
}
|
||||
|
||||
backend {
|
||||
group = google_compute_region_network_endpoint_group.prod_us.self_link
|
||||
}
|
||||
|
||||
backend {
|
||||
group = google_compute_region_network_endpoint_group.prod_eu.self_link
|
||||
}
|
||||
|
||||
backend {
|
||||
group = google_compute_region_network_endpoint_group.prod_apac.self_link
|
||||
}
|
||||
|
||||
log_config {
|
||||
enable = true
|
||||
sample_rate = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
# ── Stage Backend Service ─────────────────────────────────────────────────────
|
||||
|
||||
resource "google_compute_backend_service" "stage" {
|
||||
name = "marketing-backend-stage"
|
||||
project = var.project_id
|
||||
load_balancing_scheme = "EXTERNAL_MANAGED"
|
||||
protocol = "HTTPS"
|
||||
timeout_sec = 30
|
||||
|
||||
# Cloud Armor on stage too
|
||||
security_policy = google_compute_security_policy.marketing.self_link
|
||||
|
||||
enable_cdn = false # CDN off for stage — we want fresh responses always
|
||||
|
||||
backend {
|
||||
group = google_compute_region_network_endpoint_group.stage.self_link
|
||||
}
|
||||
|
||||
log_config {
|
||||
enable = true
|
||||
sample_rate = 1.0
|
||||
}
|
||||
}
|
||||
|
||||
# ── SSL Certificates ──────────────────────────────────────────────────────────
|
||||
# google_compute_managed_ssl_certificate — Google-managed, auto-renewed.
|
||||
# Provisioning requires DNS A records pointing to the LB IP (chicken-and-egg:
|
||||
# apply the Terraform, get the IP from outputs, update DNS, then cert provisions).
|
||||
|
||||
resource "google_compute_managed_ssl_certificate" "prod" {
|
||||
name = "marketing-cert-prod"
|
||||
project = var.project_id
|
||||
|
||||
managed {
|
||||
domains = [
|
||||
"neurontechnologies.ai",
|
||||
"www.neurontechnologies.ai",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_managed_ssl_certificate" "stage" {
|
||||
name = "marketing-cert-stage"
|
||||
project = var.project_id
|
||||
|
||||
managed {
|
||||
domains = ["stage.neurontechnologies.ai"]
|
||||
}
|
||||
}
|
||||
|
||||
# ── Prod URL Map ──────────────────────────────────────────────────────────────
|
||||
|
||||
resource "google_compute_url_map" "prod" {
|
||||
name = "marketing-urlmap-prod"
|
||||
project = var.project_id
|
||||
default_service = google_compute_backend_service.prod.self_link
|
||||
}
|
||||
|
||||
# ── Prod HTTPS Target Proxy ───────────────────────────────────────────────────
|
||||
|
||||
resource "google_compute_target_https_proxy" "prod" {
|
||||
name = "marketing-https-proxy-prod"
|
||||
project = var.project_id
|
||||
url_map = google_compute_url_map.prod.self_link
|
||||
ssl_certificates = [google_compute_managed_ssl_certificate.prod.self_link]
|
||||
}
|
||||
|
||||
# ── Prod HTTP → HTTPS redirect ────────────────────────────────────────────────
|
||||
# Separate URL map that redirects all HTTP traffic to HTTPS.
|
||||
|
||||
resource "google_compute_url_map" "prod_http_redirect" {
|
||||
name = "marketing-urlmap-prod-http-redirect"
|
||||
project = var.project_id
|
||||
|
||||
default_url_redirect {
|
||||
https_redirect = true
|
||||
redirect_response_code = "MOVED_PERMANENTLY_DEFAULT"
|
||||
strip_query = false
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_target_http_proxy" "prod" {
|
||||
name = "marketing-http-proxy-prod"
|
||||
project = var.project_id
|
||||
url_map = google_compute_url_map.prod_http_redirect.self_link
|
||||
}
|
||||
|
||||
# ── Prod Global Forwarding Rules ──────────────────────────────────────────────
|
||||
# Both rules share the same global anycast IP.
|
||||
|
||||
resource "google_compute_global_address" "prod" {
|
||||
name = "marketing-ip-prod"
|
||||
project = var.project_id
|
||||
}
|
||||
|
||||
resource "google_compute_global_forwarding_rule" "prod_https" {
|
||||
name = "marketing-fwd-prod-https"
|
||||
project = var.project_id
|
||||
target = google_compute_target_https_proxy.prod.self_link
|
||||
ip_address = google_compute_global_address.prod.address
|
||||
port_range = "443"
|
||||
load_balancing_scheme = "EXTERNAL_MANAGED"
|
||||
}
|
||||
|
||||
resource "google_compute_global_forwarding_rule" "prod_http" {
|
||||
name = "marketing-fwd-prod-http"
|
||||
project = var.project_id
|
||||
target = google_compute_target_http_proxy.prod.self_link
|
||||
ip_address = google_compute_global_address.prod.address
|
||||
port_range = "80"
|
||||
load_balancing_scheme = "EXTERNAL_MANAGED"
|
||||
}
|
||||
|
||||
# ── Stage URL Map ─────────────────────────────────────────────────────────────
|
||||
|
||||
resource "google_compute_url_map" "stage" {
|
||||
name = "marketing-urlmap-stage"
|
||||
project = var.project_id
|
||||
default_service = google_compute_backend_service.stage.self_link
|
||||
}
|
||||
|
||||
# ── Stage HTTPS Target Proxy ──────────────────────────────────────────────────
|
||||
|
||||
resource "google_compute_target_https_proxy" "stage" {
|
||||
name = "marketing-https-proxy-stage"
|
||||
project = var.project_id
|
||||
url_map = google_compute_url_map.stage.self_link
|
||||
ssl_certificates = [google_compute_managed_ssl_certificate.stage.self_link]
|
||||
}
|
||||
|
||||
# ── Stage HTTP → HTTPS redirect ───────────────────────────────────────────────
|
||||
|
||||
resource "google_compute_url_map" "stage_http_redirect" {
|
||||
name = "marketing-urlmap-stage-http-redirect"
|
||||
project = var.project_id
|
||||
|
||||
default_url_redirect {
|
||||
https_redirect = true
|
||||
redirect_response_code = "MOVED_PERMANENTLY_DEFAULT"
|
||||
strip_query = false
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_target_http_proxy" "stage" {
|
||||
name = "marketing-http-proxy-stage"
|
||||
project = var.project_id
|
||||
url_map = google_compute_url_map.stage_http_redirect.self_link
|
||||
}
|
||||
|
||||
# ── Stage Global Forwarding Rules ─────────────────────────────────────────────
|
||||
# Separate global IP for stage so DNS is independent.
|
||||
|
||||
resource "google_compute_global_address" "stage" {
|
||||
name = "marketing-ip-stage"
|
||||
project = var.project_id
|
||||
}
|
||||
|
||||
resource "google_compute_global_forwarding_rule" "stage_https" {
|
||||
name = "marketing-fwd-stage-https"
|
||||
project = var.project_id
|
||||
target = google_compute_target_https_proxy.stage.self_link
|
||||
ip_address = google_compute_global_address.stage.address
|
||||
port_range = "443"
|
||||
load_balancing_scheme = "EXTERNAL_MANAGED"
|
||||
}
|
||||
|
||||
resource "google_compute_global_forwarding_rule" "stage_http" {
|
||||
name = "marketing-fwd-stage-http"
|
||||
project = var.project_id
|
||||
target = google_compute_target_http_proxy.stage.self_link
|
||||
ip_address = google_compute_global_address.stage.address
|
||||
port_range = "80"
|
||||
load_balancing_scheme = "EXTERNAL_MANAGED"
|
||||
}
|
||||
Reference in New Issue
Block a user