ローカル開発環境に、S3操作時のSQSイベント通知のモックを作成する
2017/09/122020/11/17
IT dockerAWS

はじめに

AWSで、S3への操作に応じてSQSにメッセージを送信するように連携設定することがあると思います。

このような仕組みを利用したシステムを開発するとき、ローカル開発環境でもS3やSQSを利用すると、開発時にジワジワとお金がかかってしまいます。更に、複数人で開発しているときには、全員が同じバケットを利用するのか(他の開発者がデータをアップロードすると影響を受ける可能性がある)、開発者毎にバケットを用意するのか(誰が使用しているバケットなのか、など管理しきれなくなる恐れがある)、といったことを考慮していく必要があります。

そこで、ローカル開発環境ではS3やSQSとコンパチのアプリケーション(以下このようなアプリケーションを「モック」といいます)を使用する方が都合が良いです。

幸いS3、SQSなどよく使われるAWSサービスについては既にモックが存在しています。

しかしながら現段階では、S3モックへの操作に応じて、SQSモックにメッセージを送信してくれるようなものはありませんので、このような仕組みは自作する必要があります。

本記事では、この仕組みを自作するための一例を紹介します。

今回、S3モックとしてはminio、SQSモックとしてはelasticmqを使用しています。

なお、AWSのサービスの多くを網羅したモックとしてAtlassianが開発したLocalStackというアプリケーションもあります。minioやelasticmqではなく、こちらを利用しても実現可能です。

構成

概要

以下の構成で構築します。

図示しているように、以下のような動作になっています。

以下、dockerで環境構築していきます。下記でソースを取得できます。

1
git clone https://github.com/eidera/cooperate_s3mock_and_sqsmock.git

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
version: '3'
services:
  s3_mock:
    container_name: s3-mock
    image: minio/minio:RELEASE.2017-08-05T00-00-53Z
    ports:
      - '9000:9000'
    volumes:
      - s3_data:/export
    environment:
      - 'MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE'
      - 'MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
    command: server /export

  sqs_mock:
    container_name: sqs-mock
    build:
      context: ./build
      dockerfile: sqsMockDockerfile
    ports:
      - '9324:9324'

  sqs_sender:
    container_name: sqs-sender
    build:
      context: ./build
      dockerfile: sqsSenderDockerfile
    ports:
      - '9030:9030'
    volumes:
      - ./src/sqs_sender:/app
    command: ruby /app/sqs_sender.rb

  s3_proxy:
    container_name: s3-proxy
    build:
      context: ./build
      dockerfile: s3ProxyDockerfile
    ports:
      - '9001:9001'
    volumes:
      - ./src/s3_proxy:/app
    command: ruby /app/proxy.rb

volumes:
    s3_data:

build/s3ProxyDockerfile

1
2
3
4
5
6
7
8
FROM ruby:2.4.1-alpine

RUN apk update
RUN apk add build-base
RUN gem install kage

WORKDIR /app
EXPOSE 9001

受信したリクエストを、複数のサーバー各々に対して流すためにkageというrubyのgemを利用しています(s3_proxy.rbも参照して下さい)。

build/sqsMockDockerfile

1
2
3
4
5
6
7
8
9
10
11
FROM java:8-jdk-alpine

RUN apk update && \
    apk add ca-certificates && \
    update-ca-certificates && \
    apk add openssl

RUN wget 'https://s3-eu-west-1.amazonaws.com/softwaremill-public/elasticmq-server-0.13.8.jar'

ENTRYPOINT java -jar elasticmq-server-0.13.8.jar
EXPOSE 9324

build/sqsSenderDockerfile

1
2
3
4
5
6
FROM ruby:2.4.1-alpine

RUN gem install aws-sdk

WORKDIR /app
EXPOSE 9030

src/s3_proxy/proxy.rb

1
2
3
4
5
6
7
8
9
10
11
require 'uri'
require 'kage'

Kage::ProxyServer.start do |server|
  server.port = 9001
  server.host = '0.0.0.0'
  server.debug = false

  server.add_master_backend(:s3, 's3_mock', 9000) # S3モック
  server.add_backend(:sqs, 'sqs_sender', 9030) # SQSモック
end

src/sqs_sender/sqs_sender.rb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
require 'webrick'
require 'aws-sdk'

AWS_CONFIG = {
    access_key_id: 'TEST',
    secret_access_key: 'TEST',
    region: 'TEST',
    endpoint: 'http://sqs_mock:9324/'
}

# HTTPメソッドと、S3イベントとの対応
HTTP_TO_EVENTS = {
  'POST' => 'ObjectCreated:Put',
  'PUT' => 'ObjectCreated:Post',
  'DELETE' => 'ObjectRemoved:Delete'
}

