Skip to content

Instantly share code, notes, and snippets.

@mikesparr
Last active April 12, 2024 04:26
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mikesparr/db2f5cb7d7980cf4970c7d5c8b2194e5 to your computer and use it in GitHub Desktop.
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
#!/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
@mikesparr
Copy link
Author

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
Screenshot 2023-08-17 at 5 29 45 PM

Requests when in Australia
image (22)

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

Screenshot 2023-08-17 at 6 15 26 PM

Configuration

Screenshot 2023-08-17 at 5 32 36 PM
Screenshot 2023-08-17 at 5 31 43 PM
Screenshot 2023-08-17 at 5 30 30 PM

@RamyaMagesh10734605
Copy link

I am unable to see the application http://my-app.msparr.com/ ERR_EMPTY_RESPONSE. please help

@mikesparr
Copy link
Author

@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