Code Monkey home page Code Monkey logo

aws-lambda-serverless-reference's Introduction

AWS Lambda Serverless Reference — Formidable, We build the modern web

Travis Status Maintenance Status

A simple "hello world" reference app for the FormidableLabs/serverless/aws Terraform module, using the serverless framework targeting an AWS Lambda deploy.

Contents

Overview

Getting a serverless application into the cloud "the right way" can be a challenge. To this end, we start with a super-simple, "hello world" Express app targeting AWS Lambda using serverless. Along the way, this reference project takes care of all of the tough supporting pieces that go into a production-ready, best-practices-following cloud infrastructure like:

  • Local development workflows.
  • Terraform stack controlling IAM permissions and cloud resources to support a vanilla serverless application.
  • Remote state management for Terraform.
  • Serverless application deployment and production lifecycle management.

Using this project as a template, you can hopefully take a new serverless application and set up "everything else" to support it in AWS the right way, from the start.

A great starting point is our introduction blog post that explains existing privilege approaches, motivations for this project, and some quick example infrastructures.

Audience

This reference application is meant for developers / architects who are already familiar with AWS infrastructures (and CloudFormation), Terraform, and Serverless framework applications. This project will hopefully provide some guidance / examples to get the whole shebang all the way to a multi-environment deployment and support a team of administrators and engineers for the application.

For folks (particularly Formidables) interested in learning more about how we construct our production cloud infrastructures, head over to our learning page.

Stack

We use very simple, very common tools to allow a mostly vanilla Express server to run in localdev / Docker like a normal Node.js HTTP server and also as a Lambda function exposed via API Gateway.

Tech stack:

Infrastructure stack:

  • serverless: Build / deployment framework for getting code to Lambda.
  • serverless-http: Bridge to make a vanilla Express server run on Lambda.

Infrastructure tools:

  • AWS CloudFormation: Create AWS cloud resources using YAML. The serverless framework creates a CloudFormation stack of Lambda-supporting resources as part of a normal deployment. This project also uses a small CloudFormation stack to bootstrap an S3 bucket and DynamoDB to handle Terraform state.
  • HashiCorp Terraform: Create AWS cloud resources using HCL. Typically more flexible and expressive than CloudFormation. We have a simple Terraform stack that uses the FormidableLabs/serverless/aws module to set up a production-ready set of resources (IAM, monitoring, etc.) to support the resources/stack generated by serverless.

Naming

We use a naming convention in cloud resources and yarn tasks to separate some various high level things:

  • cf: AWS CloudFormation specific names.
  • tf: Terraform specific names.
  • sls: Serverless framework names.

Stages

Development hits a local machine, and when programmatically named, is usually referred to as:

  • localdev: A development-only setup running on a local machine.

We target four different stages/environments of AWS hosted deployments:

  • sandbox: A loose environment where developers can manually push / check things / break things with impunity. Typically deployed from developer laptops.
  • development: Tracks feature development branches. Typically deployed by CI on merges to develop branch if using git flow workflow.
  • staging: A near-production environment to validate changes before committing to actual production. Typically deployed by CI for release candidate branches before merging to master.
  • production: The real deal. Typically deployed by CI after a merge to master.

Note that these are completely arbitrary groups, both in composition and naming. There a sensible set of groups if you need just some starting point. But the final group (or even one if you want) is totally up to you!

All of our yarn run <task> tasks should be run with a STAGE=<value> prefix. The default is to assume STAGE=localdev and only commands like yarn run node:localdev or yarn run lambda:localdev can run without specification successfully. For commands actually targeting AWS, please prefix like:

$ STAGE=sandbox yarn run <task>
$ STAGE=development yarn run <task>
$ STAGE=stage yarn run <task>
$ STAGE=production yarn run <task>

Note: We separate the STAGE variable from NODE_ENV because often there are build implications of NODE_ENV that are distinct from our notion of deploy target environments.

Environment Variables

Our task runner scheme is a bash + yarn based system crafted around the following environment variables (with defaults):

  • STAGE: localdev
  • SERVICE_NAME: simple-reference (The name of the application/service in the cloud.)
  • AWS_REGION: us-east-1

... and some minor localdev only ones:

  • AWS_XRAY_CONTEXT_MISSING: LOG_ERROR (Have Xray not error in localdev)
  • SERVER_PORT: 3000
  • SERVER_HOST: 0.0.0.0

... and some implied ones:

  • FUNCTION_NAME: The name of a given Lambda function. In this project, the main one is server.

If your project supports Windows, you will want to have a more general / permissive approach.

User Roles

We rely on IAM roles to limit privileges to the minimum necessary to provision, update, and deploy the service. Typically this involves creating personalized users in the AWS console, and then assigning them groups for varying appropriate degrees of privilege. Here are the relevant ones for this reference project:

  • Superuser - Support Stack: A privileged user that can create the initial bootstrap CloudFormation stack and Terraform service module that will support a Serverless application. It should not be used for Serverless deploys.
  • IAM Groups - Serverless App: The FormidableLabs/serverless/aws module provides IAM groups and support for different types of users to create/update/delete the Serverless application. The IAM groups created are:
    • tf-${SERVICE_NAME}-${STAGE}-admin: Can create/delete/update the Severless app.
    • tf-${SERVICE_NAME}-${STAGE}-developer: Can deploy the Severless app.
    • tf-${SERVICE_NAME}-${STAGE}-ci: Can deploy the Severless app.

