Overview

Sparta is a framework for developing and deploying go based AWS Lambda-backed microservices. To help understand what that means we’ll begin with a “Hello World” lambda function and eventually deploy that to AWS. Note that we’re not going to handle all error cases to keep the example code to a minimum.

Please be aware that running Lambda functions may incur costs. Be sure to decommission Sparta stacks after you are finished using them (via the delete command line option) to avoid unwanted charges. It’s likely that you’ll be well under the free tier, but secondary AWS resources provisioned during development (eg, Kinesis streams) are not pay-per-invocation.

Preconditions

Sparta uses the AWS SDK for Go to interact with AWS APIs. Before you get started, ensure that you’ve properly configured the SDK credentials.

Note that you must use an AWS region that supports Lambda. Consult the Global Infrastructure page for the most up to date release information.

Lambda Definition

The first place to start is with the lambda function definition.

// Standard AWS λ function
func helloWorld(ctx context.Context) (string, error) {
  return "Hello World!", nil
}

The ctx parameter includes the following entries:

Creation

The next step is to create a Sparta-wrapped version of the helloWorld function.

var lambdaFunctions []*sparta.LambdaAWSInfo
helloWorldFn := sparta.HandleAWSLambda("Hello World",
  helloWorld,
  sparta.IAMRoleDefinition{})

We first declare an empty slice lambdaFunctions to which all our service’s lambda functions will be appended. The next step is to register a new lambda target via HandleAWSLambda. HandleAWSLambda accepts three parameters:

  • string: The function name. A sanitized version of this value is used as the FunctionName.
  • func(...): The go function to execute.
  • string|IAMRoleDefinition : Either a string literal that refers to a pre-existing IAM Role under which the lambda function will be executed, OR a sparta.IAMRoleDefinition value that will be provisioned as part of this deployment and used as the execution role for the lambda function.
    • In this example, we’re defining a new IAMRoleDefinition as part of the stack. This role definition will automatically include privileges for actions such as CloudWatch logging, and since our function doesn’t access any additional AWS services that’s all we need.

Delegation

The final step is to define a Sparta service under your application’s main package and provide the non-empty slice of lambda functions:

sparta.Main("MyHelloWorldStack",
  "Simple Sparta application that demonstrates core functionality",
  lambdaFunctions,
  nil,
  nil)

sparta.Main accepts five parameters:

  • serviceName : The string to use as the CloudFormation stackName. Note that there can be only a single stack with this name within a given AWS account, region pair.
    • The serviceName is used as the stable identifier to determine when updates should be applied rather than new stacks provisioned, as well as the target of a delete command line request.
    • Consider using UserScopedStackName to generate unique, stable names across a team.
  • serviceDescription: An optional string used to describe the stack.
  • []*LambdaAWSInfo : Slice of sparta.lambdaAWSInfo that define a service
  • *API : Optional pointer to data if you would like to provision and associate an API Gateway with the set of lambda functions.
    • We’ll walk through how to do that in another section, but for now our lambda function will only be accessible via the AWS SDK or Console.
  • *S3Site : Optional pointer to data if you would like to provision an static website on S3, initialized with local resources.
    • We’ll walk through how to do that in another section, but for now our lambda function will only be accessible via the AWS SDK or Console.

Delegating main() to Sparta.Main() transforms the set of lambda functions into a standalone executable with several command line options. Run go run main.go --help to see the available options.

Putting It Together

Putting everything together, and including the necessary imports, we have:

// File: main.go
package main

import (
	"context"

	sparta "github.com/mweagle/Sparta"
)

// Standard AWS λ function
func helloWorld(ctx context.Context) (string, error) {
	return "Hello World!", nil
}

func main() {
	var lambdaFunctions []*sparta.LambdaAWSInfo
	helloWorldFn := sparta.HandleAWSLambda("Hello World",
		helloWorld,
		sparta.IAMRoleDefinition{})
	lambdaFunctions = append(lambdaFunctions, helloWorldFn)
	sparta.Main("MyHelloWorldStack",
		"Simple Sparta application that demonstrates core functionality",
		lambdaFunctions,
		nil,
		nil)
}

Running It

Next download the Sparta dependencies via:

  • go get ./...
  • go get github.com/zcalusic/sysinfo

