LoginSignup
5
4

More than 1 year has passed since last update.

localstack + aws cdk でAWS S3をローカル環境に立ち上げてLambdaでアクセスするメモ

Last updated at Posted at 2021-03-27

概要

前回はローカルでデバッグ実行をできるようにした。
今回はS3をローカルで実行する。
リファクタリングは次回

ソースコード

環境

  • windows 10
  • Docker version 20.10.5, build 55c4c88
  • aws-cdk 1.95.1
  • aws-cdk-local 1.65.4
  • aws-cli/2.1.29 Python/3.8.8 Windows/10 exe/AMD64 prompt/off
  • node v14.16.0

ローカルでS3を動かす

検討: localstack

ローカルでS3を実行する方法は、Minio(S3クローン)とlocalstackがある模様。
localStackについて調べてみると、test/mock環境をローカルに構築してくれるものの模様。
サポートしているものと他の手段があるものを、今調べて分かる範囲で表にしてみた。

localstack その他の選択肢
ACM 未調査
API Gateway sam local start-api
CloudFormation 未調査
CloudWatch 未調査
CloudWatch Logs 未調査
DynamoDB Dynamo DB local
DynamoDB Streams 未調査
EC2 未調査
Elasticsearch Service 未調査
EventBridge (CloudWatch Events) 未調査
Firehose 未調査
IAM 未調査
Kinesis 未調査
KMS 未調査
Lambda sam local invoke
Redshift 未調査
Route53 未調査
S3 minio
SecretsManager 未調査
SES 未調査
SNS 未調査
SQS ElasticMQ
SSM 未調査
StepFunctions 未調査
STS 未調査

localstackでs3をデプロイする

  • aws cdkを使ってデプロイする手順。

localstack用のprofileを作成

  • aws configureでprofileを作る
aws configure --profile localstack

入力した内容は以下。

AWS Access Key ID [None]: dummy
AWS Secret Access Key [None]: dummy
Default region name [None]: us-east-1
Default output format [None]: json

localstack用のDockerを作成

  • aws-cdkを使ってS3をデプロイするため、aws-cdkの前提条件であるcloudformationもSERVICEに含めている。
docker/localstack-docker-compose.yml
version: '3.5'

networks:
  localstack:
    driver: bridge

services:
    localstack:
      image: localstack/localstack
      ports:
        - 4566:4566
      environment:
        - SERVICES=cloudformation,s3
      networks:
        - localstack

起動スクリプト。何度も使うのでshにしておく。

bin/localstack.sh
#!/bin/bash

bin_dir=$(cd $(dirname $0) && pwd)
composeFile=${1:-"localstack-docker-compose.yml"}
cd $bin_dir/../docker && docker-compose -f $composeFile up

./bin/localstack.shを実行し、localstackを起動しておく。

cdkのファイルを作成

  • シンプルにS3を作成するだけのスタック
cdk/lib/s3-stack.ts

export class S3Stack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);
    const bucket = new Bucket(this, 'my-bucket', {
      versioned: true,
      bucketName: 'my-sample-bucket'
    });
  }
}
cdk/bin/s3-index.ts
#!/usr/bin/env node

import { App } from '@aws-cdk/core';
import { S3Stack } from '../lib/s3-stack';

const app = new App();
new S3Stack(app, 'S3Stack2021');

Localstack上の S3 に Bucket/Object を作成する

  • cdk.jsonにはlambdaのstackを入れておきたいので、--appオプションでlambdaのスタックを指定している。
    • ※公式ガイドの--app "npx ts-node"を使おうとすると、Command Not Existのエラーが出たので、npm i -g ts-nodeでts-nodeをグローバルインストールしている※注

※注
  • package.jsonの中でなら、ローカルインストールでもいけるみたい。何故?
    • "s3-localdeploy": "cdklocal deploy --app \"npx ts-node --prefer-ts-exts bin/s3-index.ts\" --endpoint-url=http://localhost:4566 --profile=localstack"

stackが問題ないことを確認。

npx cdk synth --app "ts-node --prefer-ts-exts bin/s3-index.ts"

デプロイ

npx cdklocal deploy --app "ts-node --prefer-ts-exts bin/s3-index.ts" --endpoint-url=http://localhost:4566 --profile=localstack

ファイルアップロード確認

aws s3 --endpoint-url=http://localhost:4566 cp package.json s3://my-sample-bucket --profile=localstack

S3にアクセスするLambdaの作成

実装

  • イベントでKeyを受け取り、愚直にS3にアクセスする
src/lambda/handlers/s3-sample.ts
import { S3 } from 'aws-sdk';
import { Handler } from 'aws-lambda';

const config = {
  endpoint: (process.env.IS_LOCAL_STACK === "true" ? "http://localstack:4566" : undefined),
  s3ForcePathStyle: process.env.IS_LOCAL_STACK === "true",
}
const s3 = new S3(config);
const { S3_BUCKET } = process.env;
if (!S3_BUCKET) throw Error('env S3_BUCKET is empy')

