見出し画像

serverless-offlineとそのpluginを利用し、SQS→Lambda→SESのメール送信をローカル環境で検証できるようにする

はじめに

こんにちは、SHIFTの開発部門に所属している Katayama です。

前回の記事「 SQSからLambdaでSESメール配信する構成をserverless-liftでやってみた 」では、Deploy 後の実際の AWS 環境上でしかメール送信の検証ができなかった。開発者体験の向上の意味でもローカルの開発環境で AWS 上と同じようにメール送信の検証・テストができるといいだろう。

そこで今回はserverless-offlineとその関連 plugin を導入する事で、ローカルの開発環境で SQS→Lambda→SES という構成のメール送信の検証・テストを行えるようにしていきたいと思う。

serverless の plugin として今回利用するものは以下の2つになる。

serverless-offline-sqs
 ローカル環境でSQSにメッセージが送信された場合に、Lambdaをトリガーしてくれるようにするplugin。2019年で開発は止まっているが、2022年11月28日時点で最新版の serverless-offline と組み合わせて利用する事が出来たので、現時点では利用には問題ないと思われる。

serverless-offline-ses-v2
 こちらは以前まで利用できたserverless-offline-sesのv2になる。serverlessの本家サイトでも紹介されている plugin で2022年11月28日時点で見る限り、メンテナンスも行われている模様。中身としてはaws-ses-v2-localを serverless で動かせるようにしてくれているものになり、詳細は後ほど紹介するがSESからメール送信したものをブラウザ上で確認できるようにしてくれる。

※本記事では「serverless の Lambda 開発環境として serverless-webpack でトランスパイル、ESLint、エイリアス利用を設定してみた」で取り上げた serverless-webpack の設定が完了している事を前提としている。

事前準備 LocalStackを docker-compose で立てる

まずは AWS のリソースをローカル環境でエミュレーションできるようにするために LocalStack を立てる。

LocalStack とは、以下のように公式に記述されている通り、AWS の各リソース(SQS など)をエミュレーションしてくれるもので、これを利用する事で AWS 環境に実際に接続することなく、ローカルの開発環境で各 AWS サービスを結合した検証・テストが可能になる。

With LocalStack, you can run your AWS applications or Lambdas entirely on your local machine without connecting to a remote cloud provider!(LocalStack を使えば、リモートクラウドプロバイダーに接続することなく、AWS アプリケーションや Lambdas を完全にローカルマシン上で実行することが可能です)

ただし、Coverage Levelsに書かれている通り、すべてのサービスを完全にエミュレーションしてくれるわけではなく、サービスによってはエミュレーションのレベルがそこまで高くないものや、Pro版でないと利用できないものがあったりする。

今回は SQS にメッセージを送信し、それをトリガーに Lambda が起動し、SES でメールを送信するという一連の流れをローカルで検証したいが、LocalStack を利用する事で、SQS をエミュレーションして起動できるようになる(SES については後述するserverless-offline-ses-v2を利用するため、今回は LocalStack でエミュレーションされたものは利用しない)。

docker-compose で LocalStack を立てる場合の docker-compose.yaml は公式の GitHubに置かれているものが参考になる。今回はシンプルな設定でいいので以下のような docker-compose.yaml になる。

# docker-compose.yaml
version: "3.8"

services:
  localstack:
    container_name: "${LOCALSTACK_DOCKER_NAME-localstack_main}"
    image: localstack/localstack
    ports:
      - 4566:4566
    environment:
      - DOCKER_HOST=unix:///var/run/docker.sock
    volumes:
      - "${LOCALSTACK_VOLUME_DIR:-./data/localstack}:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"

"${LOCALSTACK_DOCKER_NAME-localstack_main}"などの表記についてはInterpolationに書かれている通りで、環境変数が設定されていればその値に、環境変数がない場合には"-(ハイフン)"で区切られた右の値が設定される、という表記方法。

上記のように設定した後は、docker compose up でコンテナを立ち上げれば LocalStack が利用できるようになる。

study@localhost:~/workspace/learn-serverless (main *)
$ docker compose up
[+] Running 1/1
 ⠿ Container localstack_main  Recreated                                                                        0.2s