ℹ️ Note: Our cloud infrastructure is based on an approach of a single shared AWS account (with many limited IAM users). A more secure and differently complex option is to use separate AWS accounts for different stages/environments for infrastructures/applications. We discuss these approaches more in our introductory blog post for the FormidableLabs/serverless/aws Terraform module.

In practice, many real world projects will segregate at least the ultimate production infrastructure to a separate AWS account and potentially utilize multiple infrastructures within a shared non-production AWS account. There are many ways to implement a robust production privilege approach, and this reference project implements just one of them!

Installation

Node.js (Runtime)

Our application is a Node.js server.

First, make sure you have our version of node (determined by .nvmrc) that matches our Lambda target (you will need to have nvm installed):

$ nvm use

Then, yarn install the Node.js dependencies:

$ yarn install

AWS (Deployment)

AWS Tools

Certain administrative / development work require the AWS CLI tools to prepare and deploy our staging / production services. To get those either do:

# Install via Python
$ sudo pip install awscli --ignore-installed six

# Or brew
$ brew install awscli

After this you should be able to type:

$ aws --version

Terraform

Install tfenv from Homebrew: brew install tfenv. Then, in the root of the repo, run tfenv install to download and use the pinned version of Terraform for this project.

Note that tfenv conflicts with Homebrew terraform and must be uninstalled first. You can still use tfenv to install and use the latest Terraform version in projects that don't have a .terraform-version file.

AWS Credentials

To work with this reference app, you need AWS credentials for your specific user (aka, FIRST.LAST). To create the bootstrap and service support stacks, that user will need to be a superuser. To deploy serverless applications, the user will need to be attached to given tf-${SERVICE_NAME}-${STAGE}-(admin|developer) IAM groups after the service stack is created.

Once you have a user + access + secret keys, you need to make them available to commands requiring them. There are a couple of options:

In Environment

You can append the following two environment variables to any command like:

$ AWS_ACCESS_KEY_ID=INSERT \
  AWS_SECRET_ACCESS_KEY=INSERT \
  STAGE=sandbox \
  yarn run lambda:info

This has the advantage of not storing secrets on disk. The disadvantage is needing to keep the secrets around to paste and/or export into every new terminal.

Saved to Local Disk

Another option is to store the secrets on disk. You can configure your ~/.aws credentials like:

$ mkdir -p ~/.aws
$ touch ~/.aws/credentials

Then add a default entry if you only anticipate working on this one project or a named profile entry of your username (aka, FIRST.LAST):

$ vim ~/.aws/credentials
[default|FIRST.LAST]
aws_access_key_id = INSERT
aws_secret_access_key = INSERT

If you are using a named profile, then export it into the environment in any terminal you are working in:

$ export AWS_PROFILE="FIRST.LAST"
$ STAGE=sandbox yarn run lambda:info

Or, you can declare the variable inline:

$ AWS_PROFILE="FIRST.LAST"\
  STAGE=sandbox \
  yarn run lambda:info
AWS Vault

The most secure mix of the two above options is to install and use aws-vault. Once you've followed the installation instructions, you can set up and use a profile like:

# Store AWS credentials for a profile named "FIRST.LAST"
$ aws-vault add FIRST.LAST
Enter Access Key Id: INSERT
Enter Secret Key: INSERT

# Execute a command with temporary creds
$ STAGE=sandbox aws-vault exec FIRST.LAST -- yarn run lambda:info

⚠️ Warning: Certain IAM role creation commands do not work with the default aws-vault setup if you have MFA set up (which you should).

The following commands that definitely need extra command support:

  • yarn tf:service:apply
  • yarn tf:service:_delete
  • yarn lambda:deploy

We have a research ticket to better handle sessions with MFA, but in the meantime you can simply add the --no-session flag to any aws-vault commands that need it. E.g.

$ STAGE=sandbox aws-vault exec FIRST.LAST --no-session -- <ACTUAL_COMMAND>

# E.g.
$ STAGE=sandbox aws-vault exec FIRST.LAST --no-session -- STAGE=sandbox yarn tf:service:apply

In practice, it is probably easier in the meantime to just always add the --no-session flag when using aws-vault exec.

Development

We have several options for developing a service locally, with different advantages. Here's a quick list of application ports / running commands:

  • 3000: Node server via nodemon. (yarn node:localdev)
  • 3001: Lambda offline local simulation. (yarn lambda:localdev)

Node.js

Run the server straight up in your terminal with Node.js via nodemon for instant restarts on changes. Note that because we effectively run the same server at different URL bases you must separately specify them.

# Base server
$ yarn node:localdev

# Different scenarios that reuse base.js
$ BASE_URL=/canary yarn node:localdev
$ BASE_URL=/vpc yarn node:localdev
$ BASE_URL=/layers yarn node:localdev

# Other scenarios that use a different js file.
$ SCENARIO=xray yarn node:localdev

See it in action!:

Or from the command line:

$ curl -X POST "http://127.0.0.1:3000/base/hello.json" \
  -H "Content-Type: application/json"

Lambda Offline

Run the server in a Lambda simulation via the serverless-offline plugin. In contrast to node:localdev above, all routes and functions are loaded together.

$ yarn lambda:localdev

See it in action!:

Support Stack Provisioning (Superuser)

This section discusses getting AWS resources provisioned to support Terraform and then Serverless.

The basic overview is:

  1. Bootstrap Stack: Use AWS CloudFormation to provision resources to manage Terraform state.
  2. Service Stack: Use Terraform to provision resources / permissions to accompany a Serverless deploy.

after this, then we are ready to deploy a standard serverless application with full support!

Bootstrap Stack

