iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
⌨️

Getting Started with Lambda (2): Local Execution and Deployment via CLI

に公開

Introduction

In the previous article, I introduced the overview of AWS Lambda and how to create, modify, and deploy functions from the console.

https://zenn.dev/nekoniki/articles/b1f94bfbb28e29

However, in an actual development environment, you would likely want to develop and validate functions in your local environment before deploying them.

Therefore, in this article, I will introduce how to set up a local execution environment for Lambda and the deployment method.

Installing AWS CLI

In this guide, we will eventually deploy to AWS from our local machine.
To do that, let's install the AWS CLI on your terminal.

The following is an example using Homebrew, but you can use other tools if you prefer.

brew install awscli

It's all good if the version is displayed with the following command:

aws --version

Issuing an IAM Access Key

To execute commands from the AWS CLI hereafter, we will issue an access key from IAM.

We use this access key for authentication.

Select [IAM] from [All services] on the AWS dashboard.

When a screen like the one above appears, click [Create access key] to output the access key and secret.

Make a note of these details.

Registering with the CLI

Register the authentication information to the AWS CLI with the following command:

aws configure

Enter the access key, secret, region, and output format in an interactive format.

Creating a Project Directory

First, create the project directory.

mkdir lambda-deploy-sample
cd lambda-deploy-sample

Create index.js and event.json here.
The contents of each are as follows:

index.js
exports.handler = async function (event, context) {
  console.log("EVENT: \n" + JSON.stringify(event, null, 2));
  return context.logStreamName;
};
event.json
{
  "type": "test",
  "value": 100
}

event.json is data for the arguments of the Lambda function.
In reality, the structure varies depending on the service that triggered the Lambda, but here we use the JSON data above for simplicity.

Local Execution

Now, let's go ahead and run the above function locally.
To run Lambda locally, we'll use a distributed Docker image this time.

Docker Image to Use

The following is the lambci/lambda image that we will use.

https://hub.docker.com/r/lambci/lambda/

Although we're using Node.js here, runtimes for other languages are also available. You can check the supported runtimes in the Docker tags section of the DockerHub page.

In this example, we'll use the nodejs12.x runtime.

The execution command follows this format:

docker run --rm \
  -v <code_dir>:/var/task:ro,delegated \
  [-v <layer_dir>:/opt:ro,delegated] \
  lambci/lambda:<runtime> \
  [<handler>] [<event>]

<code_dir> is the directory containing the function to be executed.
Therefore, since we want to point to the current directory, we'll use $PWD.

We'll omit the <layer_dir> part for now.

As mentioned earlier, we use nodejs12.x for <runtime>, the handler function in index.js for <handler>, and for <event>, we parse the previous event.json into a string and pass it.

Running Locally

Now, let's run it.
It can be executed with the following command:

docker run --rm -v "$PWD":/var/task lambci/lambda:nodejs12.x index.handler $(printf '%s' $(cat event.json))

If you see something like the following in your console, it's a success:

START RequestId: 26612a00-1447-14c7-04ef-b612abf62773 Version: $LATEST
2021-10-20T04:23:16.574Z        26612a00-1447-14c7-04ef-b612abf62773    INFO    EVENT: 
{
  "type": "test",
  "value": 100
}
END RequestId: 26612a00-1447-14c7-04ef-b612abf62773
REPORT RequestId: 26612a00-1447-14c7-04ef-b612abf62773  Init Duration: 117.51 ms        Duration: 6.19 ms Billed Duration: 7 ms   Memory Size: 1536 MB    Max Memory Used: 41 MB

The console.log() worked, and the contents of event.json were displayed.

Advanced: Running as an API

As an advanced topic, you can start a specific Lambda function as an API.

docker run --rm -e DOCKER_LAMBDA_STAY_OPEN=1 -p 9001:9001 -v "$PWD":/var/task lambci/lambda:nodejs12.x index.handler

