ローカルDocker環境でSQSのモックElasticMQを利用する
AWS 環境で動作させるシステムの開発をしていて、ローカルの Docker 環境に SQS と同じ API で動作するモックを用意したくて ElasticMQ を利用してみました。[!CAUTION] 追記: 結局最終的に LocalStack を使いました。Docker イメージは幾つかありますが、roribio16/alpine-sqsを利用しました。 docker-compose.yml の用意 docker-compose の設定は以下のようにしました。 Node.js 環境も dokcer-compose で用意しておきます。 version: '3.7'services: # Node.js環境。開発時のnpmコマンド実行はここで行う。 node: image: node:12.18.3-alpine3.12 volumes: - ./:/project:delegated tty: true working_dir: /project environment: # AWS-SDKを動作させるのにダミーでいいので設定しておく必要がある AWS_REGION: ap-northeast-1 AWS_ACCESS_KEY_ID: local AWS_SECRET_ACCESS_KEY: dummy# 入力キュー # SQS互換のElasticMQを利用 input_queue: image: roribio16/alpine-sqs ports: # 管理画面へアクセスするポート - 9325:9325この alpine-sqs というイメージは default という名前のキューが最初から作成された状態で起動してくれます。 Node.js 環境の用意 コンテナを起動しておきましょう。 $ docker-compose up -dnode コンテナに入って作業します。alpine 版を使ってるので bash が入っていなくて sh を指定します[^1]。 [^1]: alpine を使っているのは、本番環境の Fargate 用コンテナをサイズの小さい alpine で作るつもりでいて、開発環境も同じに合わせたかったからです。debian を使った方が色々とカスタマイズが楽だと思います。 $ docker-compose exec node shここからは node コンテナ内でのコマンド実行の場合は、先頭を $ ではなく # で表します。 package.json を作成します。中身は適当でいいです。 # npm initAWS SDK をインストールします。 $ npm install aws-sdkAWS SDK でのアクセス まず送信用のスクリプトを作成します。 endpoint に用意した ElasticMQ のコンテナを指すように設定するのがポイントです。 const AWS = require('aws-sdk');const endpointUrl = 'http://input_queue:9324'; const queueUrl = endpointUrl + '/queue/default';const sqs = new AWS.SQS({ apiVersion: '2012-11-05', endpoint: endpointUrl, });const params = { // Remove DelaySeconds parameter and value for FIFO queues DelaySeconds: 10, MessageAttributes: { Title: { DataType: 'String', StringValue: 'The Whistler', }, Author: { DataType: 'String', StringValue: 'John Grisham', }, WeeksOn: { DataType: 'Number', StringValue: '6', }, }, MessageBody: 'Information about current NY Times fiction bestseller for week of 12/11/2016.', QueueUrl: queueUrl, };sqs.sendMessage(params, function (err, data) { if (err) { console.log('Error', err); } else { console.log('Success', data.MessageId); } });node コンテナ上で実行してみます。 # node send.jsホスト側のブラウザで http://localhost:9395/ にアクセスすると、管理画面でキューにメッセージが来ていることが確認できます。 (ただこの管理画面、キューからメッセージを削除しても削除したものも表示されます。それはそれで便利なんですが。) 次はポーリングを仕掛けて受信してみましょう。 const AWS = require('aws-sdk');const endpointUrl = 'http://input_queue:9324'; const queueUrl = endpointUrl + '/queue/default';const sqs = new AWS.SQS({ apiVersion: '2012-11-05', endpoint: endpointUrl, });async function poll() { const params = { QueueUrl: queueUrl, MaxNumberOfMessages: 10, WaitTimeSeconds: 20, MessageAttributeNames: ['Title', 'Author', 'WeeksOn'], }; return new Promise((resolve, reject) => { sqs.receiveMessage(params, (err, data) => { if (err) { console.error(err); reject(err); } else { console.log(data); resolve(data); } }); }); }async function main() { const data = await poll(); }main();node コンテナ上で実行します。 # node receive.js受信できればオッケーです。
ローカルDocker環境でS3Mockを利用する
AWS 環境で動作させているシステムの開発をしていて、ローカルの Docker 環境に S3 と同じ API で動作するモックがあったらいいなと思って調べたら、当然のようにありました。 S3 のモックサーバー候補 とりあえず見つかったのは以下です。abobe/S3Mock findify/S3Mock Fake S3 Mock S3 S3 Proxy上2つが有力候補。 3つ目の Fake S3 は古いバージョンはオープンソースだけど新しいバージョンは有料っぽい? 後ろ2つは adobe/S3Mock が似たようなプロジェクトとして紹介してました。 今回は findify/S3Mock を試した後に、結局 adobe/S3Mock を利用しました。[!CAUTION] 追記: 結局最終的に LocalStack を使いました。実際のプロジェクトでは TypeScript を使っているのですが、ここでは Javascript で解説します。 なぜ adobe/S3Mock にしたか? findify/S3Mock でデフォルトのバケットをコンテナ作成時に最初から作成しておく方法がすぐに分からなかったからです。 もちろん API を呼んで CreateBucket することはできます。 dokcer-compose.yml の用意 docker-compose の設定は以下のようにしました。 Node.js 環境も dokcer-compose で用意しておきます。 version: '3.7'services: # Node.js環境。開発時のnpmコマンド実行はここで行う。 node: image: node:12.18.3-alpine3.12 volumes: - ./:/project:delegated tty: true working_dir: /project environment: # AWS-SDKを動作させるのにダミーでいいので設定しておく必要がある AWS_REGION: ap-northeast-1 AWS_ACCESS_KEY_ID: local AWS_SECRET_ACCESS_KEY: dummy s3: image: adobe/s3mock environment: - initialBuckets=my-bucketinitialBuckets を指定することでコンテナ作成時にバケットも作成することができます。inisitalBuckets と複数形になっていますが、リストで複数作成させることができるようです。 - initialBuckets=my-bucket1,my-bucket2,my-bucket3Node.js 環境の用意 コンテナを起動しておきましょう。 $ docker-compose up -dnode コンテナに入って作業します。alpine 版を使ってるので bash が入っていなくて sh を指定します[^1]。 [^1]: alpine を使っているのは、本番環境の Fargate 用コンテナをサイズの小さい alpine で作るつもりでいて、開発環境も同じに合わせたかったからです。debian を使った方が色々とカスタマイズが楽だと思います。 $ docker-compose exec node shここからは node コンテナ内でのコマンド実行の場合は、先頭を $ ではなく # で表します。 package.json を作成します。中身は適当でいいです。 # npm initAWS SDK をインストールします。 $ npm install aws-sdkAWS SDK でのアクセス 適当に index.js とかの名前でファイルを作成しましょう。 まずは s3 クライアントオブジェクトを作成します。 const AWS = require('aws-sdk');const s3 = new AWS.S3({ apiVersion: '2006-03-01', // docker-compose環境の中ではサービス名がそのままホスト名として使える。s3コンテナの9090番ポートを指定。 endpoint: 'http://s3:9090', // S3Mock使う時は <バゲット名>.hostname:9090 でなく hostname:9090/<バゲット名> になってもらわないと困る s3ForcePathStyle: true, });endpoint と s3ForcePathStyle を指定する必要があります。 S3 の URL は昔は https://s3-<リージョン >.amazonaws.com/<バケット名> というパス形式と呼ばれるものが使われていました。 今は https://<バケット名>.s3-<リージョン>.amazonaws.com/ という仮想ホスト形式が使われています。 しかし S3Mock を使うときに仮想ホスト形式になってもらってはアクセスできないので、パス形式を利用するように指定しています。 9090 番ポートは HTTP 用で、9191 番ポートが HTTPS 用だそうです。HTTP しか試してませんが。 あとは普通に使ってもらえば大丈夫です。
"parserOptions.project" has been set for @typescript-eslint/parser をなんとかする
TypeScript のプロジェクトでプログラムのソースコードを src ディレクトリ下に、テストを tests ディレクトリ下に分けて格納するようにしました。 が、VSCode でテストファイルを開くとファイル先頭で必ずエラーが表示されるように・・・。 エラーの内容は Parsing error: "parserOptions.project" has been set for @typescript-eslint/parser. The file does not match your project config: tests/sample.test.ts. The file must be included in at least one of the projects provided.というもので、以下のように必ずファイルの先頭に表示されます。考えられる原因 どうやら tsconfig.json に tests 以下を含めないようにしているのが原因のようです。 { "compilerOptions": { // 省略 }, "include": ["src/**/*.ts"] }これはビルド時にテストファイルを含めないようにするためにそうしています。 しかし eslint の @typescript-eslint/parser もこのファイルを使っているために、このエラーが出てしまっているのでしょう。 { "parser": "@typescript-eslint/parser", "parserOptions": { "sourceType": "module", "project": "./tsconfig.json" } // 省略 }@typescript-eslint/parser にとっては、tests 以下は project に指定した設定に含まれていないぞ!と。 考えられる解決方法 解決方法は2通り考えられそうです。eslint 用には別の tsconfig.eslint.json を用意して、そちらには tests 以下を含めるようにする。 tsconfig.json には tests 以下を含めるようにし、ビルド用に tsconfig.build.json を用意してそちらには tests 以下を含めないようにする。[!WARNING] 注: 設定を変更した後に VSCode を再起動しないと反映されませんでした。tsconfig.eslint.json を用意する方法 tsconfig.json を extends に指定して、変更点(include)だけを上書きしたものを用意します。 { "extends": "./tsconfig.json", "include": ["src/**/*.ts", "tests/**/*.ts"] }このファイルを @typescript-eslint/parser が利用するように指定します。 { "parser": "@typescript-eslint/parser", "parserOptions": { "sourceType": "module", "project": "./tsconfig.eslint.json" } // 省略 }tsconfig.build.json を用意する方法 tsconfig.json には tests 以下も含めるように指定しておきます。 { "compilerOptions": { // 省略 }, "include": ["src/**/*.ts", "tests/**/*.ts"] }ビルド用の tsconfig.build.json を用意し、tsconfig.json を extends に指定して、変更点だけを上書きします。 { "extends": "./tsconfig.json", "compilerOptions": { // ビルド時にのみ指定したいものがあれば設定 }, "include": ["src/**/*.ts"], "exclude": ["tests/**/*.ts"] }ビルド時にこの設定ファイルを利用するように指定します。 { // 省略 "scripts": { "build": "tsc tsconfig.build.json" // 省略 } }どっちの解決方法がいいの? ビルド時にのみ指定したいコンパイラオプションがあるかどうかで変わってきそうです。 単に問題を解決したいだけなら tsconfig.eslint.json を用意するのがちょっとだけ簡単です。 ビルド時にのみ指定したいものがあるなら、共通設定を tsconfig.json に記載して、ビルド用に tsconfig.build.json を用意した方がいいでしょう。 いっそのこと tsconfig.eslint.json と tsconfig.build.json の両方とも用意して、共通設定を tsconfig.json に記載するというのも考えられます。これが一番柔軟です。 { "compilerOptions": { // 共通に設定するコンパイラオプション }, "exclude": ["node_modules"] }{ "extends": "./tsconfig.json", "compilerOptions": { // eslint でのみ適用したいものを指定 }, "include": ["src/**/*.ts", "tests/**/*.ts"] }{ "extends": "./tsconfig.json", "compilerOptions": { // ビルド時にのみ適用したいものを指定 }, "include": ["src/**/*.ts"] }