Files
Claude-Code-Monitor/deployments/terraform/providers/gcp/main.tf
T
nntrivi2001 57dc91585d feat: Claude Code Monitor — lanes, pipelines and a merged workspace
Internal SmartGift build of a Claude Code monitoring dashboard.

Lanes: a durable unit of parallel agent work, one per working directory,
tracked across session restarts. Managed lanes are git worktrees the
dashboard provisions and can reset or remove behind a three-check destroy
guard and a counted preflight; adopted lanes are directories you already
own and are never destroyable.

Pipelines: a lane moves through pipeline stages. A stage the agent declares
with evidence renders green; a stage inferred from the tool-event stream
renders dashed amber and never counts as done. Detection is forward-only
within a 30-minute window, and never writes the declared stage.

Workspace: one page at /run with a lane grid, the selected lane's pipeline,
and a full Claude console behind a disclosure.
2026-07-30 14:39:03 +07:00

679 lines
20 KiB
Terraform
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ─────────────────────────────────────────────────────────────────────────────
# GCP Provider Full implementation for Claude Code Agent Monitor
#
# Architecture:
# VPC → Cloud Run (blue/green) → Filestore → Cloud Load Balancer
# → Cloud Monitoring → Managed SSL Certificate
#
# Cloud Run is chosen over GKE for cost efficiency and operational simplicity
# for this containerised workload. Filestore provides NFS for SQLite.
# ─────────────────────────────────────────────────────────────────────────────
provider "google" {
project = var.gcp_project_id
region = var.region
}
provider "google-beta" {
project = var.gcp_project_id
region = var.region
}
# ── Locals ──────────────────────────────────────────────────────────────────
locals {
name_prefix = lower(replace("${var.project_name}-${var.environment}", "_", "-"))
common_labels = merge(
{
project = replace(var.project_name, "-", "_")
environment = var.environment
managed_by = "terraform"
cloud_provider = "gcp"
},
{ for k, v in var.tags : replace(k, "-", "_") => replace(v, "-", "_") },
)
}
# ── Enable required APIs ───────────────────────────────────────────────────
resource "google_project_service" "apis" {
for_each = toset([
"run.googleapis.com",
"compute.googleapis.com",
"file.googleapis.com",
"vpcaccess.googleapis.com",
"monitoring.googleapis.com",
"logging.googleapis.com",
"certificatemanager.googleapis.com",
])
service = each.value
disable_on_destroy = false
}
# ─────────────────────────────────────────────────────────────────────────────
# VPC Network
# ─────────────────────────────────────────────────────────────────────────────
resource "google_compute_network" "main" {
name = "${local.name_prefix}-vpc"
auto_create_subnetworks = false
depends_on = [google_project_service.apis]
}
resource "google_compute_subnetwork" "private" {
name = "${local.name_prefix}-private"
ip_cidr_range = var.private_subnet_cidrs[0]
region = var.region
network = google_compute_network.main.id
private_ip_google_access = true
log_config {
aggregation_interval = "INTERVAL_5_SEC"
flow_sampling = 0.5
}
}
resource "google_compute_subnetwork" "proxy" {
name = "${local.name_prefix}-proxy"
ip_cidr_range = "10.0.100.0/24"
region = var.region
network = google_compute_network.main.id
purpose = "REGIONAL_MANAGED_PROXY"
role = "ACTIVE"
}
# Cloud NAT for outbound internet
resource "google_compute_router" "main" {
name = "${local.name_prefix}-router"
region = var.region
network = google_compute_network.main.id
}
resource "google_compute_router_nat" "main" {
name = "${local.name_prefix}-nat"
router = google_compute_router.main.name
region = var.region
nat_ip_allocate_option = "AUTO_ONLY"
source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES"
log_config {
enable = true
filter = "ERRORS_ONLY"
}
}
# VPC Connector for Cloud Run → Filestore
resource "google_vpc_access_connector" "main" {
name = "${local.name_prefix}-conn"
region = var.region
network = google_compute_network.main.id
ip_cidr_range = "10.0.200.0/28"
min_instances = 2
max_instances = var.environment == "production" ? 10 : 3
depends_on = [google_project_service.apis]
}
# Firewall rules
resource "google_compute_firewall" "allow_health_checks" {
name = "${local.name_prefix}-allow-health-checks"
network = google_compute_network.main.id
allow {
protocol = "tcp"
ports = [tostring(var.app_port), tostring(var.mcp_port)]
}
source_ranges = ["130.211.0.0/22", "35.191.0.0/16"] # GCP health check ranges
target_tags = ["${local.name_prefix}-app"]
}
resource "google_compute_firewall" "allow_internal" {
name = "${local.name_prefix}-allow-internal"
network = google_compute_network.main.id
allow {
protocol = "tcp"
ports = [tostring(var.app_port), tostring(var.mcp_port), "2049"]
}
source_ranges = [var.vpc_cidr]
}
# ─────────────────────────────────────────────────────────────────────────────
# Filestore (NFS for SQLite persistence)
# ─────────────────────────────────────────────────────────────────────────────
resource "google_filestore_instance" "main" {
name = "${local.name_prefix}-data"
location = "${var.region}-b"
tier = var.environment == "production" ? "BASIC_SSD" : "BASIC_HDD"
file_shares {
name = "appdata"
capacity_gb = var.storage_size_gb
}
networks {
network = google_compute_network.main.name
modes = ["MODE_IPV4"]
}
labels = local.common_labels
depends_on = [google_project_service.apis]
lifecycle {
prevent_destroy = true
}
}
# ─────────────────────────────────────────────────────────────────────────────
# Cloud Run services (Blue / Green)
# ─────────────────────────────────────────────────────────────────────────────
resource "google_cloud_run_v2_service" "blue" {
name = "${local.name_prefix}-blue"
location = var.region
ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"
template {
scaling {
min_instance_count = var.active_deployment_slot == "blue" ? var.min_replicas : 0
max_instance_count = var.active_deployment_slot == "blue" ? var.max_replicas : 1
}
vpc_access {
connector = google_vpc_access_connector.main.id
egress = "ALL_TRAFFIC"
}
containers {
image = var.app_container_image
name = "app"
ports {
container_port = var.app_port
}
resources {
limits = {
cpu = "${var.cpu}m"
memory = "${var.memory}Mi"
}
}
dynamic "env" {
for_each = var.environment_variables
content {
name = env.key
value = env.value
}
}
env {
name = "FILESTORE_IP"
value = google_filestore_instance.main.networks[0].ip_addresses[0]
}
startup_probe {
http_get {
path = var.health_check_path
port = var.app_port
}
initial_delay_seconds = 10
period_seconds = 10
failure_threshold = 5
}
liveness_probe {
http_get {
path = var.health_check_path
port = var.app_port
}
period_seconds = 30
failure_threshold = 3
}
volume_mounts {
name = "app-data"
mount_path = "/app/data"
}
}
dynamic "containers" {
for_each = var.mcp_container_image != "" ? [1] : []
content {
image = var.mcp_container_image
name = "mcp-sidecar"
ports {
container_port = var.mcp_port
}
resources {
limits = {
cpu = "250m"
memory = "256Mi"
}
}
env {
name = "NODE_ENV"
value = "production"
}
}
}
volumes {
name = "app-data"
nfs {
server = google_filestore_instance.main.networks[0].ip_addresses[0]
path = "/appdata"
read_only = false
}
}
session_affinity = true
timeout = "300s"
}
labels = local.common_labels
depends_on = [google_project_service.apis]
lifecycle {
ignore_changes = [
client,
client_version,
]
}
}
resource "google_cloud_run_v2_service" "green" {
name = "${local.name_prefix}-green"
location = var.region
ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"
template {
scaling {
min_instance_count = var.active_deployment_slot == "green" ? var.min_replicas : 0
max_instance_count = var.active_deployment_slot == "green" ? var.max_replicas : 1
}
vpc_access {
connector = google_vpc_access_connector.main.id
egress = "ALL_TRAFFIC"
}
containers {
image = var.app_container_image
name = "app"
ports {
container_port = var.app_port
}
resources {
limits = {
cpu = "${var.cpu}m"
memory = "${var.memory}Mi"
}
}
dynamic "env" {
for_each = var.environment_variables
content {
name = env.key
value = env.value
}
}
env {
name = "FILESTORE_IP"
value = google_filestore_instance.main.networks[0].ip_addresses[0]
}
startup_probe {
http_get {
path = var.health_check_path
port = var.app_port
}
initial_delay_seconds = 10
period_seconds = 10
failure_threshold = 5
}
liveness_probe {
http_get {
path = var.health_check_path
port = var.app_port
}
period_seconds = 30
failure_threshold = 3
}
volume_mounts {
name = "app-data"
mount_path = "/app/data"
}
}
volumes {
name = "app-data"
nfs {
server = google_filestore_instance.main.networks[0].ip_addresses[0]
path = "/appdata"
read_only = false
}
}
session_affinity = true
timeout = "300s"
}
labels = local.common_labels
depends_on = [google_project_service.apis]
lifecycle {
ignore_changes = [
client,
client_version,
]
}
}
# IAM restrict access to load balancer service account only
# Cloud Run ingress is set to INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER,
# so public IAM bindings are not needed. The LB routes traffic internally.
# To grant specific service account access, replace with:
# member = "serviceAccount:<your-lb-service-account>@<project>.iam.gserviceaccount.com"
#
# resource "google_cloud_run_v2_service_iam_member" "blue_invoker" {
# name = google_cloud_run_v2_service.blue.name
# location = var.region
# role = "roles/run.invoker"
# member = "serviceAccount:${var.project_id}-compute@developer.gserviceaccount.com"
# }
#
# resource "google_cloud_run_v2_service_iam_member" "green_invoker" {
# name = google_cloud_run_v2_service.green.name
# location = var.region
# role = "roles/run.invoker"
# member = "serviceAccount:${var.project_id}-compute@developer.gserviceaccount.com"
# }
# ─────────────────────────────────────────────────────────────────────────────
# External Application Load Balancer
# ─────────────────────────────────────────────────────────────────────────────
# Serverless NEGs for Cloud Run
resource "google_compute_region_network_endpoint_group" "blue" {
name = "${local.name_prefix}-blue-neg"
region = var.region
network_endpoint_type = "SERVERLESS"
cloud_run {
service = google_cloud_run_v2_service.blue.name
}
}
resource "google_compute_region_network_endpoint_group" "green" {
name = "${local.name_prefix}-green-neg"
region = var.region
network_endpoint_type = "SERVERLESS"
cloud_run {
service = google_cloud_run_v2_service.green.name
}
}
# Backend service with weighted backends for blue/green
resource "google_compute_backend_service" "main" {
name = "${local.name_prefix}-backend"
protocol = "HTTP"
load_balancing_scheme = "EXTERNAL_MANAGED"
timeout_sec = 300 # WebSocket support
session_affinity = "GENERATED_COOKIE"
backend {
group = google_compute_region_network_endpoint_group.blue.id
capacity_scaler = var.blue_weight / 100
}
backend {
group = google_compute_region_network_endpoint_group.green.id
capacity_scaler = var.green_weight / 100
}
health_checks = [google_compute_health_check.main.id]
log_config {
enable = true
sample_rate = 1.0
}
}
resource "google_compute_health_check" "main" {
name = "${local.name_prefix}-hc"
http_health_check {
port = var.app_port
request_path = var.health_check_path
}
check_interval_sec = var.health_check_interval
timeout_sec = var.health_check_timeout
healthy_threshold = var.health_check_healthy_threshold
unhealthy_threshold = var.health_check_unhealthy_threshold
}
# URL map
resource "google_compute_url_map" "main" {
name = "${local.name_prefix}-urlmap"
default_service = google_compute_backend_service.main.id
}
# Managed SSL certificate (optional)
resource "google_compute_managed_ssl_certificate" "main" {
count = var.domain_name != "" ? 1 : 0
name = "${local.name_prefix}-cert"
managed {
domains = [var.domain_name]
}
}
# HTTPS proxy
resource "google_compute_target_https_proxy" "main" {
count = var.domain_name != "" ? 1 : 0
name = "${local.name_prefix}-https-proxy"
url_map = google_compute_url_map.main.id
ssl_certificates = [google_compute_managed_ssl_certificate.main[0].id]
}
# HTTP proxy (for redirect or direct access)
resource "google_compute_target_http_proxy" "main" {
name = "${local.name_prefix}-http-proxy"
url_map = google_compute_url_map.main.id
}
# Global forwarding rules
resource "google_compute_global_forwarding_rule" "https" {
count = var.domain_name != "" ? 1 : 0
name = "${local.name_prefix}-https"
target = google_compute_target_https_proxy.main[0].id
port_range = "443"
ip_protocol = "TCP"
load_balancing_scheme = "EXTERNAL_MANAGED"
}
resource "google_compute_global_forwarding_rule" "http" {
name = "${local.name_prefix}-http"
target = google_compute_target_http_proxy.main.id
port_range = "80"
ip_protocol = "TCP"
load_balancing_scheme = "EXTERNAL_MANAGED"
}
# ─────────────────────────────────────────────────────────────────────────────
# Cloud Monitoring Alert policies
# ─────────────────────────────────────────────────────────────────────────────
resource "google_monitoring_notification_channel" "email" {
count = var.alert_email != "" ? 1 : 0
display_name = "${local.name_prefix}-email"
type = "email"
labels = {
email_address = var.alert_email
}
}
resource "google_monitoring_alert_policy" "high_latency" {
count = var.enable_monitoring ? 1 : 0
display_name = "${local.name_prefix}-high-latency"
combiner = "OR"
conditions {
display_name = "Cloud Run request latency > 2s"
condition_threshold {
filter = "resource.type=\"cloud_run_revision\" AND metric.type=\"run.googleapis.com/request_latencies\""
comparison = "COMPARISON_GT"
duration = "300s"
threshold_value = 2000 # ms
aggregations {
alignment_period = "60s"
per_series_aligner = "ALIGN_PERCENTILE_99"
}
}
}
notification_channels = var.alert_email != "" ? [google_monitoring_notification_channel.email[0].id] : []
alert_strategy {
auto_close = "604800s"
}
}
resource "google_monitoring_alert_policy" "high_error_rate" {
count = var.enable_monitoring ? 1 : 0
display_name = "${local.name_prefix}-high-error-rate"
combiner = "OR"
conditions {
display_name = "Cloud Run 5xx error rate"
condition_threshold {
filter = "resource.type=\"cloud_run_revision\" AND metric.type=\"run.googleapis.com/request_count\" AND metric.labels.response_code_class=\"5xx\""
comparison = "COMPARISON_GT"
duration = "300s"
threshold_value = 10
aggregations {
alignment_period = "60s"
per_series_aligner = "ALIGN_RATE"
}
}
}
notification_channels = var.alert_email != "" ? [google_monitoring_notification_channel.email[0].id] : []
}
# ─────────────────────────────────────────────────────────────────────────────
# Cloud Monitoring Dashboard
# ─────────────────────────────────────────────────────────────────────────────
resource "google_monitoring_dashboard" "main" {
count = var.enable_monitoring ? 1 : 0
dashboard_json = jsonencode({
displayName = "${local.name_prefix} Dashboard"
gridLayout = {
columns = 2
widgets = [
{
title = "Request Count"
xyChart = {
dataSets = [{
timeSeriesQuery = {
timeSeriesFilter = {
filter = "resource.type=\"cloud_run_revision\" AND metric.type=\"run.googleapis.com/request_count\""
aggregation = {
alignmentPeriod = "60s"
perSeriesAligner = "ALIGN_RATE"
}
}
}
}]
}
},
{
title = "Request Latency (p99)"
xyChart = {
dataSets = [{
timeSeriesQuery = {
timeSeriesFilter = {
filter = "resource.type=\"cloud_run_revision\" AND metric.type=\"run.googleapis.com/request_latencies\""
aggregation = {
alignmentPeriod = "60s"
perSeriesAligner = "ALIGN_PERCENTILE_99"
}
}
}
}]
}
},
{
title = "Instance Count"
xyChart = {
dataSets = [{
timeSeriesQuery = {
timeSeriesFilter = {
filter = "resource.type=\"cloud_run_revision\" AND metric.type=\"run.googleapis.com/container/instance_count\""
aggregation = {
alignmentPeriod = "60s"
perSeriesAligner = "ALIGN_MEAN"
}
}
}
}]
}
},
{
title = "CPU Utilization"
xyChart = {
dataSets = [{
timeSeriesQuery = {
timeSeriesFilter = {
filter = "resource.type=\"cloud_run_revision\" AND metric.type=\"run.googleapis.com/container/cpu/utilizations\""
aggregation = {
alignmentPeriod = "60s"
perSeriesAligner = "ALIGN_PERCENTILE_99"
}
}
}
}]
}
},
]
}
})
}