Windows 上で AWS をローカル開発できる環境を構築する(3)
今回はS3、SSM(パラメータストア)、シークレットマネージャを使ってみます。
1. docker-compose.yaml を拡張する(サービス追加)
ファイル修正
docker-compose.yaml
- container_name: localstack を追加
- SERVICES に s3, ssm, secretsmanager を追加
- localstack-init を /etc/localstack/init/ready.d にマウント (LocalStack 起動後に自動実行される)
services:
localstack:
image: localstack/localstack:3.0.2
container_name: localstack
ports:
- "4566:4566"
environment:
- SERVICES=lambda,s3,ssm,secretsmanager
- DEBUG=1
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
- "./localstack-init:/etc/localstack/init/ready.d"
2. 起動時に初期データを登録するスクリプトを作成
LocalStack では、/etc/localstack/init/ready.d/*.sh に置いたスクリプトが
起動完了後に自動実行されます。
以下の 2 つを作成します。
localstack-init/
├── 01-init-ssm.sh
└── 02-init-s3.sh
2-1. SSM と Secrets Manager の初期値登録
localstack-init/01-init-ssm.sh
#!/bin/bash
set -e
echo "[init] Register SSM parameters and Secrets Manager values"
# SSM: バケット名
awslocal ssm put-parameter \
--name "/demo/bucket" \
--value "demo-bucket" \
--type "String"
# Secrets Manager: 任意のテスト用シークレット
awslocal secretsmanager create-secret \
--name "demo-secret" \
--secret-string '{"token":"demo-token-123"}'
2-2. S3 にテスト用バケットを作成
localstack-init/02-init-s3.sh
#!/bin/bash
set -e
echo "[init] Create S3 bucket"
awslocal s3 mb s3://demo-bucket
2-3. 実行権限を付与
$ chmod +x localstack-init/*.sh
3. LocalStack を起動して確認
$ docker compose up -d
初期化が成功しているか確認:
$ awslocal ssm describe-parameters
$ awslocal secretsmanager list-secrets
$ awslocal s3 ls
4. demo Lambda を作成する
前回の hello Lambda に加えて、今回は demo Lambda を追加します。
.
├── docker-compose.yaml
├── hello
│ ├── handler.mjs
│ └── test
│ └── handler.test.mjs
├── demo
│ ├── handler.mjs
│ └── test
│ └── handler.test.mjs
├── package-lock.json
└── package.json
4-1. AWS SDK v3 のインストール
demo Lambda では S3 / SSM / Secrets Manager を利用するため、
必要なクライアントパッケージをインストールします。
$ npm install @aws-sdk/client-s3 @aws-sdk/client-ssm @aws-sdk/client-secrets-manager
4-2. Lambda のコードを作成
処理の流れは次のとおりです。
AWS SDK の基本的な操作(GetParameter / GetSecretValue / PutObject / GetObject)を
1つの Lambda で確認します。
- SSM パラメータストアから S3 バケット名を取得
- Secrets Manager からテスト用シークレットを取得
- 取得した情報を文字列として組み立てる
- S3 に demo.txt としてアップロード
- アップロードしたファイルを S3 からダウンロード
- ダウンロードした内容をレスポンスとして返す
補足
- LambdaからLocalStack には localhost ではなく、
docker-compose の container_name(localstack)を使って http://localstack:4566 で接続します。環境変数 LOCALSTACK_ENDPOINT はLambdaデプロイ時に設定します。 - LocalStack の S3 はバーチャルホスト形式を完全にサポートしていないため、
forcePathStyle: true を付ける必要があります。
demo/handler.mjs
// demo/handler.mjs
import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
export const handler = async () => {
// LocalStack 用の共通設定(credentials を明示)
const endpoint
= process.env.LOCALSTACK_ENDPOINT || "http://localhost:4566";
const common = {
region: "us-east-1",
endpoint: endpoint,
credentials: {
accessKeyId: "test",
secretAccessKey: "test",
},
};
const ssm = new SSMClient(common);
const secrets = new SecretsManagerClient(common);
const s3 = new S3Client({
...common,
forcePathStyle: true, // LocalStack では必須
});
// SSM からバケット名取得
const param = await ssm.send(
new GetParameterCommand({ Name: "/demo/bucket", WithDecryption: false })
);
const bucket = param.Parameter.Value;
// Secrets Manager から値取得
const secret = await secrets.send(
new GetSecretValueCommand({ SecretId: "demo-secret" })
);
const content = `bucket=${bucket}\nsecret=${secret.SecretString}`;
// S3 にアップロード
await s3.send(
new PutObjectCommand({
Bucket: bucket,
Key: "demo.txt",
Body: content,
})
);
// S3 からダウンロード
const result = await s3.send(
new GetObjectCommand({
Bucket: bucket,
Key: "demo.txt",
})
);
const body = await result.Body.transformToString();
return {
statusCode: 200,
body: JSON.stringify({
message: "demo lambda executed",
content: body
})
};
};
4-3. テストコード作成
このテストでは、demo Lambda が SSM・Secrets Manager・S3 の値を正しく取得し、処理結果を返せていることをまとめて確認しています。
demo/test/handler.test.mjs
import { describe, it, expect } from "vitest";
import { handler } from "../handler.mjs";
describe("demo lambda", () => {
it("runs demo lambda", async () => {
const result = await handler();
expect(result.statusCode).toBe(200);
const body = JSON.parse(result.body);
expect(body.message).toBe("demo lambda executed");
expect(body.content).toContain("bucket=");
});
});
5. Vitest で実行
$ npx vitest
今回追加した demo のみテストする場合
$ npx vitest demo/test/handler.test.mjs
6. LocalStack にデプロイして実行
6-1. Lambda 用コードを ZIP 化する
ファイルを ZIP の直下に置きます。
$ zip -j demo.zip demo/handler.mjs
6-2. Lambda 関数を LocalStack にデプロイする
作成した ZIP を LocalStack に登録します。
環境変数 LOCALSTACK_ENDPOINT を設定します。
$ awslocal lambda create-function \
--function-name demo \
--runtime nodejs20.x \
--handler handler.handler \
--zip-file fileb://demo.zip \
--role arn:aws:iam::000000000000:role/lambda-role \
--environment "Variables={LOCALSTACK_ENDPOINT=http://localstack:4566}"
6-3. 状態の確認
$ awslocal lambda get-function --function-name demo
出力内容に
"State": "Active"
が含まれていれば正常です。
6-4. 実行
$ awslocal lambda invoke --function-name demo out.json
$ cat out.json
出力例
{"statusCode":200,"body":"{\"message\":\"demo lambda executed\",\"content\":\"bucket=demo-bucket\\nsecret={\\\"token\\\":\\\"demo-token-123\\\"}\"}"}
以上です。