This step creates an S3 bucket and DynamoDB data store to enable Terraform to remotely manage it's state. We do this via AWS CloudFormation.

All commands in this section should be run by an AWS superuser. The configuration for all of this section is controlled by: aws/bootstrap.yml. Commands and resources created are all prefixed with cf as a project-specific choice for ease of identification in the AWS console (vs. Terraform vs. Serverless-generated).

Create the CloudFormation stack:

# Provision stack.
$ STAGE=sandbox yarn run cf:bootstrap:create
{
    "StackId": "arn:aws:cloudformation:${AWS_REGION}:${AWS_ACCOUNT}:stack/cf-${SERVICE_NAME}-${STAGE}-bootstrap/HASH"
}

# Check status until reach `CREATE_COMPLETE`
$ STAGE=sandbox yarn run cf:bootstrap:status
"CREATE_COMPLETE"

Once this is complete, you can move on to provisioning the service stack section. The remaining commands below are only if you need to update / delete the bootstrap stack, which shouldn't happen that often.

Update the CloudFormation stack:

# Update, then check status.
$ STAGE=sandbox yarn run cf:bootstrap:update
$ STAGE=sandbox yarn run cf:bootstrap:status

Delete the CloudFormation stack:

The bootstrap stack should only be deleted after you have removed all of the -admin|-developer|-ci groups from users and deleted the Serverless and Terraform service stacks.

# **WARNING**: Use with extreme caution!!!
$ STAGE=sandbox yarn run cf:bootstrap:_delete

# Check status. (A status or error with `does not exist` when done).
$ STAGE=sandbox yarn run cf:bootstrap:status
An error occurred (ValidationError) when calling the DescribeStacks operation: Stack with id cf-SERVICE_NAME-STAGE does not exist

Service Stack

This step provisions a Terraform stack to provide us with IAM groups and other AWS resources to support and enhance a Serverless provision (in the next section).

All commands in this section should be run by an AWS superuser. The configuration for all of this section is controlled by: terraform/main.tf. Commands and resources created are all prefixed with tf as a project-specific choice for ease of identification.

ℹ️ Note: We use the terraform CLI program under the hood directly for all of our Terraform work. This is simple and good for learning, but in a real world infrastructure has several limitations (such as the pain of remembering to re-init environments on switching, etc.). Consequently, if you're looking to maintain multiple environments with Terraform in the real world, consider more flexible meta tools like terragrunt.

Init your local Terraform state.

This needs to be run once to be able to run any other Terraform commands.

$ STAGE=sandbox yarn run tf:service:init --reconfigure

⚠️ Warning: You need to run yarn run tf:service:init every time you change STAGE or other core environmental setup before you can mutate anything with the stack (like yarn run tf:service:apply). Failure to do so will result in bad things like incorrect stage variables applied to an old, stale stage in the underlying Terraform local disk cache.

ℹ️ Note: We suggest using the --reconfigure flag every time you run init when switching environments so that the remote state (in S3) remains the source of truth and accidental stuff you do on local disk doesn't end up corrupting things.

Plan the Terraform stack.

Terraform allows you to see what's going to happen / change in your cloud infrastructure before actually committing to it, so it is always a good idea to run a plan before any Terraform mutating command.

$ STAGE=sandbox yarn run tf:service:plan

Apply the Terraform stack:

This creates / updates as appropriate.

# Type in `yes` to go forward
$ STAGE=sandbox yarn run tf:service:apply

# YOLO: run without checking first
$ STAGE=sandbox yarn run tf:service:apply -auto-approve

# **WARNING**: If using `aws-vault`, remember `--no-session`!
$ STAGE=sandbox aws-vault exec FIRST.LAST --no-session -- STAGE=sandbox yarn tf:service:apply

Delete the Terraform stack:

The service stack should only be deleted after you have removed all of the -admin|-developer|-ci groups from users and deleted the Serverless stack.

# **WARNING**: Use with extreme caution!!!
# Type in `yes` to go forward
$ STAGE=sandbox yarn run tf:service:_delete

# YOLO: run without checking first
$ STAGE=sandbox yarn run tf:service:_delete -auto-approve

# **WARNING**: If using `aws-vault`, remember `--no-session`!
$ STAGE=sandbox aws-vault exec FIRST.LAST --no-session -- STAGE=sandbox yarn tf:service:_delete

Visualize the Terraform stack:

These are Mac-based instructions, but analogous steps are available on other platforms. First, you'll need GraphViz for the dot tool:

$ brew install graphviz

From there, you can visualize with:

# Generate SVG
$ STAGE=sandbox yarn run -s tf:terraform graph | dot -Tsvg > ~/Desktop/infrastructure.svg

Serverless Deployment (IAM Roles)

This section discusses developers getting code and secrets deployed (manually from local machines to an AWS development playground or automated via CI).

All commands in this section should be run by AWS users with attached IAM groups provisioned by our support stack of tf-${SERVICE_NAME}-${STAGE}-(admin|developer|ci). The configuration for this section is controlled by: serverless.yml

⚠️ Prod/Real World Warning: This reference application deploys from local laptops for ease of instruction. However, our laptops are usually a different operating system than the target Lambda Linux execution environment. This is an issue for binary dependencies in node_modules which are OS-specific and zipped up and shipped with the Lambda application.

Our reference application presently does not have binary dependencies, but as a best practice for a real world Lambda application, you should not package and deploy from a different OS than your target Lambda execution environment. This means if locally deploying using an appropriate Docker setup for packaging, or using a CI/CD system that matches the Lambda OS to package and deploy the application.

