Creating, running and deploying a Google Cloud Function (Part 1)

Serverless computing using Google Cloud Functions, AWS Lambda or Azure Functions is all the rage at the moment. Here I register for the Google Cloud Platform, then write and deploy a ridiculously simple function using Google Cloud Functions.

I have previously experimented with Google Cloud Endpoints, via a Java application deployed to Google App Engine. I was never convinced by this offering; especially around the maturity of the deployment process and management of the endpoints. Since then Amazon made all the headlines for introducing their serverless architecture, called AWS Lambda. Google responded with Cloud Functions. And Microsoft weighed in too with Azure functions. The basic premise is you deploy a function and, apart from choosing the region and size of the machine which will execute it, everything is managed for you by the cloud provider. If you want further info there is a recent software engineering podcast dedicated to serverless computing. But the key part is you don’t have an application in the traditional sense of the word - just a function. Your function can respond to events - the simplest and, perhaps most common, being HTTP events like a GET, PUT or POST. All the providers have generous free tiers and a promise of unlimited scalability (which admittedly most people will not need).

I will provide a series of posts which look at how easy and quick it is to deploy functions to the Amazon, Google and Microsoft clouds. It will also focus on the user experience and, crucially, the ease at which a function can be locally tested. This posts starts with Google and aims to create a Google Cloud Platform (GCP) account and deploy a couple of trivial functions. In the next post I will expand on how to test the functions locally and create a simple blood pressure datastore using Google Cloud functions and the Google Cloud datastore. Then I’ll repeat the whole exercise with Amazon and Microsoft. I will use JavaScript for all examples.

It’s worth saying straightaway that Google Cloud Functions are in beta. There is a very large drawback that you cannot, at the time of writing, use a custom domain name. So you are, for the time being, stuck with a URL like: https://us-central1-name-i-have-chosen.cloudfunctions.net/name-of-function. Hopefully this will change soon and hopefully Google don’t charge a lot for it (for App Engine apps they wanted $39 a month to use SSL with your custom domain).

Signing up & setting up

I have to say this part could have gone better. There is a rather conspicuous button which says “Try For Free” on all of the pages relating to the Google Cloud Platform. Clicking it prompts you to enter a billing address and credit card number - it takes only a few minutes to complete this form. The “Get started with Google Cloud Platform” page then appears and unfortunately the “Quick starts” content didn’t load, despite me hitting reload a few times:

Google Cloud Platform landing page which didn't load properly

Not to be deterred I found the Quickstart page by clicking around and I followed the instructions one-by-one. The documentation was clear, but running gcloud init from my terminal eventually loaded the Google login page in Safari, despite my default browser being FireFox. I then realised I had forgotten my Google password, so I requested a reset. The phone number Google have for me is correct, but after 10 minutes no SMS was recieved, so I asked for a code via e-mail. This did arrive, but entering it and continuing unfortunately generated a 404:

Google 404

The terminal window mentioned something about Chrome; so when I switched my default web browser to Chrome and retried gcloud init it worked perfectly ok. I know these things can be difficult, but it’s a shame a large company like Google has so many annoying bugs (I found the same thing when using App Engine regularly). As a further example, I later tried to sign out of my Google account and I got this (in Chrome):

Google invalid page

The set-up documentation on the Google page is pretty good so I won’t repeat it here, but I will emphasise that given Cloud Functions are in beta it’s crucial to install the beta components:

gcloud components update &&
gcloud components install beta

My first function

The tutorials on the Google page show the first function being created entirely in the web browser, using the Google Cloud platform web console. My first function was no different. I first had to enable billing (again!) and then the function view / edit page is really self explanatory. It comes with a sample helloWorld function, which prints the body of the request if it has one, or returns a HTTP 400 (Bad Request) if it does not:

Create and edit a cloud function on Google Cloud Platform

You can test in the Google Cloud Platform console by keying a sample request and seeing how the function responds:

GCP test console for Cloud Functions

Pretty neat. And the Google Cloud Platform free tier means I can call this function 2 million times a month :)

Underneath Google Cloud Functions are executed using a Node.js run-time. Currently Google’s execution environment is following the Node V6 Long Term Support (LTS) releases. If you want to be at the cutting edge of node.js (currently V8 LTS) then Cloud Functions may not be the serverless environment for you.

