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
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.