Admin Deployment

These actions are reserved for -admin users.

Create the Lambda app. The first time through a deploy, an -admin user is required (to effect the underlying CloudFormation changes).

$ STAGE=sandbox yarn run lambda:deploy

# **WARNING**: If using `aws-vault`, remember `--no-session`!
$ STAGE=sandbox aws-vault exec FIRST.LAST --no-session -- STAGE=sandbox yarn lambda:deploy

# Check on app and endpoints.
$ STAGE=sandbox yarn run lambda:info

Delete the Lambda app.

# **WARNING**: Use with extreme caution!!!
$ STAGE=sandbox yarn run lambda:_delete

# Confirm (with expected error).
$ STAGE=sandbox yarn lambda:info
...

  Serverless Error ---------------------------------------

  Stack with id sls-${SERVICE_NAME}-${STAGE} does not exist

Metrics:

# Show metrics for an application
$ STAGE=sandbox yarn run lambda:metrics

User Deployment

These actions can be performed by any user (-admin|developer|ci).

Get server information:

$ STAGE=sandbox yarn run lambda:info
...
endpoints:
  ANY - https://HASH.execute-api.AWS_REGION.amazonaws.com/STAGE/base
  ANY - https://HASH.execute-api.AWS_REGION.amazonaws.com/STAGE/base/{proxy+}
  ANY - https://HASH.execute-api.AWS_REGION.amazonaws.com/STAGE/xray
  ANY - https://HASH.execute-api.AWS_REGION.amazonaws.com/STAGE/xray/{proxy+}
...

See the logs:

$ STAGE=sandbox yarn run lambda:logs -f FUNCTION_NAME

Note: To see the logs in the AWS console, you unfortunately cannot just click on "CloudWatch > Logs" and see the relevant potential ones listed because a wildcard would be needed for log:DescribeLogGroups|Streams. However, if you know the log group generated name, and we do here, you can fill in the blanks and navigate to:

https://console.aws.amazon.com/cloudwatch/home?#logStream:group=/aws/lambda/sls-SERVICE_NAME-STAGE-FUNCTION_NAME;streamFilter=typeLogStreamPrefix

Update the Lambda server.

$ STAGE=sandbox yarn run lambda:deploy

# **WARNING**: If using `aws-vault`, remember `--no-session`!
$ STAGE=sandbox aws-vault exec FIRST.LAST --no-session -- STAGE=sandbox yarn lambda:deploy

Rollback to a previous Lambda deployment:

If something has gone wrong, you can see the list of available states to roll back to with:

$ STAGE=sandbox yarn run lambda:rollback

Then choose a datestamp and add with the -t flag like:

$ STAGE=sandbox yarn run lambda:rollback -t 2019-02-07T00:35:56.362Z

terraform-aws-serverless Development

For contributors to FormidableLabs/serverless/aws

To test out a local branch of terraform-aws-serverless, first clone it to the relative path of ../terraform-aws-serverless from this project's checkout. Then run the command:

$ yarn _dev:on

To switch to the local modules instead of published ones. Next, make sure to re-initialize Terraform to pick up the local modules:

$ STAGE=sandbox yarn run tf:service:init --reconfigure

Then, do all your testing of the local module. When you're ready to unwind the changes, you can do:

$ yarn _dev:off

To switch back to the published version. Typically then you'll do a version bump if we've published a new module. Then again re-initialize:

$ STAGE=sandbox yarn run tf:service:init --reconfigure

...and you're up and running the published module again!

Maintenance Status

Stable: Formidable is not planning to develop any new features for this project. We are still responding to bug reports and security concerns. We are still welcoming PRs for this project, but PRs that include new features should be small and easy to integrate and should not include breaking changes.

aws-lambda-serverless-reference's People

Contributors

cpresler avatar davetapley avatar dependabot[bot] avatar jpdriver avatar keithcom avatar kevinmstephens avatar paulmarsicloud avatar ryan-roemer avatar stevenmusumeche avatar tptee avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aws-lambda-serverless-reference's Issues

Second environment destroys previous RDS

Hi hackers,

I am glad I was able to create the development stage, but once I try to create the staging environment, it crashes --> deleting the previous rds:

Do we have to use workspaces or we are missing something in the process?

Howto setup a stage environment

We recommend explicitly constraining the acceptable version numbers for each external module to avoid unexpected or unwanted changes.

yarn run install

yarn run postinstall
brew uninstall terraform

brew install tfenv

tfenv install

Result:

[INFO] Installation of terraform v0.11.14 successful
[INFO] Switching to v0.11.14
[INFO] Switching completed

terraform -v

Terraform v0.11.14

Create Stack

rm -fr terraform/.terraform
NODE_ENV=staging STAGE=staging SERVICE_NAME=myapp yarn run cf:bootstrap:create

Wait until create is complete

NODE_ENV=staging STAGE=staging SERVICE_NAME=myapp yarn run cf:bootstrap:status
aws cloudformation describe-stacks --query 'Stacks[0].StackStatus' $(yarn -s cf:bootstrap:_stack)

"CREATE_IN_PROGRESS"
aws cloudformation describe-stacks --query 'Stacks[0].StackStatus' $(yarn -s cf:bootstrap:_stack)

"CREATE_COMPLETE"

Create imfraestructure

NODE_ENV=staging STAGE=staging SERVICE_NAME=myapp yarn run tf:service:init

* provider.aws: version = "~> 2.19"
* provider.random: version = "~> 2.1"

