Envoy based API Gateway on Cloud Run

If you need to access multiple API services from a single endpoint, the only solution is to use an API Gateway. They are many commercial and open source solutions for an API Gateway, but they are hard to configure, deploy and manage. If you don’t have the time and resources to deploy such a service, especially in development phase of a project, you must find a better solution. However, Envoy has become the first choice for many proxy solutions. Also, some of the current API Gateways are based on Envoy because it has high performance and it’s open source.

With very little coding and combining multiples services together, it is possible to build your own API Gateway and deploy it on Google Cloud Run.

TL;DR

You can find the full example of how to create a simple API Gateway and deploy it on Google Cloud Run on GitHub.

The solution overview

diagram

The solution is to use an Envoy proxy deployed on Cloud Run that routes all the requests to other Cloud Run services and to use a separate service to combine and deliver the swagger document.

Features:

  • easy configuration
  • fast deployment
  • combines multiple swagger files together

Not supported:

  • authentication at the API level. The authentication must be handled by ones individual backend service.

The setup

First clone the solution repository.

git https://github.com/gabihodoroaga/api-gateway-envoy.git
cd api-gateway-envoy

And define the environment variables.

PROJECT_ID=$(gcloud config list project --format='value(core.project)')
REGION=us-central1

Make sure that the cloud build service account has the necessary permissions to deploy a Cloud Run service.

Next, let’s deploy our dummy services to Cloud Run.

gcloud builds submit \
    --config service1/cloudbuild.yaml \
    --substitutions="_PROJECT=$PROJECT_ID,_REGION=$REGION" \
    --project=$PROJECT_ID \
    --region=$REGION \
    .

gcloud builds submit \
    --config service2/cloudbuild.yaml \
    --substitutions="_PROJECT=$PROJECT_ID,_REGION=$REGION" \
    --project=$PROJECT_ID \
    --region=$REGION \
    .

Each service has a swagger definition that can accessed from this path: /swagger/index.html , but the goal is to be able to have a unified swagger definition.

Install go-swagger.

download_url=$(curl -s https://api.github.com/repos/go-swagger/go-swagger/releases/latest | \
  jq -r '.assets[] | select(.name | contains("'"$(uname | tr '[:upper:]' '[:lower:]')"'_amd64")) | .browser_download_url')
curl -o /usr/local/bin/swagger -L'#' "$download_url"
chmod +x /usr/local/bin/swagger

Combine swagger definitions of the two services into one.

swagger mixin swagger.host.json \
    ../service1/docs/swagger.json \
    ../service2/docs/swagger.json > swagger.json

Deploy a new service based on Nginx to serve this swagger file and the swagger ui.

gcloud builds submit \
    --config swagger/cloudbuild.yaml \
    --substitutions="_PROJECT=$PROJECT_ID,_REGION=$REGION" \
    --project=$PROJECT_ID \
    --region=$REGION \
    .

The final piece is the API Gateway.

First, let’s grab the public url of the other services.

SERVICE1_URL=$(gcloud run services describe service1 \
    --format 'value(status.url)' \
    --project=$PROJECT_ID \
    --region=$REGION)
SERVICE1_HOST="${SERVICE1_URL#https://}"
echo $SERVICE1_HOST

SERVICE2_URL=$(gcloud run services describe service2 \
    --format 'value(status.url)' \
    --project=$PROJECT_ID \
    --region=$REGION)
SERVICE2_HOST="${SERVICE2_URL#https://}"
echo $SERVICE2_HOST

SWAGGER_URL=$(gcloud run services describe swagger \
    --format 'value(status.url)' \
    --project=$PROJECT_ID \
    --region=$REGION)
SWAGGER_HOST="${SWAGGER_URL#https://}"
echo $SWAGGER_HOST

Deploy the gateway.

gcloud builds submit \
    --config gateway/cloudbuild.yaml \
    --substitutions="_PROJECT=$PROJECT_ID,_REGION=$REGION,_SERVICE1_HOST=$SERVICE1_HOST,_SERVICE1_PORT=443,_SERVICE1_SSL=true,_SERVICE2_HOST=$SERVICE2_HOST,_SERVICE2_PORT=443,_SERVICE2_SSL=true,_SWAGGER_HOST=$SWAGGER_HOST,_SWAGGER_PORT=443,_SWAGGER_SSL=true" \
    --project=$PROJECT_ID \
    --region=$REGION \
    .

All set, jut grab the public url of your API Gateway and open it in your browser.

GATEWAY_URL=$(gcloud run services describe gateway \
    --format 'value(status.url)' \
    --project=$PROJECT_ID \
    --region=$REGION)

echo "swagger url is $GATEWAY_URL/swagger/index.html"

Done!

Cleaning up

In order to remove all the resources created and avoid unnecessary charges run the following commands:

# delete the gateway
gcloud -q run services delete gateway --project=$PROJECT_ID --region=$REGION
# delete the swagger
gcloud -q run services delete swagger --project=$PROJECT_ID --region=$REGION
# delete the service2
gcloud -q run services delete service2 --project=$PROJECT_ID --region=$REGION
# delete the service1
gcloud -q run services delete service1 --project=$PROJECT_ID --region=$REGION

Conclusion

Cloud Run is actually a very flexible solution to deploy any application, including an API Gateway.