In this post, I'll show how the recently open-sourced Functions Framework for Python makes Cloud Functions portable across multiple products and ecosystems. I’ll specifically show how to migrate a Python function from Cloud Functions to a service like Cloud Run.
I'll also show you how the Functions Framework gives you the ability to test your functions locally as well.
The Function Frameworks are open source libraries for writing portable functions -- brought to you by the Google Cloud Functions team.
The Functions Framework lets you write lightweight functions that run in many different environments, including:
- Google Cloud Functions
- Cloud Run and Cloud Run on GKE
- Knative-based environments
- Your local development machine
- and elsewhere.
You can think of the Functions Framework for a given language as a wrapper around a single function in that language, which handles everything necessary for your function to receive HTTP requests or Cloud Events such as Pub/Sub.
Cloud Functions is a powerful and simple tool for quickly deploying a standalone function that lets you integrate various APIs, products, services, or just respond to events.
However, that simplicity also comes at a cost: the Cloud Functions Runtime makes some opinionated choices about the environment in which your function runs, such as the runtime’s base image, the patch version of Python, and the underlying system libraries that are installed.
For most developers, this is totally fine, as the choices that have been made for the runtime are appropriate for almost all use cases.
However, occasionally you will want to make a small change, such as install a special platform-level package that you need for a particular task.
Or, you'll want to continue using your function as-is, but move it to run on Cloud Run to lower costs.
Let's say we have this minimal Cloud Function:
def hello(request): return "Hello world!"
This function doesn't do much: it takes an HTTP request, and returns "Hello world!" as a response.
However, if we wanted to migrate it from Cloud Functions to a service like Cloud Run, which hosts applications, not functions, we'd have to do a lot:
- choose a web framework
- configure the web application
- refactor this function as a view in our web app
- add the view as a route
By this point, our "new" function wouldn't look much at all like your original function. If you had chosen Flask as your web framework, it might look something like this:
from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'Hello world!'
That's a lot of extra boilerplate that you don't need.
With the Functions Framework, none of this refactoring is necessary. You can deploy a function to Cloud Run without changing a single line of code in your
Instead, you can define a
Dockerfile as follows:
# Use the official Python image. # https://hub.docker.com/_/python FROM python:3.7-slim # Copy local code to the container image. ENV APP_HOME /app WORKDIR $APP_HOME COPY . . # Install production dependencies. RUN pip install gunicorn functions-framework RUN pip install -r requirements.txt # Run the web service on container startup. Here we use the gunicorn # webserver, with one worker process and 8 threads. # For environments with multiple CPU cores, increase the number of workers # to be equal to the cores available. CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 -e FUNCTION_TARGET=hello functions_framework:app
Dockerfile largely borrows from the
Dockerfile in the Python Cloud Run quickstart, but with two key differences:
- it installs the
- it invokes the Functions Framework in the
The last steps are building and deploying the container as you would with any Cloud Run application.
Another useful feature of the Functions Framework is the ability to run your Cloud Function locally.
Using the example above, you can run your function on your host machine direction using the Function Framework's handy command-line utility:
$ functions-framework --target hello
The downside to this is that any dependencies need to be installed globally and that there may be conflicts between what your function requires and what your host machine has available.
Ideally, we would isolate our function by building the container image we defined in our
Dockerfile and running it locally:
$ docker build -t helloworld . && docker run --rm -p 8080:8080 -e PORT=8080 helloworld
Both of these will start a local development server at http://localhost:8080 that you can send HTTP traffic to.
One additional benefit to the Functions Framework is that it makes all of the the necessary types available to statically type your functions.
For example, an HTTP function could be statically typed before:
from typing import Tuple, Union from flask.wrappers import Request def my_function(request: Request) -> str: ...
But now the
Context type is available for background functions:
from typing import Dict from google.cloud.functions.context import Context def my_function(data: Dict, context: Context) -> None: ...
- Found this project useful? Give it a star it on Github
- Found a bug in the Python Functions Framework? File an issue
- Want similar updates? Follow me on Twitter