Google Cloud Functions: Scheduled Trigger

Google Cloud Functions, at the time of writing, has the following native triggers:

  • HTTP — invoke functions directly via HTTP requests
  • Google Cloud Storage — invoke functions as a result of object change notification
  • Google Cloud Pub/Sub — invoke functions from a Pub/Sub message

Additional triggers are available via Firebase, as well:

  • Cloud Firestore
  • Realtime Database
  • Firebase Authentication
  • Google Analytics for Firebase
  • Crashlytics

But, sometimes it is desirable to have Google Cloud Functions executed on a schedule versus waiting to execute in response to one of the aforementioned events. For example, AWS Lambda has CloudWatch Events triggers to allow for Lambda functions to be executed on a schedule. By default, there is no direct scheduled trigger event builtin for Google Cloud Functions, though.

On the bright-side, there are workarounds for this documented by Google:

The core of this workaround is built on Google App Engine, which provides built-in Cron functionality insides its’ Task Queues. Check out the firebase/functions-cron GitHub repo that uses this methodology using Pub/Sub as the intermediary event; it’s well-documented and describes everything in detail.

Essentially, the end result is that Google App Engine Cron Jobs are used to call a Python application running in GAE that then calls Pub/Sub which the Google Cloud Function uses as the event source to execute. A bit more complicated than it needs to be, but not too difficult.

Swapping out Pub/Sub

I didn’t want to use Pub/Sub, and preferred to keep everything inside HTTP, though.

There is one problem with using HTTP compared to using Pub/Sub, though: we can protect the GAE URL handler (e.g. <url>/publish/hourly-tick) through the GAE app.yaml file through the login element, but we can’t really protect it the same way with an HTTP trigger. The login element can be configured to login: admin to provide additional security before allowing the handler to run, but even if we add that to our HTTP-based trigger, someone could still navigate to the function HTTP URL directly bypassing GAE and its’ security.

For the record, if you want the most secure option, use the GAE & Pub/Sub method. But, if you really want to use HTTP instead, we can instead do the following method (based on an extension of Valerii Iatsko’s Google Cloud Functions: Scheduling):

App Engine Configuration — app.yaml

runtime: python27
api_version: 1
threadsafe: true
handlers:
- url: /.*
script: main.app

App Engine Cron Configuration — cron.yaml

cron:
- description: "Hourly Job"
url: /hour
schedule: every 1 hours

App Engine Handler — main.py

import webapp2
import urllib2
class HourCronPage(webapp2.RequestHandler):
def get(self):
request = urllib2.Request('<GCF URL>', headers={"cronrequest" : "true"})
contents = urllib2.urlopen(request).read()
app = webapp2.WSGIApplication([
('/hour', HourCronPage),
], debug=True)

With the above App Engine configuration, the <GCF URL> will be hit every 1 hour. If you’re wondering why this is written in Python instead of Node.JS like the Cloud Function itself, that’s because Python is supported within the Google App Engine Standard Environment which is much cheaper than the Google App Engine Flexible Environment. Node.JS requires the Flexible Environment, and it’s not worth paying more when it’s only a few lines of Python.

To deploy the above, just place the files in a directory inside your Google Cloud Shell then run: gcloud app deploy app.yaml to deploy the App and then gcloud app deploy cron.yaml to deploy the Cronjob.

Cloud Functions Handler— index.js

exports.scheduledfunction = (req, res) => {
if (req.headers["cronrequest"] === "true") {
res.status(200).send('Success'); // Function logic
} else {
res.status(200).send('Failed'); // Aborted due to no header
}
};

With the above configuration, the function will only run if it’s executed and the header called cronrequest is set to true. You could replace this with something more complicated or specific, if desired. It’s not going to be as good as the Pub/Sub method… but it does provide some basic validation.

Putting it all together…

For simplicity’s sake, I forked the firebase/functions-cron repo into eeg3/gcp-functions-cron-http and made the above modifications. You can simply deploy that project to get this effect.