In this state, you can run the Lambda by sending a request to localhost:9001 using curl or similar tools.

curl -d '{"aaa": "bbb"}' http://127.0.0.1:9001/2015-03-31/functions/index/invocations

In a real scenario, you would use something like API Gateway, but you can simulate that situation artificially.

Deploying

So far, we have been able to create and run functions for Lambda in a local environment.

Ultimately, you need to deploy this function to AWS.
To make this more practical, we will use the AWS CLI mentioned earlier, but you can also deploy the source from the console.

In either case, you need to package the set of source files into a ZIP file.

zip -r9 deploy_package.zip .

This will create deploy_package.zip.

Creating a Role

To deploy a new Lambda function, you need to create a corresponding role.

Creating a role can also be done via command, but since you need to provide some information in JSON format, first create a role.json file as follows:

role.json
{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Principal": {
          "Service": "lambda.amazonaws.com"
        },
        "Action": "sts:AssumeRole"
      }
    ]
}

I will omit a detailed explanation of the content, but this is where you describe information such as "what services to use," not just for Lambda.

You can create the role with the following command.
Here, we will create it with the name testFunction2.

aws iam create-role --role-name testFunction2 --assume-role-policy-document file://role.json

Then, the following JSON was output:

{
    "Role": {
        "Path": "/",
        "RoleName": "testFunction2",
        "RoleId": "[Role ID]",
        "Arn": "[ARN]",
        "CreateDate": "2021-10-20T07:21:53+00:00",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Effect": "Allow",
                    "Principal": {
                        "Service": "lambda.amazonaws.com"
                    },
                    "Action": "sts:AssumeRole"
                }
            ]
        }
    }
}

We will use the ARN in this JSON to perform the deployment.

Deployment

To deploy a Lambda function that has not yet been created on the AWS side, use the create-function command.

Execute it with options such as the ZIP file path, the ARN of the role we just created, and runtime information.

aws lambda create-function --role [ARN value] --function-name testFunction2 --zip-file fileb://deploy_package.zip --handler index.handler --runtime nodejs12.x

If a JSON response is returned after execution, it is a success.
You should see that testFunction2 has been created on the AWS Lambda dashboard.

Another Pattern: Creating a Deployment File Using lambci/lambda

https://qiita.com/anfangd/items/bb448e0dd30db3894d92

The article above introduces a method to deploy using lambci/lambda, which we used for the local execution of Lambda.

In this example, we didn't use any libraries, but in most cases, some kind of library is used (meaning package.json and node_modules exist).

To include all such components in the deployment, I think it's better to create a Docker image specifically for deployment.

Let's create a Dockerfile as follows:

# Image to use
FROM lambci/lambda:build-nodejs12.x
# Encoding settings
ENV LANG C.UTF-8
# Region settings
ENV AWS_DEFAULT_REGION us-east-2

COPY . .

# Install dependency packages
CMD npm install
# Create ZIP
CMD zip -r9 deploy_package.zip .

Once the Dockerfile is created, build the image and run it.

# Build image from Dockerfile
docker build -t deploypackage .
# Run
docker run --rm -v "$PWD":/var/task deploypackage:latest

It's all good if the ZIP file is created after execution. The deployment process follows the same steps as the previous pattern.

Bonus: Setting Environment Variables

This is a tip for when you want to switch environment variables used during local execution after deployment.
You can set environment variables for the target Lambda function with the following command.

For example, here is an example of storing the value testtest in an environment variable named HOGE.

aws lambda update-function-configuration --function-name [FUNCTION_NAME] \
    --environment "Variables={HOGE=testtest}"

Summary

In this article, I summarized the steps for creating and validating a Lambda function locally and then deploying it using the AWS CLI.

While there were more steps involved than I expected and it felt a bit daunting, the latter deployment part is mostly handled by CI in actual practice, so it may not be that difficult after all.

I hope this content is helpful.

References

Discussion