export const lambdaHandler: Handler = async (event, context, callback) => {
  try {
    const params = {
      Bucket: S3_BUCKET,
      Key: event.Key
    };
    const ret = await s3.getObject(params).promise();
    if (!ret.Body) throw Error(`s3 object is empty. Bucket: ${S3_BUCKET}, Key: ${event.Key}`)
    const message = ret.Body.toString();
    console.log(message);
    callback(null, `access ${event.Key}`)
  } catch (err) {
    console.log(err);
    return err;
  }
};

S3にアクセスするLambda用のStackの作成

cdk/bin/s3-lambda-index.ts
#!/usr/bin/env node

import { App } from '@aws-cdk/core';
import { S3LambdaStack } from '../lib/s3-lambda-stack';
import { createLaunch } from '../lib/process/after';

const app = new App();
const stack = new S3LambdaStack(app, 'S3LambdaStack2021');
createLaunch(stack);
cdk/lib/s3-lambda-stack.ts
import * as cdk from '@aws-cdk/core';
import * as lambda from '@aws-cdk/aws-lambda';
import { NodejsFunction } from '@aws-cdk/aws-lambda-nodejs';
import { entryHandlerDir } from './constants';

export class S3LambdaStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    new NodejsFunction(this, 's3-sample', {
      runtime: lambda.Runtime.NODEJS_14_X,
      entry: `${entryHandlerDir}/s3-sample.ts`,
      handler: 'lambdaHandler',
      bundling: {
        sourceMap: true,
      },
      environment: {
        IS_LOCAL_STACK: process.env.IS_LOCAL_STACK || '',
        S3_BUCKET: process.env.S3_BUCKET || ''
      }
    });
  }
}
cdk/lib/constants.ts
export const entryHandlerDir = '../src/lambda/handlers/';

動作確認

実行準備

  • 環境変数の準備
  • 実行用のスクリプトをpackage.jsonに用意
cdk/env.json
{
  "s3sampleA17B5BBA": {
    "IS_LOCAL_STACK": "true",
    "S3_BUCKET": "my-sample-bucket"
  }
}
  • docker-composeで立ち上げているlocalstackのネットワークの名前はdocker_localstackとなる
    • --docker-networkにそれを指定。
cdk/package.json
  "scripts": {
+    "s3-localdeploy": "cdklocal deploy --app \"npx ts-node --prefer-ts-exts bin/s3-index.ts\" --endpoint-url=http://localhost:4566 --profile=localstack",
+    "s3-bundle": "cdk synth --app \"npx ts-node --prefer-ts-exts bin/s3-lambda-index.ts\" > cdk.out/s3-template.yaml",
+    "s3-invoke": "cd cdk.out && echo {\"Key\": \"package.json\"} | sam.cmd local invoke s3sampleA17B5BBA --docker-network docker_localstack --profile=localstack -t s3-template.yaml -n ../env.json --event -",
+    "debug-s3-invoke": "cd cdk.out && echo {\"Key\": \"package.json\"} | sam.cmd local invoke -d 5858 s3sampleA17B5BBA --docker-network docker_localstack --profile=localstack -t s3-template.yaml -n ../env.json --event -"

実行

cd cdk
npm run s3-bundle
npm run s3-invoke

image.png

実行できた。

失敗:ネットワークエラー

  • 指定ポートを間違えて4577にしているのが原因。
{"message":"Inaccessible host: `localstack'. This service may not be available in the `us-east-1' region.","code":"UnknownEndpoint","region":"us-east-1","hostname":"localstack","retryable":true,"originalError":{"message":"connect ECONNREFUSED 172.21.0.2:4572","errno":-111,"code":"NetworkingError","syscall":"connect","address":"172.21.0.2","port":4572,"region":"us-east-1","hostname":"localstack","retryable":true,"time":"2021-03-27T10:32:44.322Z"},"time":"2021-03-27T10:32:44.322Z"

参考

Testable - lambda : 和田卓人
aws-sam-localstack-example
aws cdk s3
ローカル環境でLambda+S3のテストをする
github - localstack
github - minio
github - localstack-aws-cdk
github - awscli-local
Localstack + CDK = local AWS development
localstackのローカル環境にAWS CDKでS3バケットを作成してみる
LocalStackに向けてTerraformを実行する
LocalStackをつかってローカルでLambdaを実行してみた
LocalStackでAWSサービスを試してみた
Local Development with LocalStack
AWS SAM CLI + DynamoDB localを使ってローカル上で完結するAPI開発
elastcisq - sws local
docker elasticsq
Lambda Function の開発について悩んだところをまとめてみた
LocalStackをGitHub Actions上で動かしてテストする
LaravelをElasticMQ(Amazon SQS互換)と連携してみる
開発のためにローカルにもS3が欲しいというわがまま、MINIOが叶えてくれました
【AWS S3】S3 Presigned URLの仕組みを調べてみた
local invoke option`
localstack有料版をちゃんと使ってみる

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4