in the directory that you saved main.go. Once the packages are downloaded, first get a view of what’s going on by the describe command (replacing $S3_BUCKET with an S3 bucket you own):

The sysinfo package is used at Lambda execution time only. However, it must be locally available in order to successfully cross compile your AWS Lambda binary. See the GitHub issue for more information.

$ go run main.go --level info describe --out ./graph.html --s3Bucket $S3_BUCKET
INFO[0000] ════════════════════════════════════════════════
INFO[0000] ╔═╗┌─┐┌─┐┬─┐┌┬┐┌─┐   Version : 1.0.2
INFO[0000] ╚═╗├─┘├─┤├┬┘ │ ├─┤   SHA     : b37b93e
INFO[0000] ╚═╝┴  ┴ ┴┴└─ ┴ ┴ ┴   Go      : go1.9.2
INFO[0000] ════════════════════════════════════════════════
INFO[0000] Service: MyHelloWorldStack                    LinkFlags= Option=describe UTC="2018-01-27T21:54:16Z"
INFO[0000] ════════════════════════════════════════════════
INFO[0000] Provisioning service                          BuildID=N/A CodePipelineTrigger= InPlaceUpdates=false NOOP=true Tags=
INFO[0000] Verifying IAM Lambda execution roles
INFO[0000] IAM roles verified                            Count=1
INFO[0000] Skipping S3 preconditions check due to -n/-noop flag  Bucket=weagle Region=us-west-2 VersioningEnabled=false
INFO[0000] Running `go generate`
INFO[0000] Compiling binary                              Name=Sparta.lambda.amd64
INFO[0009] Creating code ZIP archive for upload          TempName=./.sparta/MyHelloWorldStack-code.zip
INFO[0009] Lambda code archive size                      Size="12 MB"
INFO[0009] Skipping S3 upload due to -n/-noop flag       Bucket=weagle File=MyHelloWorldStack-code.zip Key=MyHelloWorldStack/MyHelloWorldStack-code-710836d403083ac46999f02d2f1cfe69e270207c.zip Size="12 MB"
INFO[0009] Skipping Stack creation due to -n/-noop flag  Bucket=weagle TemplateName=MyHelloWorldStack-cftemplate.json
INFO[0009] ════════════════════════════════════════════════
INFO[0009] MyHelloWorldStack Summary
INFO[0009] ════════════════════════════════════════════════
INFO[0009] Verifying IAM roles                           Duration (s)=0
INFO[0009] Verifying AWS preconditions                   Duration (s)=0
INFO[0009] Creating code bundle                          Duration (s)=10
INFO[0009] Uploading code                                Duration (s)=0
INFO[0009] Ensuring CloudFormation stack                 Duration (s)=0
INFO[0009] Total elapsed time                            Duration (s)=10

Then open graph.html in your browser (also linked here ) to see what will be provisioned.

Since everything looks good, we’ll provision the stack via provision and verify the lambda function. Note that the $S3_BUCKET value must be an S3 bucket to which you have write access since Sparta uploads the lambda package and CloudFormation template to that bucket as part of provisioning.

