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.