# キュー名
QUEUE_NAME = 'queue1'

def send_message(method, bucket_name, path)
  event = HTTP_TO_EVENTS[method]
  return if event.nil?

  queue_url = sprintf("http://sqs_mock:9324/queue/%s", QUEUE_NAME)
  message = sprintf('{"Message": "{\"Records\":[{\"eventName\":\"%s\",\"s3\":{\"object\":{\"key\":\"%s\"},\"bucket\":{\"name\":\"%s\"}}}]}"}', event, path, bucket_name)

  sqs = Aws::SQS::Client.new(AWS_CONFIG)
  sqs.send_message(queue_url: queue_url, message_body: message)
  message
end

def process_request(req)
  # request_lineの中身は以下のような感じ
  #   PUT /bucket-name/filename.txt HTTP/1.1

  requests = req.request_line.split(' ')

  method = requests[0]
  path = requests[1]

  dirs = path.split('/')
  bucket_name = dirs[1]
  path = dirs[2..-1].join('/') # バケット名は削除

  send_message(method, bucket_name, path)
end

module WEBrick
  module HTTPServlet
    class ProcHandler < AbstractServlet
      alias do_PUT    do_GET
      alias do_DELETE do_GET
    end
  end
end

srv = WEBrick::HTTPServer.new({
  DocumentRoot:   './',
  BindAddress:    '0.0.0.0',
  Port:           '9030',
})

srv.mount_proc '/' do |req, res|
  res.body = process_request(req)
end

srv.start

S3のイベント設定内容に応じてsqs_sender.rbを修正することになります。

動作確認手順

S3モックは http://127.0.0.1:9001 にアクセスするとブラウザ上でアップロードされたファイルなどの存在確認・ダウンロードができます(9001番ポートでアクセスして下さい。9000番ポートにアクセスしてもSQSに連携されません)。

1
2
Access Key: AKIAIOSFODNN7EXAMPLE
Secret Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

以下、AWS-CLIを使用して動作確認をします。

各モックの動作確認準備

S3モックにbucket-nameという名前のバケットを作成します。

1
AWS_ACCESS_KEY_ID='AKIAIOSFODNN7EXAMPLE' AWS_SECRET_ACCESS_KEY='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' AWS_DEFAULT_REGION='' aws s3 mb s3://bucket-name --endpoint-url 'http://127.0.0.1:9001'

バケットが作成されたかを確認します。下記コマンドでバケットが無いというエラーが出なければOKです。

1
AWS_ACCESS_KEY_ID='AKIAIOSFODNN7EXAMPLE' AWS_SECRET_ACCESS_KEY='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' AWS_DEFAULT_REGION='' aws s3 ls s3://bucket-name --endpoint-url 'http://127.0.0.1:9001'

SQSモックにqueue1という名前のキューを作成します。

1
AWS_ACCESS_KEY_ID='' AWS_SECRET_ACCESS_KEY='' AWS_DEFAULT_REGION='' aws sqs create-queue --queue-name queue1 --endpoint-url 'http://127.0.0.1:9324'

キューが作成されているかを確認します。

1
AWS_ACCESS_KEY_ID='' AWS_SECRET_ACCESS_KEY='' AWS_DEFAULT_REGION='' aws sqs list-queues --endpoint-url 'http://127.0.0.1:9324'

動作確認

S3プロキシを介してファイルをアップロードしてみます。

1
2
echo 'hogehoge' > hoge.txt
AWS_ACCESS_KEY_ID='AKIAIOSFODNN7EXAMPLE' AWS_SECRET_ACCESS_KEY='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' AWS_DEFAULT_REGION='' aws s3 cp hoge.txt s3://bucket-name/hoge.txt --endpoint-url 'http://127.0.0.1:9001'

ファイルのアップロードを確認します。

1
AWS_ACCESS_KEY_ID='AKIAIOSFODNN7EXAMPLE' AWS_SECRET_ACCESS_KEY='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY' AWS_DEFAULT_REGION='' aws s3 ls s3://bucket-name --endpoint-url 'http://127.0.0.1:9001'

queue1にメッセージが配信されていることを確認します。

1
AWS_ACCESS_KEY_ID='' AWS_SECRET_ACCESS_KEY='' AWS_DEFAULT_REGION='' aws sqs receive-message --endpoint-url 'http://127.0.0.1:9324' --queue-url 'http://127.0.0.1:9324/queue/queue1'

おわりに

以上のようにS3プロキシを介することで、S3モックを操作するとSQSモックにメッセージを送信することができました。

S3の操作に応じたメッセージ送信の設定に応じて、sqs_sender.rbの中身を書き換えればOKです。