When a HTTP function is called Google have chosen to use the express request and response objects - so to see what methods and variables are available, simply refer to the Express JS documentation for their request and response objects. For instance you could send JSON back:

exports.helloWorld = (req, res) => {
  res.json({ payload: 'planetjones' });
};

or return a HTTP status code 404:

exports.helloWorld = (req, res) => {
  res.status(404).end();
};

or a HTTP redirect:

exports.helloWorld = (req, res) => {
  res.redirect('https://www.planetjones.co.uk').send();
};

Just take care to note the following on Google’s documentation:

Note: You should always call a termination method such as send(), json(), or end() when your function has completed.

Every deployment of a function, whether by editing on the console directly or not, is versioned by Google Cloud Platform. You can switch versions using the Google Cloud Platform web console.

Developing locally

The easiest way is to get node.js installed and simply set-up a new package / project using:

npm init

You only need one file - so it’s fine to leave the entry point as index.js. I thought the name of the package you create would have to match the name of the Google Cloud Platform project you are deploying to. But it does not. The Google Cloud Platform on your machine seems to set a default Cloud project, based on the first interaction with it: subsequent deployments always target that project. To see which project is the default you can execute gcloud config list. Sample output below:

account = your_account@gmail.com
project = planetjones-cloud-project

You can change projects with gcloud config set project 'new-project-name'.

I must say, the gcloud command line client is, on the whole, neatly done. The content of my index.js is trivial. I added two functions which just return a String literal:

exports.helloHTTP = (req, res) => {
    res.send('hello planetjones');
};

exports.goodbyeHTTP = (req, res) => {
    res.send('goodbye planetjones');
};

I always like to ensure there are some tests to pin the behaviour of any code I write. So I installed the Mocha (for running the tests) and chai (for making the assertions a little prettier) packages. I have added overly verbose comments to package.json below, to explain what each of the scripts does:

{
  "name": "my-functions",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    // deploys the function exported as helloHTTP as a Google Cloud Function, with a HTTP trigger
    "deploy_helloHTTP": "gcloud beta functions deploy helloHTTP --trigger-http",
    // deploys the function exported as goodbyeHTTP as a Google Cloud Function, with a HTTP trigger
    "deploy_goodbyeHTTP": "gcloud beta functions deploy goodbyeHTTP --trigger-http",
    // runs all tests defined with a filename ending test.js
    "test": "mocha *.test.js --reporter spec"
  },
  "dependencies": {
    "chai": "^4.1.2",
    "mocha": "^5.0.4"
  }
}

Running the scripts is simple via npm e.g. npm run deploy_helloHTTP

Unit testing

In the next post I will look at how to do some integrated testing locally using the Cloud Functions emulator. And also how to test other services locally, like the Google Cloud datastore. In the meantime I wrote a simple unit test, which tests each of the functions as a single unit. The test case is trivial, but provides a solid basis from which to build something more useful. I have mocked the request and response objects enough so that I can test the “logic” I have written in each of the functions:

var expect = require('chai').expect;
var functions = require('./index.js');

var request = {
    body: {
        name:null
    }

};
var response = {
    the_response: null,
    send: function(response) {
        this.the_response = response;
    }
};

describe('helloHTTP', function () {

    it('should say hello', function () {
        functions.helloHTTP(this.request, response);
        expect(response.the_response).to.equal('hello planetjones');
    });

});

describe('goodbyeHTTP', function () {

    it('should say goodbye', function () {
        functions.goodbyeHTTP(request, response);
        expect(response.the_response).to.equal('goodbye planetjones');
    });

});

Summary

Google Cloud Functions is a product in beta and the inability to use a custom domain name will be a dealbreaker for many; though I expect this limitation to be short-lived. A few minor annoyances aside I cannot complain about how simple it is to write and deploy a simple function to Google’s infrastructure. The gcloud command line seems comprehensive and reliable - having an infrastructure where all operations can be scripted is a must-have and Google Cloud Functions tick this box. Though, clearly it will get more interesting once I do something realistic with my functions - especially as being able to work in a test driven style, without too much mocking, is an important requirement for me.

You can see all the code on github.

>