This tutorial walks through how to use a custom Docker image to define an Fn function. Although Fn functions are packaged as Docker images, when developing functions using the fn CLI developers are not directly exposed to the underlying Docker platform. Docker isn't hidden (you can see Docker build output and image names and tags in routes), but you aren't required to be very Docker-savvy to develop functions with Fn. However, sometimes you need to handle advanced use cases and must take complete control of the creation of the function image. Fortunately the design and implementation of Fn enables you to do exactly that. Let's build a simple custom function image to walk through the process.
As you make your way through this tutorial, look out for this icon.
Whenever you see it, it's time for you to
perform an action.
This tutorial requires you to have both Docker and Fn installed. If you need help with Fn installation you can find instructions in the Introduction to Fn tutorial.
Before we can get starting there are a couple of configuration steps to take care of.
To make it possible to push images you need to authenticate yourself with your Docker repository (default is Docker Hub).
docker login <yourdockerid>
Next you need to start the Fn server. We'll run it in the foreground to let us see the server log messages so let's open a new terminal for this.
-
Define the FN_REGISTRY environment variable to point the Fn server to where it should pull function images from. If using the default Docker Hub registry you just need to specify your docker user id:
export FN_REGISTRY=<yourdockerid> -
Start the Fn server using the
fncli:fn start
In this tutorial we only have two artifacts: a Dockerfile and a very simple Node.js "Hello World" application that returns a customized greeting given a name.
The func.js file is nothing special and simply reads from standard input
and writes to standard output. This is the standard Fn supported mechanism
for functions to receive input and return output.
'Hot Functions'
(not discussed in this tutorial) are slightly different.
name = "World";
fs = require('fs');
try {
input = fs.readFileSync('/dev/stdin').toString();
if (input) {
name = input;
}
} catch(e) {}
console.log("Hello", name, "from Node!");The Dockerfile for our function is also very simple. It starts with
a light alpine Node.js base image, copies the func.js into the image,
and sets the entrypoint so that when the container is started the
func.js is run.
FROM node:8-alpine
WORKDIR /function
# cli should forbid this name
ADD func.js /function/func.js
# Run the handler, with a payload in the future.
ENTRYPOINT ["node", "./func.js"]You build and run the image as you would any Docker image:
-
Open a new terminal
-
Build your function container image with
docker build:docker build . -t <yourdockerid>/node-hello:0.0.1 -
Test the image by running it with no input:
docker run --rm <yourdockerid>/node-hello:0.0.1The output should be:
Hello World from Node! -
Test the image by running it with a name parameter:
echo -n "Jane" | docker run -i --rm <yourdockerid>/node-hello:0.0.1The output should be the same as be except "Jane" in place of "World":
Hello Jane from Node!
Great! We have a working Docker image. Now let's deploy it as a function.
When developing locally you don't need to deploy to Docker Hub--the
local Fn server can find your function image on the local machine. But
eventually you are going to want to run your function on a remote
Fn server which requires you to publish your function image in
a repository like Docker Hub. You can do this with a standard docker push
but again this step is optional when we're working locally.
docker push <yourdockerid>/node-hello:0.0.1
Once we have a function container image we can associate that image with a 'route'.
-
First we need an 'application' to contain our functions. Applications define a namespace to organize functions and can contain configuration values that are shared across all functions in that application:
fn apps create demoappSuccessfully created app: demoapp -
We then manually create a route that uses our manually built container image:
fn routes create demoapp /hello -i <yourdockerid>/node-hello:0.0.1/hello created with <yourdockerid>/node-hello:0.0.1 -
We can confirm the route is correctly defined by getting a list of the routes defined for an application:
fn routes list demoappYou should see something like:
path image endpoint /hello <yourdockerid>/node-hello:0.0.1 localhost:8080/r/demoapp/hello
Note that at this point all the Fn server has is essentially configuration metadata. It has the name of an application and a function route that is part of that application that points to a named and tagged Docker image. It's not until that route is invoked that this metadata is used.
Calling a function that was created through a manually defined route is no
different from calling a function defined using fn deploy--which is exactly
as intended!
-
Call the function using
fn call:echo -n "Jane" | fn call demoapp /helloThis will produce the expected output:
Hello Jane from Node! -
Call the function with curl using it's http endpoint. You can find out the endpoints for each of your routes using the
fn routes listcommand we used above.curl -d "Jane" http://localhost:8080/r/demoapp/helloThis will produce exactly the same output as when using
fn call, as expected.Hello Jane from Node!
When the function is invoked, regardless of the mechanism, the Fn server looks up the function image name and tag associated with the route and has Docker run a container. If the required image is not already available locally then Docker will attempt to pull the image from the registry that was specified by the FN_REGISTRY environment variable.
In our local development scenario, the image is already on the local machine so you won't see a 'pull' message in the Fn server log.
Having completed this tutorial you've successfully built a Docker image, defined a function as implemented by that image, and invoked the function resulting in the creation of a container using that image. Congratulations!
For more hands on fun checkout the other Fn Tutorials!