NODE_ENV=staging STAGE=staging SERVICE_NAME=myapp yarn run tf:service:plan

NODE_ENV=staging STAGE=staging SERVICE_NAME=myapp yarn run tf:service:apply

Test MySQL Connection

mycli -u manager -h scbtfslsstagingdb.cojdi25hzhqv.us-east-1.rds.amazonaws.com scbtfslsstagingdb -pHackerman

Destroy myapp

NODE_ENV=staging STAGE=staging SERVICE_NAME=myapp yarn run tf:service:_delete

NODE_ENV=staging STAGE=staging SERVICE_NAME=myapp yarn run cf:bootstrap:_delete

CI

  • Basic checks
  • Deploy something automatically! Maybe just to production or something?

BUG: Layers function is missing permissions.

Reproduction

Doing this diff:

~/Desktop/temp.diff 
diff --git a/serverless.yml b/serverless.yml
index 7d34950..72bf43a 100644
--- a/serverless.yml
+++ b/serverless.yml
@@ -132,38 +132,38 @@ functions:
   #                                    |___/
   # ```
   #############################################################################
-  layers:
-    handler: src/server/layers.handler
-    events:
-      - http: ANY /layers
-      - http: 'ANY /layers/{proxy+}'
-    environment:
-      BASE_URL: /layers
-    layers:
-      - { Ref: VendorLambdaLayer }
-      - { Ref: RepeatLambdaLayer }
+  # layers:
+  #   handler: src/server/layers.handler
+  #   events:
+  #     - http: ANY /layers
+  #     - http: 'ANY /layers/{proxy+}'
+  #   environment:
+  #     BASE_URL: /layers
+  #   layers:
+  #     - { Ref: VendorLambdaLayer }
+  #     - { Ref: RepeatLambdaLayer }
 
 ###############################################################################
 # OPTION(layers): Add some layers.
 ###############################################################################
-layers:
-  # Dependencies: Adds common vendor libs to a `node_modules` directory.
-  # Note: Expands to `/opt/nodejs/node_modules/**/*.js`, so should only do this once.
-  vendor:
-    path: layers/vendor
-    name: sls-${self:custom.service}-${self:custom.stage}-vendor
-    jetpack:
-      roots:
-        - layers/vendor/nodejs
+# layers:
+#   # Dependencies: Adds common vendor libs to a `node_modules` directory.
+#   # Note: Expands to `/opt/nodejs/node_modules/**/*.js`, so should only do this once.
+#   vendor:
+#     path: layers/vendor
+#     name: sls-${self:custom.service}-${self:custom.stage}-vendor
+#     jetpack:
+#       roots:
+#         - layers/vendor/nodejs
 
-  # No dependencies: A vanilla JS function.
-  # Note: Expands to `/opt/repeat/*.js` (we have `layers/repeat/repeat` here).
-  repeat:
-    path: layers/repeat
-    name: sls-${self:custom.service}-${self:custom.stage}-repeat
-    package:
-      include:
-        - "*.js"
+#   # No dependencies: A vanilla JS function.
+#   # Note: Expands to `/opt/repeat/*.js` (we have `layers/repeat/repeat` here).
+#   repeat:
+#     path: layers/repeat
+#     name: sls-${self:custom.service}-${self:custom.stage}-repeat
+#     package:
+#       include:
+#         - "*.js"
 
 resources:
   Resources:

Is deployable with:

$ STAGE=sandbox aws-vault exec FIRST.LAST-admin --no-session -- \
  yarn lambda:deploy

However, with those Layers enabled, we instead get:

  An error occurred: LayersLambdaFunction - User: arn:aws:iam::ACCOUNT:user/FIRST.LAST-admin is not authorized to perform: iam:PassRole on resource: arn:aws:iam::ACCOUNT:role/tf-simple-reference-sandbox-lambda-execution (Service: AWSLambdaInternal; Status Code: 403; Error Code: AccessDeniedException; Request ID: d8232195-ddbe-4932-a489-32d01129c12c).

CI/CD configuration examples

It would be a nice improvement to provide configuration examples with documentation for some common CI/CD use cases.

For instance, configuration files for CircleCI or Github actions that would:

  • on pull-request creation: would spawn a platform and run all integration and end-to-end tests
  • on merge on staging branch would deploy changes to preproduction environment
  • on merge on main/master branch would deploy changes to production environment

If interest is confirmed, I could provide an example for CircleCI.

(btw thanks a lot for this repository ❤️ it's the best ressource I've found on making terraform and serverless work together in a proper way)

Infra: Remove _dev:on and _dev:off scripts?

Amazing work :)

I was wondering, what is the purpose of:

    "_dev:on": "sed -i '' 's/FormidableLabs\\/serverless\\/aws/\\.\\.\\/\\.\\.\\/terraform-aws-serverless/g' terraform/main.tf",

    "_dev:off": "sed -i '' 's/\\.\\.\\/\\.\\.\\/terraform-aws-serverless/FormidableLabs\\/serverless\\/aws/g' terraform/main.tf",

and when do we need to run those scripts?

BUG: Serverless Canary Plugin permissions aren't valid for -developer/-admin users

Trying this:

$ STAGE=sandbox aws-vault exec FIRST.NAME-admin --no-session -- \
  yarn lambda:deploy

# ...

  Serverless Error ---------------------------------------
 
  An error occurred: CodeDeployServiceRole - API: iam:CreateRole User: arn:aws:iam::ACCOUNT:user/FIRST.NAME-admin is not authorized to perform: iam:CreateRole on resource: arn:aws:iam::ACCOUNT:role/sls-simple-reference-sandbox-CodeDeployServiceRole-236BX48WS9ZX.

