# πŸ‡¬πŸ‡§ Discovering Fn project - Part I

I decided to understand the concept of "ServerLess" better. So in the coming months I will install and use different platforms to learn how to use them but also see how to integrate them into existing projects (my side bot project for example, which will be born one day here: http://www.jay.chat/) and how to manage the project workflows (management of the source code, CI, CD, ...).

My 1st choice was Fn project.

⚠️ These are my baby steps with Fn project, don't hesitate to comment, improve, contribute, etc. ... πŸ™‚

# What is Fn project?

The description on the homepage of Fn project (opens new window) is very clear; I'll copy it paste here:

The Fn project is an open-source container-native serverless platform that you can run anywhere -- any cloud or on-premise. It’s easy to use, supports every programming language, and is extensible and performant.

My description would be:

  • Fn project is a FaaS platform
  • You can write functions in different languages (JavaScript, Java, Go, Python, ...)

# Install Fn project (server side)

I decided to test the Fn project on a virtual machine. For that, I used VirtualBox and Vagrant. So, on the host computer you need:

  • Docker (functions are executed inside containers)
  • VirtualBox
  • Vagrant

# The Vagrant file

First create a directory (e.g.: /fnproject) and in this directory, create a file name Vagrantfile:

BOX_IMAGE = "bento/ubuntu-16.04"
VERSION = "1.0"

FN_SERVER_NAME = "fnproject"
# This is the IP you will see on your computer
# eg: add this line to your hosts file:
# 172.16.245.200 fnproject.test
# Then you can access to the server with this url http://fnproject.test:8080/
FN_IP = "172.16.245.200"
# This is the IP you will see from another computer on the same network
FN_PUBLIC_IP = "192.168.1.200"

Vagrant.configure("2") do |config|
  config.vm.box = BOX_IMAGE
  ENV['LC_ALL']="en_US.UTF-8"

  config.vm.define "#{FN_SERVER_NAME}" do |node|

    node.vm.hostname = "#{FN_SERVER_NAME}"
    
    node.vm.network "public_network", ip: "#{FN_PUBLIC_IP}", bridge: "en0: Wi-Fi (AirPort)"
    node.vm.network "private_network", ip: "#{FN_IP}"

    node.vm.provider "virtualbox" do |vb|
      vb.memory = 1024
      vb.cpus = 2
      vb.name = "#{FN_SERVER_NAME}"
    end

    node.vm.provision :shell, inline: <<-SHELL

      apt-get update

      # ----- Docker Installation -----
      curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
      apt-key fingerprint 0EBFCD88
      add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
      apt-get update
      apt-get install -y docker-ce

      # ----- **Fn project** Installation -----
      curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh

    SHELL

    node.vm.provision :shell, run: "always", inline: <<-SHELL
      nohup sudo fn start > /dev/null 2>&1 & 
    SHELL

  end

end

This Vagrantfile create a VM with:

  • Ubuntu as the OS
  • Docker
  • Fn project

Also, in the end, it starts the Fn Server.

So, run this command (and wait a moment):

vagrant up

You can see that I provide IP addresses for the virtual machine (you can change the values, it depends of your network):

FN_IP = "172.16.245.200"
FN_PUBLIC_IP = "192.168.1.200"

If you "stay" on your laptop (you are doing your test locally), edit your hosts file and add this entry:

172.16.245.200 fnproject.test

If you plan to do your tests from another computer on the same local network, use the other IP address:

192.168.1.200 fnproject.test

Then, later, you can access to the server with this url: http://fnproject.test:8080 (opens new window) (8080 is the default port of the Fn server).

Now, your Fn server should be started, you can test it with curl http://fnproject.test:8080, if all is ok, you'll get: {"goto":"https://github.com/fnproject/fn","hello":"world!"}.

# Create your first function

You need to install the Fn CLI on your computer (the CLI is the same for the Fn project server and for the client):

# On OSX
brew install fn
# On Linux and OSX
curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh

Now, we are going to create our first JavaScript function. Type this command:

fn init --runtime node hello

The command created a directory with the necessary files:

.
└── hello
    β”œβ”€β”€ func.js
    β”œβ”€β”€ func.yaml
    β”œβ”€β”€ package.json
    └── test.json

The source code is simple and easy to read:

var fdk=require('@fnproject/fdk');

fdk.handle(function(input){
  var name = 'World';
  if (input.name) {
    name = input.name;
  }
  response = {'message': 'Hello ' + name}
  return response
})

# Execute (locally) the function

πŸ‘‹ Requirements:

  • ⚠️ You need to have Docker installed on the client computer
  • ⚠️ You need to have a DockerHub account

To test the function locally, we use the run command. You need to set before the environment variable FN_REGISTRY with your DockerHub handle:

cd hello
# use the handle of your DockerHub account
export FN_REGISTRY=k33g
fn run

Wait a moment and you'll get the result:

Building image k33g/hello:0.0.1 .
{"message":"Hello World"}

You wan to "send" arguments to the function?

echo '{"name":"Bob"}' | fn run --content-type application/json  

And you'll get a new result:

Building image hello:0.0.1 .
{"message":"Hello Bob"}

Now, it's time to deploy the function to the server.

# Deploy the function to the Fn server

To deploy the function, we use the deploy command with the --app argument to name the function. You need to set before the environment variables:

  • FN_REGISTRY with your DockerHub handle
  • FN_API_URL with the url of your Fn server
export FN_REGISTRY=k33g
export FN_API_URL=http://fnproject.test:8080
fn deploy --app hello

Wait some seconds, you should get something like that:

Deploying hello to app: hello at path: /hello
Bumped to version 0.0.2
Building image k33g/hello:0.0.2 .
Pushing k33g/hello:0.0.2 to docker registry...The push refers to repository [docker.io/k33g/hello]
b44153e4998e: Pushed
6642efdc4930: Pushed
0.0.2: digest: sha256:518fad05ad6bd3a86fa990ff444e875327683e47b7605c241a2838630c45555b size: 1572
Updating route /hello using image k33g/hello:0.0.2...

Now, type this command:

fn list apps

You'll get:

NAME
hello

And try this command:

fn list routes hello

You'll get:

PATH    IMAGE                   ENDPOINT
/hello  k33g/hello:0.0.2        fnproject.test:8080/r/hello/hello

In the ENDPOINT the first hello is the function name and the second /hello is the path

# Call the remote function

You can call your function with the Fn CLI or with an http request

# With fn call

fn call hello /hello

Or like that with arguments:

echo '{"name":"Bob"}' | fn call hello /hello --content-type application/json

Where the first hello is the function name and the second /hello is the path

# With curl

curl -H "Content-Type: application/json" http://fnproject.test:8080/r/hello/hello

Or like that with arguments:

curl -H "Content-Type: application/json" -d '{"name":"Bob"}' http://fnproject.test:8080/r/hello/hello 

It's pretty simple πŸ˜‰ no?

But one more thing. We are going to see how to use a private Docker registry with Fn project

# Use a private Docker registry

First we need to create a Docker registry in a new virtual machine, so somewhere in another directory, create a new Vagrantfile:

BOX_IMAGE = "bento/ubuntu-16.04"
VERSION = "1.0"

REGISTRY_NAME = "private-registry"
REGISTRY_IP = "172.16.245.160"
REGISTRY_PUBLIC_IP = "192.168.1.160"

Vagrant.configure("2") do |config|
  config.vm.box = BOX_IMAGE
  ENV['LC_ALL']="en_US.UTF-8"

  # === REGISTRY ===
  config.vm.define "#{REGISTRY_NAME}" do |node|

    node.vm.hostname = "#{REGISTRY_NAME}"    
    node.vm.network "public_network", ip: "#{REGISTRY_PUBLIC_IP}", bridge: "en0: Wi-Fi (AirPort)"
    node.vm.network "private_network", ip: "#{REGISTRY_IP}"

    node.vm.provider "virtualbox" do |vb|
      vb.memory = 256
      vb.cpus = 1
      vb.name = "#{REGISTRY_NAME}"
    end

    node.vm.provision :shell, inline: <<-SHELL
      apt-get update

      curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
      apt-key fingerprint 0EBFCD88
      add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
      apt-get update
      apt-get install -y docker-ce

      # run registry
      docker run -d -p 5000:5000 --restart=always --name registry registry:2

    SHELL

  end

end

This time, I provide again IP addresses for the new virtual machine, so don't forget to add an entry like that in your hosts file:

172.16.245.160 registry.test

or like that:

192.168.1.160 registry.test

And now, start the new virtual machine by typing this command in the directory of the Vagrant file:

vagrant up

# Update the Fn server VM

We need to update the Vagrantfile of the first VM (the Fn server), because we need that the first VM could "see" the Docker registry on the network:

BOX_IMAGE = "bento/ubuntu-16.04"
VERSION = "1.0"

FN_SERVER_NAME = "fnproject"
# This is the IP you will see on your computer
# eg: add this line to your hosts file:
# 172.16.245.200 fnproject.test
# Then you can access to the server with this url http://fnproject.test:8080/
FN_IP = "172.16.245.200"
# This is the IP you will see from an other computer on the same network
FN_PUBLIC_IP = "192.168.1.200"

REGISTRY_IP = "172.16.245.160"
REGISTRY_DOMAIN = "registry.test"
REGISTRY = "registry.test:5000"

Vagrant.configure("2") do |config|
  config.vm.box = BOX_IMAGE
  ENV['LC_ALL']="en_US.UTF-8"

  config.vm.define "#{FN_SERVER_NAME}" do |node|

    node.vm.hostname = "#{FN_SERVER_NAME}"
    
    node.vm.network "public_network", ip: "#{FN_PUBLIC_IP}", bridge: "en0: Wi-Fi (AirPort)"
    node.vm.network "private_network", ip: "#{FN_IP}"

    node.vm.provider "virtualbox" do |vb|
      vb.memory = 1024
      vb.cpus = 2
      vb.name = "#{FN_SERVER_NAME}"
    end

    node.vm.provision :shell, inline: <<-SHELL

      apt-get update

      # ----- Docker Installation -----
      curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
      apt-key fingerprint 0EBFCD88
      add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
      apt-get update
      apt-get install -y docker-ce

      # ----- Fn Project Installation -----
      curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh

      # Add entries to hosts file:
      echo "" >> /etc/hosts
      echo '#{REGISTRY_IP} #{REGISTRY_DOMAIN}' >> /etc/hosts 
      echo "" >> /etc/hosts

      # Add unsecure registry
      echo "" >> /etc/docker/daemon.json
      echo '{' >> /etc/docker/daemon.json
      echo '  "insecure-registries" : ["#{REGISTRY}"]' >> /etc/docker/daemon.json
      echo '}' >> /etc/docker/daemon.json
      echo "" >> /etc/docker/daemon.json

      service docker restart

    SHELL

    node.vm.provision :shell, run: "always", inline: <<-SHELL
      nohup sudo fn start > /dev/null 2>&1 & 
    SHELL

  end

end

I added 3 parts in the Vagrantfile:

The information about the Docker registry:

REGISTRY_IP = "172.16.245.160"
REGISTRY_DOMAIN = "registry.test"
REGISTRY = "registry.test:5000"

I updated the hosts file of the VM, now the Fn server can "ping" the Docker registry:

# Add entries to hosts file:
echo "" >> /etc/hosts
echo '#{REGISTRY_IP} #{REGISTRY_DOMAIN}' >> /etc/hosts 
echo "" >> /etc/hosts

I updated the daemon.json to allow the use of an insecure registry (without https) and I restart the Docker client:

# Add unsecure registry
echo "" >> /etc/docker/daemon.json
echo '{' >> /etc/docker/daemon.json
echo '  "insecure-registries" : ["#{REGISTRY}"]' >> /etc/docker/daemon.json
echo '}' >> /etc/docker/daemon.json
echo "" >> /etc/docker/daemon.json

service docker restart

We are almost done, before testing, youe have to delete the Fn server virtual machine with this command vagrant destroy -f (in the same place as the Vagrant file).

Now you can start again your Fn server:

vagrant up

# Create, Deploy, Call a new function

We are going to "export" the environment variables like that:

export FN_REGISTRY=registry.test:5000
export FN_API_URL=http://fnproject.test:8080

πŸ‘‹ this time, we set FN_REGISTRY with the domain name and the port of the Docker registry, instead of using the DockerHub handle.

We initialize and deploy a new function (in Go this time):

fn init --runtime go yo
cd yo
fn deploy --app yoapp

You can check the deployment with the list routes command:

fn list routes yoapp
PATH    IMAGE                           ENDPOINT
/yo     registry.test:5000/yo:0.0.3     fnproject.test:8080/r/yoapp/yo

Where yoapp is the function name and /yo is the path

And, now, you can call yoapp like that:

echo '{"name":"Bob"}' | fn call yoapp /yo --content-type application/json

Or like that:

curl -H "Content-Type: application/json" -d '{"name":"Bob"}' http://fnproject.test:8080/r/yoapp/yo 

That's all for today. The Fn Project community which is very friendly 😍, helped me a lot with my first steps. So, I can only encourage you to give it a try to the Fn project.

My next experiment with Fn project will be about how to use it with GitLab CI/CD πŸ˜‰.

Last Articles