How to Deploy OpenFaaS Serverless PHP Functions

created November 20, 2017, last updated November 22, 2017.

.

If you follow Alex Ellis on Twitter one acronym that you will have been seeing a lot in your Twitter feed lately is FaaS. Alex is a pretty smart chappy who does a lot of cool stuff with Docker, Amazon Alexa and his Raspberry Pi. This year he has been developing OpenFaaS an open source functions as a service framework.

What is OpenFaaS?

OpenFaaS lets you build and distribute “serverless” functions in a cloud computing environment. The “function” could be simple application logic that you program – or execution of a binary to perform an action. The term serverless is a slight misnomer as you still require a server, or indeed many servers, but by deploying your function into the cloud you don’t have to worry about scaling or managing physical server resources yourself – which is pretty cool.

OpenFaaS works by packaging your function up into a docker container customised to work with OpenFaaS. The OpenFaaS framework stack handles the creation and deployment of the container allowing you to execute your function with a simple command line or http request.

OpenFaaS doesn’t really care how you create your function as long as the container behaves in the way the framework and the function watchdog expects. This means whilst OpenFaaS ships with support for languages like Python and NODE.js it should work with just about anything you throw at it which means you can package up just about anything as a serverless function.

I’m developing with good old PHP so the first thing I did when I decided to take a closer look at OpenFaaS was to google “open faas php” to see how I could create serverless functions written in PHP. My search led me to a php template for OpenFaaS developed by Minh-Quan Tran. The template tells OpenFaaS how to build PHP5 or PHP7 containers to run your PHP code as OpenFaaS functions. After trying out the template there were a couple of issues with it I wanted to address, so I forked it and made a few changes.

If you want to deploy OpenFaaS PHP functions here’s a step by step guide that will get you from Zero to Hero in just a few minutes.

Install the OpenFaaS Stack and CLI

Assuming you already have Docker installed, the first thing you need to do is initialise a Docker swarm and deploy the OpenFaaS stack.

docker swarm init --advertise-addr $(hostname -i)

Change into a suitable working folder and install the OpenFaaS stack by cloning it from github and executing the deploy script.

git clone https://github.com/openfaas/faas && \
  cd faas && \
  ./deploy_stack.sh

Test that the stack is working by invoking the echoit example function that echoes back any input data you send it.

curl 127.0.0.1:8080/function/func_echoit -d 'hello world'

Now that OpenFaaS is working you need to install the OpenFaaS cli framework (faas-cli) that allows you to build, deploy and invoke your own functions easily from the command line.

curl -sL https://cli.openfaas.com | sudo sh

Next install the OpenFaaS PHP template

faas-cli template pull https://github.com/gaiterjones/openfaas-template-php
2017/11/19 09:52:30 Attempting to expand templates from master.zip
2017/11/19 09:52:30 Fetched 2 template(s) :  from https://github.com/gaiterjones/openfaas-template-php
2017/11/19 09:52:30 Cleaning up zip file...

Build and Deploy a PHP OpenFaaS function

Once the template is installed we can test it by creating our first PHP function. The OpenFaaS command line syntax for creating a new function is faas-cli new functionname –lang languagename. The PHP template uses the language name php for PHP7 functions and php5 for PHP5 functions. We will create a PHP7 function called func_php1 using the code in the template.

faas-cli new func_php1 --lang php
Folder: func_php1 created.
  ___                   _____           ____
 / _ \ _ __   ___ _ __ |  ___|_ _  __ _/ ___|
