AWS Lambda came out in late 2014 solving "run code without thinking about servers". But since I've been deploying code to Cloud Foundry installations since 2012, I have not been thinking about servers for much longer than the birth of AWS Lambda. So I didn't get excited about Lambda. It is proprietary. It only runs a subset of programming languages natively. And at $2.70/mth to run a 128M container, it's so easy and cost effective to run apps on Pivotal Web Services (a cheap, production-grade Cloud Foundry http://run.pivotal.io/pricing/).

The idea of only paying for applications when they are being used is attractive. I wish Cloud Foundry and Pivotal Web Service had this feature. Many programming languages/code frameworks are very quick to launch and so they are well positioned to run in containers that stop & restart quickly on demand.

AWS Lambda style pricing - pay for running the code, not just keeping containers running - is the inevitable pricing/consumption model for platforms such as Cloud Foundry. Just not yet. I am currently exploring https://github.com/cloudfoundry-community/autosleep which might offer similar features for auto-shutdown of inactive containers.

But today I wanted to play with the state-of-the-art in deploying applications/functions to AWS Lambda. I found Apex (github). I was impressed.

With apex, creating/updating AWS Lambda functions is as easy as:

apex deploy  

Getting started

http://apex.run/ has a good introduction to getting the apex CLI installed, and for setting up AWS credentials (~/.aws/credentials).

Create ~/.aws/credentials with your AWS IAM access key/secret:

[default]
aws_access_key_id = xxxxxxxx  
aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxx  

Next, install apex CLI:

curl https://raw.githubusercontent.com/apex/apex/master/install.sh | sh  

For a new project, run the apex init command. It helpfully creates a sample Lambda "function" (Lambda language for "application"), and Apex also creates an AWS IAM role to allow it to manage AWS Lambda.

mkdir helloworld  
cd helloworld  
apex init -r us-east-1  

This will prompt for some getting started questions. It also includes ASCII art, so you know its good.

  Enter the name of your project. It should be machine-friendly, as this
  is used to prefix your functions in Lambda.

    Project name: helloworld

  Enter an optional description of your project.

    Project description: Hello World

  [+] creating IAM helloworld_lambda_function role
  [+] creating IAM helloworld_lambda_logs policy
  [+] attaching policy to lambda_function role.
  [+] creating ./project.json
  [+] creating ./functions

  Setup complete, deploy those functions!

    $ apex deploy

The sample scaffolding includes a Node.js application.

$ tree
.
├── functions
│   └── hello
│       └── index.js
└── project.json

Let's deploy and run it; then next we'll change to a Golang application (one of Apex's magic tricks).

apex deploy  

The output will look similar to:

   • creating function         env= function=hello
   • created alias current     env= function=hello version=1
   • function created          env= function=hello name=helloworld_hello version=1

The function=hello comes from the functions/hello folder name. The name=helloworld_hello comes from the project name (mkdir helloworld) and the function name.

BTW, you might get the following error on your first apex deploy:

$ apex deploy
   • creating function         env= function=hello
   ⨯ Error: function hello: InvalidParameterValueException: The role defined for the function cannot be assumed by Lambda.
    status code: 400, request id: cf345c7d-d6a2-11e6-bb94-273fa8f9aa40

This looks to be a quirk of Amazon AWS's fancy "eventual consistency" behavior, which is otherwise known as "sometimes wrong". Wait 5 seconds and run apex deploy again and the issue should have resolved itself.

Apex also allows you to invoke a function directly and get the output to the terminal.

apex invoke hello  

The output is:

{"hello":"world"}

If you visit AWS Lambda you will see your Lambda function registered:

lambda

Looking at the sample Node.js function we can see that it emits logs:

console.log('starting function')  
exports.handle = function(e, ctx, cb) {  
  console.log('processing event: %j', e)
  cb(null, { hello: 'world' })
}

To see the logs for a function's recent activity:

apex logs hello  

This might include the following for each time you run apex invoke hello:

/aws/lambda/helloworld_hello START RequestId: a8e0093e-d693-11e6-9fa0-25cc5d72455f Version: 1
/aws/lambda/helloworld_hello 2017-01-09T17:47:16.333Z      a8e0093e-d693-11e6-9fa0-25cc5d72455f    processing event: {}
/aws/lambda/helloworld_hello END RequestId: a8e0093e-d693-11e6-9fa0-25cc5d72455f
/aws/lambda/helloworld_hello REPORT RequestId: a8e0093e-d693-11e6-9fa0-25cc5d72455f        Duration: 0.70 ms       Billed Duration: 100 ms         Memory Size: 128 MB     Max Memory Used: 16 MB

Deploying Golang to AWS Lambda

To create a new function, create a new functions/ subfolder:

mkdir -p functions/golang-hello  

Next, create the functions/golang-hello/main.go file for your Golang application:

package main

import (  
    "encoding/json"

    "github.com/apex/go-apex"
)

type message struct {  
    Hello string `json:"hello"`
}

func main() {  
    apex.HandleFunc(func(event json.RawMessage, ctx *apex.Context) (interface{}, error) {
        return map[string]string{"hello": "world"}, nil
    })
}

As above, deploy any new functions or updated functions:

apex deploy  

The output will show that function=hello did not change; but we are deploying a new function=golang-hello:

   • creating function         env= function=golang-hello
   • config unchanged          env= function=hello
   • code unchanged            env= function=hello
   • created alias current     env= function=golang-hello version=1
   • function created          env= function=golang-hello name=helloworld_golang-hello version=1

Invoke the new Golang function:

apex invoke golang-hello  
{"hello":"world"}

How does Apex support Golang?

AWS Lambda supports Node.js but does not support Golang applications.

Apex bridges the two: apex deploy uploads a wrapper/shim Node.js function that calls out to your Golang application. It passes incoming and outgoing data via STDOUT. So, logging is performed via STDERR.

As referenced in the sample code above, Apex includes a Golang library https://github.com/apex/go-apex for support for Apex/Lambda – providing handlers for Lambda sources, environment variables, and the runtime requirements such as implementing the Node.js shim stdio interface.

See https://github.com/apex/go-apex readme for sample code for parsing incoming parameters.

Cleanup

Unlike deployed web applications running on Pivotal Web Services, or AWS EC2 servers, it does not cost anything to forget about your AWS Lambda functions.

But Apex makes it easy to cleanup anyway:

apex delete  

The output might look like:

The following will be deleted:

  - golang-hello
  - hello

Are you sure? (yes/no) yes  
   • deleting                  env= function=golang-hello
   • function deleted          env= function=golang-hello
   • deleting                  env= function=hello
   • function deleted          env= function=hello