Serverless functions with FN project
Tags: beginners, serverless, java, opensource
Published: 2021-10-07 03:49:58


As probably most of you know, every application (API, Kafka, MQ, and so on) runs on a server; if you are a java or Kotlin developer, you are very aware of frameworks like Spring, or Http4K or Ktor, which still use underneath servers like Netty or Jetty, the node server in JS, the HTTP Go server in Golang, Flask/Django in Python.

However, there are also other types of applications popping up more often into enterprise-level architecture discussions; we will call them functions, and these functions have a unique talent not to require a server to run, making them smaller, easier to deploy, and my personal favorite easier to maintain, nevertheless they need some specific infrastructure to run, this could be from AWS lambda to any of the other FaaS services around. 

Still, for today I would like to talk to you about the [FN project]( https://fnproject.io/), an open-source alternative.

Let's install FN first (if you have a mac, this will be easier, but if you are using Windows, I will add a version for that as soon as I can)

## Prerequisites

- Docker 17.10.0-ce or later installed and running (Yeah, that's it)

## We will use first user Homebrew to install fn:

* `brew install fn`

```shell
brew install fn
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/core).
==> Updated Formulae
Updated 44 formulae.

==> Downloading https://ghcr.io/v2/homebrew/core/fn/manifests/0.6.9
Already downloaded: /Users/sergiomarcial/Library/Caches/Homebrew/downloads/cf271e3abf6c80238db5a02bc270b9393ee289fd71469c4d6c6ffbd7b66e1283--fn-0.6.9.bottle_manifest.json
==> Downloading https://ghcr.io/v2/homebrew/core/fn/blobs/sha256:f9242da69c2ff818effdd57b29441c5b70f919e203fad4ac758d3d53f1368df9
Already downloaded: /Users/sergiomarcial/Library/Caches/Homebrew/downloads/890c94f09da45b56221935d254f091fd8f5bea73fbcce2148212e5dc3f9ef368--fn--0.6.9.big_sur.bottle.tar.gz
==> Pouring fn--0.6.9.big_sur.bottle.tar.gz
🍺  /usr/local/Cellar/fn/0.6.9: 6 files, 11.7MB
```
<br />

Once installed, we will now make it run; for this, there are multiple options, but for this, I will stick to the basic ones, log level, and attached console

Now let's run the command `fn start`, which will give you the following output

<br />

```shell
fn start
2021/10/06 08:37:56 ¡¡¡ 'fn start' should NOT be used for PRODUCTION !!! see https://github.com/fnproject/fn-helm/
Unable to find image 'fnproject/fnserver:latest' locally
latest: Pulling from fnproject/fnserver
ff3a5c916c92: Pull complete
1a649ea86bca: Pull complete
ce35f4d5f86a: Pull complete
b6206661264b: Pull complete
b8b71dba24d3: Pull complete
3873004a68ee: Pull complete
f4205b132661: Pull complete
91a85eeeb257: Pull complete
93c96d032b32: Pull complete
bb761748d6e1: Pull complete
81f6c51c4ac2: Pull complete
2ba715696dba: Pull complete
f46c2b56aaf3: Pull complete
aef258868c13: Pull complete
9a72ccea4c0a: Pull complete
Digest: sha256:34a51ad87dbe9e360ad194556f3cc21b6a75d73e41abd7d541a223beeb7f7271
Status: Downloaded newer image for fnproject/fnserver:latest
time="2021-10-06T13:38:09Z" level=info msg="Setting log level to" fields.level=info
time="2021-10-06T13:38:09Z" level=info msg="Registering data store provider 'sql'"
time="2021-10-06T13:38:09Z" level=info msg="Connecting to DB" url="sqlite3:///app/data/fn.db"
time="2021-10-06T13:38:09Z" level=info msg="datastore dialed" datastore=sqlite3 max_idle_connections=256 url="sqlite3:///app/data/fn.db"
time="2021-10-06T13:38:09Z" level=info msg="agent starting cfg={MinDockerVersion:17.10.0-ce ContainerLabelTag: DockerNetworks: DockerLoadFile: DisableUnprivilegedContainers:false FreezeIdle:50ms HotPoll:200ms HotLauncherTimeout:1h0m0s HotPullTimeout:10m0s HotStartTimeout:5s DetachedHeadRoom:6m0s MaxResponseSize:0 MaxHdrResponseSize:0 MaxLogSize:1048576 MaxTotalCPU:0 MaxTotalMemory:0 MaxFsSize:0 MaxPIDs:50 MaxOpenFiles:0xc420230bf8 MaxLockedMemory:0xc420230c10 MaxPendingSignals:0xc420230c18 MaxMessageQueue:0xc420230c20 PreForkPoolSize:0 PreForkImage:busybox PreForkCmd:tail -f /dev/null PreForkUseOnce:0 PreForkNetworks: EnableNBResourceTracker:false MaxTmpFsInodes:0 DisableReadOnlyRootFs:false DisableDebugUserLogs:false IOFSEnableTmpfs:false EnableFDKDebugInfo:false IOFSAgentPath:/iofs IOFSMountRoot:/Users/sergiomarcial/.fn/iofs IOFSOpts: ImageCleanMaxSize:0 ImageCleanExemptTags: ImageEnableVolume:false}"
time="2021-10-06T13:38:09Z" level=info msg="no docker auths from config files found (this is fine)" error="open /root/.dockercfg: no such file or directory"
time="2021-10-06T13:38:09Z" level=info msg="available memory" cgroup_limit=9223372036854771712 head_room=268435456 total_memory=1118244864
time="2021-10-06T13:38:09Z" level=info msg="ram reservations" avail_memory=849809408
time="2021-10-06T13:38:09Z" level=info msg="available cpu" avail_cpu=8000 total_cpu=8000
time="2021-10-06T13:38:09Z" level=info msg="cpu reservations" cpu=8000
time="2021-10-06T13:38:09Z" level=info msg="\n        ______\n       / ____/___\n      / /_  / __ \\\n     / __/ / / / /\n    /_/   /_/ /_/\n"
time="2021-10-06T13:38:09Z" level=info msg="Fn serving on `:8080`" type=full version=0.3.749
```

<br />

As you might notice, fn is trying to pull the latest docker image from the docker registry if you don't have it already
```shell
Unable to find image 'fnproject/fnserver:latest' locally
latest: Pulling from fnproject/fnserver
ff3a5c916c92: Pull complete
1a649ea86bca: Pull complete
ce35f4d5f86a: Pull complete
b6206661264b: Pull complete
b8b71dba24d3: Pull complete
3873004a68ee: Pull complete
f4205b132661: Pull complete
91a85eeeb257: Pull complete
93c96d032b32: Pull complete
bb761748d6e1: Pull complete
81f6c51c4ac2: Pull complete
2ba715696dba: Pull complete
f46c2b56aaf3: Pull complete
aef258868c13: Pull complete
9a72ccea4c0a: Pull complete
```
<br />

Something important to mention is if you are planning to create a function using Java currently JDK 11, the latest JDK version supported by FN if you try to compile a function with Java 17, the current LTS version, you won't be able to take full advantage of it.

If you want to run the FN UI, you have to run the following command 

<br />

```shell
docker run --rm -it --link fnserver:api -p 4000:4000 -e "FN_API_URL=http://api:8080" fnproject/ui
Unable to find image 'fnproject/ui:latest' locally
latest: Pulling from fnproject/ui
b56ae66c2937: Pull complete
e93c4ef66dd7: Pull complete
a9e499bf0a12: Pull complete
ba1608f40908: Pull complete
6464d2649fbf: Pull complete
ebc7db4cf098: Pull complete
f34c1cd5ef21: Pull complete
dc688e6ebaad: Pull complete
Digest: sha256:82c5b2fd02d702d2294bb107c1c022dba699241f64e4e14b77519d4c25bbb5f9
Status: Downloaded newer image for fnproject/ui:latest

> FunctionsUI@0.0.39 start /app
> node server

WARNING: NODE_ENV value of 'production' did match any deployment config file names.
WARNING: See https://github.com/lorenwest/node-config/wiki/Strict-Mode
info: Using API url: api:8080
info: Server running on port 4000
```

<br />

To stop the FN server in attached mode, `CTR + C` will do the trick; if you run it in a detached way, use `fn stop`, this will automatically disable any deployed functions and the server behind the UI

Now to create your first function

`fn init --runtime <runner> <project-name>`

If you are curious Fn supports the next type of runners:

* go
    * go1.15
* java
    * java11
    * java8
* kotlin
* node
    * node11
    * node14
* python
    * python3.6
    * python3.7
    * python3.8
* ruby
    * ruby2.7

```shell
% fn init --runtime java test-function
Creating function at: ./test-function
Function boilerplate generated.
func.yaml created.
% cd test-function
% ls -la
drwxr-xr-x user staff 160 B  Wed Oct  6 21:53:46 2021 .
drwxr-xr-x user staff 512 B  Wed Oct  6 21:53:45 2021 ..
.rw-r--r-- user staff 225 B  Wed Oct  6 21:53:46 2021 func.yaml
.rw-r--r-- user staff 2.1 KB Wed Oct  6 21:53:46 2021 pom.xml
drwxr-xr-x user staff 128 B  Wed Oct  6 21:53:46 2021 src
```

As you can see in the case of Java, it creates the boilerplate code base on Maven, but Gradle is also an option, and I will create another blog entry to show how to configure FN to use Gradle and Gradle with Kotlin

<br />

## Now, let's create an application

This step will create the logical function in the FN server based on the name we provide; as a note, the app name doesn't need to match the name of the function you created

`fn create app <app-name>`

```shell
 % fn create app test-function
Successfully created app:  test-function
```

Function descriptor `func.yaml`

```yaml
schema_version: 20180708
name: test-function
version: 0.0.1
runtime: java
build_image: fnproject/fn-java-fdk-build:jdk11-1.0.133
run_image: fnproject/fn-java-fdk:jre11-1.0.133
cmd: com.example.fn.HelloFunction::handleRequest

```

Explanation:
- `name` -> name of the function.
`version` -> semantic version function helps keep track of the version and changes deployed, and it is automatically bumped up with every deployment unless otherwise indicated.
- `runtime` -> designated runnner.
- `build_image` -> base image which to build the function, these images are maintained by the FN project.
- `run_image` -> runner image, this is the image your function will be running over once deployed.
- `cmd` is the command called, in this case, the package, class, and method name that identifies the function we will invoke.

<br />

## And now finally, the deployment

`fn deploy --app <app-name> --local` 

```shell
 % fn deploy --app test-function --local
Deploying test-function to app: test-function
Bumped to version 0.0.2 
Building image test-function:0.0.2 ...................................................................
Updating function test-function using image test-function:0.0.2... 
Successfully created function: test-function with test-function:0.0.2
```

Line by line explanation:

- `Bumped to version 0.0.2` <- **FN will automatically increase the version of your function every time it is deployed**
- `Building image test-function:0.0.2 ..................` <- **FN is creating a docker image automatically**
- `Successfully created function: test-function with test-function:0.0.2` <- **Function has been deployed to FN server successfully**

<br />

## Wondering how to invoke your function?

To invoke your function you, just need to call the following command

`fn invoke <app-name> <function-name>`

<br />

And you can see the logs in the FN server for the execution

```shell
time="2021-10-06T13:38:09Z" level=info msg="Setting log level to" fields.level=info
time="2021-10-06T13:38:09Z" level=info msg="Registering data store provider 'sql'"
time="2021-10-06T13:38:09Z" level=info msg="Connecting to DB" url="sqlite3:///app/data/fn.db"
time="2021-10-06T13:38:09Z" level=info msg="datastore dialed" datastore=sqlite3 max_idle_connections=256 url="sqlite3:///app/data/fn.db"
time="2021-10-06T13:38:09Z" level=info msg="agent starting cfg={MinDockerVersion:17.10.0-ce ContainerLabelTag: DockerNetworks: DockerLoadFile: DisableUnprivilegedContainers:false FreezeIdle:50ms HotPoll:200ms HotLauncherTimeout:1h0m0s HotPullTimeout:10m0s HotStartTimeout:5s DetachedHeadRoom:6m0s MaxResponseSize:0 MaxHdrResponseSize:0 MaxLogSize:1048576 MaxTotalCPU:0 MaxTotalMemory:0 MaxFsSize:0 MaxPIDs:50 MaxOpenFiles:0xc420230bf8 MaxLockedMemory:0xc420230c10 MaxPendingSignals:0xc420230c18 MaxMessageQueue:0xc420230c20 PreForkPoolSize:0 PreForkImage:busybox PreForkCmd:tail -f /dev/null PreForkUseOnce:0 PreForkNetworks: EnableNBResourceTracker:false MaxTmpFsInodes:0 DisableReadOnlyRootFs:false DisableDebugUserLogs:false IOFSEnableTmpfs:false EnableFDKDebugInfo:false IOFSAgentPath:/iofs IOFSMountRoot:/Users/sergiomarcial/.fn/iofs IOFSOpts: ImageCleanMaxSize:0 ImageCleanExemptTags: ImageEnableVolume:false}"
time="2021-10-06T13:38:09Z" level=info msg="no docker auths from config files found (this is fine)" error="open /root/.dockercfg: no such file or directory"
time="2021-10-06T13:38:09Z" level=info msg="available memory" cgroup_limit=9223372036854771712 head_room=268435456 total_memory=1118244864
time="2021-10-06T13:38:09Z" level=info msg="ram reservations" avail_memory=849809408
time="2021-10-06T13:38:09Z" level=info msg="available cpu" avail_cpu=8000 total_cpu=8000
time="2021-10-06T13:38:09Z" level=info msg="cpu reservations" cpu=8000
time="2021-10-06T13:38:09Z" level=info msg="\n        ______\n       / ____/___\n      / /_  / __ \\\n     / __/ / / / /\n    /_/   /_/ /_/\n"
time="2021-10-06T13:38:09Z" level=info msg="Fn serving on `:8080`" type=full 

time="2021-10-07T03:22:53Z" level=info msg="starting call" action="server.handleFnInvokeCall)-fm" app_id=01FHCB4CPTNG8G00GZJ0000001 
call_id=01FHCCCWAFNG8G00GZJ0000003 container_id=01FHCCCWAZNG8G00GZJ0000004 fn_id=01FHCBDC6ENG8G00GZJ0000002
```

<br />

which will render the following result

<br />

```shell
 % fn invoke test-function test-function
Hello, world!
```

In the next post, I will go deeper into different runners and more specific commands like HTTP invocation; let me know if you have any questions or comments or want any content.