Attaching to localstack_main
localstack_main  | Waiting for all LocalStack services to be ready
localstack_main  | 2022-11-21 11:56:12,391 CRIT Supervisor is running as root.  Privileges were not dropped because no user is specified in the config file.  If you intend to run as root, you can set user=root in the config file to avoid this message.
localstack_main  | 2022-11-21 11:56:12,400 INFO supervisord started with pid 16
localstack_main  | 2022-11-21 11:56:13,403 INFO spawned: 'infra' with pid 21
localstack_main  | 2022-11-21 11:56:14,405 INFO success: infra entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
localstack_main  |
localstack_main  | LocalStack version: 1.2.1.dev
localstack_main  | LocalStack Docker container id: cbb87d6a4f38
localstack_main  | LocalStack build date: 2022-11-21
localstack_main  | LocalStack build git hash: bfb3c7e6
localstack_main  |
localstack_main  | 2022-11-21T11:56:15.247  WARN --- [-functhread3] hypercorn.error            : ASGI Framework Lifespan error, continuing without Lifespan support
localstack_main  | 2022-11-21T11:56:15.247  WARN --- [-functhread3] hypercorn.error            : ASGI Framework Lifespan error, continuing without Lifespan support
localstack_main  | 2022-11-21T11:56:15.250  INFO --- [-functhread3] hypercorn.error            : Running on https://0.0.0.0:4566 (CTRL + C to quit)
localstack_main  | 2022-11-21T11:56:15.250  INFO --- [-functhread3] hypercorn.error            : Running on https://0.0.0.0:4566 (CTRL + C to quit)
localstack_main  | Ready.

serverless-offline-sqs を導入し、SQS→Lambda の部分をローカルの開発環境でエミュレーション・検証できるようにする

続いて SQS にメッセージを送信し、Lambda が起動する、という部分をローカルの開発環境でエミュレーション・検証するための設定を行っていく。まず、serverless の resources.Resources に SQS を定義する(AWS Infrastructure Resourcesに書かれている通り、resources.Resources は CloudFormation そのままになるので、SQS の設定の場合はAWS::SQS::Queueが参考になる)。

resources:
  Resources:
    sqsToSesQqueue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: sqs2ses-queue-${sls:stage}
        MaximumMessageSize: 1024
        RedrivePolicy:
          deadLetterTargetArn: !GetAtt sqs2ses-dead-queue.Arn
          maxReceiveCount: 3
    sqsToSesDeadQueue:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: sqs2ses-dead-queue-${sls:stage}
        MaximumMessageSize: 1024
        MessageRetentionPeriod: 1209600 # 14 days

今回はシンプルな設定として上記のようにキューとデッドレターキューを定義した。1 点補足として、MessageRetentionPeriod は以下の通りである。

  • MessageRetentionPeriod
    SQS がメッセージを保持する秒数を指定できる項目で、今回は最大時間である 1,209,600 秒(14 日間)としている

次に、Lambda を serverless.yaml 上に定義する。Lambda の serverless.yaml で設定できる内容についてはAWS Lambda Functionsに書かれている。また、Lambda をトリガーするイベントの定義の仕方はAWS Lambda Eventsに書かれており、今回は SQS なのでSQS Queuesが参考になる。

functions:
  sqs2ses:
    name: ses2ses-${sls:stage}
    handler: src/sqs2ses.handler
    timeout: 5
    environment:
      STAGE: ${self:provider.stage}
      REGION: ${self:provider.region}
      SES_IDENTITY_ARN: !Sub arn:aws:ses:ap-northeast-1:${AWS::AccountId}:identity/${param:fromEmailAddress}
    events:
      - sqs:
          arn: !GetAtt sqsToSesQqueue.Arn

上記のように functions.{関数名}.events に Lambda をトリガーするイベントを列挙する事で、CloudFormation でいう所のAWS::Lambda::EventSourceMappingが作成される。

ここまでで、SQS、Lambda というそれぞれのリソースの定義と、Lambda のトリガーするイベントとして SQS を設定できた。最後に、SQS→Lambda のイベント駆動をローカルの開発環境で検証・テストできるようにserverless-offline-sqsを導入する。

serverless-offline-sqs をインストールした後は、plugins に serverless-offline-sqs を追記し、ローカルの開発環境で利用している SQS のエミュレーションサービスが立っているエンドポイントなどの設定を行う(今回は LocalStack のエンドポイント"http://localhost:4566"になる)。

plugins:
  - serverless-webpack
  - serverless-offline-sqs # <- ここを追記
  - serverless-offline

custom:
  # 省略
  serverless-offline-sqs:
    autoCreate: true
    apiVersion: "2012-11-05"
    endpoint: http://localhost:4566
    region: ap-northeast-1
    accessKeyId: root
    secretAccessKey: root

