From 0ae8cf66a3aa940cf644b07583d12df056aa24cc Mon Sep 17 00:00:00 2001 From: Renato Rudnicki Date: Fri, 12 Dec 2025 14:50:09 -0300 Subject: [PATCH 1/7] add missing code --- .../image_fail_502/Dockerfile | 10 + .../v2_multi_regions/image_fail_502/app.py | 14 ++ examples/v2_multi_regions/load_balancer.tf | 193 ++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 examples/v2_multi_regions/image_fail_502/Dockerfile create mode 100644 examples/v2_multi_regions/image_fail_502/app.py create mode 100644 examples/v2_multi_regions/load_balancer.tf diff --git a/examples/v2_multi_regions/image_fail_502/Dockerfile b/examples/v2_multi_regions/image_fail_502/Dockerfile new file mode 100644 index 000000000..e07f88620 --- /dev/null +++ b/examples/v2_multi_regions/image_fail_502/Dockerfile @@ -0,0 +1,10 @@ +FROM python:3.11-slim + +RUN pip install flask + +ENV PORT=8080 + +COPY app.py /app.py + +CMD ["python", "/app.py"] + diff --git a/examples/v2_multi_regions/image_fail_502/app.py b/examples/v2_multi_regions/image_fail_502/app.py new file mode 100644 index 000000000..b508f6a06 --- /dev/null +++ b/examples/v2_multi_regions/image_fail_502/app.py @@ -0,0 +1,14 @@ +import os +import sys +from flask import Flask + +app = Flask(__name__) + +@app.route("/") +def fail(): + # Isso mata o container abruptamente. + # O Cloud Run/Load Balancer retornará 502 Bad Gateway. + os._exit(1) + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=8080) diff --git a/examples/v2_multi_regions/load_balancer.tf b/examples/v2_multi_regions/load_balancer.tf new file mode 100644 index 000000000..90ddd80e0 --- /dev/null +++ b/examples/v2_multi_regions/load_balancer.tf @@ -0,0 +1,193 @@ +/** + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +########################################### +# 1. Global IP +########################################### + +resource "google_compute_global_address" "lb_ip" { + count = var.lb_ip_address == null ? 1 : 0 + name = "cloudrun-global-ip" + project = var.project_id +} + +locals { + lb_ip = var.lb_ip_address != null ? var.lb_ip_address : google_compute_global_address.lb_ip[0].address + lb_domain = var.lb_domain != null ? var.lb_domain : "${local.lb_ip}.sslip.io" +} + +########################################### +# 2. Serverless NEGs +########################################### + +resource "google_compute_region_network_endpoint_group" "cloudrun_negs" { + for_each = module.cloud_run_v2_multiregion + project = var.project_id + region = each.key + name = "neg-cloudrun-${each.key}" + network_endpoint_type = "SERVERLESS" + + cloud_run { + service = each.value.service_name + } +} + +########################################### +# 3. Backend Service Global +########################################### + +resource "google_compute_backend_service" "cloudrun_backend_global" { + project = var.project_id + name = "cloudrun-backend-global" + protocol = "HTTP" + load_balancing_scheme = "EXTERNAL_MANAGED" + enable_cdn = false + + connection_draining_timeout_sec = 0 + + outlier_detection { + #Defines the number of consecutive errors a backend can have before being considered an “outlier” and potentially ejected. + consecutive_errors = 1 + + #Defines the maximum percentage of backend instances that can be ejected from a service. + consecutive_gateway_failure = 1 + + #Percentage of time that the consecutive_errors rule is enforced. + enforcing_consecutive_errors = 100 + enforcing_consecutive_gateway_failure = 100 + + max_ejection_percent = 100 + + interval { + #The LB checks the backend state every 10 seconds. + seconds = 1 + } + + base_ejection_time { + #If us-west1 is ejected due to infrastructure errors, it will remain out of service for 300 seconds before the LB tries to send it traffic again. + seconds = 300 + } + } + + dynamic "backend" { + for_each = google_compute_region_network_endpoint_group.cloudrun_negs + content { + group = backend.value.id + } + } + + log_config { + enable = true + sample_rate = 1.0 + } +} + +########################################### +# 4. URL Map +########################################### + +resource "google_compute_url_map" "cloudrun_urlmap" { + name = "cloudrun-urlmap" + project = var.project_id + + default_service = google_compute_backend_service.cloudrun_backend_global.id + + host_rule { + hosts = ["*"] + path_matcher = "allpaths" + } + + path_matcher { + name = "allpaths" + default_service = google_compute_backend_service.cloudrun_backend_global.id + + path_rule { + paths = ["/*"] + + route_action { + weighted_backend_services { + backend_service = google_compute_backend_service.cloudrun_backend_global.id + weight = 100 + } + + retry_policy { + #Specifies that the load balancer will retry the request up to 20 times if it fails under the conditions listed below. + num_retries = 20 + + per_try_timeout { + #Each retry is allowed to run for a maximum of 10 seconds before being considered a failed attempt. + seconds = 10 + } + + #Lists the types of errors that will trigger a retry. + retry_conditions = [ + "5xx", + "gateway-error", + "connect-failure", + "retriable-4xx", + "gateway-timeout", + "dead-deadline-exceeded", + "503" + ] + } + } + } + } +} + +########################################### +# 5. Managed SSL Certificate +########################################### + +resource "google_compute_managed_ssl_certificate" "cloudrun_cert" { + name = "cloudrun-cert" + project = var.project_id + + managed { + domains = [local.lb_domain] + } +} + +########################################### +# 6. HTTPS Proxy +########################################### + +resource "google_compute_target_https_proxy" "cloudrun_https_proxy" { + name = "cloudrun-https-proxy" + project = var.project_id + ssl_certificates = [google_compute_managed_ssl_certificate.cloudrun_cert.id] + url_map = google_compute_url_map.cloudrun_urlmap.id +} + +########################################### +# 7. Forwarding Rule (HTTPS) +########################################### + +resource "google_compute_global_forwarding_rule" "cloudrun_https_rule" { + name = "cloudrun-https-rule" + project = var.project_id + + ip_address = var.lb_ip_address != null ? var.lb_ip_address : google_compute_global_address.lb_ip[0].id + ip_protocol = "TCP" + port_range = "443" + target = google_compute_target_https_proxy.cloudrun_https_proxy.id + load_balancing_scheme = "EXTERNAL_MANAGED" + + depends_on = [ + google_compute_target_https_proxy.cloudrun_https_proxy, + google_compute_managed_ssl_certificate.cloudrun_cert + ] +} From b56e9c335a7da6245a9b92145cced75b509ad2a1 Mon Sep 17 00:00:00 2001 From: Renato Rudnicki Date: Mon, 22 Dec 2025 13:01:46 -0300 Subject: [PATCH 2/7] fixed cloudrun multiregion --- README.md | 3 + .../image_fail_502/Dockerfile | 10 -- .../v2_multi_regions/image_fail_502/app.py | 14 --- examples/v2_multi_regions/load_balancer.tf | 68 +++------- examples/v2_multi_regions/main.tf | 119 ++++++++++++------ examples/v2_multi_regions/outputs.tf | 114 ++++++++++++++--- examples/v2_multi_regions/variables.tf | 51 ++++++-- metadata.yaml | 19 ++- modules/job-exec/metadata.yaml | 6 +- modules/secure-cloud-run-core/metadata.yaml | 6 +- .../secure-cloud-run-security/metadata.yaml | 6 +- modules/secure-cloud-run/metadata.yaml | 4 +- .../secure-serverless-harness/metadata.yaml | 2 + modules/secure-serverless-net/metadata.yaml | 2 + modules/v2/main.tf | 7 ++ modules/v2/metadata.yaml | 12 +- modules/v2/variables.tf | 8 ++ 17 files changed, 295 insertions(+), 156 deletions(-) delete mode 100644 examples/v2_multi_regions/image_fail_502/Dockerfile delete mode 100644 examples/v2_multi_regions/image_fail_502/app.py diff --git a/README.md b/README.md index 44aeac172..d29981edf 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ module "cloud_run" { | force\_override | Option to force override existing mapping | `bool` | `false` | no | | generate\_revision\_name | Option to enable revision name generation | `bool` | `true` | no | | image | GCR hosted image URL to deploy | `string` | n/a | yes | +| ingress | Restricts network access to your Cloud Run service | `string` | `"INGRESS_TRAFFIC_ALL"` | no | | limits | Resource limits to the container | `map(string)` | `null` | no | | liveness\_probe | Periodic probe of container liveness. Container will be restarted if the probe fails.
More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes |
object({
failure_threshold = optional(number, null)
initial_delay_seconds = optional(number, null)
timeout_seconds = optional(number, null)
period_seconds = optional(number, null)
http_get = optional(object({
path = optional(string)
http_headers = optional(list(object({
name = string
value = string
})), null)
}), null)
grpc = optional(object({
port = optional(number)
service = optional(string)
}), null)
})
| `null` | no | | location | Cloud Run service deployment location | `string` | n/a | yes | @@ -82,6 +83,8 @@ module "cloud_run" { | verified\_domain\_name | List of Custom Domain Name | `list(string)` | `[]` | no | | volume\_mounts | [Beta] Volume Mounts to be attached to the container (when using secret) |
list(object({
mount_path = string
name = string
}))
| `[]` | no | | volumes | [Beta] Volumes needed for environment variables (when using secret) |
list(object({
name = string
secret = set(object({
secret_name = string
items = map(string)
}))
}))
| `[]` | no | +| vpc\_connector | The full resource name of the VPC Access connector to use. Leave null to use Direct VPC Egress. | `string` | `null` | no | +| vpc\_egress | The outbound traffic setting for VPC. Use 'PRIVATE\_RANGES\_ONLY' with a connector. Use 'ALL\_TRAFFIC' for Direct VPC Egress. Set to null to disable all VPC egress. | `string` | `"ALL_TRAFFIC"` | no | ## Outputs diff --git a/examples/v2_multi_regions/image_fail_502/Dockerfile b/examples/v2_multi_regions/image_fail_502/Dockerfile deleted file mode 100644 index e07f88620..000000000 --- a/examples/v2_multi_regions/image_fail_502/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM python:3.11-slim - -RUN pip install flask - -ENV PORT=8080 - -COPY app.py /app.py - -CMD ["python", "/app.py"] - diff --git a/examples/v2_multi_regions/image_fail_502/app.py b/examples/v2_multi_regions/image_fail_502/app.py deleted file mode 100644 index b508f6a06..000000000 --- a/examples/v2_multi_regions/image_fail_502/app.py +++ /dev/null @@ -1,14 +0,0 @@ -import os -import sys -from flask import Flask - -app = Flask(__name__) - -@app.route("/") -def fail(): - # Isso mata o container abruptamente. - # O Cloud Run/Load Balancer retornará 502 Bad Gateway. - os._exit(1) - -if __name__ == "__main__": - app.run(host="0.0.0.0", port=8080) diff --git a/examples/v2_multi_regions/load_balancer.tf b/examples/v2_multi_regions/load_balancer.tf index 90ddd80e0..f31b6227e 100644 --- a/examples/v2_multi_regions/load_balancer.tf +++ b/examples/v2_multi_regions/load_balancer.tf @@ -33,15 +33,16 @@ locals { # 2. Serverless NEGs ########################################### -resource "google_compute_region_network_endpoint_group" "cloudrun_negs" { - for_each = module.cloud_run_v2_multiregion +resource "google_compute_region_network_endpoint_group" "cloudrun_neg" { + for_each = toset(var.regions) + project = var.project_id region = each.key name = "neg-cloudrun-${each.key}" network_endpoint_type = "SERVERLESS" cloud_run { - service = each.value.service_name + service = var.service_name } } @@ -50,49 +51,29 @@ resource "google_compute_region_network_endpoint_group" "cloudrun_negs" { ########################################### resource "google_compute_backend_service" "cloudrun_backend_global" { - project = var.project_id - name = "cloudrun-backend-global" + project = var.project_id + name = "cloudrun-backend-global" + protocol = "HTTP" load_balancing_scheme = "EXTERNAL_MANAGED" enable_cdn = false - connection_draining_timeout_sec = 0 - outlier_detection { - #Defines the number of consecutive errors a backend can have before being considered an “outlier” and potentially ejected. - consecutive_errors = 1 - - #Defines the maximum percentage of backend instances that can be ejected from a service. - consecutive_gateway_failure = 1 - - #Percentage of time that the consecutive_errors rule is enforced. + consecutive_errors = 5 + consecutive_gateway_failure = 3 enforcing_consecutive_errors = 100 enforcing_consecutive_gateway_failure = 100 - max_ejection_percent = 100 - - interval { - #The LB checks the backend state every 10 seconds. - seconds = 1 - } - - base_ejection_time { - #If us-west1 is ejected due to infrastructure errors, it will remain out of service for 300 seconds before the LB tries to send it traffic again. - seconds = 300 - } + base_ejection_time { seconds = 30 } + interval { seconds = 10 } } dynamic "backend" { - for_each = google_compute_region_network_endpoint_group.cloudrun_negs + for_each = google_compute_region_network_endpoint_group.cloudrun_neg content { group = backend.value.id } } - - log_config { - enable = true - sample_rate = 1.0 - } } ########################################### @@ -124,15 +105,8 @@ resource "google_compute_url_map" "cloudrun_urlmap" { } retry_policy { - #Specifies that the load balancer will retry the request up to 20 times if it fails under the conditions listed below. - num_retries = 20 - - per_try_timeout { - #Each retry is allowed to run for a maximum of 10 seconds before being considered a failed attempt. - seconds = 10 - } - - #Lists the types of errors that will trigger a retry. + num_retries = 10 + per_try_timeout { seconds = 30 } retry_conditions = [ "5xx", "gateway-error", @@ -149,7 +123,7 @@ resource "google_compute_url_map" "cloudrun_urlmap" { } ########################################### -# 5. Managed SSL Certificate +# 5. SSL & Proxy & Forwarding ########################################### resource "google_compute_managed_ssl_certificate" "cloudrun_cert" { @@ -161,10 +135,6 @@ resource "google_compute_managed_ssl_certificate" "cloudrun_cert" { } } -########################################### -# 6. HTTPS Proxy -########################################### - resource "google_compute_target_https_proxy" "cloudrun_https_proxy" { name = "cloudrun-https-proxy" project = var.project_id @@ -172,13 +142,9 @@ resource "google_compute_target_https_proxy" "cloudrun_https_proxy" { url_map = google_compute_url_map.cloudrun_urlmap.id } -########################################### -# 7. Forwarding Rule (HTTPS) -########################################### - resource "google_compute_global_forwarding_rule" "cloudrun_https_rule" { - name = "cloudrun-https-rule" - project = var.project_id + name = "cloudrun-https-rule" + project = var.project_id ip_address = var.lb_ip_address != null ? var.lb_ip_address : google_compute_global_address.lb_ip[0].id ip_protocol = "TCP" diff --git a/examples/v2_multi_regions/main.tf b/examples/v2_multi_regions/main.tf index 217f526a8..a5bf35570 100644 --- a/examples/v2_multi_regions/main.tf +++ b/examples/v2_multi_regions/main.tf @@ -14,24 +14,34 @@ * limitations under the License. */ -locals { - vpc_connectors_ids = { - for region, conn in google_vpc_access_connector.vpc_connectors : - region => conn.id - } +# ############################################################################## +# LOCALS +# ############################################################################## - vpc_config_valid = ( - (var.vpc_mode == "vpc-access-connector" && length(var.vpc_connectors) > 0) || - (var.vpc_mode == "direct-vpc-egress" && var.vpc_network != null && length(var.vpc_subnets) > 0) +locals { + vpc_flags = ( + var.vpc_mode == "vpc-access-connector" ? + "--vpc-connector=${var.vpc_connectors[var.primary_region].name} --vpc-egress=${var.vpc_egress}" : + var.vpc_mode == "direct-vpc-egress" ? + "--network=${var.vpc_network} --subnet=${var.vpc_subnets[var.primary_region]} --vpc-egress=${var.vpc_egress}" : + "" ) } +############################################################################### +# SERVICE ACCOUNT +############################################################################### + resource "google_service_account" "sa" { project = var.project_id account_id = "ci-cloud-run-v2-sa" - display_name = "Service account for ci-cloud-run-v2-sa" + display_name = "Service account for Cloud Run multi-region" } +############################################################################### +# SERVERLESS VPC ACCESS CONNECTORS (opcional) +############################################################################### + resource "google_vpc_access_connector" "vpc_connectors" { for_each = var.vpc_mode == "vpc-access-connector" ? var.vpc_connectors : {} @@ -47,51 +57,86 @@ resource "google_vpc_access_connector" "vpc_connectors" { max_instances = 4 } +############################################################################### +# VALIDATION +############################################################################### + +resource "null_resource" "validate_primary_region" { + count = contains(var.regions, var.primary_region) ? 0 : 1 + + provisioner "local-exec" { + command = "echo \"ERROR: primary_region (${var.primary_region}) must be one of: ${join(", ", var.regions)}\" && exit 1" + } +} + resource "null_resource" "validate_vpc" { - count = local.vpc_config_valid ? 0 : 1 + count = ( + var.vpc_mode != "default" && ( + (var.vpc_mode == "vpc-access-connector" && length(var.vpc_connectors) == 0) || + (var.vpc_mode == "direct-vpc-egress" && + (var.vpc_network == null || length(var.vpc_subnets) == 0) + ) + ) + ) ? 1 : 0 provisioner "local-exec" { - command = "echo 'ERROR: VPC configuration invalid. Check your vpc_mode, vpc_connectors, vpc_network and vpc_subnets'' && exit 1" + command = "echo 'ERROR: Invalid VPC configuration for selected vpc_mode' && exit 1" } } +############################################################################### +# CLOUD RUN MULTI-REGION DEPLOY +############################################################################### + module "cloud_run_v2_multiregion" { source = "../../modules/v2" - for_each = toset(var.regions) - - service_name = "cloudrun-multiregion-${each.key}" - project_id = var.project_id - location = each.key - + service_name = "cloudrun-service" + location = "global" + project_id = var.project_id create_service_account = false service_account = google_service_account.sa.email - cloud_run_deletion_protection = var.cloud_run_deletion_protection - vpc_access = ( - var.vpc_mode == "vpc-access-connector" - ? - { - connector = local.vpc_connectors_ids[each.key] - egress = "PRIVATE_RANGES_ONLY" - network_interfaces = null - } - : - { - connector = null - egress = var.vpc_egress - network_interfaces = { - network = var.vpc_network - subnetwork = var.vpc_subnets[each.key] - } - } - ) + multi_region_settings = { + regions = [ + "us-west1", + "europe-west1" + ] + } - containers = [ + containers = [ { container_image = "us-docker.pkg.dev/cloudrun/container/hello" container_name = "hello-world" + + # Check if app startd. If it fails,cloud run restart the container + + startup_probe = { + initial_delay_seconds = 5 # wait 5 seconds before start test + timeout_seconds = 3 # request has 3s to reply + period_seconds = 10 # Test each 10s + failure_threshold = 3 # fails after 3 wrong attempts (ex: 500, 502, timeout) + + http_get = { + path = "/" + port = 8080 + } + } + + # Liveness Probe: check if app remains running health + # If start get 5xx the container is restarted + liveness_probe = { + initial_delay_seconds = 5 + timeout_seconds = 3 + period_seconds = 15 + failure_threshold = 3 + + http_get = { + path = "/" + port = 8080 + } + } } ] } diff --git a/examples/v2_multi_regions/outputs.tf b/examples/v2_multi_regions/outputs.tf index d0a0512f4..f63d39250 100644 --- a/examples/v2_multi_regions/outputs.tf +++ b/examples/v2_multi_regions/outputs.tf @@ -14,49 +14,125 @@ * limitations under the License. */ +############################################################################### +# CLOUD RUN +############################################################################### + output "cloud_run_service_account" { description = "Service account used by Cloud Run instances." value = google_service_account.sa.email } -output "service_name" { - description = "Service names per region." - value = { - for region, mod in module.cloud_run_v2_multiregion : - region => mod.service_name - } +# output "service_name" { +# description = "Cloud Run service name (multi-region)." +# value = module.cloud_run_v2_multiregion.service_name +# } + +# output "service_uri" { +# description = "Cloud Run service URI (multi-region)." +# value = module.cloud_run_v2_multiregion.service_uri +# } + +# output "service_id" { +# description = "Cloud Run service ID (multi-region)." +# value = module.cloud_run_v2_multiregion.service_id +# } + +output "cloud_run_regions" { + description = "Regions where the Cloud Run service is deployed." + value = var.regions } -output "service_uri" { - description = "Service URIs per region." - value = { - for region, mod in module.cloud_run_v2_multiregion : - region => mod.service_uri - } +output "cloud_run_service_name" { + description = "Cloud Run service name." + value = var.service_name } -output "service_id" { - description = "Service IDs per region." +output "backend_service_name" { + description = "Global backend service name." + value = google_compute_backend_service.cloudrun_backend_global.name +} + +output "serverless_negs" { + description = "Serverless NEGs by region." value = { - for region, mod in module.cloud_run_v2_multiregion : - region => mod.service_id + for region, neg in google_compute_region_network_endpoint_group.cloudrun_neg : + region => neg.name } } +output "url_map_name" { + description = "URL map name." + value = google_compute_url_map.cloudrun_urlmap.name +} + +output "https_proxy_name" { + description = "HTTPS proxy name." + value = google_compute_target_https_proxy.cloudrun_https_proxy.name +} + +output "https_forwarding_rule_name" { + description = "HTTPS forwarding rule name." + value = google_compute_global_forwarding_rule.cloudrun_https_rule.name +} + + +############################################################################### +# VPC ACCESS CONNECTORS +############################################################################### + output "vpc_connectors_ids" { description = "VPC Access Connectors IDs by region." value = { for region, connector in google_vpc_access_connector.vpc_connectors : region => connector.id } - sensitive = false } output "vpc_connectors_names" { - description = "VPC Access Connectors name by region." + description = "VPC Access Connectors names by region." value = { for region, connector in google_vpc_access_connector.vpc_connectors : region => connector.name } - sensitive = false } + +############################################################################### +# LOAD BALANCER / NETWORKING +############################################################################### + +# output "serverless_neg_id" { +# description = "Serverless Network Endpoint Group ID used by the global load balancer." +# value = google_compute_region_network_endpoint_group.cloudrun_neg.id +# } + +output "backend_service_global" { + description = "Global backend service backing the Cloud Run multi-region service." + value = google_compute_backend_service.cloudrun_backend_global.id +} + +output "lb_ip" { + description = "Global IP address of the Load Balancer." + value = local.lb_ip +} + +output "lb_domain" { + description = "Domain for the Load Balancer (IP.sslip.io if no custom domain is provided)." + value = local.lb_domain +} + +output "lb_https_url" { + description = "HTTPS URL for the global Load Balancer." + value = "https://${local.lb_domain}" +} + +output "ssl_certificate_id" { + description = "Managed SSL certificate ID." + value = google_compute_managed_ssl_certificate.cloudrun_cert.id +} + +output "global_entrypoint" { + description = "Global HTTPS entrypoint for the Cloud Run multi-region service." + value = "https://${local.lb_domain}" +} + diff --git a/examples/v2_multi_regions/variables.tf b/examples/v2_multi_regions/variables.tf index 120cf5906..44984543f 100644 --- a/examples/v2_multi_regions/variables.tf +++ b/examples/v2_multi_regions/variables.tf @@ -21,10 +21,20 @@ variable "project_id" { variable "regions" { type = list(string) - description = "Regions where serverless vpc access connectors will be created." + description = "Regions where serverless VPC Access connectors will be created." default = ["us-west1", "europe-west1"] } +variable "service_name" { + type = string + default = "cloudrun-multiregion" +} + +variable "image" { + type = string + default = "us-docker.pkg.dev/cloudrun/container/hello:latest" +} + variable "cloud_run_deletion_protection" { type = bool description = "Prevents Terraform from destroying/recreating Cloud Run jobs/services." @@ -33,17 +43,20 @@ variable "cloud_run_deletion_protection" { variable "vpc_mode" { type = string - description = "VPC Mode: direct-vpc-egress (default) or vpc-access-connector." - default = "direct-vpc-egress" + description = "VPC Mode: default, direct-vpc-egress or vpc-access-connector." + default = "default" validation { - condition = contains(["direct-vpc-egress", "vpc-access-connector"], var.vpc_mode) - error_message = "vpc_mode must be 'direct-vpc-egress' or 'vpc-access-connector'." + condition = contains( + ["default", "direct-vpc-egress", "vpc-access-connector"], + var.vpc_mode + ) + error_message = "vpc_mode must be 'default', 'direct-vpc-egress' or 'vpc-access-connector'." } } variable "vpc_connectors" { - description = "Configuration for Serverless VPC Access connectors by regions." + description = "Configuration for Serverless VPC Access connectors by region." type = map(object({ name = string region = string @@ -65,10 +78,28 @@ variable "vpc_subnets" { } variable "vpc_egress" { - type = string - default = "PRIVATE_RANGES_ONLY" + type = string + default = "private-ranges-only" validation { - condition = var.vpc_egress == null || can(regex("^(PRIVATE_RANGES_ONLY|ALL_TRAFFIC)$", var.vpc_egress)) - error_message = "vpc_egress must be PRIVATE_RANGES_ONLY, ALL_TRAFFIC or null." + condition = var.vpc_egress == null || can(regex("^(private-ranges-only|all-traffic)$", var.vpc_egress)) + error_message = "vpc_egress must be private-ranges-only, all-traffic or null." } } + +variable "primary_region" { + type = string + description = "Primary region for reference." + default = "us-west1" +} + +variable "lb_ip_address" { + type = string + description = "Optional: Use an existing Global IP for Load Balancer. Leave empty to create a new one." + default = null +} + +variable "lb_domain" { + type = string + description = "Optional: Use an existing domain. Leave empty to use .sslip.io." + default = null +} diff --git a/metadata.yaml b/metadata.yaml index 46f92dd60..c7a6fbf72 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -64,6 +64,8 @@ spec: location: examples/simple_job_exec - name: v2 location: examples/v2 + - name: v2_multi_regions + location: examples/v2_multi_regions - name: v2_with_gmp location: examples/v2_with_gmp - name: v2_with_gpu @@ -119,6 +121,17 @@ spec: description: A set of key/value label pairs to assign to the container metadata varType: map(string) defaultValue: {} + - name: ingress + description: Restricts network access to your Cloud Run service + varType: string + defaultValue: INGRESS_TRAFFIC_ALL + - name: vpc_connector + description: The full resource name of the VPC Access connector to use. Leave null to use Direct VPC Egress. + varType: string + - name: vpc_egress + description: The outbound traffic setting for VPC. Use 'PRIVATE_RANGES_ONLY' with a connector. Use 'ALL_TRAFFIC' for Direct VPC Egress. Set to null to disable all VPC egress. + varType: string + defaultValue: ALL_TRAFFIC - name: template_annotations description: Annotations to the container metadata including VPC Connector and SQL. See [more details](https://cloud.google.com/run/docs/reference/rpc/google.cloud.run.v1#revisiontemplate) varType: map(string) @@ -323,13 +336,13 @@ spec: roles: - level: Project roles: - - roles/cloudkms.admin - - roles/resourcemanager.projectIamAdmin - - roles/run.admin - roles/iam.serviceAccountAdmin - roles/artifactregistry.admin - roles/iam.serviceAccountUser - roles/serviceusage.serviceUsageViewer + - roles/cloudkms.admin + - roles/resourcemanager.projectIamAdmin + - roles/run.admin services: - accesscontextmanager.googleapis.com - cloudbilling.googleapis.com diff --git a/modules/job-exec/metadata.yaml b/modules/job-exec/metadata.yaml index 8247c5733..6d90fcf47 100644 --- a/modules/job-exec/metadata.yaml +++ b/modules/job-exec/metadata.yaml @@ -48,6 +48,8 @@ spec: location: examples/simple_job_exec - name: v2 location: examples/v2 + - name: v2_multi_regions + location: examples/v2_multi_regions - name: v2_with_gmp location: examples/v2_with_gmp - name: v2_with_gpu @@ -219,13 +221,13 @@ spec: roles: - level: Project roles: - - roles/run.admin - - roles/iam.serviceAccountAdmin - roles/artifactregistry.admin - roles/iam.serviceAccountUser - roles/serviceusage.serviceUsageViewer - roles/cloudkms.admin - roles/resourcemanager.projectIamAdmin + - roles/run.admin + - roles/iam.serviceAccountAdmin services: - accesscontextmanager.googleapis.com - cloudbilling.googleapis.com diff --git a/modules/secure-cloud-run-core/metadata.yaml b/modules/secure-cloud-run-core/metadata.yaml index c2a6df4a7..00aa85803 100644 --- a/modules/secure-cloud-run-core/metadata.yaml +++ b/modules/secure-cloud-run-core/metadata.yaml @@ -48,6 +48,8 @@ spec: location: examples/simple_job_exec - name: v2 location: examples/v2 + - name: v2_multi_regions + location: examples/v2_multi_regions - name: v2_with_gmp location: examples/v2_with_gmp - name: v2_with_gpu @@ -306,13 +308,13 @@ spec: roles: - level: Project roles: - - roles/cloudkms.admin - - roles/resourcemanager.projectIamAdmin - roles/run.admin - roles/iam.serviceAccountAdmin - roles/artifactregistry.admin - roles/iam.serviceAccountUser - roles/serviceusage.serviceUsageViewer + - roles/cloudkms.admin + - roles/resourcemanager.projectIamAdmin services: - accesscontextmanager.googleapis.com - cloudbilling.googleapis.com diff --git a/modules/secure-cloud-run-security/metadata.yaml b/modules/secure-cloud-run-security/metadata.yaml index 0b9de8caf..3fe91395b 100644 --- a/modules/secure-cloud-run-security/metadata.yaml +++ b/modules/secure-cloud-run-security/metadata.yaml @@ -48,6 +48,8 @@ spec: location: examples/simple_job_exec - name: v2 location: examples/v2 + - name: v2_multi_regions + location: examples/v2_multi_regions - name: v2_with_gmp location: examples/v2_with_gmp - name: v2_with_gpu @@ -133,13 +135,13 @@ spec: roles: - level: Project roles: - - roles/cloudkms.admin - - roles/resourcemanager.projectIamAdmin - roles/run.admin - roles/iam.serviceAccountAdmin - roles/artifactregistry.admin - roles/iam.serviceAccountUser - roles/serviceusage.serviceUsageViewer + - roles/cloudkms.admin + - roles/resourcemanager.projectIamAdmin services: - accesscontextmanager.googleapis.com - cloudbilling.googleapis.com diff --git a/modules/secure-cloud-run/metadata.yaml b/modules/secure-cloud-run/metadata.yaml index 284d4d749..55e1f5b94 100644 --- a/modules/secure-cloud-run/metadata.yaml +++ b/modules/secure-cloud-run/metadata.yaml @@ -48,6 +48,8 @@ spec: location: examples/simple_job_exec - name: v2 location: examples/v2 + - name: v2_multi_regions + location: examples/v2_multi_regions - name: v2_with_gmp location: examples/v2_with_gmp - name: v2_with_gpu @@ -250,13 +252,13 @@ spec: roles: - level: Project roles: + - roles/iam.serviceAccountUser - roles/serviceusage.serviceUsageViewer - roles/cloudkms.admin - roles/resourcemanager.projectIamAdmin - roles/run.admin - roles/iam.serviceAccountAdmin - roles/artifactregistry.admin - - roles/iam.serviceAccountUser services: - accesscontextmanager.googleapis.com - cloudbilling.googleapis.com diff --git a/modules/secure-serverless-harness/metadata.yaml b/modules/secure-serverless-harness/metadata.yaml index 466cd1126..ce9fc6009 100644 --- a/modules/secure-serverless-harness/metadata.yaml +++ b/modules/secure-serverless-harness/metadata.yaml @@ -48,6 +48,8 @@ spec: location: examples/simple_job_exec - name: v2 location: examples/v2 + - name: v2_multi_regions + location: examples/v2_multi_regions - name: v2_with_gmp location: examples/v2_with_gmp - name: v2_with_gpu diff --git a/modules/secure-serverless-net/metadata.yaml b/modules/secure-serverless-net/metadata.yaml index f7c089b4f..ffc463ea7 100644 --- a/modules/secure-serverless-net/metadata.yaml +++ b/modules/secure-serverless-net/metadata.yaml @@ -48,6 +48,8 @@ spec: location: examples/simple_job_exec - name: v2 location: examples/v2 + - name: v2_multi_regions + location: examples/v2_multi_regions - name: v2_with_gmp location: examples/v2_with_gmp - name: v2_with_gpu diff --git a/modules/v2/main.tf b/modules/v2/main.tf index e94534e66..ef124883b 100644 --- a/modules/v2/main.tf +++ b/modules/v2/main.tf @@ -104,6 +104,13 @@ resource "google_cloud_run_v2_service" "main" { iap_enabled = length(var.iap_members) > 0 deletion_protection = var.cloud_run_deletion_protection + dynamic "multi_region_settings" { + for_each = var.multi_region_settings == null ? [] : [var.multi_region_settings] + content { + regions = multi_region_settings.value.regions + } + } + template { revision = var.revision labels = var.template_labels diff --git a/modules/v2/metadata.yaml b/modules/v2/metadata.yaml index 56515638e..a742b60f3 100644 --- a/modules/v2/metadata.yaml +++ b/modules/v2/metadata.yaml @@ -48,6 +48,8 @@ spec: location: examples/simple_job_exec - name: v2 location: examples/v2 + - name: v2_multi_regions + location: examples/v2_multi_regions - name: v2_with_gmp location: examples/v2_with_gmp - name: v2_with_gpu @@ -565,13 +567,13 @@ spec: roles: - level: Project roles: + - roles/resourcemanager.projectIamAdmin + - roles/compute.viewer + - roles/iap.admin - roles/run.admin - roles/iam.serviceAccountAdmin - roles/iam.serviceAccountUser - roles/serviceusage.serviceUsageViewer - - roles/resourcemanager.projectIamAdmin - - roles/compute.viewer - - roles/iap.admin services: - cloudresourcemanager.googleapis.com - compute.googleapis.com @@ -583,6 +585,6 @@ spec: - storage-api.googleapis.com providerVersions: - source: hashicorp/google - version: ">= 6, < 7" + version: ">= 6, < 8" - source: hashicorp/google-beta - version: ">= 6, < 7" + version: ">= 6, < 8" diff --git a/modules/v2/variables.tf b/modules/v2/variables.tf index c62537e7f..142c22e2c 100644 --- a/modules/v2/variables.tf +++ b/modules/v2/variables.tf @@ -20,6 +20,14 @@ variable "project_id" { type = string } +variable "multi_region_settings" { + description = "Settings for creating a Multi-Region Service. " + type = object({ + regions = list(string) + }) + default = null +} + variable "location" { description = "Cloud Run service deployment location" type = string From 280c8c5052a6e5f7c1475646065c7425b9220475 Mon Sep 17 00:00:00 2001 From: Renato Rudnicki Date: Fri, 26 Dec 2025 17:38:07 -0300 Subject: [PATCH 3/7] update code for cloud run multiregion example --- examples/v2_multi_regions/README.md | 37 +++++++-- examples/v2_multi_regions/load_balancer.tf | 7 +- examples/v2_multi_regions/main.tf | 48 ++++++------ examples/v2_multi_regions/outputs.tf | 88 +++++++++------------- examples/v2_multi_regions/variables.tf | 38 ++++++---- modules/v2/README.md | 1 + modules/v2/metadata.yaml | 12 ++- 7 files changed, 126 insertions(+), 105 deletions(-) diff --git a/examples/v2_multi_regions/README.md b/examples/v2_multi_regions/README.md index bc14ca762..16b3437a7 100644 --- a/examples/v2_multi_regions/README.md +++ b/examples/v2_multi_regions/README.md @@ -21,20 +21,43 @@ This example assumes that below mentioned prerequisites are in place before cons | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | cloud\_run\_deletion\_protection | Prevents Terraform from destroying/recreating Cloud Run jobs/services. | `bool` | `true` | no | +| cloud\_run\_vpc\_egress\_mode | Defines how Cloud Run connects to the VPC for outbound traffic. Modes can be default, direct-vpc-egress or vpc-access-connector. | `string` | `"default"` | no | +| image | Name of the image used by cloud run. | `string` | `"us-docker.pkg.dev/cloudrun/container/hello:latest"` | no | +| lb\_domain | Optional: Use an existing domain. Leave empty to use .sslip.io. | `string` | `null` | no | +| lb\_ip\_address | Optional: Use an existing Global IP for Load Balancer. Leave empty to create a new one. | `string` | `null` | no | +| location | Settings for creating a Multi-Region Service. Make sure to use region = 'global' if deploying a multi-region cloud run. | `string` | `"global"` | no | +| primary\_region | Primary region for reference. | `string` | `"us-west1"` | no | | project\_id | Project where the Cloud Run v2 will be deployed. | `string` | n/a | yes | -| regions | Regions where serverless vpc access connectors will be created. | `list(string)` |
[
"us-west1",
"europe-west1"
]
| no | -| vpc\_connectors | Configuration for Serverless VPC Access connectors by regions. |
map(object({
name = string
region = string
subnet_name = string
}))
| n/a | yes | +| regions | Regions where serverless VPC Access connectors will be created. | `list(string)` |
[
"us-west1",
"europe-west1"
]
| no | +| service\_name | Cloud Run service name. | `string` | n/a | yes | +| vpc\_connectors | Configuration for Serverless VPC Access connectors by region. |
map(object({
name = string
region = string
subnet_name = string
}))
| `{}` | no | +| vpc\_egress\_traffic | Defines which outbound traffic from Cloud Run is routed through the VPC (private IP ranges only or all traffic) when VPC egress is enabled. | `string` | `"private-ranges-only"` | no | +| vpc\_network | Configuration of VPC Network by region (only for direct-vpc-egress). | `string` | `null` | no | +| vpc\_subnets | Configuration of VPC subnets by region (only for direct-vpc-egress). | `map(string)` | `{}` | no | ## Outputs | Name | Description | |------|-------------| +| backend\_service\_global | Global backend service backing the Cloud Run multi-region service. | +| cloud\_run\_regions | Regions where the Cloud Run service is deployed. | | cloud\_run\_service\_account | Service account used by Cloud Run instances. | -| service\_id | Service IDs per region | -| service\_name | Service names per region | -| service\_uri | Service URIs per region | -| vpc\_connectors\_ids | Serverless VPC Access Connectors IDs by region. | -| vpc\_connectors\_names | VPC Access Connectors. | +| cloud\_run\_service\_id | Cloud Run service ID. | +| cloud\_run\_vpc\_egress\_mode | Cloud Run VPC egress mode in use. | +| cloud\_run\_vpc\_egress\_setting | Cloud Run VPC egress setting. | +| global\_entrypoint | Global HTTPS entrypoint for the Cloud Run multi-region service. | +| global\_forwarding\_rule\_id | ID of the global HTTPS forwarding rule. | +| https\_proxy\_id | ID of the HTTPS target proxy. | +| lb\_domain | Domain for the Load Balancer (IP.sslip.io if no custom domain is provided). | +| lb\_https\_url | HTTPS URL for the global Load Balancer. | +| lb\_ip | Global IP address of the Load Balancer. | +| serverless\_neg\_self\_links | Serverless NEG self links by region. | +| serverless\_negs | Serverless NEGs by region. | +| ssl\_certificate\_domains | Domains covered by the managed SSL certificate. | +| ssl\_certificate\_id | Managed SSL certificate ID. | +| url\_map\_id | ID of the URL map. | +| vpc\_connectors\_ids | VPC Access Connectors IDs by region. | +| vpc\_connectors\_names | VPC Access Connectors names by region. | diff --git a/examples/v2_multi_regions/load_balancer.tf b/examples/v2_multi_regions/load_balancer.tf index f31b6227e..45b96ddcc 100644 --- a/examples/v2_multi_regions/load_balancer.tf +++ b/examples/v2_multi_regions/load_balancer.tf @@ -59,8 +59,8 @@ resource "google_compute_backend_service" "cloudrun_backend_global" { enable_cdn = false outlier_detection { - consecutive_errors = 5 - consecutive_gateway_failure = 3 + consecutive_errors = 3 + consecutive_gateway_failure = 5 enforcing_consecutive_errors = 100 enforcing_consecutive_gateway_failure = 100 @@ -112,9 +112,8 @@ resource "google_compute_url_map" "cloudrun_urlmap" { "gateway-error", "connect-failure", "retriable-4xx", - "gateway-timeout", + "unavailable", "dead-deadline-exceeded", - "503" ] } } diff --git a/examples/v2_multi_regions/main.tf b/examples/v2_multi_regions/main.tf index a5bf35570..5a556ced9 100644 --- a/examples/v2_multi_regions/main.tf +++ b/examples/v2_multi_regions/main.tf @@ -20,10 +20,10 @@ locals { vpc_flags = ( - var.vpc_mode == "vpc-access-connector" ? - "--vpc-connector=${var.vpc_connectors[var.primary_region].name} --vpc-egress=${var.vpc_egress}" : - var.vpc_mode == "direct-vpc-egress" ? - "--network=${var.vpc_network} --subnet=${var.vpc_subnets[var.primary_region]} --vpc-egress=${var.vpc_egress}" : + var.cloud_run_vpc_egress_mode == "vpc-access-connector" ? + "--vpc-connector=${var.vpc_connectors[var.primary_region].name} --vpc-egress=${var.vpc_egress_traffic}" : + var.cloud_run_vpc_egress_mode == "direct-vpc-egress" ? + "--network=${var.vpc_network} --subnet=${var.vpc_subnets[var.primary_region]} --vpc-egress=${var.vpc_egress_traffic}" : "" ) } @@ -43,7 +43,7 @@ resource "google_service_account" "sa" { ############################################################################### resource "google_vpc_access_connector" "vpc_connectors" { - for_each = var.vpc_mode == "vpc-access-connector" ? var.vpc_connectors : {} + for_each = var.cloud_run_vpc_egress_mode == "vpc-access-connector" ? var.vpc_connectors : {} name = each.value.name project = var.project_id @@ -71,16 +71,16 @@ resource "null_resource" "validate_primary_region" { resource "null_resource" "validate_vpc" { count = ( - var.vpc_mode != "default" && ( - (var.vpc_mode == "vpc-access-connector" && length(var.vpc_connectors) == 0) || - (var.vpc_mode == "direct-vpc-egress" && + var.cloud_run_vpc_egress_mode != "default" && ( + (var.cloud_run_vpc_egress_mode == "vpc-access-connector" && length(var.vpc_connectors) == 0) || + (var.cloud_run_vpc_egress_mode == "direct-vpc-egress" && (var.vpc_network == null || length(var.vpc_subnets) == 0) ) ) ) ? 1 : 0 provisioner "local-exec" { - command = "echo 'ERROR: Invalid VPC configuration for selected vpc_mode' && exit 1" + command = "echo 'ERROR: Invalid VPC configuration for selected cloud_run_vpc_egress_mode' && exit 1" } } @@ -89,13 +89,13 @@ resource "null_resource" "validate_vpc" { ############################################################################### module "cloud_run_v2_multiregion" { - source = "../../modules/v2" + source = "marcelorobj/cloud-run/google//modules/v2" - service_name = "cloudrun-service" - location = "global" - project_id = var.project_id - create_service_account = false - service_account = google_service_account.sa.email + service_name = var.service_name + location = var.location + project_id = var.project_id + create_service_account = false + service_account = google_service_account.sa.email cloud_run_deletion_protection = var.cloud_run_deletion_protection multi_region_settings = { @@ -105,18 +105,18 @@ module "cloud_run_v2_multiregion" { ] } - containers = [ + containers = [ { - container_image = "us-docker.pkg.dev/cloudrun/container/hello" + container_image = var.image container_name = "hello-world" # Check if app startd. If it fails,cloud run restart the container startup_probe = { - initial_delay_seconds = 5 # wait 5 seconds before start test - timeout_seconds = 3 # request has 3s to reply - period_seconds = 10 # Test each 10s - failure_threshold = 3 # fails after 3 wrong attempts (ex: 500, 502, timeout) + initial_delay_seconds = 10 # wait 10 seconds before start test + timeout_seconds = 3 # request has 3s to reply + period_seconds = 3 # Test each 3s + failure_threshold = 5 # fails after 5 wrong attempts (ex: 500, 502, timeout) http_get = { path = "/" @@ -127,10 +127,10 @@ module "cloud_run_v2_multiregion" { # Liveness Probe: check if app remains running health # If start get 5xx the container is restarted liveness_probe = { - initial_delay_seconds = 5 + initial_delay_seconds = 10 timeout_seconds = 3 - period_seconds = 15 - failure_threshold = 3 + period_seconds = 3 + failure_threshold = 5 http_get = { path = "/" diff --git a/examples/v2_multi_regions/outputs.tf b/examples/v2_multi_regions/outputs.tf index f63d39250..a64a053c2 100644 --- a/examples/v2_multi_regions/outputs.tf +++ b/examples/v2_multi_regions/outputs.tf @@ -14,72 +14,66 @@ * limitations under the License. */ -############################################################################### -# CLOUD RUN -############################################################################### - output "cloud_run_service_account" { description = "Service account used by Cloud Run instances." value = google_service_account.sa.email } -# output "service_name" { -# description = "Cloud Run service name (multi-region)." -# value = module.cloud_run_v2_multiregion.service_name -# } - -# output "service_uri" { -# description = "Cloud Run service URI (multi-region)." -# value = module.cloud_run_v2_multiregion.service_uri -# } - -# output "service_id" { -# description = "Cloud Run service ID (multi-region)." -# value = module.cloud_run_v2_multiregion.service_id -# } +output "cloud_run_service_id" { + description = "Cloud Run service ID." + value = module.cloud_run_v2_multiregion.service_id +} -output "cloud_run_regions" { - description = "Regions where the Cloud Run service is deployed." - value = var.regions +output "global_forwarding_rule_id" { + description = "ID of the global HTTPS forwarding rule." + value = google_compute_global_forwarding_rule.cloudrun_https_rule.id } -output "cloud_run_service_name" { - description = "Cloud Run service name." - value = var.service_name +output "https_proxy_id" { + description = "ID of the HTTPS target proxy." + value = google_compute_target_https_proxy.cloudrun_https_proxy.id } -output "backend_service_name" { - description = "Global backend service name." - value = google_compute_backend_service.cloudrun_backend_global.name +output "url_map_id" { + description = "ID of the URL map." + value = google_compute_url_map.cloudrun_urlmap.id } -output "serverless_negs" { - description = "Serverless NEGs by region." +output "serverless_neg_self_links" { + description = "Serverless NEG self links by region." value = { for region, neg in google_compute_region_network_endpoint_group.cloudrun_neg : - region => neg.name + region => neg.self_link } } -output "url_map_name" { - description = "URL map name." - value = google_compute_url_map.cloudrun_urlmap.name +output "ssl_certificate_domains" { + description = "Domains covered by the managed SSL certificate." + value = google_compute_managed_ssl_certificate.cloudrun_cert.managed[0].domains } -output "https_proxy_name" { - description = "HTTPS proxy name." - value = google_compute_target_https_proxy.cloudrun_https_proxy.name +output "cloud_run_vpc_egress_mode" { + description = "Cloud Run VPC egress mode in use." + value = var.cloud_run_vpc_egress_mode } -output "https_forwarding_rule_name" { - description = "HTTPS forwarding rule name." - value = google_compute_global_forwarding_rule.cloudrun_https_rule.name +output "cloud_run_vpc_egress_setting" { + description = "Cloud Run VPC egress setting." + value = var.vpc_egress_traffic } +output "cloud_run_regions" { + description = "Regions where the Cloud Run service is deployed." + value = var.regions +} -############################################################################### -# VPC ACCESS CONNECTORS -############################################################################### +output "serverless_negs" { + description = "Serverless NEGs by region." + value = { + for region, neg in google_compute_region_network_endpoint_group.cloudrun_neg : + region => neg.name + } +} output "vpc_connectors_ids" { description = "VPC Access Connectors IDs by region." @@ -97,15 +91,6 @@ output "vpc_connectors_names" { } } -############################################################################### -# LOAD BALANCER / NETWORKING -############################################################################### - -# output "serverless_neg_id" { -# description = "Serverless Network Endpoint Group ID used by the global load balancer." -# value = google_compute_region_network_endpoint_group.cloudrun_neg.id -# } - output "backend_service_global" { description = "Global backend service backing the Cloud Run multi-region service." value = google_compute_backend_service.cloudrun_backend_global.id @@ -135,4 +120,3 @@ output "global_entrypoint" { description = "Global HTTPS entrypoint for the Cloud Run multi-region service." value = "https://${local.lb_domain}" } - diff --git a/examples/v2_multi_regions/variables.tf b/examples/v2_multi_regions/variables.tf index 44984543f..a0a77fafd 100644 --- a/examples/v2_multi_regions/variables.tf +++ b/examples/v2_multi_regions/variables.tf @@ -25,14 +25,21 @@ variable "regions" { default = ["us-west1", "europe-west1"] } +variable "location" { + type = string + description = " Settings for creating a Multi-Region Service. Make sure to use region = 'global' if deploying a multi-region cloud run." + default = "global" +} + variable "service_name" { - type = string - default = "cloudrun-multiregion" + type = string + description = "Cloud Run service name." } variable "image" { - type = string - default = "us-docker.pkg.dev/cloudrun/container/hello:latest" + type = string + description = "Name of the image used by cloud run." + default = "us-docker.pkg.dev/cloudrun/container/hello:latest" } variable "cloud_run_deletion_protection" { @@ -41,28 +48,28 @@ variable "cloud_run_deletion_protection" { default = true } -variable "vpc_mode" { +variable "cloud_run_vpc_egress_mode" { type = string - description = "VPC Mode: default, direct-vpc-egress or vpc-access-connector." + description = "Defines how Cloud Run connects to the VPC for outbound traffic. Modes can be default, direct-vpc-egress or vpc-access-connector." default = "default" validation { condition = contains( ["default", "direct-vpc-egress", "vpc-access-connector"], - var.vpc_mode + var.cloud_run_vpc_egress_mode ) - error_message = "vpc_mode must be 'default', 'direct-vpc-egress' or 'vpc-access-connector'." + error_message = "cloud_run_vpc_egress_mode must be 'default', 'direct-vpc-egress' or 'vpc-access-connector'." } } variable "vpc_connectors" { - description = "Configuration for Serverless VPC Access connectors by region." type = map(object({ name = string region = string subnet_name = string })) - default = {} + description = "Configuration for Serverless VPC Access connectors by region." + default = {} } variable "vpc_network" { @@ -77,12 +84,13 @@ variable "vpc_subnets" { default = {} } -variable "vpc_egress" { - type = string - default = "private-ranges-only" +variable "vpc_egress_traffic" { + type = string + description = "Defines which outbound traffic from Cloud Run is routed through the VPC (private IP ranges only or all traffic) when VPC egress is enabled." + default = "private-ranges-only" validation { - condition = var.vpc_egress == null || can(regex("^(private-ranges-only|all-traffic)$", var.vpc_egress)) - error_message = "vpc_egress must be private-ranges-only, all-traffic or null." + condition = var.vpc_egress_traffic == null || can(regex("^(private-ranges-only|all-traffic)$", var.vpc_egress_traffic)) + error_message = "vpc_egress_traffic must be private-ranges-only, all-traffic or null." } } diff --git a/modules/v2/README.md b/modules/v2/README.md index 017b81765..b8e16634a 100644 --- a/modules/v2/README.md +++ b/modules/v2/README.md @@ -67,6 +67,7 @@ Functional examples are included in the | location | Cloud Run service deployment location | `string` | n/a | yes | | max\_instance\_request\_concurrency | Sets the maximum number of requests that each serving instance can receive. This is optional. | `string` | `null` | no | | members | Users/SAs to be given invoker access to the service. Grant invoker access by specifying the users or service accounts (SAs). Use allUsers for public access, allAuthenticatedUsers for access by logged-in Google users, or provide a list of specific users/SAs. [See the complete list of available options here](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service_iam#member/members-1) | `list(string)` | `[]` | no | +| multi\_region\_settings | Settings for creating a Multi-Region Service. |
object({
regions = list(string)
})
| `null` | no | | node\_selector | Node Selector describes the hardware requirements of the GPU resource. [More info](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service#nested_template_node_selector). |
object({
accelerator = string
})
| `null` | no | | project\_id | The project ID to deploy to | `string` | n/a | yes | | revision | The unique name for the revision. If this field is omitted, it will be automatically generated based on the Service name | `string` | `null` | no | diff --git a/modules/v2/metadata.yaml b/modules/v2/metadata.yaml index a742b60f3..b64f37bfa 100644 --- a/modules/v2/metadata.yaml +++ b/modules/v2/metadata.yaml @@ -62,6 +62,12 @@ spec: description: The project ID to deploy to varType: string required: true + - name: multi_region_settings + description: "Settings for creating a Multi-Region Service. " + varType: |- + object({ + regions = list(string) + }) - name: location description: Cloud Run service deployment location varType: string @@ -567,13 +573,13 @@ spec: roles: - level: Project roles: - - roles/resourcemanager.projectIamAdmin - - roles/compute.viewer - - roles/iap.admin - roles/run.admin - roles/iam.serviceAccountAdmin - roles/iam.serviceAccountUser - roles/serviceusage.serviceUsageViewer + - roles/resourcemanager.projectIamAdmin + - roles/compute.viewer + - roles/iap.admin services: - cloudresourcemanager.googleapis.com - compute.googleapis.com From 733e615c1ba9cb558030ffffebb7c05d5305ec9b Mon Sep 17 00:00:00 2001 From: Renato Rudnicki Date: Mon, 29 Dec 2025 10:39:20 -0300 Subject: [PATCH 4/7] update readme --- examples/v2_multi_regions/README.md | 49 ++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/examples/v2_multi_regions/README.md b/examples/v2_multi_regions/README.md index 16b3437a7..90064da5a 100644 --- a/examples/v2_multi_regions/README.md +++ b/examples/v2_multi_regions/README.md @@ -1,13 +1,17 @@ # Cloud Run Service using v2 API and multi-regions Example -This example showcases the basic deployment of containerized applications on Cloud Run and IAM policy for the service in multi-regions. +This example showcases the basic deployment of containerized applications on Cloud Run and IAM policy for the service in multi-regions, including a Global Load Balancer for traffic distribution and SSL termination. The resources/services/activations/deletions that this example will create/trigger are: * Deploys a Cloud Run V2 service across multiple regions. * Creates a Service Account to be used by Cloud Run Service. -* Creates a Serverless VPC Access Connectors per region. -* Cloud Run -> VPC integration through regional connectors. +* Creates Serverless VPC Access Connectors per region (or configures Direct VPC Egress). +* Reserves a Global Static IP Address. +* Creates Serverless Network Endpoint Groups (NEGs) per region to bridge the Load Balancer and Cloud Run. +* Provisions a Global External Application Load Balancer (HTTP/S) with URL Maps and Backend Services. +* Sets up Google-managed SSL Certificates for secure HTTPS access. +* Configures outlier detection to handle failover between regions automatically. ## Assumptions and Prerequisites @@ -15,6 +19,19 @@ This example assumes that below mentioned prerequisites are in place before cons * All required APIs are enabled in the GCP Project +> **DISCLAIMER**: Please pay attention to the following important details regarding the Cloud Run **Service Health** feature used in this project: + +* **Pre-GA Feature:** This feature is subject to the "Pre-GA Offerings Terms" in the General Service Terms section of the Service Specific Terms. Pre-GA features are available "as is" and might have limited support. +* **Load Balancer Behavior:** Revisions without configured probes are treated as **unknown**. Please note that the Load Balancer considers instances with "unknown" status as **healthy** and will route traffic to them. +* **Manual Configuration Required:** Currently, the `google_cloud_run_v2_service` Terraform resource does not natively support `readiness_probe` configuration. Due to this limitation, you must manually execute the following `gcloud` command to correctly enable this check after deployment: + +```bash +gcloud beta run deploy SERVICE \ + --image=IMAGE_URL \ + --readiness-probe httpGet.path=PATH,httpGet.port=CONTAINER_PORT,successThreshold=SUCCESS_THRESHOLD,failureThreshold=FAILURE_THRESHOLD,timeoutSeconds=TIMEOUT,periodSeconds=PERIOD +``` +For more information, please visit: [Cloud Run Service Health Documentation](https://docs.cloud.google.com/run/docs/configuring/healthchecks#readiness-probes) + ## Inputs @@ -22,16 +39,16 @@ This example assumes that below mentioned prerequisites are in place before cons |------|-------------|------|---------|:--------:| | cloud\_run\_deletion\_protection | Prevents Terraform from destroying/recreating Cloud Run jobs/services. | `bool` | `true` | no | | cloud\_run\_vpc\_egress\_mode | Defines how Cloud Run connects to the VPC for outbound traffic. Modes can be default, direct-vpc-egress or vpc-access-connector. | `string` | `"default"` | no | -| image | Name of the image used by cloud run. | `string` | `"us-docker.pkg.dev/cloudrun/container/hello:latest"` | no | +| image | n/a | `string` | `"us-docker.pkg.dev/cloudrun/container/hello:latest"` | no | | lb\_domain | Optional: Use an existing domain. Leave empty to use .sslip.io. | `string` | `null` | no | | lb\_ip\_address | Optional: Use an existing Global IP for Load Balancer. Leave empty to create a new one. | `string` | `null` | no | | location | Settings for creating a Multi-Region Service. Make sure to use region = 'global' if deploying a multi-region cloud run. | `string` | `"global"` | no | | primary\_region | Primary region for reference. | `string` | `"us-west1"` | no | | project\_id | Project where the Cloud Run v2 will be deployed. | `string` | n/a | yes | | regions | Regions where serverless VPC Access connectors will be created. | `list(string)` |
[
"us-west1",
"europe-west1"
]
| no | -| service\_name | Cloud Run service name. | `string` | n/a | yes | +| service\_name | n/a | `string` | `"cloudrun-multiregion"` | no | | vpc\_connectors | Configuration for Serverless VPC Access connectors by region. |
map(object({
name = string
region = string
subnet_name = string
}))
| `{}` | no | -| vpc\_egress\_traffic | Defines which outbound traffic from Cloud Run is routed through the VPC (private IP ranges only or all traffic) when VPC egress is enabled. | `string` | `"private-ranges-only"` | no | +| vpc\_egress | n/a | `string` | `"private-ranges-only"` | no | | vpc\_network | Configuration of VPC Network by region (only for direct-vpc-egress). | `string` | `null` | no | | vpc\_subnets | Configuration of VPC subnets by region (only for direct-vpc-egress). | `map(string)` | `{}` | no | @@ -40,22 +57,19 @@ This example assumes that below mentioned prerequisites are in place before cons | Name | Description | |------|-------------| | backend\_service\_global | Global backend service backing the Cloud Run multi-region service. | +| backend\_service\_name | Global backend service name. | | cloud\_run\_regions | Regions where the Cloud Run service is deployed. | | cloud\_run\_service\_account | Service account used by Cloud Run instances. | -| cloud\_run\_service\_id | Cloud Run service ID. | -| cloud\_run\_vpc\_egress\_mode | Cloud Run VPC egress mode in use. | -| cloud\_run\_vpc\_egress\_setting | Cloud Run VPC egress setting. | +| cloud\_run\_service\_name | Cloud Run service name. | | global\_entrypoint | Global HTTPS entrypoint for the Cloud Run multi-region service. | -| global\_forwarding\_rule\_id | ID of the global HTTPS forwarding rule. | -| https\_proxy\_id | ID of the HTTPS target proxy. | +| https\_forwarding\_rule\_name | HTTPS forwarding rule name. | +| https\_proxy\_name | HTTPS proxy name. | | lb\_domain | Domain for the Load Balancer (IP.sslip.io if no custom domain is provided). | | lb\_https\_url | HTTPS URL for the global Load Balancer. | | lb\_ip | Global IP address of the Load Balancer. | -| serverless\_neg\_self\_links | Serverless NEG self links by region. | | serverless\_negs | Serverless NEGs by region. | -| ssl\_certificate\_domains | Domains covered by the managed SSL certificate. | | ssl\_certificate\_id | Managed SSL certificate ID. | -| url\_map\_id | ID of the URL map. | +| url\_map\_name | URL map name. | | vpc\_connectors\_ids | VPC Access Connectors IDs by region. | | vpc\_connectors\_names | VPC Access Connectors names by region. | @@ -77,6 +91,8 @@ These sections describe requirements for using this example. A service account can be used with required roles to execute this example: * Cloud Run Admin: `roles/run.admin` +* Compute Load Balancer Admin: `roles/compute.loadBalancerAdmin` (required for LB resources) +* Compute Network Admin: `roles/compute.networkAdmin` (required for NEGs and IP reservation) Know more about [Cloud Run Deployment Permissions](https://cloud.google.com/run/docs/reference/iam/roles#additional-configuration). @@ -87,6 +103,9 @@ The [Project Factory module](https://registry.terraform.io/modules/terraform-goo A project with the following APIs enabled must be used to host the main resource of this example: +* Google Artifact Registry `artifactregistry.googleapis.com` +* Google Cloud Build: `cloudbuild.googleapis.com` * Google Cloud Run: `run.googleapis.com` * Google Compute Engine: `compute.googleapis.com` -* Google Servless VPC Acces: `vpcaccess.googleapis.com` +* Google Network Services: `networkservices.googleapis.com` + From 535a4935b262f4464350e5d4ddad0a5d398328ed Mon Sep 17 00:00:00 2001 From: Renato Rudnicki Date: Mon, 29 Dec 2025 17:41:19 -0300 Subject: [PATCH 5/7] move load balancer configuration to V2 module --- examples/v2_multi_regions/README.md | 34 +++-- examples/v2_multi_regions/load_balancer.tf | 158 --------------------- examples/v2_multi_regions/main.tf | 46 ++---- examples/v2_multi_regions/outputs.tf | 30 ++-- examples/v2_multi_regions/variables.tf | 6 + modules/v2/README.md | 14 ++ modules/v2/main.tf | 140 ++++++++++++++++++ modules/v2/metadata.yaml | 35 ++++- modules/v2/outputs.tf | 42 ++++++ modules/v2/variables.tf | 30 +++- 10 files changed, 307 insertions(+), 228 deletions(-) delete mode 100644 examples/v2_multi_regions/load_balancer.tf diff --git a/examples/v2_multi_regions/README.md b/examples/v2_multi_regions/README.md index 90064da5a..616cbf9c5 100644 --- a/examples/v2_multi_regions/README.md +++ b/examples/v2_multi_regions/README.md @@ -1,17 +1,18 @@ # Cloud Run Service using v2 API and multi-regions Example -This example showcases the basic deployment of containerized applications on Cloud Run and IAM policy for the service in multi-regions, including a Global Load Balancer for traffic distribution and SSL termination. +This example showcases the basic deployment of containerized applications on Cloud Run and IAM policy for the service in multi-regions. It allows for the optional provisioning of a Global Load Balancer for traffic distribution and SSL termination depending on the configuration. The resources/services/activations/deletions that this example will create/trigger are: * Deploys a Cloud Run V2 service across multiple regions. * Creates a Service Account to be used by Cloud Run Service. * Creates Serverless VPC Access Connectors per region (or configures Direct VPC Egress). -* Reserves a Global Static IP Address. -* Creates Serverless Network Endpoint Groups (NEGs) per region to bridge the Load Balancer and Cloud Run. -* Provisions a Global External Application Load Balancer (HTTP/S) with URL Maps and Backend Services. -* Sets up Google-managed SSL Certificates for secure HTTPS access. -* Configures outlier detection to handle failover between regions automatically. +* **If `enable_load_balancer` is set to `true`:** + * Reserves a Global Static IP Address (if not provided). + * Creates Serverless Network Endpoint Groups (NEGs) per region to bridge the Load Balancer and Cloud Run. + * Provisions a Global External Application Load Balancer (HTTP/S) with URL Maps and Backend Services. + * Sets up Google-managed SSL Certificates for secure HTTPS access. + * Configures outlier detection to handle failover between regions automatically. ## Assumptions and Prerequisites @@ -39,16 +40,17 @@ For more information, please visit: [Cloud Run Service Health Documentation](htt |------|-------------|------|---------|:--------:| | cloud\_run\_deletion\_protection | Prevents Terraform from destroying/recreating Cloud Run jobs/services. | `bool` | `true` | no | | cloud\_run\_vpc\_egress\_mode | Defines how Cloud Run connects to the VPC for outbound traffic. Modes can be default, direct-vpc-egress or vpc-access-connector. | `string` | `"default"` | no | -| image | n/a | `string` | `"us-docker.pkg.dev/cloudrun/container/hello:latest"` | no | +| enable\_load\_balancer | If true, creates the Global Load Balancer resources. Defaults to false. | `bool` | `false` | no | +| image | Name of the image used by cloud run. | `string` | `"us-docker.pkg.dev/cloudrun/container/hello:latest"` | no | | lb\_domain | Optional: Use an existing domain. Leave empty to use .sslip.io. | `string` | `null` | no | | lb\_ip\_address | Optional: Use an existing Global IP for Load Balancer. Leave empty to create a new one. | `string` | `null` | no | | location | Settings for creating a Multi-Region Service. Make sure to use region = 'global' if deploying a multi-region cloud run. | `string` | `"global"` | no | | primary\_region | Primary region for reference. | `string` | `"us-west1"` | no | | project\_id | Project where the Cloud Run v2 will be deployed. | `string` | n/a | yes | | regions | Regions where serverless VPC Access connectors will be created. | `list(string)` |
[
"us-west1",
"europe-west1"
]
| no | -| service\_name | n/a | `string` | `"cloudrun-multiregion"` | no | +| service\_name | Cloud Run service name. | `string` | n/a | yes | | vpc\_connectors | Configuration for Serverless VPC Access connectors by region. |
map(object({
name = string
region = string
subnet_name = string
}))
| `{}` | no | -| vpc\_egress | n/a | `string` | `"private-ranges-only"` | no | +| vpc\_egress\_traffic | Defines which outbound traffic from Cloud Run is routed through the VPC (private IP ranges only or all traffic) when VPC egress is enabled. | `string` | `"private-ranges-only"` | no | | vpc\_network | Configuration of VPC Network by region (only for direct-vpc-egress). | `string` | `null` | no | | vpc\_subnets | Configuration of VPC subnets by region (only for direct-vpc-egress). | `map(string)` | `{}` | no | @@ -57,19 +59,22 @@ For more information, please visit: [Cloud Run Service Health Documentation](htt | Name | Description | |------|-------------| | backend\_service\_global | Global backend service backing the Cloud Run multi-region service. | -| backend\_service\_name | Global backend service name. | | cloud\_run\_regions | Regions where the Cloud Run service is deployed. | | cloud\_run\_service\_account | Service account used by Cloud Run instances. | -| cloud\_run\_service\_name | Cloud Run service name. | +| cloud\_run\_service\_id | Cloud Run service ID. | +| cloud\_run\_vpc\_egress\_mode | Cloud Run VPC egress mode in use. | +| cloud\_run\_vpc\_egress\_setting | Cloud Run VPC egress setting. | | global\_entrypoint | Global HTTPS entrypoint for the Cloud Run multi-region service. | -| https\_forwarding\_rule\_name | HTTPS forwarding rule name. | -| https\_proxy\_name | HTTPS proxy name. | +| global\_forwarding\_rule\_id | ID of the global HTTPS forwarding rule. | +| https\_proxy\_id | ID of the HTTPS target proxy. | | lb\_domain | Domain for the Load Balancer (IP.sslip.io if no custom domain is provided). | | lb\_https\_url | HTTPS URL for the global Load Balancer. | | lb\_ip | Global IP address of the Load Balancer. | +| serverless\_neg\_self\_links | Serverless NEG self links by region. | | serverless\_negs | Serverless NEGs by region. | +| ssl\_certificate\_domains | Domains covered by the managed SSL certificate. | | ssl\_certificate\_id | Managed SSL certificate ID. | -| url\_map\_name | URL map name. | +| url\_map\_id | ID of the URL map. | | vpc\_connectors\_ids | VPC Access Connectors IDs by region. | | vpc\_connectors\_names | VPC Access Connectors names by region. | @@ -108,4 +113,3 @@ A project with the following APIs enabled must be used to host the main resource * Google Cloud Run: `run.googleapis.com` * Google Compute Engine: `compute.googleapis.com` * Google Network Services: `networkservices.googleapis.com` - diff --git a/examples/v2_multi_regions/load_balancer.tf b/examples/v2_multi_regions/load_balancer.tf deleted file mode 100644 index 45b96ddcc..000000000 --- a/examples/v2_multi_regions/load_balancer.tf +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Copyright 2025 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -########################################### -# 1. Global IP -########################################### - -resource "google_compute_global_address" "lb_ip" { - count = var.lb_ip_address == null ? 1 : 0 - name = "cloudrun-global-ip" - project = var.project_id -} - -locals { - lb_ip = var.lb_ip_address != null ? var.lb_ip_address : google_compute_global_address.lb_ip[0].address - lb_domain = var.lb_domain != null ? var.lb_domain : "${local.lb_ip}.sslip.io" -} - -########################################### -# 2. Serverless NEGs -########################################### - -resource "google_compute_region_network_endpoint_group" "cloudrun_neg" { - for_each = toset(var.regions) - - project = var.project_id - region = each.key - name = "neg-cloudrun-${each.key}" - network_endpoint_type = "SERVERLESS" - - cloud_run { - service = var.service_name - } -} - -########################################### -# 3. Backend Service Global -########################################### - -resource "google_compute_backend_service" "cloudrun_backend_global" { - project = var.project_id - name = "cloudrun-backend-global" - - protocol = "HTTP" - load_balancing_scheme = "EXTERNAL_MANAGED" - enable_cdn = false - - outlier_detection { - consecutive_errors = 3 - consecutive_gateway_failure = 5 - enforcing_consecutive_errors = 100 - enforcing_consecutive_gateway_failure = 100 - - base_ejection_time { seconds = 30 } - interval { seconds = 10 } - } - - dynamic "backend" { - for_each = google_compute_region_network_endpoint_group.cloudrun_neg - content { - group = backend.value.id - } - } -} - -########################################### -# 4. URL Map -########################################### - -resource "google_compute_url_map" "cloudrun_urlmap" { - name = "cloudrun-urlmap" - project = var.project_id - - default_service = google_compute_backend_service.cloudrun_backend_global.id - - host_rule { - hosts = ["*"] - path_matcher = "allpaths" - } - - path_matcher { - name = "allpaths" - default_service = google_compute_backend_service.cloudrun_backend_global.id - - path_rule { - paths = ["/*"] - - route_action { - weighted_backend_services { - backend_service = google_compute_backend_service.cloudrun_backend_global.id - weight = 100 - } - - retry_policy { - num_retries = 10 - per_try_timeout { seconds = 30 } - retry_conditions = [ - "5xx", - "gateway-error", - "connect-failure", - "retriable-4xx", - "unavailable", - "dead-deadline-exceeded", - ] - } - } - } - } -} - -########################################### -# 5. SSL & Proxy & Forwarding -########################################### - -resource "google_compute_managed_ssl_certificate" "cloudrun_cert" { - name = "cloudrun-cert" - project = var.project_id - - managed { - domains = [local.lb_domain] - } -} - -resource "google_compute_target_https_proxy" "cloudrun_https_proxy" { - name = "cloudrun-https-proxy" - project = var.project_id - ssl_certificates = [google_compute_managed_ssl_certificate.cloudrun_cert.id] - url_map = google_compute_url_map.cloudrun_urlmap.id -} - -resource "google_compute_global_forwarding_rule" "cloudrun_https_rule" { - name = "cloudrun-https-rule" - project = var.project_id - - ip_address = var.lb_ip_address != null ? var.lb_ip_address : google_compute_global_address.lb_ip[0].id - ip_protocol = "TCP" - port_range = "443" - target = google_compute_target_https_proxy.cloudrun_https_proxy.id - load_balancing_scheme = "EXTERNAL_MANAGED" - - depends_on = [ - google_compute_target_https_proxy.cloudrun_https_proxy, - google_compute_managed_ssl_certificate.cloudrun_cert - ] -} diff --git a/examples/v2_multi_regions/main.tf b/examples/v2_multi_regions/main.tf index 5a556ced9..749a6eeb3 100644 --- a/examples/v2_multi_regions/main.tf +++ b/examples/v2_multi_regions/main.tf @@ -14,10 +14,6 @@ * limitations under the License. */ -# ############################################################################## -# LOCALS -# ############################################################################## - locals { vpc_flags = ( var.cloud_run_vpc_egress_mode == "vpc-access-connector" ? @@ -28,20 +24,12 @@ locals { ) } -############################################################################### -# SERVICE ACCOUNT -############################################################################### - resource "google_service_account" "sa" { project = var.project_id account_id = "ci-cloud-run-v2-sa" display_name = "Service account for Cloud Run multi-region" } -############################################################################### -# SERVERLESS VPC ACCESS CONNECTORS (opcional) -############################################################################### - resource "google_vpc_access_connector" "vpc_connectors" { for_each = var.cloud_run_vpc_egress_mode == "vpc-access-connector" ? var.vpc_connectors : {} @@ -57,10 +45,6 @@ resource "google_vpc_access_connector" "vpc_connectors" { max_instances = 4 } -############################################################################### -# VALIDATION -############################################################################### - resource "null_resource" "validate_primary_region" { count = contains(var.regions, var.primary_region) ? 0 : 1 @@ -84,12 +68,8 @@ resource "null_resource" "validate_vpc" { } } -############################################################################### -# CLOUD RUN MULTI-REGION DEPLOY -############################################################################### - module "cloud_run_v2_multiregion" { - source = "marcelorobj/cloud-run/google//modules/v2" + source = "../../modules/v2" service_name = var.service_name location = var.location @@ -99,24 +79,26 @@ module "cloud_run_v2_multiregion" { cloud_run_deletion_protection = var.cloud_run_deletion_protection multi_region_settings = { - regions = [ - "us-west1", - "europe-west1" - ] + regions = var.regions } + load_balancer_config = var.enable_load_balancer ? { + regions = var.regions + global_ip_address = var.lb_ip_address + domain = var.lb_domain + name_prefix = var.service_name + } : null + containers = [ { container_image = var.image container_name = "hello-world" - # Check if app startd. If it fails,cloud run restart the container - startup_probe = { - initial_delay_seconds = 10 # wait 10 seconds before start test - timeout_seconds = 3 # request has 3s to reply - period_seconds = 3 # Test each 3s - failure_threshold = 5 # fails after 5 wrong attempts (ex: 500, 502, timeout) + initial_delay_seconds = 10 + timeout_seconds = 3 + period_seconds = 3 + failure_threshold = 5 http_get = { path = "/" @@ -124,8 +106,6 @@ module "cloud_run_v2_multiregion" { } } - # Liveness Probe: check if app remains running health - # If start get 5xx the container is restarted liveness_probe = { initial_delay_seconds = 10 timeout_seconds = 3 diff --git a/examples/v2_multi_regions/outputs.tf b/examples/v2_multi_regions/outputs.tf index a64a053c2..ff543edff 100644 --- a/examples/v2_multi_regions/outputs.tf +++ b/examples/v2_multi_regions/outputs.tf @@ -26,30 +26,27 @@ output "cloud_run_service_id" { output "global_forwarding_rule_id" { description = "ID of the global HTTPS forwarding rule." - value = google_compute_global_forwarding_rule.cloudrun_https_rule.id + value = module.cloud_run_v2_multiregion.global_forwarding_rule_id } output "https_proxy_id" { description = "ID of the HTTPS target proxy." - value = google_compute_target_https_proxy.cloudrun_https_proxy.id + value = module.cloud_run_v2_multiregion.https_proxy_id } output "url_map_id" { description = "ID of the URL map." - value = google_compute_url_map.cloudrun_urlmap.id + value = module.cloud_run_v2_multiregion.url_map_id } output "serverless_neg_self_links" { description = "Serverless NEG self links by region." - value = { - for region, neg in google_compute_region_network_endpoint_group.cloudrun_neg : - region => neg.self_link - } + value = module.cloud_run_v2_multiregion.serverless_negs } output "ssl_certificate_domains" { description = "Domains covered by the managed SSL certificate." - value = google_compute_managed_ssl_certificate.cloudrun_cert.managed[0].domains + value = module.cloud_run_v2_multiregion.ssl_certificate_domains } output "cloud_run_vpc_egress_mode" { @@ -69,10 +66,7 @@ output "cloud_run_regions" { output "serverless_negs" { description = "Serverless NEGs by region." - value = { - for region, neg in google_compute_region_network_endpoint_group.cloudrun_neg : - region => neg.name - } + value = module.cloud_run_v2_multiregion.serverless_negs } output "vpc_connectors_ids" { @@ -93,30 +87,30 @@ output "vpc_connectors_names" { output "backend_service_global" { description = "Global backend service backing the Cloud Run multi-region service." - value = google_compute_backend_service.cloudrun_backend_global.id + value = module.cloud_run_v2_multiregion.backend_service_global_id } output "lb_ip" { description = "Global IP address of the Load Balancer." - value = local.lb_ip + value = module.cloud_run_v2_multiregion.lb_ip } output "lb_domain" { description = "Domain for the Load Balancer (IP.sslip.io if no custom domain is provided)." - value = local.lb_domain + value = module.cloud_run_v2_multiregion.lb_domain } output "lb_https_url" { description = "HTTPS URL for the global Load Balancer." - value = "https://${local.lb_domain}" + value = module.cloud_run_v2_multiregion.lb_https_url } output "ssl_certificate_id" { description = "Managed SSL certificate ID." - value = google_compute_managed_ssl_certificate.cloudrun_cert.id + value = module.cloud_run_v2_multiregion.ssl_certificate_id } output "global_entrypoint" { description = "Global HTTPS entrypoint for the Cloud Run multi-region service." - value = "https://${local.lb_domain}" + value = module.cloud_run_v2_multiregion.lb_https_url } diff --git a/examples/v2_multi_regions/variables.tf b/examples/v2_multi_regions/variables.tf index a0a77fafd..884ff4963 100644 --- a/examples/v2_multi_regions/variables.tf +++ b/examples/v2_multi_regions/variables.tf @@ -111,3 +111,9 @@ variable "lb_domain" { description = "Optional: Use an existing domain. Leave empty to use .sslip.io." default = null } + +variable "enable_load_balancer" { + type = bool + description = "If true, creates the Global Load Balancer resources. Defaults to false." + default = false +} diff --git a/modules/v2/README.md b/modules/v2/README.md index b8e16634a..605514c4b 100644 --- a/modules/v2/README.md +++ b/modules/v2/README.md @@ -57,6 +57,7 @@ Functional examples are included in the | create\_service\_account | Create a new service account for cloud run service | `bool` | `true` | no | | custom\_audiences | One or more custom audiences that you want this service to support. Specify each custom audience as the full URL in a string. [Refer](https://cloud.google.com/run/docs/configuring/custom-audiences) | `list(string)` | `null` | no | | description | Cloud Run service description. This field currently has a 512-character limit. | `string` | `null` | no | +| enable\_load\_balancer | If true, creates the Global Load Balancer resources. Defaults to false. | `bool` | `false` | no | | enable\_prometheus\_sidecar | Enable Prometheus sidecar in Cloud Run instance. | `bool` | `false` | no | | encryption\_key | A reference to a customer managed encryption key (CMEK) to use to encrypt this container image. This is optional. | `string` | `null` | no | | execution\_environment | The sandbox environment to host this Revision. | `string` | `"EXECUTION_ENVIRONMENT_GEN2"` | no | @@ -64,6 +65,9 @@ Functional examples are included in the | iap\_members | Valid only when launch stage is set to 'BETA'. IAP is enabled automatically when users or service accounts (SAs) are provided. Use allUsers for public access, allAuthenticatedUsers for any Google-authenticated user, or specify individual users/SAs. [More info](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/iap_web_cloud_run_service_iam#member/members-2) | `list(string)` | `[]` | no | | ingress | Restricts network access to your Cloud Run service | `string` | `"INGRESS_TRAFFIC_ALL"` | no | | launch\_stage | The launch stage as defined by Google Cloud Platform Launch Stages. Cloud Run supports ALPHA, BETA, and GA. If no value is specified, GA is assumed. | `string` | `"GA"` | no | +| lb\_domain | Optional: Use an existing domain. Leave empty to use .sslip.io. (Only used if enable\_load\_balancer is true) | `string` | `null` | no | +| lb\_ip\_address | Optional: Use an existing Global IP for Load Balancer. Leave empty to create a new one. (Only used if enable\_load\_balancer is true) | `string` | `null` | no | +| load\_balancer\_config | Configuration for the Load Balancer. If null, no LB resources are created. |
object({
regions = list(string)
global_ip_address = optional(string, null)
domain = optional(string, null)
name_prefix = optional(string, "cloudrun")
})
| `null` | no | | location | Cloud Run service deployment location | `string` | n/a | yes | | max\_instance\_request\_concurrency | Sets the maximum number of requests that each serving instance can receive. This is optional. | `string` | `null` | no | | members | Users/SAs to be given invoker access to the service. Grant invoker access by specifying the users or service accounts (SAs). Use allUsers for public access, allAuthenticatedUsers for access by logged-in Google users, or provide a list of specific users/SAs. [See the complete list of available options here](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service_iam#member/members-1) | `list(string)` | `[]` | no | @@ -91,18 +95,28 @@ Functional examples are included in the | Name | Description | |------|-------------| | apphub\_service\_uri | Service URI in CAIS style to be used by Apphub. | +| backend\_service\_global\_id | n/a | | creator | Email address of the authenticated creator. | | effective\_annotations | All of annotations (key/value pairs) present on the resource in GCP, including the annotations configured through Terraform, other clients and services. | +| global\_forwarding\_rule\_id | n/a | +| https\_proxy\_id | n/a | | last\_modifier | Email address of the last authenticated modifier. | | latest\_created\_revision | Name of the last created revision. See comments in reconciling for additional information on reconciliation process in Cloud Run. | | latest\_ready\_revision | Name of the latest revision that is serving traffic. See comments in reconciling for additional information on reconciliation process in Cloud Run. | +| lb\_domain | n/a | +| lb\_https\_url | n/a | +| lb\_ip | n/a | | location | Location in which the Cloud Run service was created | | observed\_generation | The generation of this Service currently serving traffic. | | project\_id | Google Cloud project in which the service was created | +| serverless\_negs | n/a | | service\_account\_id | Service account id and email | | service\_id | Unique Identifier for the created service with format projects/{{project}}/locations/{{location}}/services/{{name}} | | service\_name | Name of the created service | | service\_uri | The main URI in which this Service is serving traffic. | +| ssl\_certificate\_domains | n/a | +| ssl\_certificate\_id | n/a | | traffic\_statuses | Detailed status information for corresponding traffic targets. | +| url\_map\_id | n/a | diff --git a/modules/v2/main.tf b/modules/v2/main.tf index ef124883b..89b6b1e3d 100644 --- a/modules/v2/main.tf +++ b/modules/v2/main.tf @@ -76,6 +76,146 @@ locals { startup_probe = [] liveness_probe = [] }] + + create_lb = var.load_balancer_config != null + + lb_prefix = try(var.load_balancer_config.name_prefix, "cloudrun") + + create_ip = local.create_lb && try(var.load_balancer_config.global_ip_address, null) == null + lb_ip_address = local.create_lb ? ( + local.create_ip ? google_compute_global_address.lb_ip[0].address : var.load_balancer_config.global_ip_address + ) : null + + lb_domain = local.create_lb ? ( + try(var.load_balancer_config.domain, null) != null + ? var.load_balancer_config.domain + : "${local.lb_ip_address}.sslip.io" + ) : null + + neg_regions = local.create_lb ? toset(var.load_balancer_config.regions) : toset([]) +} + +resource "google_compute_global_address" "lb_ip" { + count = local.create_ip ? 1 : 0 + name = "${local.lb_prefix}-global-ip" + project = var.project_id +} + +resource "google_compute_region_network_endpoint_group" "cloudrun_neg" { + for_each = local.neg_regions + + project = var.project_id + region = each.key + name = "${local.lb_prefix}-neg-${each.key}" + network_endpoint_type = "SERVERLESS" + + cloud_run { + service = google_cloud_run_v2_service.main.name + } +} + +resource "google_compute_backend_service" "cloudrun_backend_global" { + count = local.create_lb ? 1 : 0 + project = var.project_id + name = "${local.lb_prefix}-backend-global" + + protocol = "HTTP" + load_balancing_scheme = "EXTERNAL_MANAGED" + enable_cdn = false + + outlier_detection { + consecutive_errors = 3 + consecutive_gateway_failure = 5 + enforcing_consecutive_errors = 100 + enforcing_consecutive_gateway_failure = 100 + + base_ejection_time { seconds = 30 } + interval { seconds = 10 } + } + + dynamic "backend" { + for_each = google_compute_region_network_endpoint_group.cloudrun_neg + content { + group = backend.value.id + } + } +} + +resource "google_compute_url_map" "cloudrun_urlmap" { + count = local.create_lb ? 1 : 0 + name = "${local.lb_prefix}-urlmap" + project = var.project_id + + default_service = google_compute_backend_service.cloudrun_backend_global[0].id + + host_rule { + hosts = ["*"] + path_matcher = "allpaths" + } + + path_matcher { + name = "allpaths" + default_service = google_compute_backend_service.cloudrun_backend_global[0].id + + path_rule { + paths = ["/*"] + + route_action { + weighted_backend_services { + backend_service = google_compute_backend_service.cloudrun_backend_global[0].id + weight = 100 + } + + retry_policy { + num_retries = 10 + per_try_timeout { seconds = 30 } + retry_conditions = [ + "5xx", + "gateway-error", + "connect-failure", + "retriable-4xx", + "unavailable", + "dead-deadline-exceeded", + ] + } + } + } + } +} + +resource "google_compute_managed_ssl_certificate" "cloudrun_cert" { + count = local.create_lb ? 1 : 0 + name = "${local.lb_prefix}-cert" + project = var.project_id + + managed { + domains = [local.lb_domain] + } +} + +resource "google_compute_target_https_proxy" "cloudrun_https_proxy" { + count = local.create_lb ? 1 : 0 + name = "${local.lb_prefix}-https-proxy" + project = var.project_id + ssl_certificates = [google_compute_managed_ssl_certificate.cloudrun_cert[0].id] + url_map = google_compute_url_map.cloudrun_urlmap[0].id +} + +resource "google_compute_global_forwarding_rule" "cloudrun_https_rule" { + count = local.create_lb ? 1 : 0 + name = "${local.lb_prefix}-https-rule" + project = var.project_id + + ip_address = local.lb_ip_address + ip_protocol = "TCP" + port_range = "443" + target = google_compute_target_https_proxy.cloudrun_https_proxy[0].id + load_balancing_scheme = "EXTERNAL_MANAGED" + + depends_on = [ + google_compute_target_https_proxy.cloudrun_https_proxy, + google_compute_managed_ssl_certificate.cloudrun_cert + ] } resource "google_service_account" "sa" { diff --git a/modules/v2/metadata.yaml b/modules/v2/metadata.yaml index b64f37bfa..fe492834e 100644 --- a/modules/v2/metadata.yaml +++ b/modules/v2/metadata.yaml @@ -63,7 +63,7 @@ spec: varType: string required: true - name: multi_region_settings - description: "Settings for creating a Multi-Region Service. " + description: " Settings for creating a Multi-Region Service." varType: |- object({ regions = list(string) @@ -509,6 +509,25 @@ spec: description: The sandbox environment to host this Revision. varType: string defaultValue: EXECUTION_ENVIRONMENT_GEN2 + - name: load_balancer_config + description: Configuration for the Load Balancer. If null, no LB resources are created. + varType: |- + object({ + regions = list(string) + global_ip_address = optional(string, null) + domain = optional(string, null) + name_prefix = optional(string, "cloudrun") + }) + - name: enable_load_balancer + description: If true, creates the Global Load Balancer resources. Defaults to false. + varType: bool + defaultValue: false + - name: lb_ip_address + description: "Optional: Use an existing Global IP for Load Balancer. Leave empty to create a new one. (Only used if enable_load_balancer is true)" + varType: string + - name: lb_domain + description: "Optional: Use an existing domain. Leave empty to use .sslip.io. (Only used if enable_load_balancer is true)" + varType: string outputs: - name: apphub_service_uri description: Service URI in CAIS style to be used by Apphub. @@ -517,6 +536,7 @@ spec: - location: string service_id: string service_uri: string + - name: backend_service_global_id - name: creator description: Email address of the authenticated creator. type: string @@ -525,6 +545,8 @@ spec: type: - map - string + - name: global_forwarding_rule_id + - name: https_proxy_id - name: last_modifier description: Email address of the last authenticated modifier. type: string @@ -534,6 +556,9 @@ spec: - name: latest_ready_revision description: Name of the latest revision that is serving traffic. See comments in reconciling for additional information on reconciliation process in Cloud Run. type: string + - name: lb_domain + - name: lb_https_url + - name: lb_ip - name: location description: Location in which the Cloud Run service was created type: string @@ -543,6 +568,7 @@ spec: - name: project_id description: Google Cloud project in which the service was created type: string + - name: serverless_negs - name: service_account_id description: Service account id and email type: @@ -559,6 +585,8 @@ spec: - name: service_uri description: The main URI in which this Service is serving traffic. type: string + - name: ssl_certificate_domains + - name: ssl_certificate_id - name: traffic_statuses description: Detailed status information for corresponding traffic targets. type: @@ -569,17 +597,18 @@ spec: tag: string type: string uri: string + - name: url_map_id requirements: roles: - level: Project roles: - - roles/run.admin - - roles/iam.serviceAccountAdmin - roles/iam.serviceAccountUser - roles/serviceusage.serviceUsageViewer - roles/resourcemanager.projectIamAdmin - roles/compute.viewer - roles/iap.admin + - roles/run.admin + - roles/iam.serviceAccountAdmin services: - cloudresourcemanager.googleapis.com - compute.googleapis.com diff --git a/modules/v2/outputs.tf b/modules/v2/outputs.tf index 4e76d00f9..1c948563f 100644 --- a/modules/v2/outputs.tf +++ b/modules/v2/outputs.tf @@ -87,3 +87,45 @@ output "apphub_service_uri" { } description = "Service URI in CAIS style to be used by Apphub." } + +output "https_proxy_id" { + value = try(google_compute_target_https_proxy.cloudrun_https_proxy[0].id, null) +} + +output "url_map_id" { + value = try(google_compute_url_map.cloudrun_urlmap[0].id, null) +} + +output "ssl_certificate_domains" { + value = try(google_compute_managed_ssl_certificate.cloudrun_cert[0].managed[0].domains, []) +} + +output "ssl_certificate_id" { + value = try(google_compute_managed_ssl_certificate.cloudrun_cert[0].id, null) +} + +output "serverless_negs" { + value = { + for k, v in google_compute_region_network_endpoint_group.cloudrun_neg : k => v.self_link + } +} + +output "backend_service_global_id" { + value = try(google_compute_backend_service.cloudrun_backend_global[0].id, null) +} + +output "lb_ip" { + value = local.lb_ip_address +} + +output "lb_domain" { + value = local.lb_domain +} + +output "lb_https_url" { + value = local.lb_domain != null ? "https://${local.lb_domain}" : null +} + +output "global_forwarding_rule_id" { + value = try(google_compute_global_forwarding_rule.cloudrun_https_rule[0].id, null) +} \ No newline at end of file diff --git a/modules/v2/variables.tf b/modules/v2/variables.tf index 142c22e2c..684345e9c 100644 --- a/modules/v2/variables.tf +++ b/modules/v2/variables.tf @@ -21,7 +21,7 @@ variable "project_id" { } variable "multi_region_settings" { - description = "Settings for creating a Multi-Region Service. " + description = " Settings for creating a Multi-Region Service." type = object({ regions = list(string) }) @@ -365,3 +365,31 @@ variable "execution_environment" { } } +variable "load_balancer_config" { + description = "Configuration for the Load Balancer. If null, no LB resources are created." + type = object({ + regions = list(string) + global_ip_address = optional(string, null) + domain = optional(string, null) + name_prefix = optional(string, "cloudrun") + }) + default = null +} + +variable "enable_load_balancer" { + type = bool + description = "If true, creates the Global Load Balancer resources. Defaults to false." + default = false +} + +variable "lb_ip_address" { + type = string + description = "Optional: Use an existing Global IP for Load Balancer. Leave empty to create a new one. (Only used if enable_load_balancer is true)" + default = null +} + +variable "lb_domain" { + type = string + description = "Optional: Use an existing domain. Leave empty to use .sslip.io. (Only used if enable_load_balancer is true)" + default = null +} From 137dd1563b250e7b3fe66ae679315dca31e51994 Mon Sep 17 00:00:00 2001 From: Renato Rudnicki Date: Tue, 30 Dec 2025 10:00:40 -0300 Subject: [PATCH 6/7] update readme.md --- examples/v2_multi_regions/README.md | 36 +++++++++++++++ .../v2_multi_regions/terraform.tfvars.example | 46 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 examples/v2_multi_regions/terraform.tfvars.example diff --git a/examples/v2_multi_regions/README.md b/examples/v2_multi_regions/README.md index 616cbf9c5..e0e089adb 100644 --- a/examples/v2_multi_regions/README.md +++ b/examples/v2_multi_regions/README.md @@ -20,6 +20,42 @@ This example assumes that below mentioned prerequisites are in place before cons * All required APIs are enabled in the GCP Project +## Usage + +- Rename the `tfvars` file by running `mv terraform.tfvars.example terraform.tfvars` and update `terraform.tfvars` with values from your environment. + + ```bash + mv terraform.tfvars.example terraform.tfvars + ``` + +- Run `terraform init` to get the plugins. + + ```bash + terraform init + ``` + +- Run `terraform plan` and review the plan. + + ```bash + terraform plan + ``` + +- Run `terraform apply` to apply the infrastructure build. + + ```bash + terraform apply + ``` + + +### Clean up + +- Run `terraform destroy` to clean up your environment. +The input `delete_contents_on_destroy` must have been set to `true` in the original `apply` for the `terraform destroy` command to work. + + ```bash + terraform destroy + ``` + > **DISCLAIMER**: Please pay attention to the following important details regarding the Cloud Run **Service Health** feature used in this project: * **Pre-GA Feature:** This feature is subject to the "Pre-GA Offerings Terms" in the General Service Terms section of the Service Specific Terms. Pre-GA features are available "as is" and might have limited support. diff --git a/examples/v2_multi_regions/terraform.tfvars.example b/examples/v2_multi_regions/terraform.tfvars.example new file mode 100644 index 000000000..dac1ffbbe --- /dev/null +++ b/examples/v2_multi_regions/terraform.tfvars.example @@ -0,0 +1,46 @@ +project_id = "YOUR-PROJECT-ID" +regions = ["us-west1", "europe-west1"] +primary_region = "us-west1" +service_name = "cloudrun-multiregion" + +# Choose between direct-vpc-egress or vpc-access-connector +cloud_run_vpc_egress_mode = "direct-vpc-egress" +vpc_egress_traffic = "all-traffic" + +# Only for Direct VPC Egress +#vpc_network = "projects/YOUR-PROJECT/global/networks/YOUR-VPCE" + +# vpc_subnets = { +# "us-west1" = "projects/YOUR-PROJECT/regions/us-west1/subnetworks/sb-restricted-us-west1" +# "europe-west1" = "projects/YOUR-PROJECT/regions/europe-west1/subnetworks/sb-restricted-europe-west1" +# } + +# Set to true to create the load balancer infrastructure +# enable_load_balancer = true +lb_domain = null # Opcional - Your LB domain +lb_ip_address = null # Opcional - Your LB Global IP + +# Un-comment if you want to use the default vpc network +# cloud_run_vpc_egress_mode = "default" +# vpc_egress = null +# vpc_network = null +# vpc_subnets = {} +# vpc_connectors = {} +# lb_ip_address = null +# lb_domain = null + +# Un-comment if you want to use the vpc-access-connector +# vpc_connectors = { +# "us-west1" = { +# name = "con-cloud-run-us" +# region = "us-west1" +# subnet_name = "cloudrun-us-west1" +# } +# "europe-west1" = { +# name = "con-cloud-run-eu" +# region = "europe-west1" +# subnet_name = "cloudrun-europe-west1" +# } +# } + +# cloud_run_deletion_protection = false From 48d08ca4f655441acab2fc570b560f5d5c6993a7 Mon Sep 17 00:00:00 2001 From: Renato Rudnicki Date: Tue, 30 Dec 2025 16:40:10 -0300 Subject: [PATCH 7/7] adds custom image --- .../v2_multi_regions/custom_image/Dockerfile | 17 ++++++++ .../v2_multi_regions/custom_image/main.go | 42 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 examples/v2_multi_regions/custom_image/Dockerfile create mode 100644 examples/v2_multi_regions/custom_image/main.go diff --git a/examples/v2_multi_regions/custom_image/Dockerfile b/examples/v2_multi_regions/custom_image/Dockerfile new file mode 100644 index 000000000..0445f538c --- /dev/null +++ b/examples/v2_multi_regions/custom_image/Dockerfile @@ -0,0 +1,17 @@ +FROM golang:1.23-alpine as builder + +WORKDIR /app + +COPY main.go . + +RUN CGO_ENABLED=0 go build -o server main.go + +FROM alpine:3 + +WORKDIR /app + +COPY --from=builder /app/server . + +ENV PORT 8080 + +CMD ["./server"] diff --git a/examples/v2_multi_regions/custom_image/main.go b/examples/v2_multi_regions/custom_image/main.go new file mode 100644 index 000000000..e47a14116 --- /dev/null +++ b/examples/v2_multi_regions/custom_image/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "os" +) + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/healthcheck" { + healthHandler(w, r) + return + } + + w.Header().Set("Content-Type", "text/html") + fmt.Fprintf(w, ` +
+

Hello Cloud Run!

+

Path: %s

+
+ `, r.URL.Path) + }) + + http.HandleFunc("/healthcheck", healthHandler) + + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + + log.Printf("Listening on port %s", port) + if err := http.ListenAndServe(":"+port, nil); err != nil { + log.Fatal(err) + } +} + +func healthHandler(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, "OK") +}