Looks like from generated resources from serverless-plugin-canary-deployments. What do we need to do to get that hooked up to our -admin role to be able to create?

DynamoDb Locking not used

No clear idea how to deal with this.

You guys create a DynamoDB table: https://github.com/FormidableLabs/aws-lambda-serverless-reference/blob/master/aws/bootstrap.yml#L46

But then here you are not using it: https://github.com/FormidableLabs/aws-lambda-serverless-reference/blob/master/terraform/main.tf#L6

I tried adding dynamodb_table = "cf-${var.service_name}-${var.stage}-terraform-locks" but interpolation is not allowed in a backend, so to make it work I would need to manually hardcode change it each time.

Any ideas?

Question - Is there an easy way to get authorized to perform: states:StartExecution?

Calling stepfunctions.startExecution(params).promise() from a express route/controller is giving me this exception:

{
"message": "User: arn:aws:sts::3333333333:assumed-role/tf-ourservice-development-lambda-execution/sls-ourservice-development-api is not authorized to perform: states:StartExecution on resource: arn:aws:states:us-east-1:3333333333:stateMachine:generateCourse",
"code": "AccessDeniedException",
"time": "2019-09-27T15:44:01.864Z",
"requestId": "c0c52ee3-fa60-4f49-9b3f-90608696004b",
"statusCode": 400,
"retryable": false,
"retryDelay": 70.21923469758191
}

route/controller

  getZipById(req, res) {
    return Course.findOne({ where: { id: req.params.id } })
      .then(async (course) => {
        let generateCourseWorkflowArn = process.env.GENERATE_COURSE_ARN;

        if (process.env.STAGE === "localdev") {
          generateCourseWorkflowArn = "arn:aws:states:us-east-1:3333333333:stateMachine:GenerateCourse";
        }

        const params = {
          name: `StateMachineGenerateCourse${uuidv4()}`,
          stateMachineArn: generateCourseWorkflowArn,
          input: JSON.stringify({course: course.dataValues})
        };

        try {
          const data = await stepfunctions.startExecution(params).promise();
        } catch (e) {
          res.status(400).send(e);
        }
      })
      .catch((error) => res.status(400).send(error));
  },

serverless.yml

provider:
...
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "states:*"
      Resource:
        - ${self:resources.Outputs.GenerateCourse.Value}
functions:
  api:
    handler: src/server/api.handler
    environment:
      GENERATE_COURSE_ARN: ${self:resources.Outputs.GenerateCourse.Value}
      DATABASE_ENDPOINT: ${self:custom.environment.databaseEndpoint}
      DATABASE_USER: ${self:custom.environment.databaseUser}
      DATABASE_PASSWORD: ${self:custom.environment.databasePassword}
      DATABASE_NAME: ${self:custom.environment.databaseName}
    events: # Use a generic proxy to allow Express app to route.
      - http: ANY /api
      - http: 'ANY /api/{proxy+}'
  generateCourseStart:
    handler: src/server/step-functions/generate-course/start.handler
    events:
      - http: ANY /generate/course/start
  generateCourseSistem:
    handler: src/server/step-functions/generate-course/sistem.handler
    events:
      - http: ANY /generate/course/sistem

stepFunctions:
  stateMachines:
    generateCourse:
      name: generateCourse
      definition:
        Comment: "Generate Course Step Functions Workflow"
        StartAt: GenerateCourseStart
        States:
          GenerateCourseStart:
            Type: Task
            Resource: ${self:custom.environment.generateCourseStartArn}
            Next: GenerateCourseSistem
          GenerateCourseSistem:
            Type: Task
            Resource: ${self:custom.environment.generateCourseSistemArn}
            End: true
resources:
  Outputs:
    GenerateCourse:
      Description: The ARN of the example state machine
      Value:
        Ref: GenerateCourse

CI: Per-PR environments

Figure out if we could somehow support shared per-PR environments for a new sls deploy on each one but with maybe a shared service stack? Work might be here and/or in the TF service stack module.

Docs: Add note about terraform init --reconfigure

Add more detailed note about switching TF environments with yarn tf:service:init and the --reconfigure flag with comments and links to documentation.

or just add it to yarn tf:service:init in the script.

Docs: Add more on layers.

  • LEARNING.md on what layers are, how they work at runtime (with locations), and maybe an exercise
  • Code comment additions too?

Lock down Terraform State

In spirit of terraform-aws-serverless where you guys lock everything down as much as possible, how about changing aws/bootstrap.yml to incorporate the limited restrictions needed for terraform:

https://www.terraform.io/docs/backends/types/s3.html

S3 Bucket Permissions

Terraform will need the following AWS IAM permissions on
the target backend bucket:

  • s3:ListBucket on arn:aws:s3:::mybucket
  • s3:GetObject on arn:aws:s3:::mybucket/path/to/my/key
  • s3:PutObject on arn:aws:s3:::mybucket/path/to/my/key

This is seen in the following AWS IAM Statement:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::mybucket"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::mybucket/path/to/my/key"
    }
  ]
}

DynamoDB Table Permissions

If you are using state locking, Terraform will need the following AWS IAM
permissions on the DynamoDB table (arn:aws:dynamodb:::table/mytable):

  • dynamodb:GetItem
  • dynamodb:PutItem
  • dynamodb:DeleteItem