$ go run main.go provision --s3Bucket $S3_BUCKET
INFO[0000] ════════════════════════════════════════════════
INFO[0000] ╔═╗┌─┐┌─┐┬─┐┌┬┐┌─┐   Version : 1.0.2
INFO[0000] ╚═╗├─┘├─┤├┬┘ │ ├─┤   SHA     : b37b93e
INFO[0000] ╚═╝┴  ┴ ┴┴└─ ┴ ┴ ┴   Go      : go1.9.2
INFO[0000] ════════════════════════════════════════════════
INFO[0000] Service: MyHelloWorldStack                    LinkFlags= Option=provision UTC="2018-01-27T21:56:27Z"
INFO[0000] ════════════════════════════════════════════════
INFO[0000] Provisioning service                          BuildID=c4e21df1b7a1f2d82d396561db1aa168574f7a22 CodePipelineTrigger= InPlaceUpdates=false NOOP=false Tags=
INFO[0000] Verifying IAM Lambda execution roles
INFO[0000] IAM roles verified                            Count=1
INFO[0000] Checking S3 versioning                        Bucket=weagle VersioningEnabled=true
INFO[0000] Checking S3 region                            Bucket=weagle Region=us-west-2
INFO[0000] Running `go generate`
INFO[0000] Compiling binary                              Name=Sparta.lambda.amd64
INFO[0010] Creating code ZIP archive for upload          TempName=./.sparta/MyHelloWorldStack-code.zip
INFO[0010] Lambda code archive size                      Size="12 MB"
INFO[0010] Uploading local file to S3                    Bucket=weagle Key=MyHelloWorldStack/MyHelloWorldStack-code.zip Path=./.sparta/MyHelloWorldStack-code.zip Size="12 MB"
INFO[0019] Uploading local file to S3                    Bucket=weagle Key=MyHelloWorldStack/MyHelloWorldStack-cftemplate.json Path=./.sparta/MyHelloWorldStack-cftemplate.json Size="2.3 kB"
INFO[0020] Creating stack                                StackID="arn:aws:cloudformation:us-west-2:123412341234:stack/MyHelloWorldStack/f87007f0-03ac-11e8-a93f-50d5ca789e1e"
INFO[0049] CloudFormation provisioning metrics:
INFO[0049] Operation duration                            Duration=24.77s Resource=MyHelloWorldStack Type="AWS::CloudFormation::Stack"
INFO[0049] Operation duration                            Duration=16.43s Resource=IAMRole254383dffcf02e393981e3b2731226f97b1d212b Type="AWS::IAM::Role"
INFO[0049] Operation duration                            Duration=1.73s Resource=HelloWorldLambda7d01d27fe422d278bcc652b4a989528718eb88af Type="AWS::Lambda::Function"
INFO[0049] Stack provisioned                             CreationTime="2018-01-27 21:56:47.268 +0000 UTC" StackId="arn:aws:cloudformation:us-west-2:123412341234:stack/MyHelloWorldStack/f87007f0-03ac-11e8-a93f-50d5ca789e1e" StackName=MyHelloWorldStack
INFO[0049] ════════════════════════════════════════════════
INFO[0049] MyHelloWorldStack Summary
INFO[0049] ════════════════════════════════════════════════
INFO[0049] Verifying IAM roles                           Duration (s)=0
INFO[0049] Verifying AWS preconditions                   Duration (s)=0
INFO[0049] Creating code bundle                          Duration (s)=10
INFO[0049] Uploading code                                Duration (s)=9
INFO[0049] Ensuring CloudFormation stack                 Duration (s)=30
INFO[0049] Total elapsed time                            Duration (s)=49

Once the stack has been provisioned (CREATE_COMPLETE), login to the AWS console and navigate to the Lambda section.

Testing

Find your Lambda function in the list of AWS Lambda functions and click the hyperlink. The display name will be prefixed by the name of your stack (MyHelloWorldStack in our example):

AWS Lambda List

On the Lambda details page, click the Test button:

AWS Lambda Test

Accept the and name the Hello World event template sample (our Lambda function doesn’t consume the event data) and click Save and test. The execution result pane should display something similar to:

AWS Lambda Execution

Cleaning Up

To prevent unauthorized usage and potential charges, make sure to delete your stack before moving on:

$ go run main.go delete

INFO[0000] ════════════════════════════════════════════════
INFO[0000] ╔═╗┌─┐┌─┐┬─┐┌┬┐┌─┐   Version : 1.0.2
INFO[0000] ╚═╗├─┘├─┤├┬┘ │ ├─┤   SHA     : b37b93e
INFO[0000] ╚═╝┴  ┴ ┴┴└─ ┴ ┴ ┴   Go      : go1.9.2
INFO[0000] ════════════════════════════════════════════════
INFO[0000] Service: MyHelloWorldStack                    LinkFlags= Option=delete UTC="2018-01-27T22:01:59Z"
INFO[0000] ════════════════════════════════════════════════
INFO[0000] Stack existence check                         Exists=true Name=MyHelloWorldStack
INFO[0000] Delete request submitted                      Response="{\n\n}"

Conclusion

Congratulations! You’ve just deployed your first “serverless” service. The following sections will dive deeper into what’s going on under the hood as well as how to integrate your lambda function(s) into the broader AWS landscape.

Next Steps

Walkthrough what Sparta actually does to deploy your application in the next section.