Skip to content

Instantly share code, notes, and snippets.

@mikesparr
Last active August 8, 2023 23:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikesparr/0a2b53a6d972825158b331bdff7ce83d to your computer and use it in GitHub Desktop.
Save mikesparr/0a2b53a6d972825158b331bdff7ce83d to your computer and use it in GitHub Desktop.
Experiment with GCP application load balancer in front of Cloud Run service testing URL rewrite capabilities
#!/usr/bin/env bash
#####################################################################
# REFERENCES
# - https://cloud.google.com/run/docs/deploying#command-line
# - https://hub.docker.com/r/ealen/echo-server
# - https://cloud.google.com/load-balancing/docs/https/setup-global-ext-https-serverless
# - https://cloud.google.com/load-balancing/docs/url-map-concepts#wildcards-regx-dynamic-route
# - https://cloud.google.com/load-balancing/docs/url-map-concepts#example_url_map_workflow_with_an
#####################################################################
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 \
containerregistry.googleapis.com
# configure gcloud sdk
gcloud config set compute/region $GCP_REGION
gcloud config set compute/zone $GCP_ZONE
#################################################################
# Cloud Run Service (sample echoserver app)
#################################################################
export APP_NAME="echo"
export IMAGE_NAME="ealen/echo-server:0.7.1"
gcloud run deploy $APP_NAME \
--image $IMAGE_NAME \
--allow-unauthenticated \
--ingress=internal-and-cloud-load-balancing \
--region=$GCP_REGION
##########################################################
# Load Balancer
##########################################################
export DOMAIN="demo.msparr.com" # CHANGE ME TO DESIRED DOMAIN
export EXT_IP_NAME="external-address"
export BACKEND_SERVICE_NAME="$APP_NAME-service"
export SERVERLESS_NEG_NAME="$APP_NAME-neg"
export HTTP_KEEPALIVE_TIMEOUT_SEC="610" # default
# create static IP
gcloud compute addresses create $EXT_IP_NAME \
--network-tier=PREMIUM \
--ip-version=IPV4 \
--global
export EXT_IP=$(gcloud compute addresses describe $EXT_IP_NAME --global --format="value(address)")
# create serverless NEG
gcloud compute network-endpoint-groups create $SERVERLESS_NEG_NAME \
--region=$GCP_REGION \
--network-endpoint-type=serverless \
--cloud-run-service=$APP_NAME
# create backend service
gcloud compute backend-services create $BACKEND_SERVICE_NAME \
--load-balancing-scheme=EXTERNAL_MANAGED \
--global
# add serverless NEG to backend service
gcloud compute backend-services add-backend $BACKEND_SERVICE_NAME \
--network-endpoint-group=$SERVERLESS_NEG_NAME \
--network-endpoint-group-region=$GCP_REGION \
--global
# create URL map
gcloud compute url-maps create $APP_NAME-url-map \
--default-service $BACKEND_SERVICE_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 \
--http-keep-alive-timeout-sec=$HTTP_KEEPALIVE_TIMEOUT_SEC \
--ssl-certificates=$APP_NAME-cert \
--url-map=$APP_NAME-url-map
gcloud compute forwarding-rules create $APP_NAME-fwd-rule \
--load-balancing-scheme=EXTERNAL_MANAGED \
--target-https-proxy=$APP_NAME-https-proxy \
--global \
--ports=443 \
--address=$EXT_IP_NAME
# verify app is running (wait 10-15 minutes until cert provisions)
curl "https://$DOMAIN" # Unauthorized request
##########################################################
# URL rewrite experimentation
# - TODO (test if can add ?customer_id={customer}) to rewrite URL somehow
##########################################################
export URL_MAP_CONFIG_FILE="url-map.conf"
export DEFAULT_SVC_URL="https://www.googleapis.com/compute/v1/projects/$PROJECT_ID/global/backendServices/$BACKEND_SERVICE_NAME"
cat > $URL_MAP_CONFIG_FILE << EOF
kind: compute#urlMap
name: $APP_NAME-url-map
defaultService: $DEFAULT_SVC_URL
hostRules:
- hosts:
- '*'
pathMatcher: pathmap
pathMatchers:
- defaultService: $DEFAULT_SVC_URL
name: pathmap
routeRules:
- description: EchoService
matchRules:
- pathTemplateMatch: '/echo/{customer=*}/profile'
service: $DEFAULT_SVC_URL
priority: 1
routeAction:
urlRewrite:
pathTemplateRewrite: '/echo/profile/{customer}'
EOF
# validate new config
gcloud beta compute url-maps validate --source=$(pwd)/$URL_MAP_CONFIG_FILE
# import updated config
gcloud beta compute url-maps import $APP_NAME-url-map \
--source=$(pwd)/$URL_MAP_CONFIG_FILE \
--global
@mikesparr
Copy link
Author

mikesparr commented Aug 8, 2023

URL Rewrite with ALB

Testing whether you can capture url path template parameters and then reallocate them to a querystring. It appears you currently CANNOT append captured path parameters to the querystring based on these tests.

Experiment

Given input: /echo/3/profile where 3 represents customer_id
Desired output: /echo/profile/3/?customer_id=3

Results

It does not appear possible to accomplish this, even with the new Application Load Balancer. At first there were errors when trying to import the new url map config in the terminal using gcloud compute url-maps import ... command.

ERROR: (gcloud.beta.compute.url-maps.import) HTTPError 400: Invalid value for field 'resource': '{  "id": "5501649119833165093",  "name": "echo-url-map",  "hostRules": [{    "hosts": ["*"],    "pat...'. Invalid resource: URL_MAP/561701526140.echo-url-map

Errors disappeared after removing the querystring from line 131 with pathTemplateRewrite field.

After various tests, I was successful at importing a config using urlencoded parameters %3F for ? and %3D for =. That resulted, unfortunately, in the same output successfully swapping the {customer} second path param with 3rd, but leaving the {query: object blank.

Screenshots

Screenshot 2023-08-08 at 5 27 04 PM

Screenshot 2023-08-08 at 5 24 41 PM

@mikesparr
Copy link
Author

Failed tests

##########################################################
# URL rewrite experimentation
# - TODO (test if can add ?customer_id={customer}) to rewrite URL somehow
# - Tests (failed):
#   - /echo/profile/{customer}/?foo=bar
#   - /echo/profile/{customer}/?customer_id={customer}
#   - /echo/profile/{customer}?customer_id={customer}
#   - /echo/profile/{customer}/%3Fcustomer_id={customer}
##########################################################

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment