ElasticMQを使ってローカルでAWS SQSをモックしてみる
ElasticMQのセットアップ
参考記事
docker-compose.yml 作成
version: "3"
services:
elasticmq:
container_name: elasticmq
image: softwaremill/elasticmq:latest
ports:
- "9324:9324"
- "9325:9325"
以下コマンドでElasticMQを起動する
docker-compose up -d
以下のURLで管理画面を開くことができる
http://localhost:9325/
キューを作成
--endpoint-url
を指定することで、AWSではなくローカルのElasticMQへ接続する
aws sqs create-queue --queue-name test-queue --endpoint-url http://localhost:9324
作成されたキューを確認(管理画面でもOK)
aws sqs list-queues --endpoint-url http://localhost:9324
{
"QueueUrls": [
"http://localhost:9324/000000000000/test-queue"
]
}
メッセージの送受信
キューを送信してみる
aws sqs send-message --queue-url http://localhost:9324/queue/test-queue --message-body "ElasticMQ Test Message" --endpoint-url http://localhost:9324
管理画面を見るとキューがあることを確認できる
受信してみる
aws sqs receive-message --queue-url http://localhost:9324/queue/test-queue --endpoint-url http://localhost:9324
{
"Messages": [
{
"MessageId": "65ba8f9b-3c22-4220-9e92-b0f5b469ef85",
"ReceiptHandle": "65ba8f9b-3c22-4220-9e92-b0f5b469ef85#06e57300-cb75-45fb-ae7c-27b349179980",
"MD5OfBody": "b336d90ba4892d57a9cc3140502d4634",
"Body": "ElasticMQ Test Message"
}
]
}
先ほど送ったメッセージを受信できた
メッセージの削除
管理画面を確認すると、受信しただけではメッセージは削除されていないっぽい
以下のコマンドでメッセージを削除する
aws sqs delete-message --queue-url http://localhost:9324/queue/test-queue --receipt-handle 65ba8f9b-3c22-4220-9e92-b0f5b469ef85#06e57300-cb75-45fb-ae7c-27b349179980 --endpoint-url http://localhost:9324
--receipt-handle
は先ほど受信したときの値を入力
削除されたことを管理画面で確認できる
ローカルでSQSのメッセージを送信してみる
SQSへメッセージを送信するようなLambdaを作成する。
(本筋ではないのでセットアップは省略する)
SAMを利用して以下のようなコードを作成する
import { SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs";
const client = new SQSClient();
const SQS_QUEUE_URL = "http://localhost:9324/queue/test-queue";
export const lambdaHandler = async (event, context) => {
const command = new SendMessageCommand({
QueueUrl: SQS_QUEUE_URL,
MessageBody: "ElasticMQ Test Message on Lambda",
});
const response = await client.send(command);
console.log(response);
return response;
};
前回同様、test-sqs
というキューにメッセージを送信する。
しかし、このまま実行すると、AWSのSQSにメッセージを送信しようとしてしまう。
SQSClientのコンストラクタにendpoint: "http://localhost:9324"
を指定してやればElasticMQに送信される。
が、コード上でendpointを指定するのはあまり好きではない。(環境の切り替えなど、本筋ではない処理をコード上に含ませたくない)
なので、以下の記事を参考に、環境変数にendpointを渡すようにしてみる。
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
sqs-test
Sample SAM Template for sqs-test
Parameters:
AwsEndpointSQSUrl:
Type: String
Default: ""
Globals:
Function:
Timeout: 3
Resources:
SQSTestFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: sqs-test/
Handler: app.lambdaHandler
Runtime: nodejs20.x
Architectures:
- x86_64
Environment:
Variables:
AWS_ENDPOINT_URL: !Ref AwsEndpointSQSUrl
AwsEndpointSQSUrl
というパラメータを作成して、実行時に渡してみる。
sam local invoke --parameter-overrides AwsEndpointSQSUrl=http://localhost:9324
これでメッセージを送信、と思ったらエラーが
Invoke Error {"errorType":"Error","errorMessage":"connect ECONNREFUSED 127.0.0.1:9324","code":"ECONNREFUSED","errno":-111,"syscall":"connect","address":"127.0.0.1","port":9324,"$metadata":{"attempts":3,"totalRetryDelay":184},"stack":["Error: connect ECONNREFUSED 127.0.0.1:9324"," at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1607:16)"]}
接続が拒否されてるっぽい
調べてみると、sam localのコンテナとElasticMQのコンテナ間で通信ができていない様子。
docker networkを作成し、docker-compose.ymlにnetworkの設定を追加する
docker network create sam-local
version: "3"
services:
elasticmq:
container_name: elasticmq
image: softwaremill/elasticmq:latest
ports:
- "9324:9324"
- "9325:9325"
networks:
- sam-local
networks:
sam-local:
external: true
ElasticMQを再起動して、docker-networkを指定して再度実行
sam local invoke --parameter-overrides AwsEndpointSQSUrl=http://localhost:9324 --docker-network sam-local
今度こそ、と思ったらまだ同じエラーが出る。
コンテナが異なるからURLをlocalhostで指定できなかったぽい。初歩的なミス。
今回、ElasticMQのコンテナのホストはelasticmq
となっている。
コード、コマンドを以下のように修正して実行。
import { SendMessageCommand, SQSClient } from "@aws-sdk/client-sqs";
const client = new SQSClient();
const SQS_QUEUE_URL = "http://elasticmq:9324/queue/test-queue";
export const lambdaHandler = async (event, context) => {
const command = new SendMessageCommand({
QueueUrl: SQS_QUEUE_URL,
MessageBody: "ElasticMQ Test Message on Lambda",
});
const response = await client.send(command);
console.log(response);
return response;
};
sam local invoke --parameter-overrides AwsEndpointSQSUrl=http://elasticmq:9324 --docker-network sam-local
実行するとようやくレスポンスが返ってきた
{"$metadata": {"httpStatusCode": 200, "attempts": 1, "totalRetryDelay": 0}, "MD5OfMessageBody": "d47936c65dcf06da701ae504dff350f3", "MessageId": "245bb1a3-b0d1-4500-b5ab-9a8cd3ffe6ca"}
管理画面からもキューが送信されたことが確認できる
起動時にキューを作成したい
ElasticMQ起動時に毎回初期化されてしまうため、起動するたび毎回キューを作成しなくてはいけない。
あまりにめんどうなので、起動時に必要なキューをまとめて作成するようにしたい。
elasticmq.conf
/opt/elasticmq.confの値を変更することで、起動時にキューを作成することができるらしい。
(typesafeってので記述されているっぽい?)可視性タイムアウトやFIFOキュー、デットレターキューなどの設定もここで行う。
ではconfファイルを作成して、docker-compose.ymlでマウントするようにしよう。
今回は公式で例として挙げられている通りに作ってみた。
# the include should be done only once, at the beginning of the custom configuration file
include classpath("application.conf")
queues {
queue1 {
defaultVisibilityTimeout = 10 seconds
delay = 5 seconds
receiveMessageWait = 0 seconds
deadLettersQueue {
name = "queue1-dead-letters"
maxReceiveCount = 3 // from 1 to 1000
}
fifo = false
contentBasedDeduplication = false
copyTo = "audit-queue-name"
moveTo = "redirect-queue-name"
tags {
tag1 = "tagged1"
tag2 = "tagged2"
}
}
queue1-dead-letters { }
audit-queue-name { }
redirect-queue-name { }
}
version: "3"
services:
elasticmq:
container_name: elasticmq
image: softwaremill/elasticmq:latest
ports:
- "9324:9324"
- "9325:9325"
networks:
- sam-local
volumes:
- ./elasticmq.conf:/opt/elasticmq.conf # ここ追加
ElasticMQを起動しなおせばこのようにキューが作成されていることが確認できる。