This is seen in the following AWS IAM Statement:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "dynamodb:GetItem",
        "dynamodb:PutItem",
        "dynamodb:DeleteItem"
      ],
      "Resource": "arn:aws:dynamodb:*:*:table/mytable"
    }
  ]
}

Canary deploys

  • Teh codez/configz
  • Document/research how to hit the new canary before it's at 100%

Documentation updates

Substantive

  • Add warning have to re-initialize terraform when switching stages. #35
  • Figure out that AWS vault mfa iam issue, document how to hook up MFA for IAM in README. #33 (comment) (Captured in #38)
  • Get Terraform deployed and Serverless deployed from this branch (substantively master).
  • More code comments about canary.

Learning

  • add a subnote about disabling VPCs for learning a "from scratch" setup
  • Consider adding the exercises

Hitting error: Cannot find module 'babel-runtime/core-js/get-iterator'

API Gateway Log:

Wed Aug 07 18:14:14 UTC 2019 : Lambda execution failed with status 200 due to customer function error: Cannot find module 'babel-runtime/core-js/get-iterator'. Lambda request id: c15fe0ce-ac90-4c23-b156-ca78e1b42fc7
Wed Aug 07 18:14:14 UTC 2019 : Method completed with status: 502

FIRST.LAST-admin : Insufficient permissions to delete CF Stack

Steps to reproduce:

  1. Create a lambda deployment for first time (where no existing CF Stack) STAGE=sandbox aws-vault exec FIRST.LAST-admin --no-session -- yarn lambda:deploy
  2. Destroy the lambda stack. STAGE=sandbox aws-vault exec FIRST.LAST-admin --no-session -- yarn lambda:_delete
STAGE=sandbox aws-vault exec FIRST.LAST-admin --no-session --   yarn lambda:_delete
yarn run v1.21.1
$ yarn run lambda:sls remove
$ eval $(yarn -s env) && sls -s ${STAGE} remove
Serverless: Getting all objects in S3 bucket...
Serverless: Removing objects in S3 bucket...
Serverless: Removing Stack...
Serverless: Checking Stack removal progress...
....................................
Serverless: Operation failed!
Serverless: View the full error output: https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stack/detail?stackId=sls-simple-reference-sandbox

  Serverless Error ---------------------------------------

  An error occurred: ServerlessDeploymentBucketPolicy - API: s3:DeleteBucketPolicy Access Denied.

  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues
     Issues:        forum.serverless.com

  Your Environment Information ---------------------------
     Operating System:          darwin
     Node Version:              8.10.0
     Framework Version:         1.59.2
     Plugin Version:            3.2.5
     SDK Version:               2.2.1
     Components Core Version:   1.1.2
     Components CLI Version:    1.4.0

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Looking in the AWS Console for more detail two resources failed to delete.

Screen Shot 2019-12-26 at 2 48 31 PM

Screen Shot 2019-12-26 at 2 49 20 PM

So there are two issues

Feature/Bug: Add tags.

Add tags to mirror TF.

Diff:

diff --git a/serverless.yml b/serverless.yml
index 7d34950..dbd122e 100644
--- a/serverless.yml
+++ b/serverless.yml
@@ -36,6 +36,7 @@ plugins:
 
 provider:
   name: aws
+
   # Required: import the default role that terraform-aws-serverless generates.
   role:
     Fn::ImportValue: tf-${self:custom.service}-${self:custom.stage}-LambdaExecutionRoleArn
@@ -44,14 +45,26 @@ provider:
     # IAM role in place of its autogenerated one. If you provide a custom role,
     # expose it as an export on a CloudFormation stack and import it like so:
     # Fn::ImportValue: tf-${self:custom.service}-${self:custom.stage}-LambdaExecutionRoleCustomArn
+
+  # Lambda configuration
   runtime: nodejs8.10
+  timeout: 30 # seconds (`300` max)
+  memorySize: 128 # MB value (`1024` default)
+
+  # Deployment / environment configuration
   region: ${self:custom.region}
   stage: ${self:custom.stage}
   environment:
     STAGE: ${self:custom.stage}
     SERVICE_NAME: ${self:custom.service}
-  timeout: 30 # seconds (`300` max)
-  memorySize: 128 # MB value (`1024` default)
+
+  # AWS Resource Tags: Match terraform module
+  stackTags: # For CF stack
+    Stage: ${self:custom.stage}
+    Service: ${self:custom.service}
+  tags: # For resources
+    Stage: ${self:custom.stage}
+    Service: ${self:custom.service}
 
 functions:
   # SCENARIO - base: The simplest, vanilla Serverless app.

But get error:

  Serverless Error ---------------------------------------
 
  User: arn:aws:iam::ACCOUNT:user/FIRST.LAST-admin is not authorized to perform: apigateway:PUT on resource: arn:aws:apigateway:us-east-1::/tags/arn%3Aaws%3Aapigateway%3Aus-east-1%3A%3A%2Frestapis%2Fvgrecx13z6%2Fstages%2Fsandbox

Also in SLS CF updates, finding messages like:

Failed to set tags on AWS::Lambda::Function. IAM permissions lambda:ListTags, lambda:TagResource and lambda:UntagResource are required to tag AWS::Lambda::Function resources and to propagate stack level tags.

Likely need additional permissions in: https://github.com/FormidableLabs/terraform-aws-serverless/blob/d370fd518dc9f86704bd3e3b597a18682f8930c8/policy-developer.tf#L124