ここまで設定できたら、LocalStack を起動してから以下のように sls offline start コマンドを実行すると、SQS のキューが自動で作成され、また、AWS CLI で SQS にメッセージをを送信すると、Lambda が起動して console.log(event)が実行されている事が確認できる(動画ではエラーがロギングされているが、それは SES の設定の部分なので、SQS→Lambda の部分については問題なく動いている事が確認できている)。

ここまででローカルの開発環境で SQS→Lambda の検証・テストを行うようにする設定は完了になる。

次に、Lambda→SES の部分をローカルで同様に検証できるようにするための設定・開発を行っていく。

serverless-offline-ses-v2 を導入し、Lambda→SES の部分をエミュレーションし、ローカルの開発環境で検証できるようにする

今回はserverless-offline-ses-v2を利用して、SES により送信されたメールをブラウザ上で確認できるようにする事で、Lambda→SES の部分をローカルの開発環境で検証・テストできるようにする。
まず、serverless-offline-ses-v2 をインストールし、serverless.yaml の plugin と custom に以下のように追加で設定を行う。

plugins:
  - serverless-webpack
  - serverless-offline-sqs
  - serverless-offline
  - serverless-offline-ses-v2 # <- 追記

custom:
  # 省略
  serverless-offline-ses-v2:
    port: 8005

上記の設定を行った後、sls offline start コマンドを実行すると、以下のように http://localhost:8005 でサーバー(aws-ses-v2-localを参照)が立ち上がり、ブラウザ上で送信されたメールの確認を行う準備が整った事が確認できる。

study@localhost:~/workspace/learn-serverless (main *)
$ yarn dev
yarn run v1.22.19
$ sls offline start --param=fromEmailAddress=dumy@outlook.jp --param=toEmailAddress=dumy@gmail.com
serverless-offline-ses-v2: starting server...
serverless-offline-ses-v2: server running at http://localhost:8005
Starting Offline SQS at stage local (ap-northeast-1)

Starting Offline at stage local (ap-northeast-1)

Offline [http for lambda] listening on http://localhost:3002
Function names exposed for local invocation by aws-sdk:
           * hello: hello-lambda-local
           * sqs2ses: ses2ses-local

続いて実際にメール送信を serverless-offline-ses-v2 で立ち上げたサーバに行うように Lambda 関数の実装を修正していく。stage が local の時は SES の Client を作成する時のエンドポイントなどを変えたいので、以下のように実装すればいいだろう(省略されている部分以外はSQSからLambdaでSESメール配信する構成をserverless-liftでやってみた と同じ)。

// src/sqs2ses.js
// eslint-disable-next-line import/prefer-default-export
export const handler = async (event) => {
    // 省略

	const isLocal = stage === 'local';

	const sesClient = new SESv2Client(
		isLocal ? { endpoint: 'http://localhost:8005', region } : { region }
	);

	// 省略

ここまで実装できた所で、SQS→Lambda の部分の検証と同じように SQS にメッセージを送信するというのをやってみると、以下の動画のように SQS にメッセージを送信後、Lambda が起動し SES がメールを送信した後、ブラウザ上で送信されたメールを確認する、という一連の流れがローカルの開発環境で検証・テストできている事が確認できる。

まとめとして

今回は serverless-offline とその plugin を利用して、サーバレスアプリケーション(SQS→Lambda→SES のメール配信サービス)をローカルの開発環境で検証・テストするための設定を行ってみた。
実際に AWS 上に Deploy する事なくローカルの開発環境で検証できるのは開発者体験としてもよく、十分に検証してから AWS 上に Deploy する事で実際の AWS 上での検証で単純なエラーでうまくいかず修正して再度 Deploy という開発効率の低下も減らせると思う。

サーバレスアプリケーションの開発でも、ローカルの開発環境で検証できるようにして開発するというのを今後も意識してやっていきたいと思った。

《この公式ブロガーの記事一覧》


★★★\クリスマスまで毎日公開!明日はどんな記事?!/★★★


執筆者プロフィール:Katayama Yuta
認証認可(SHIFTアカウント)や課金決済のプラットフォーム開発に従事。リードエンジニア。
経歴としては、SaaS ERPパッケージベンダーにて開発を2年経験。 SHIFTでは、GUIテストの自動化やUnitテストの実装などテスト関係の案件に従事したり、DevOpsの一環でCICD導入支援をする案件にも従事。その後現在のプラットフォーム開発に参画。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/

みんなにも読んでほしいですか?

オススメした記事はフォロワーのタイムラインに表示されます!