Creating Your Own Serverless Cloud with Fn Project
In this Fn Project tutorial, you will learn the basic features of Fn Project by creating a serverless cloud and installing it on your own infrastructure. Then, you will create a couple of functions in different programming languages. This will illustrate some of the most useful concepts of Fn Project and help you get familiarized with this lightweight and simple serverless platform.
What is Serverless?
Serverless computing, or more simply “Serverless,” is not easy to define, but for the sake of this article, we can loosely define it as a software architecture trend that reduces the notion of infrastructure. While this trend still requires servers, developers don’t need to worry about load balancing, multithreading, or any other infrastructure subject. The chosen platform manages the resources, allowing developers to just focus on their code.
Currently, there are many frameworks and platforms for serverless solutions, such as AWS Lambda (Amazon), Azure Functions (Microsoft), Cloud Functions (Google), Cloudflare Workers (Cloudflare), and OpenWhisk (IBM). When using these frameworks, you are usually charged for the computation of your code and the number of times it gets executed. But what about a solution or platform where you are not charged for these services?
Fn Project
The official Fn Project website defines Fn Project as “an open-source container-native serverless platform that you can run anywhere—any cloud or on-premise.” It’s important to mention that, even though it’s open-source, it has the support of Oracle.
Fn Project is different from most other solutions in that it is not tied to any specific cloud vendor; in other words, it’s cloud-agnostic. The solution can be hosted in any environment that supports Docker, like AWS, Microsoft Azure, Google Cloud Platform, and even your own server.
Let’s imagine you have a server that is underused. By taking 15 minutes to install and set up Fn Project on that server, you can create your own FaaS platform for your developers without any computing or execution costs in the future. They can start coding and deploying their functions written in several programming languages (Go, Java, Node.js, Python, Ruby, C#, Kotlin, PHP, Rust, etc.) without needing to worry about infrastructure.
Getting Started with Fn Project
Installing Fn Project
The only prerequisite to install Fn Project is to have Docker v.17.0.
You can download and install Fn Project with the following command:
$ curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh
Then, it’s time to start the Fn Server.
$ fn start
There you go! You can check if Fn Project is running by accessing http://localhost:8080.
And that’s all you need to do! You now have Fn Project installed, and you are ready to start coding and deploying your functions on your own serverless platform.
Of course, there are some more advanced configurations like changing the port or customizing the registry and context, but that’s another story for another blog post. If you want to dive in deeper, you can check out this article.
Your First App and Functions
As I mentioned before, Fn Project allows you to create functions in different programming languages. On this occasion, we will create a basic “Hello World” function in Node.js and then a less basic “Math Calculations” function in Java.
Let’s start by creating a directory that will contain the code of our functions.
$ mkdir myFnApp
Then, we can create our very first function in Fn Project.
$ mkdir myfirstfn $ cd myfirstfn $ fn init --runtime node
This will create the structure of the Node.js project in the current folder. These are the automatically generated boilerplate files:
$ find . ./func.yaml ./package.json ./func.js
Now for the “Hello World” function:
const fdk=require('@fnproject/fdk'); fdk.handle(function(input){ let name = 'World'; if (input.name) { name = input.name; } return {'message': 'Hello ' + name} })
There you go! We have our first function in Node.js with just a couple lines of code. Now, I would like to provide more details regarding the initialization of the project. Let’s use our second function as an example of that.
$ cd .. $ fn init --runtime java --trigger http --name math-calculations mathcalculation
The previous command created a new directory, “mathcalculation”, inside of which is a Maven project with a func.yaml file in the root directory. The function is named “math-calculations”. Let’s see what was created.
$ cd mathcalculations $ find . ./func.yaml ./pom.xml ./src/test/java/com/example/fn/HelloFunctionTest.java ./src/main/java/com/example/fn/HelloFunction.java
If we open the HelloFunction.java file, this is its default content.
package com.example.fn; public class HelloFunction { public String handleRequest(String input) { String name = (input == null || input.isEmpty()) ? "world" : input; return "Hello, " + name + "!"; } }
Let’s start to code our function! Since a normal “Hello World” example is very boring, we are going to replace the HelloFunction with something more interesting. In the real world, serverless functions are usually used for heavier computational procedures, so we can try to simulate that by creating a function that receives an input number and performs a set of mathematical operations on that number. The operations in this example will be called squareRoot, squarePow, fibonacci, summation, and factorial.
MathCalculationFunction.java
package com.example.fn; import com.example.fn.model.NumberRequest; import com.example.fn.model.NumberResponse; public class MathCalculationFunction { public CalculationResponse handleRequest(CalculationRequest input) { Integer theNumber = input.getNumber(); if(theNumber != null){ CalculationResponse response = new CalculationResponse(); response.setNumber(theNumber); response.setSquarePow(MathCalculationUtil.squarePow(theNumber)); response.setSquareRoot(MathCalculationUtil.squareRoot(theNumber)); response.setFibonacci(MathCalculationUtil.fibonacci(theNumber)); response.setSummation(MathCalculationUtil.summation(theNumber)); response.setFactorial(MathCalculationUtil.factorial(theNumber)); return response; } CalculationResponse response = new CalculationResponse(); response.setNumber(0); return response; } }
MathCalculationUtil.java
package com.example.fn; public class MathCalculationUtil { public static long squarePow(int number){ return (long) Math.pow(number, 2); } public static double squareRoot(int number){ return Math.sqrt(number); } public static long summation(int number){ long result = 0; for(int i=1; i <= number; i++){ result+=i; } return result; } public static long factorial(int number){ long result = 0; for(int i=1; i <= number; i++){ result*=i; } return result; } public static long fibonacci(int n) { if (n <= 1) return n; else return fibonacci(n - 1) + fibonacci(n - 2); } }
CalculationRequest
package com.example.fn.model; public class CalculationRequest { private Integer number; //Getters and Setters }
CalculationResponse
package com.example.fn.model; public class CalculationResponse { private int number; private long squarePow; private double squareRoot; private long fibonacci; private long summation; private long factorial; //Getters and Setters }
After adding the above Java classes to the project, the project structure should now look like this:
$ find . ./func.yaml ./pom.xml ./src/test/java/com/example/fn/HelloFunctionTest.java ./src/main/java/com/example/fn/HelloFunction.java ./src/main/java/com/example/fn/MathCalculationUtil.java ./src/main/java/com/example/fn/MathCalculationFunction.java ./src/main/java/com/example/fn/model/CalculationRequest.java ./src/main/java/com/example/fn/model/CalculationResponse.java
Finally, it’s necessary to set the new MathCalculationFunction class as the entry point to our function. The file func.yaml is in charge of our function’s configuration, so we will modify it in order to set the new entry point.
func.yaml
schema_version: 20180708 name: math-calculations version: 0.0.1 runtime: java build_image: fnproject/fn-java-fdk-build:jdk11-1.0.95 run_image: fnproject/fn-java-fdk:jre11-1.0.95 cmd: com.example.fn.MathCalculationFunction::handleRequest triggers: - name: math-calculations type: http source: /math-calculations
For a complete list of all the different values that can be set on func.yaml, check out this page.
Deploying a Function
It’s time to publish our functions and make them accessible to other applications or users. The command to do this is: $ fn deploy –app <app name> –local.
Before deploying our functions, we will need to create an application to organize them:
$ cd .. $ fn create app my-first-app
Now we can deploy our functions!
• The “Hello World” function coded in Node.js.
$ cd myfirstfn $ fn deploy --app my-first-app --local
• The “Math Calculation” function coded in Java.
$ cd .. $ cd mathcalculation $ fn deploy --app my-first-app --local
The parameter “- – local” indicates the function will be deployed to the local Fn server, and it won’t push the Docker image to Docker Hub.
Invoking our Functions
Finally, we are ready to invoke our functions. There are two ways we can call our “Math Calculations” function:
1. Executing the function by its function ID:
We have to find out the function ID, which can be done via the Fn command line interface using the command “fn list functions <my-app>”.
$ fn list functions my-first-app NAME IMAGE ID math-calculations math-calculations:0.0.5 01DAW3K3N9NG8G00GZJ0000007 myfirstfn myfirstfn:0.0.2 01DAW3HEK2NG8G00GZJ0000006
Once we have identified the ID of the function, we can invoke the function by using the URL http://<fn server>:8080/invoke/<function-id>.
curl -X POST -d '{"number":9}' http://localhost:8080/invoke/01DAW3K3N9NG8G00GZJ0000007
2. Executing the function by the defined trigger route.
Where is the trigger route, you might be wondering? We added it to the func.yaml file a few steps before.
triggers: - name: math-calculations type: http source: /math-calculations
Once we have identified the ID of the function, we can invoke the function by using the URL http://<fn server>:8080/t/<my-app>/<trigger route>.
curl -X POST -d '{"number":9}' http://localhost:8080/t/my-first-app/math-calculations
Both invocations will return the same JSON result.
{ "number": 9, "squarePow": 81, "squareRoot": 3, "fibonacci": 34, "summation": 45, "factorial": 362880 }
Fn UI and Monitor
Fn Project comes with a basic UI tool that lets you visually run and check the status of the applications and functions you have deployed. In order to install and run this UI tool, just execute the following command:
$ docker run --rm -it --link fnserver:api -p 4000:4000 -e "FN_API_URL=http://api:8080" fnproject/ui
Then, access the UI tool at http://localhost:4000.
There, we can create, edit, and remove apps. If we open an app, we will be able to see the list of functions for that application. We can use this UI to see statistics about how many times a function has been executed, monitor the current running process, and even invoke a function.
What Else is There?
This article is only the tip of the Fn Project iceberg. There are other topics for further reading that I would also like to mention:.
• Serverless Framework supports creating, running and deploying functions on Fn Project.
• Fn Server provides Prometheus metrics out of the box just by accessing the endpoint http://<fn-server>:<fn-port>/metrics.
• There is official support for Fn Project in Kubernetes, which can be installed using Helm and deployed on a Kubernetes Cluster.
Due to the grand variety of serverless platforms, it’s not possible to determine if one is better than another. All of them have their advantages, and finding the best fit for your solution depends on your specific requirements. That being said, Fn Project is now an option you can keep in mind in the future!
References for Additional Reading
1. https://hackernoon.com/what-the-hell-does-serverless-mean-219a5f6e3c6a
2. https://fnproject.io/tutorials/
3. https://github.com/vladimir-dejanovic/java-in-fn-project
4. https://github.com/fnproject/docs/blob/master/fn/develop/func-file.md
5. https://medium.com/fnproject/announcing-spring-cloud-function-support-for-fn-project-921e54f49d99