Last active
April 16, 2025 07:02
-
-
Save mikesparr/db2f5cb7d7980cf4970c7d5c8b2194e5 to your computer and use it in GitHub Desktop.
Demonstrating how you can deploy Cloud Run (serverless) or Compute Engine instance groups across regions and balance with global load balancer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
##################################################################### | |
# REFERENCES | |
# - https://cloud.google.com/run/docs/multiple-regions | |
# - https://cloud.google.com/compute/docs/instance-groups/distributing-instances-with-regional-instance-groups | |
# - https://cloud.google.com/load-balancing/docs/https/setup-global-ext-https-compute | |
# - https://cloud.google.com/load-balancing/docs/backend-service#named_ports | |
##################################################################### | |
export PROJECT_ID=$(gcloud config get-value project) | |
export PROJECT_USER=$(gcloud config get-value core/account) # set current user | |
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)") | |
export IDNS=${PROJECT_ID}.svc.id.goog # workflow identity domain | |
export GCP_REGION="us-central1" # CHANGEME (OPT) | |
export GCP_ZONE="us-central1-a" # CHANGEME (OPT) | |
export NETWORK_NAME="default" | |
# enable apis | |
gcloud services enable compute.googleapis.com \ | |
storage.googleapis.com \ | |
cloudbuild.googleapis.com \ | |
run.googleapis.com \ | |
artifactregistry.googleapis.com | |
# configure gcloud sdk | |
gcloud config set compute/region $GCP_REGION | |
gcloud config set compute/zone $GCP_ZONE | |
########################################################## | |
# Demo App Initialization | |
########################################################## | |
# declare demo app name | |
export APP_NAME="my-app" | |
export APP_REGION_1="us-central1" | |
export APP_REGION_2="australia-southeast1" | |
export APP_IMAGE_URL="gcr.io/google-samples/zone-printer:0.2" | |
########################################################## | |
# Load Balancing | |
########################################################## | |
export DOMAIN="my-app.msparr.com" # CHANGE ME TO DESIRED DOMAIN | |
export EXT_IP_NAME="global-ip" | |
export BACKEND_NAME="$APP_NAME-backend" | |
export SERVERLESS_NEG_NAME_1="$APP_NAME-neg-us-2" | |
export SERVERLESS_NEG_NAME_2="$APP_NAME-neg-aus-2" | |
export HTTP_KEEPALIVE_TIMEOUT_SEC="610" # default | |
# create global public IP | |
gcloud compute addresses create --global $EXT_IP_NAME | |
export EXT_IP=$(gcloud compute addresses describe $EXT_IP_NAME --global --format="value(address)") | |
echo "** remember to update DNS for $DOMAIN -> $EXT_IP **" | |
# create backend service | |
gcloud compute backend-services create $BACKEND_NAME \ | |
--global | |
# create URL map | |
gcloud compute url-maps create $APP_NAME-url-map \ | |
--default-service=$BACKEND_NAME | |
# create managed SSL cert | |
gcloud beta compute ssl-certificates create $APP_NAME-cert \ | |
--domains $DOMAIN | |
# create target HTTPS proxy | |
gcloud compute target-https-proxies create $APP_NAME-https-proxy \ | |
--ssl-certificates=$APP_NAME-cert \ | |
--url-map=$APP_NAME-url-map | |
# create forwarding rule using static IP (classic LB, use EXTERNAL_MANAGED for global ALB) | |
gcloud compute forwarding-rules create $APP_NAME-fwd-rule \ | |
--load-balancing-scheme=EXTERNAL \ | |
--target-https-proxy=$APP_NAME-https-proxy \ | |
--global \ | |
--ports=443 \ | |
--address=$EXT_IP_NAME | |
# create target HTTP proxy | |
gcloud compute target-http-proxies create $APP_NAME-http-proxy \ | |
--url-map=$APP_NAME-url-map | |
# create forwarding rule using static IP (classic LB, use EXTERNAL_MANAGED for global ALB) | |
gcloud compute forwarding-rules create $APP_NAME-fwd-rule-http \ | |
--load-balancing-scheme=EXTERNAL \ | |
--target-http-proxy=$APP_NAME-http-proxy \ | |
--global \ | |
--ports=80 \ | |
--address=$EXT_IP_NAME | |
########################################################## | |
# Cloud Run Serverless Runtime | |
########################################################## | |
export SERVERLESS_NEG_NAME="$APP_NAME-neg" | |
export SERVERLESS_BACKEND_NAME="$APP_NAME-run-backend" | |
# deploy app to 2 regions | |
gcloud run deploy $APP_NAME \ | |
--allow-unauthenticated \ | |
--ingress=internal-and-cloud-load-balancing \ | |
--image=$APP_IMAGE_URL \ | |
--region=$APP_REGION_1 | |
gcloud run deploy $APP_NAME \ | |
--allow-unauthenticated \ | |
--ingress=internal-and-cloud-load-balancing \ | |
--image=$APP_IMAGE_URL \ | |
--region=$APP_REGION_2 | |
# create serverless network endpoint groups for each region | |
gcloud compute network-endpoint-groups create $SERVERLESS_NEG_NAME \ | |
--network-endpoint-type=serverless \ | |
--cloud-run-service=$APP_NAME \ | |
--region=$APP_REGION_1 | |
gcloud compute network-endpoint-groups create $SERVERLESS_NEG_NAME \ | |
--network-endpoint-type=serverless \ | |
--cloud-run-service=$APP_NAME \ | |
--region=$APP_REGION_2 | |
# add custom backend for Cloud Run apps (assuming legacy) | |
gcloud compute backend-services create $SERVERLESS_BACKEND_NAME \ | |
--global | |
# add serverless negs to backend for each region | |
gcloud compute backend-services add-backend $SERVERLESS_BACKEND_NAME \ | |
--global \ | |
--network-endpoint-group=$SERVERLESS_NEG_NAME \ | |
--network-endpoint-group-region=$APP_REGION_1 | |
gcloud compute backend-services add-backend $SERVERLESS_BACKEND_NAME \ | |
--global \ | |
--network-endpoint-group=$SERVERLESS_NEG_NAME \ | |
--network-endpoint-group-region=$APP_REGION_2 | |
# update existing load balancer URL map to point to new backend | |
gcloud compute url-maps set-default-service $APP_NAME-url-map \ | |
--default-service=$SERVERLESS_BACKEND_NAME \ | |
--global | |
# test from different regions and confirm backend switches | |
########################################################## | |
# (OPTIONAL ALTERNATIVE) Compute Engine Runtime | |
########################################################## | |
export INSTANCE_TEMPLATE_NAME="$APP_NAME-template" | |
export INSTANCE_GROUP_NAME="$APP_NAME-rmig" | |
export MIG_BACKEND_NAME="$APP_NAME-rmig-backend" | |
export HEALTH_CHECK_NAME="http-basic-check" | |
# create instance template that runs container image | |
gcloud compute instance-templates create-with-container $INSTANCE_TEMPLATE_NAME \ | |
--container-image $APP_IMAGE_URL \ | |
--tags "$APP_NAME,allow-health-check,allow-ssh" | |
# create regional migs for each region | |
gcloud compute instance-groups managed create $INSTANCE_GROUP_NAME \ | |
--base-instance-name $INSTANCE_GROUP_NAME \ | |
--size 1 \ | |
--template $INSTANCE_TEMPLATE_NAME \ | |
--region $APP_REGION_1 | |
gcloud compute instance-groups managed create $INSTANCE_GROUP_NAME \ | |
--base-instance-name $INSTANCE_GROUP_NAME \ | |
--size 1 \ | |
--template $INSTANCE_TEMPLATE_NAME \ | |
--region $APP_REGION_2 | |
# add named port (internal) | |
gcloud compute instance-groups set-named-ports $INSTANCE_GROUP_NAME \ | |
--named-ports http:80,$APP_NAME:8080 \ | |
--region $APP_REGION_1 | |
gcloud compute instance-groups set-named-ports $INSTANCE_GROUP_NAME \ | |
--named-ports http:80,$APP_NAME:8080 \ | |
--region $APP_REGION_2 | |
# enable firewall rules | |
gcloud compute firewall-rules create fw-allow-health-check \ | |
--network=default \ | |
--action=allow \ | |
--direction=ingress \ | |
--source-ranges=130.211.0.0/22,35.191.0.0/16 \ | |
--target-tags=allow-health-check \ | |
--rules=tcp | |
gcloud compute firewall-rules create fw-allow-ssh \ | |
--network=default \ | |
--action=allow \ | |
--direction=ingress \ | |
--source-ranges=0.0.0.0/0 \ | |
--target-tags=allow-ssh \ | |
--rules=tcp:22 | |
# create health check | |
gcloud compute health-checks create http $HEALTH_CHECK_NAME \ | |
--use-serving-port \ | |
--global | |
# create custom backend for MIGs | |
gcloud compute backend-services create $MIG_BACKEND_NAME \ | |
--load-balancing-scheme=EXTERNAL \ | |
--protocol=HTTP \ | |
--port-name=$APP_NAME \ | |
--health-checks=$HEALTH_CHECK_NAME \ | |
--global | |
# add instance groups to MIG backend for each region | |
gcloud compute backend-services add-backend $MIG_BACKEND_NAME \ | |
--global \ | |
--instance-group=$INSTANCE_GROUP_NAME \ | |
--instance-group-region=$APP_REGION_1 | |
gcloud compute backend-services add-backend $MIG_BACKEND_NAME \ | |
--global \ | |
--instance-group=$INSTANCE_GROUP_NAME \ | |
--instance-group-region=$APP_REGION_2 | |
# update existing load balancer URL map to point to MIG backend | |
gcloud compute url-maps set-default-service $APP_NAME-url-map \ | |
--default-service=$MIG_BACKEND_NAME \ | |
--global | |
# test from different regions and confirm backend switches |
I am unable to see the application http://my-app.msparr.com/ ERR_EMPTY_RESPONSE. please help
@RamyaMagesh10734605 this is example how I set this up in my environment, with a domain name (msparr.com) that I own. You would have to set up with your own domain/dns and in your own GCP project.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Global load balancing serving multi-region backends
Google Cloud's global load balancer and Google Front End can detect where requests are coming from and if you configure multiple regional services within a backend, it will route traffic to the closest backend. This demo illustrates how you could update an existing load balancer URL map using a single command to route traffic to new backends.
Potential use case for demo
Assume you have containerized workloads running on App Engine Flexible and they are serving up a Docker image. App Engine services are bound to a project and region, so cannot be load balanced in multiple regions. Assuming you had an existing app and load balancer (classic), you could spin up Cloud Run services using your same Docker image in multiple regions, add serverless network endpoint groups (NEGs) in each region respectively, and then add these to a new backend. Then by simply changing the default backend for your existing load balancer URL map, you could be serving traffic closer to where your users are and reduce latency.
Results
Requests when in US

Requests when in Australia

Multiple backends and can update URL map to serve up desired one
Configuration