Simulate Google GCP API Gateway and Cloud Functions for Local Dev with Nginx
Posted on Thu 17 November 2022 in Computing
One of the things I really like about Google's Cloud Functions is that it's really easy to develop functions locally exactly as they would be hosted. This makes for a far quicker development feedback loop and negates the need to go anywhere near the console or Terraform for developing and testing functions.
However, when using the Functions Framework (in Python at least) each function runs on its own instance, with it's own port. So if you have multiple functions and therefore multiple paths, you are not simulating what this would look like behind API Gateway (assuming you are developing an API on a single domain with multiple paths).
The can easily be remedied using a simple proxy setup. I use Nginx in this example, along with a simple shell script for starting each functions-framework instance.
The following solution demonstrates simulating a API the has two paths defined in Swagger/OpenAPI. e.g.
...
paths:
/service:
get:
...
/records/{fqdn}:
put:
parameters:
- in: path
name: fqdn
type: string
It also simulates the
CONSTANT_ADDRESS
path translation mode where path parameters are transformed to query strings.
For example, in the Swagger API definition above, the fqdn
path parameter in the record
path/function is actually rewritten by API Gateway from /records/foo/
to
/records?fqdn=foo
Solution
First we have a script that launches two functions-framework instances like this: (I've also left in the env var that needs to be set when using a local instance or Firebase/Firestore)
#run_functions.sh
FIRESTORE_EMULATOR_HOST=127.0.0.1:8080
export FIRESTORE_EMULATOR_HOST
functions-framework --target records --debug --host localhost --port 2600 &
functions-framework --target service --debug --host localhost --port 2601 &
After launching the functions-framework instances, we simulate API Gateway by launching an
instance of Nginx with daemon off
so it launches in the shell foreground that we can
quickly and easily kill when needed.
#nginx.conf
daemon off;
error_log stderr notice;
http {
server {
access_log /dev/stdout;
listen 8888;
location /records {
#rewrite_log on;
rewrite ^/records/(.*)$ /?fqdn=$1 break;
proxy_pass http://localhost:2600;
}
location /service {
#rewrite_log on;
rewrite ^/service/ / break;
proxy_pass http://localhost:2601;
}
}
}
events {
worker_connections 1024;
use epoll;
}
Finally, we launch Nginx and we have a single endpoint on http://localhost:8888 simulating an API Gateway instance.
sudo nginx -c /absolute_path_to/nginx.conf
This is absolutely perfect when we want to run integration and systems tests against an entire system, rather than having to test a single path at a time.