| | | | '_ \ / _ \ '_ \| |_ / _` |/ _` \___ \
| |_| | |_) |  __/ | | |  _| (_| | (_| |___) |
 \___/| .__/ \___|_| |_|_|  \__,_|\__,_|____/
      |_|


Function created in folder: func_php1
Stack file written: func_php1.yml

You will notice a new stack file func_php1.yml and function folder is created. For my tests using an Ubuntu virtual machine I had to edit func_php1.yml and change the hostname localhost to 127.0.0.1 as shown below.

provider:
  name: faas
  gateway: http://127.0.0.1:8080

functions:
  func_php1:
    lang: php
    handler: ./func_php1
    image: func_php1

In the func_php1 folder you will see three files

composer.json
Handler.php
php-extension.sh

Handler.php contains the entrypoint for our OpenFaaS PHP function and looks like this

<?php

class Handler
{
    public function handle(string $_data): void {
        $_version = phpversion();
        echo 'PHP Version : '. $_version ."\n". 'Data : '. $_data. "\n\n";
    }
}

You can see the PHP method Handler::handle() is going to echo the PHP version, and whatever data is supplied to it from STDIN.

Build the service container for the function with

faas-cli build -f func_php1.yml

Once the container has been built deploy it with

faas-cli deploy -f func_php1.yml
Deploying: func_php1.
No existing function to remove
Deployed.
URL: http://127.0.0.1:8080/function/func_php1

200 OK

And that’s it, our PHP OpenFaaS function is ready to test we can invoke it from faas-cli

faas-cli invoke -f func_php1.yml func_php1

Or use curl

curl 127.0.0.1:8080/function/func_php1 -d 'hello world'
PHP Version : 7.1.11
Data : hello world

You can also access all functions via the nifty OpenFaaS UI at http://yourhost/:8080/ui/

Build and Deploy a GEO IP OpenFaaS PHP function

Now let’s deploy a function with a little more functionality…

One API I use a lot is GEOIP2 by MaxMind it’s very useful for getting geographic data from an IP address. So let’s build an OpenFaaS PHP function to do just that.

Create a new PHP7 function called func_geoip

faas-cli new func_geoip --lang php

Before building the function we need to add our PHP code and dependencies. First let’s add the PHP dependencies to the functions composer file by editing func_geoip/composer.json

{
    "name": "function-php7",
    "description": "OpenFaaS GEOIP PHP function",
    "type": "project",
    "license": "MIT",
    "require": {
        "php": "^7.0",
        "maxmind-db/reader": "~1.0",
        "geoip2/geoip2": "~2.0"
    }
}

The geo ip data is read from a free GEOLITE2 database so we need to make that database available to our function. We can add data to our container using the func_geoip/php-extension.sh shell file which is executed when we build the template container.

echo "Installing function dependencies"
cd /usr/local/faas/function
curl -o GeoLite2-City.tar.gz http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz
tar -xvzf GeoLite2-City.tar.gz --strip 1

Now add the geo ip lookup code to our PHP Handler class by editing func_geoip/Handler.php

Handler.php
<?php
use GeoIp2\Database\Reader;

class Handler
{
    public function handle(string $_data): void {

        echo json_encode($this->geoIPLookup($_data));

    }

    protected function geoIPLookup($_ip)
	{
		$_reader = new Reader('/usr/local/faas/function/GeoLite2-City.mmdb');

			try // & get geoip info
			{
				$_record = $_reader->city($_ip);

				$_geoIPData=array(
					'ip' => $_ip,
					'country' => $_record->country->name,
					'isocode' => $_record->country->isoCode,
					'region' => $_record->mostSpecificSubdivision->name,
					'city' => $_record->city->name,
					'postcode' => $_record->postal->code,
					'latitude' => $_record->location->latitude,
					'longitude' => $_record->location->longitude,
					'googlemap' => 'https://maps.google.com/?q='. $_record->location->latitude. ','. $_record->location->longitude
				);
			}
			catch (\Exception $e)
			{
				$_geoIPData=array(
					'ip' => $_ip,
					'country' => 'Not Found',
					'isocode' => 'Not Found',
					'region' => 'Not Found',
					'city' => 'Not Found',
					'postcode' => 'Not Found',
					'latitude' => 'Not Found',
					'longitude' => 'Not Found',
					'googlemap' => 'Not Found',
                    'exception' => $e->getMessage()
				);
			}

		unset($_reader);

		return ($_geoIPData);

	}
}

Build and deploy the function

faas-cli build -f func_geoip.yml
faas-cli deploy -f func_geoip.yml

Now we can test it with

curl 127.0.0.1:8080/function/func_geoip -d '128.101.101.101'

The function returns the JSON formatted array of Geo data for the requested IP address

{"ip":"128.101.101.101","country":"United States","isocode":"US","region":"Minnesota","city":"Minneapolis","postcode":"55414","latitude":44.9759,"longitude":-93.2166,"googlemap":"https:\/\/maps.google.com\/?q=44.9759,-93.2166"}

FaaS in Production

We can easily access function data from other PHP applications using php curl to post the input data and return the output array. In production you need to consider how you will secure access to the function using some form of frontend proxy authentication.

Whether you will deploy simple php code as serverless functions in this way depends very much on the type of applications you are developing and the infrastructure available to you. I can
certainly think of a lot of my commonly used PHP library functions and classes that would become very useful deployed as OpenFaaS serverless functions.

References

Tested on

  • Ubuntu Server 17.10
  • Docker 17.09.0-ce

Comments