Most of us are familiar with Amazon Web Services (AWS) and have probably used their cloud computing services at some point in our careers. AWS is a go-to solution for many developers when it comes to building and deploying applications on the cloud.
However, working with AWS can come with its challenges, particularly when it comes to testing and development. While AWS offers a wide range of powerful services and tools, navigating through the vast array of options and configuring them correctly can be complex and costly. As AWS provides a pay-as-you-go pricing model, the costs can quickly escalate if resources are not managed effectively while developing and testing. Herein lies the role of LocalStack.
What is LocalStack?
LocalStack is an open-source project that provides a fully functional local AWS cloud stack. It allows developers to test and develop their applications offline without needing an internet connection or any cloud resources. LocalStack allows developers to emulate the behavior of AWS services such as S3, Lambda, DynamoDB, and more, all within a local environment, eliminating the need for an AWS account.
Features and Capabilities
- Simulation of entire AWS Services: LocalStack enables developers to simulate the entire AWS ecosystem within its local environment.
- Offline Development: With LocalStack, developers can work offline without relying on an internet connection or AWS account.
- Cost Savings: By simulating AWS services locally, LocalStack eliminates the costs associated with running resources in the cloud.
- Easy Setup and Configuration: LocalStack simplifies the setup process with a user-friendly command-line interface (CLI) and APIs.
- Realistic Testing Scenarios: By emulating the actual behavior of AWS services, LocalStack allows developers to create realistic testing scenarios. This early identification and resolution of potential issues lead to the development of more robust and reliable applications.
Installation & Setup
There are several ways to install LocalStack on our local machine.
We will use the Docker-Compose
method in this article. Docker and docker-compose should be installed on our machine as a prerequisite.
- Create a
docker-compose.yml
file and paste the following contents.
version: "3.8"
services:
localstack:
image: localstack/localstack
ports:
- "127.0.0.1:4566:4566"
- "127.0.0.1:4510-4559:4510-4559"
environment:
- DEBUG=${DEBUG-}
- DOCKER_HOST=unix:///var/run/docker.sock
volumes:
- "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"
Start the container by running
docker-compose up
.LocalStack should be started at
http://localhost:4566
.-
To check it’s running properly, check the health by visiting
https://localhost:4566/health
.
Limitation
Before continuing the article, we should also know about the limitations of LocalStack. One of the major limitations of LocalStack is that the data are not persistent in the free/community version. Meaning, that every time we stop LocalStack, the data including the configuration we ran via command, the buckets, and the databases, all will be removed and it will start fresh on the next start.
Usage
We are going to create a Lambda function and configure a trigger for Amazon S3 to demonstrate how LocalStack can be used for AWS mocking. When a new image is uploaded to the S3 bucket, the Lambda function should create a thumbnail of that image in the same bucket.
Install AWS CLI
As we are mocking AWS, the official AWS CLI should be installed on our machine. If it’s not installed, follow this guide to install it first. After installing it, we will need to configure it as a first-time user by running aws configure
. Set any value as Access Key ID and Secret Access Key and set a Default Region (e.g. ap-northeast-2). If you use your original Access key and secret, it’s okay too, as LocalStack will ignore it ultimately.
How will the AWS CLI communicate with LocalStack?
By default, when we run a command in AWS CLI, it will try to communicate with AWS API. But here, we want to communicate with LocalStack instead AWS API.
When running any AWS command, if we specify the endpoint, it will send the requests to that endpoint instead of the default endpoint.
For example, let’s run a command aws s3api list-buckets --endpoint-url=http://localhost:4566
.
It displays the bucket list from LocalStack and does not throw any error even though we used dummy data as the AWS configuration.
How will the AWS SDK communicate with LocalStack?
We can also use LocalStack in our AWS SDKs. We just have to point to the endpoint while initilizting the SDK. Here’s an example of JavaScript aws-sdk
:
const AWS = require('aws-sdk');
const s3 = new AWS.S3({
endpoint: 'http://localhost:4566', // Only needed for localstack
s3ForcePathStyle: true, // Only needed for localstack
});
Then we can use the s3
object to interact with the S3 service locally through LocalStack.
Create the Lambda function
- Open a new terminal and create a directory by running
mkdir lambda-src
. Open the directory by running
cd lambda-src
.-
Create a new file
package.json
and paste the following code:
{ "main": "index.js", "dependencies": { "aws-sdk": "^2.1376.0", "sharp": "^0.32.1" } }
Run
npm install
to install the dependencies.-
Create a new file
index.js
and paste the following code:
const AWS = require('aws-sdk'); const sharp = require('sharp'); const WIDTH = 64; const s3 = new AWS.S3({ endpoint: `http://${process.env.LOCALSTACK_HOSTNAME}:${process.env.EDGE_PORT}`, // Only needed for localstack s3ForcePathStyle: true, // Only needed for localstack }); exports.handler = async (event) => { try { const srcBucket = event.Records[0].s3.bucket.name; const srcKey = decodeURIComponent( event.Records[0].s3.object.key.replace(/\+/g, ' ') ); if (srcKey.includes(`${WIDTH}x${WIDTH}`)) { console.info('Skipping processed image.'); return; } const destinationKey = srcKey.replace( /(\.[^.]*)?$/, `-${WIDTH}x${WIDTH}$1` ); // output will be name-200x200.png const typeMatch = srcKey.match(/\.([^.]*)$/); if (!typeMatch) { console.error('Could not determine the image type.'); return; } const imageType = typeMatch[1].toLowerCase(); if (imageType != 'jpg' && imageType != 'png' && imageType != 'jpeg') { console.error(`Unsupported image type: ${imageType}`); return; } const params = { Bucket: srcBucket, Key: srcKey, }; const originalImage = await s3.getObject(params).promise(); const buffer = await sharp(originalImage.Body).resize(WIDTH).toBuffer(); const destinationParams = { Bucket: srcBucket, Key: destinationKey, Body: buffer, ContentType: 'image', }; await s3.putObject(destinationParams).promise(); console.log('Successfully resized ' + srcBucket + '/' + srcKey + ' and uploaded to ' + srcBucket + '/' + destinationKey ); } catch (error) { console.error(error); return; } };
-
Then zip the directory. If you have
zip
installed, you can run:
zip -r function.zip .
By running
cd ..
return to the root directory.-
Create the Lambda function by running:
aws --endpoint-url=http://localhost:4566 \ lambda create-function --function-name CreateThumbnail \ --zip-file fileb://lambda-src/function.zip --handler index.handler --runtime nodejs16.x \ --role arn:aws:iam::000000000000:role/lambda-s3-role
-
Create a S3 bucket:
aws --endpoint-url=http://localhost:4566 s3 mb s3://avatar-bucket
-
Create a file
notification-configuration.json
:
{ "LambdaFunctionConfigurations": [ { "LambdaFunctionArn": "arn:aws:lambda:ap-northeast-2:000000000000:function:CreateThumbnail", "Events": ["s3:ObjectCreated:*"] } ] }
-
Configure Amazon S3 to publish events:
aws --endpoint-url=http://localhost:4566 \ s3api put-bucket-notification-configuration --bucket avatar-bucket \ --notification-configuration file://notification-configuration.json
-
Copy an image to the directory and upload the image to the S3 bucket:
aws --endpoint-url=http://localhost:4566 \ s3api put-object --bucket avatar-bucket \ --key test-image.png --body=test-image.png
-
Check the log:
aws --endpoint-url=http://localhost:4566 logs tail '/aws/lambda/CreateThumbnail' --follow
-
To update the code:
aws --endpoint-url=http://localhost:4566 lambda update-function-code --function-name CreateThumbnail --zip-file fileb://lambda-src/function.zip
Now, a thumbnail will be created in the same bucket whenever a new image is uploaded. Once our development and testing phase is complete, proceed to replicate these actions in the production environment. Do remember to remove the endpoint
and s3ForcePathStyle
from the code as they are only needed to communicate with LocalStack.
References
- https://localstack.cloud
- https://docs.localstack.cloud
- Cover image from https://localstack.cloud
- AWS Services image from https://allcode.com/top-aws-services
Top comments (0)