(local.sls_apigw_arn doesn't cover /tags/*)

Task

  • Fix tags for terraform-aws-serverless
  • Enable diff above.
  • Add tags for reference app TF resources (like VPCs, etc.)

Infra: Research a "no options"/"starter" mode.

OPTION 1: dogs

  • Add https://github.com/FormidableLabs/aws-lambda-dogs to this LEARNING.md as a place to review and try out.
  • Include instructions for creating a temporary branch, naming it, and deploying a full infrastructure. And teardown instructions at the end.
  • Review aws-lamba-dogs for any changes that can help learning.

OPTION 2: Starter mode in this project

Needs some research, but basically something like:

  • Run yarn starter and have all the TF config + files, JS files, and SLS config that are part of an OPTION(NAME) only.
  • Update the SERVICE_NAME to not conflict with existing deployment.
  • Search on all options tokens like OPTION, layer, vpc, canary, custom_role and verify we got everything.
  • Add LEARNING.md notes on everything including how to do a custom SERVICE_NAME, how to provision, then how to tear down at the end.

Some potential things:

  • (Maybe) create a long-running branch for the simple version
    • Create documentation (and possibly scripts) for maintainers to keep up to date with master
  • (Maybe) create an AWS env for simple version for just deployment (but not experimentation). (We would probably still want folks to rename SERVICE_NAME to a custom name for experimentation).

Some other minor complexities

  • Remove postinstall (or make it accomodate layers stuff disappearing for ease)

Related, but on master

Other things I noticed for master:

  • Maybe hone down comments in TF config files (like listing all options for terraform-aws-serverless) module anyways to start.
  • Combine versions.tf into main.tf for simplicity in (could be a separate ticket).
  • Have both custom_role and custom_roles in OPTION(). Harmonize these to just one. (could be a separate ticket).
  • Look to hone down serverless.yml more if possible (could be a separate ticket).

Error: data.aws_caller_identity.current: resource repeated multiple times

Hi!

Using latest master, I'm hitting:

NODE_ENV=development STAGE=development SERVICE_NAME=scbawstfsls yarn run cf:bootstrap:create

wait until creation is completed.

NODE_ENV=development STAGE=development SERVICE_NAME=scbawstfsls yarn run cf:bootstrap:status

then when I run:

NODE_ENV=development STAGE=development SERVICE_NAME=scbawstfsls yarn run tf:service:init

something goes wrong:

image

image

"env": "echo export NODE_ENV=${NODE_ENV:-localdev}; echo export STAGE=${STAGE:-localdev}; echo export SERVICE_NAME=${SERVICE_NAME:-scbservice}; echo export AWS_REGION=${AWS_REGION:-us-east-1}; echo export AWS_XRAY_CONTEXT_MISSING=LOG_ERROR",

Thanks, awesome work!

Docs: Research aws-vault usage with instructions for MFA for IAM roles stuff.

Things like:

$ STAGE=sandbox aws-vault exec <superadmin-user> -- yarn tf:service:apply

Will hit errors like:

14 errors occurred:
	* module.serverless_vpc.aws_iam_policy.developer: 1 error occurred:
	* aws_iam_policy.developer: Error creating IAM policy tf-simple-reference-sandbox-developer-vpc: InvalidClientTokenId: The security token included in the request is invalid
	status code: 403, request id: 2ba766b9-356d-41cb-bc8e-fe5445760c6e

... similar stuff ...

If they are creating/deleting IAM roles from normal aws-vault usage.--no-session gets around this, but underlying issues:

Task

  • Come up with way for MFA to work as normal.
  • Document how to apply this for the IAM (or all) commands

Bypass need for SLS

I've been looking at some provider documentation and it looks like we could possibly cut SLS out of the equation completely.

The below (primitive) example enables archiving of a function (one file for now) and deploying to lambda.

Every time plan/apply is run, the archive file is recreated and lambda will check if the new archive checksum matches that of the deployed archive. There is also a similar funciton for layers.

provider "archive" {}

locals {
  sourcefile = "${path.module}/../${var.function["filepath"]}"
  archive = "${path.module}/../dist/${replace("${var.function["filepath"]}", "/.js/", ".zip")}"
}


data "archive_file" "init" {
  type        = "zip"
  source_file = "${local.sourcefile}"
  output_path = "${local.archive}"
}

resource "aws_lambda_function" "test_lambda" {
  filename      = "${data.archive_file.init.output_path}"
  function_name = "${var.function["name"]}"
  handler       = "${var.function["handler"]}"

  # The filebase64sha256() function is available in Terraform 0.11.12 and later
  # For Terraform 0.11.11 and earlier, use the base64sha256() function and the file() function:
  # source_code_hash = "${base64sha256(file("lambda_function_payload.zip"))}"
  source_code_hash = "${filebase64sha256("${data.archive_file.init.output_path}")}"
  runtime = "${var.function["runtime"]}"
}

variable "function" {
  type = "map"
  default = {
    name = "base_fn"
    filepath = "src/server/base.js"
    runtime = "nodejs8.10"
    handler = "exports.handler"
  }
}

There's probably a bunch of scenarios still to consider:

  • Archival of multiple files
  • Permission issues (can a dev run this along with the existing terraform config?)

Feature/Bug: Enhance CF bootstrap delete to delete all versions of all files.

When deleting a CF bootstrap stack that has TF state with:

$ yarn cf:boostrap:_delete

This can fail with: "bucket not empty" type messages for either files in the bucket or hidden old versions of files in the buckets (since we turn object versioning on). You typically have to go to s3 list in console, select appropriate bucket, and click Empty button.

Task:

  • Use aws CLI to programmatically empty the bucket in cf:bootstrap:_delete command like the console has.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.