{%hackmd xIp4k0sIS6SuPecdKUPMlw %} [TOC] --- > [name=Alvin] [time=Now] # aws Lambda - Localstack 輔助開發工具 ## 動機  開發 `Serverless` 應用程序時,傳統的開發方式可能會遇到一些挑戰。特別是當我們使用 `aws Lambda` 時,每次進行測試都需要部署到雲端環境,這樣的開發流程效率相對較低,且成本也相對較高。 因此,開發者通常會尋找一種在本地環境中測試 `Lambda` 函式的方法,以提高開發效率和降低成本。 ## 目的 為了實現在 `localhost` 執行 `Lambda` 這一目標,選擇 `Localstack` 作為開發工具,它可以模擬 `aws` 雲端環境的大部分功能。同時,選擇了 `NestJS` 的 `serverless-offline` 插件,可以與 `Localstack` 無縫集成,使得在本地環境中測試和 `debug`,而無需實際部署到 `aws Lambda`(只是改成部署到本地端的 `Localstack` 工具上)。 通過上述方式,可以達到降低開發成本,提高開發效率,並更快地交付高質量的 `Serverless` 程式代碼。 ## :memo: Environment - [x] Mac OS - [x] Docker - [x] [Localstack](https://app.localstack.cloud/inst/default/status) - [x] NestJS - Serverless Plugins - [x] [serverless-localstack](https://www.npmjs.com/package/serverless-localstack) - [x] [serverless-offline](https://www.npmjs.com/package/serverless-offline#run-modes) - [ ] ~~[serverless-offline-sqs](https://www.npmjs.com/package/serverless-offline-sqs)~~ ### Setup - `docker_compose.yml` ``` yml= version: "1.0" services: localstack: hostname: mylocalstack container_name: "localstack-main" image: localstack/localstack:latest network_mode: "bridge" privileged: true ports: - "0.0.0.0:4566:4566" - "0.0.0.0:4510-4559:4510-4559" environment: - DEBUG=1 - SERVICES=sqs, sns, lambda, iam, logs, cloudformation - DATA_DIR=/tmp/localstack/data - HOSTNAME=localstack - HOSTNAME_EXTERNAL=localstack volumes: - "${LOCALSTACK_VOLUME_DIR:-./volume}:/var/lib/localstack" - "/var/run/docker.sock:/var/run/docker.sock" ``` --- - `serverless.yml` ``` yml= { "service": "serverless-setup", "provider": { "memorySize": 128, // 記憶體大小 "timeout": 10, "name": "aws", "runtime": "nodejs20.x", // NodeJS 版號 "region": "${self:custom.env.REGION}", "iam": { // 權限 "role": { "statements": [ { "Effect": "Allow", "Action": ["*"], "Resource": "*" }, ] } }, // "deploymentBucket": { // "serverSideEncryption": "AES256", // "name": "my-s3-bucket" // }, "tracing": { "lambda": true } }, "package": { "individually": true }, "plugins": [ // 注入要使用的插件 "serverless-localstack", "serverless-offline", // "serverless-lift", // "serverless-offline-sqs", ], "functions": { "tester": { "name": "tester", // "timeout": 10, // # optional, in seconds, default is 6 "handler": `dist/apps/mailer/src/main.tester`, "environment": { "QUEUE_URL": "${self:custom.env.QUEUE_URL}" }, }, "sender": { "name": "sender", "maximumEventAge": 60, "maximumRetryAttempts": 0, // "timeout": 30, // # optional, in seconds, default is 6 "reservedConcurrency": 1, "handler": `dist/apps/mailer/src/main.sender`, "url": true, "environment": { "QUEUE_URL": "${self:custom.env.QUEUE_URL}" }, "events": [ { "sqs": "arn:aws:sqs:${self:custom.env.REGION}:${self:custom.env.ACCOUNT_ID}:${self:custom.env.QUEUE_NAME}" // "sqs": { // "arn": "arn:aws:sqs:${self:custom.env.REGION}:${self:custom.env.ACCOUNT_ID}:${self:custom.env.QUEUE_NAME}", // "batchSize": 1, // "maximumBatchingWindow": 1, // "enabled": true, // }, // "sqs": { // "arn": { // "Fn::GetAtt": [ // "${self:custom.env.QUEUE_NAME}", // "Arn" // ] // } // }, http: { method: 'GET', path: '/sender', }, }, ] }, }, "resources": { "Resources": { "LocalstackQueue": { "Type": "AWS::SQS::Queue", "Properties": { "QueueName": "${self:custom.env.QUEUE_NAME}", "VisibilityTimeout": 60, "MessageRetentionPeriod": 3600, } }, }, }, "custom": { "env": { "QUEUE_PORT": "4566", "QUEUE_NAME": "LocalstackQueue", "ACCOUNT_ID": "000000000000", "REGION": "us-east-1", "QUEUE_URL": "http://host.docker.internal:${self:custom.env.QUEUE_PORT}/${self:custom.env.ACCOUNT_ID}/${self:custom.env.QUEUE_NAME}", // "QUEUE_URL": "https://localhost.localstack.cloud:${self:custom.env.QUEUE_PORT}/${self:custom.env.ACCOUNT_ID}/${self:custom.env.QUEUE_NAME}", }, "localstack": { "host": "http://localhost/", "debug": true, "autostart": true, "compose_file": "./localstack/compose.yml", "stages": [ "offline", ], "networks": [ "host", "bridge", ], "lambda": { "mountCode": false }, "docker": { "sudo": true, } }, "serverless-offline": { // "reloadHandler": true, "terminateIdleLambdaTime": 50000, "useDocker": true, "useInProcess": true, "noAuth": true, "dockerNetwork": 'bridge', "httpPort": 3000, "host": "0.0.0.0", // "lambdaPort": 4566, // "host": "localhost", "dockerHostServicePath": `${__dirname}/../../`, }, // "serverless-offline-sqs": { // "autoCreate": true, // "apiVersion": "2012-11-05", // "endpoint": "http://0.0.0.0:4566", // "region": "us-east-1", // "accessKeyId": "root", // "secretAccessKey": "root", // "skipCacheInvalidation": false, // } } } ``` --- - `handler.ts` ``` typescript= import { HttpStatus } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; import { Context, Handler } from 'aws-lambda'; import { AwsSqsService } from '@libs/aws-sqs'; import { MailWorkerModule } from './mailer.module'; import { MailWorkerService } from './mailer.service'; import { SQSEvent } from 'aws-lambda'; const { QUEUE_URL } = process.env; export const sender: Handler = async ( event: SQSEvent, context: Context, callback: any, ) => { const appContext = await NestFactory.createApplicationContext(MailWorkerModule); const awsSqsService = appContext.get(AwsSqsService); const mailWorkerService = appContext.get(MailWorkerService); const queueData = await awsSqsService.fetchMessageFromQueue(QUEUE_URL); const legitimateMessages = mailWorkerService.filterLegitimateMessage(queueData); console.log('legitimateMessages', legitimateMessages); await mailWorkerService.send(legitimateMessages); return { statusCode: HttpStatus.OK, }; return callback( null, JSON.stringify({ statusCode: HttpStatus.OK, body: { queueData, event, }, }), ); }; export const tester = async function (event, context) { const appContext = await NestFactory.createApplicationContext(MailWorkerModule); const awsSqsService = appContext.get(AwsSqsService); const dataMeta = { date: new Date(), input: event, }; await awsSqsService.sendMessage({ QueueUrl: QUEUE_URL, MessageBody: JSON.stringify(dataMeta), }); return { statusCode: HttpStatus.OK, body: JSON.stringify(dataMeta), }; }; ``` ### Command - Run docker compose ``` shellscript docker compose -f "$path"/compose.yml/ up -d --force-recreate ``` > https://app.localstack.cloud/inst/default/status >  - Run serverless plugin ```shellscript yarn run build mailer && export SLS_DEBUG=* && APP=mailer NODE_ENV=dev IS_OFFLINE=true serverless mailer:offline start --stage=offline --noTimeout ```  - Invoke function ``` shellscript APP=mailer NODE_ENV=dev IS_OFFLINE=true serverless mailer:invoke --stage=offline --function=tester -d '{}' -l ```  :rocket: Refer: > - [Docker Compose](https://docs.docker.com/compose/gettingstarted/) > - [Localstack](https://www.localstack.cloud/) > - [Localstack Dashboard](https://app.localstack.cloud/dashboard) > - [plugin - serverless-localstack](https://www.npmjs.com/package/serverless-localstack) > - [plugin - serverless-offline](https://www.npmjs.com/package/serverless-offline#run-modes) > - [Serverless Document](https://www.serverless.com/framework/docs) > - [aws Lambda - invoke cli](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/lambda/invoke.html)
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up