Serverless Application Model – First Impressions

If you’ve been paying attention, you’ll know that deploying software applications is evolving from bare metal servers -> virtual machines -> containers -> serverless. Here at Quick Base, we have a handful of services now running in containers in the cloud via AWS ECS. We were able to put together an automated software delivery life-cycle (SDLC) for these services in order to build, test, and deploy them all in the same way. While we were already using AWS Lambda for some behind-the-scenes orchestration, we were intentionally choosing to not deploy customer-facing serverless services because we had yet to find a good solution for the serverless SDLC.

We wanted a serverless framework to be able to support the following:

  • Deployment via CloudFormation. All of our other infrastructure and services already use CloudFormation, so we don’t want to introduce another deployment tool into the mix
  • Local development and debugging.
  • Deployment to multiple environments across multiple AWS accounts.

I was made aware of a whitespace project underway on my team to deploy an internal facing dashboard which would show the status of our many environments. The back-end of this dashboard was just some API calls which would poll the information and return it to a node front-end. It seemed to me that a simple lambda was the right solution for the API, and I was aware of the AWS Serverless Application Model (SAM) so I decided to attempt to use it to deploy the back-end solution.

Deployment

The first benefit I noticed about SAM was the template format.

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
    HelloWorldFunction:
        Type: AWS::Serverless::Function
        Properties:
            CodeUri: hello_world/
            Handler: app.lambda_handler
            Runtime: nodejs8.10
            Events:
                HelloWorld:
                    Type: Api
                    Properties:
                        Path: /hello
                        Method: get

With this template, you can deploy a lambda function and an API Gateway. Creating these same resources with a standard CloudFormation template would be a much larger effort as you would have to either embed the source code (where you’re limited to 4096 characters) in the template, or use a task runner to package up your function and libraries, push them to S3, and then use a pointer in your CloudFormation template to that S3 object.

The AWS CLI makes deployment of a SAM application easy. With 2 commands, you can package up your function (either standalone, or a zip with any libraries), and deploy it.

aws cloudformation package --template-file template.yaml --output-template-file packaged.yaml --s3-bucket mybucket
aws cloudformation deploy --template-file packaged.yaml --stack-name mystack

Local Development and Debugging

The next thing I found was the sam-cli.  This CLI allows you to run your function locally in a docker container which simulates a lambda environment. It can also simulate an API gateway, and allows you to generate and send simulated events to trigger your function from sources such as S3, Dynamo, Kinesis, etc. You can also attach a debugger to your function to leverage debugging features of your IDE.

For example, to test the function behind API Gateway:

$ sam local start-api
2018-08-21 12:31:34 Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
2018-08-21 12:31:34 You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2018-08-21 12:31:34  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
2018-08-21 12:31:36 Invoking app.lambda_handler (nodejs8.10)
2018-08-21 12:31:36 Starting new HTTP connection (1): 169.254.169.254

Fetching lambci/lambda:nodejs8.10 Docker container image......
2018-08-21 12:31:38 Mounting ~/dev/scratch/sam-app/hello_world as /var/task:ro inside runtime container

With this running, I’m able to browse to http://127.0.0.1:3000/, which executes the function and shows me the output.

START RequestId: c023d97e-b667-1a95-38c0-9e2c71ffc9dc Version: $LATEST
END RequestId: c023d97e-b667-1a95-38c0-9e2c71ffc9dc
REPORT RequestId: c023d97e-b667-1a95-38c0-9e2c71ffc9dc  Duration: 355.89 ms     Billed Duration: 400 ms Memory Size: 128 MB     Max Memory Used: 35 MB
2018-08-21 12:31:39 No Content-Type given. Defaulting to 'application/json'.
2018-08-21 12:31:39 127.0.0.1 - - [21/Aug/2018 12:31:39] "GET /hello HTTP/1.1" 200 -

I’m also able to make a change to the function in my IDE, and just refresh my browser to reflect those changes (hot reloads). Of course, I also run automated tests against this solution locally.

Deployment to multiple environments across multiple AWS accounts

Now that we had our internal dashboard API running via SAM, I wanted to take it a step further. We needed a solution to send our AWS Load Balancer logs to our Splunk Cloud instance. Splunk provides some great solutions to accomplish this, and they are essentially one-click deployments. We had to make some minor modifications to the provided solution due to security concerns, which was simple enough. I also wanted to see how we could automate the deployment of this SAM application to all of our AWS accounts. We are currently using Jenkins pipelines to deploy our AWS infrastructure and services, so I was easily able to create a new Jenkinsfile which grabbed the SAM application from GitHub, ran tests, packaged it up, and deployed it to each of our accounts.

The cross-account deployment of the application is a stage in a Jenkins pipeline:


stage ("Deploy") {
    steps {
        unstash name: "package"
        getAWSFunctions()
        script {
            def accounts = ["${AWS_ACCOUNT1}": "splunk-index1",
                            "${AWS_ACCOUNT2}": "splunk-index2",
                            "${AWS_ACCOUNT3}": "splunk-index3",
                            "${AWS_ACCOUNT4}": "splunk-index4"]          

            accounts.each{ accountId, splunkindex ->
                sh ". bin/functions;with_role arn:aws:iam::${accountId}:role/CrossAccountRole-us-west-2 aws cloudformation deploy --template-file serverless/splunk-hec-forwarder/template.output.yaml --stack-name splunk-hec-forwarder --parameter-overrides SplunkIndex=${splunkindex} --capabilities CAPABILITY_IAM --no-fail-on-empty-changeset"
            }
        }
    }
}

Final Thoughts

I believe that SAM could be a solution for our business to successfully develop, test, and deploy serverless services to our customers. At this point, only the Site Reliability Team is using it, but I’m eager to have a service team adopt this model to see how it fits (or doesn’t) in our customer facing services.