tangled developer

The truth is out there. Anybody got the URL?!

Migrating AWS Lambda to Pulumi Project from Serverless

Written by Venky Koneru · 26 Jul 2020 · 4 min read

Introduction

In this article, We will be looking at how to migrate an AWS lambda function deployed as a Serverless application into a Pulumi project without modifying the underlying lambda code.

This article assumes that you have a basic understanding of serverless framework1 and pulumi2.

The code snippets and the projects are available on github here

Our existing Serverless Framework implementation

Let us first imagine that we have a serverless application with a single lambda function (createTodo) deployed in AWS. To make it interesting, we will use the code from typescript todo rest api example serverless examples repository.

The project structure could look something like this:

todos-api
├─ .eslintrc.json
├─ functions
│  └─ create.ts
├─ package.json
├─ package-lock.json
├─ serverless.yml
└─ tsconfig.json

serverless.yml could look as follows:

And we deploy with the following command

$ cd todos-api
$ serverless deploy

This would create the following AWS resources:

  • CloudFormation template used to provision the stack
  • S3 bucket where zip files of the function are stored
  • Lambda serverless application
  • Lambda function, belonging to the application
  • CloudWatch log group with log streams for each instance of the function
  • Dynamodb Table resource
  • Role attached to the function with the following policies:
    • An "assume role" policy with a permission for AWS Lambda service to assume this role
    • A policy allowing the CloudWatch log group to create log streams and put log events to these streams
    • A policy allowing dynamodb PUT operation
  • REST API Gateway with:
    • the /new POST endpoint integrated with the function
    • a permission to invoke the function

After we implement the Lambda function with Pulumi, we will have pretty much the same set of resources, except for the:

  • CloudFormation template
  • S3 bucket
  • Lambda serverless application

So you can remove them after you've moved your function.

Converting to pulumi project

At the end of the conversion, we would have the following structure.

By having the above structure, we are separating the business logic and infrastructure code. This is useful, especially when the project grows with various services and resources.

Initialize pulumi

$ cd todos-api
$ npm i -D -E @pulumi/pulumi @pulumi/aws

Above command adds the necessary pulumi dependencies as a dev dependency to already existing package.json file.

After adding pulumi dependencies, create a pulumi config file (Pulumi.yaml) to initialize a new pulumi project. The same can also be accomplished via pulumi templates using pulumi new command.

Pulumi.yaml file could look as follows:

Now, we will create a new stack for the above pulumi project. Let's name it as dev

$ pulumi stack init

The above command will create a new pulumi stack named dev. Also, it is suggested to set a region for the stack using below command.

$ pulumi config set aws:region eu-central-1

This will create a stack file Pulumi.dev.yaml with the following content.

config:
  aws:region: eu-central-1

With this, we are done initializing pulumi project and a dev stack.

Infrastructure code

Before proceeding, let's create an empty directory named infrastructure along with index module to place the infra code.

$ mkdir -p infrastructure && touch infrastructure/index.ts

Also, let's prepare a script to help packaging node modules and code archive files. The script could look as follows:

Additionally, we could add a simple npm script command to our package.json file so that we could run packaging via npm.

Now, let's migrate the resources one by one.

Resources: DynamoDB Table

To provision dynamodb table,

💡 We could also import the existing table instead of creating a new one.

Resources: IAM Role

To create a role similar to the one from serverless,

Lambda function

To create lambda function resource with a node modules layer,

Api Gateway

Finally, we are going to hookup above lambda function with api gateway.

💡 Do also checkout pulumi crosswalk for aws on effortlessly creating aws gateway. I opted for the above conventional approach as I do not want to alter the source code of the lambda.

Deployment

We are now ready to deploy these resources via pulumi. We are going to run our packaging script first before issuing pulumi up.

$ npm run package
$ pulumi up -y

The output for the above command will look like below:

Updating (dev):
     Type                           Name                                    Status
 +   pulumi:pulumi:Stack            todos-api-dev                           created
 +   ├─ aws:apigateway:RestApi      dev-todos-api-rest                      created
 +   ├─ aws:iam:Role                dev-todos-api-executionRole             created
 +   ├─ aws:dynamodb:Table          dev-todos-api                           created
 +   ├─ aws:lambda:LayerVersion     dev-todos-api-lambda-layer-nodemodules  created
 +   ├─ aws:apigateway:Resource     dev-todos-api-resource                  created
 +   ├─ aws:iam:RolePolicy          dev-todos-api-executionRole-policy      created
 +   ├─ aws:apigateway:Method       dev-todos-api-method                    created
 +   ├─ aws:lambda:Function         dev-todos-api-createTodo                created
 +   ├─ aws:lambda:Permission       dev-todos-api-createTodo-permission     created
 +   ├─ aws:apigateway:Integration  dev-todos-api-integration-post          created
 +   └─ aws:apigateway:Deployment   dev-todos-api-deployment                created

Outputs:
    createTodoApiUrl: "https://xxxxxxxxxx.execute-api.eu-central-1.amazonaws.com/dev"

Resources:
    + 12 created

Duration: 27s

Cleanup

Our lambda function is now entirely managed by pulumi. So, we can remove the serverless stack, and it's resources.

$ serverless remove
$ npm uninstall serverless serverless-plugin-typescript serverless-pseudo-parameters
$ rm serverless.yml

The code snippets and the projects are available on github here

This post is highly inspired by Moving Lambda function from Serverless to Terraform


  1. serverless framework - zero-friction serverless development; easily build apps that auto-scale on low cost, next-gen cloud infrastructure. ↩︎

  2. Pulumi - Modern Infrastructure as Code. By leveraging familiar programming languages for infrastructure as code, Pulumi makes you more productive, and enables sharing and reuse of common patterns. A single delivery workflow across any cloud helps developers and operators work better together. ↩︎

comments powered by Disqus