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:
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