1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AWS CopilotでWorker Serviceを試す

Last updated at Posted at 2022-02-27

はじめに

AWS ECSを使ったアプリケーションを簡単にデプロイできるツールであるAWS Copilot CLIでSQSを使ってみます。
Copilotにはインターネットからアクセスされるウェブアプリケーションをデプロイできる Load Balanced Web Service や定期的に起動するバッジである Scheduled Job と並んで Worker Service というメッセージを受信して処理するタイプのサービスがあります。が、この Worker Service で具体的にどのようなアプリケーションがデプロイされるのかいま一つイメージがつかなかったため、実際にWorker Serviceをデプロイして、非同期サービス間通信を体験してみました。

参考

公式ドキュメント

前提

$ copilot version
version: v1.14.0, built for linux

CopilotにおけるApplication、Serviceとはなにか、などCopilotに関する基礎知識に関する説明は省略します。また、Application、Environmentは作成済みとしてそこへServiceをデプロイする部分の手順だけ記載します。

目標

次の図のような一連の流れを実現させようと思います。(ALBなどを省略した簡略図です)

worker_service

Load Balanced Web Service をECSにデプロイし、これをメッセージを送信するパブリッシャーとします。
具体的には、今回の試行ではAPIをデプロイし、インターネットからPOSTアクセスがあればそのアクセスに含まれるデータをSNSに送信します。

さらにもう一つ、 Worker Service をECSにデプロイし、メッセージを処理するサブスクライバーとします。
このサービスがSQSからメッセージを取得し、処理します。

Copilotのドキュメントによると Worker Service はpub/sub アーキテクチャを実装するものらしいです。
私はpub/sub アーキテクチャについての知見が無いため、これについて解説することはできないのですが、ともかくまずは最小の構成で試してみます。

パブリッシャーをデプロイ

まず、SNS経由でSQSにメッセージを送信する側であるパブリッシャーをデプロイします。
Load Balanced Web Service で試してみましたが、パブリッシャー側は Scheduled Job などどのタイプのサービスでも良さそうです。
今回デプロイしてみたアプリケーションはGitLabに置いてあります。send-message-apiで確認できます。
Spring Boot + JavaによるWebアプリケーションになっており、POSTリクエストがあるとそのBodyをSNSへ送信します。Controllerで全ての処理を実施している簡易的な実装です。

Service作成

copilot svc init -a demo-app -n send-message-api -t "Load Balanced Web Service"

demo-appというApplicationは事前にできているものとします。send-message-apiというServiceをLoad Balanced Web Serviceタイプでinitします。

次にcopilotがプロジェクト内に作成したmanifestに次のようなpublishセクションを追加します。

publish:
  topics:
    - name: order-events

参考のためにmanifest全体を折りたたみ中に記載しておきます。

manifest.yml
name: send-message-api
type: Load Balanced Web Service

http:
  path: 'send-message-api'
  healthcheck: '/send-message-api/actuator/health'

image:
  build: Dockerfile
  port: 8080

cpu: 256
memory: 512
count: 1
exec: true

publish:
  topics:
    - name: sample-sns

デプロイ

copilot svc deploy -a demo-app -e environment -n send-message-api

でデプロイします。developEnvironmentは作成済みとします。
demo-appApplicationのdevelopEnvironmentにsend-message-apiというServiceがデプロイされます。

publishセクションの内容に沿って、SNSが作成されます。
自分が試したときにはdemo-app-develop-send-message-api-sample-snsという名前のSNSになりました。
SNSの名称は
${Application}-${Environment}-${Service}-${manifestファイル中のpublish.topics.name}
のように命名され、一意になるということのようです。
なお、実際はSNSのARNは環境変数経由で取得できるため、SNSが具体的にどのような名前で作られたかは基本的に意識する必要はありません。

環境変数に格納されるSNSのARN

作成されたSNSのARNは環境変数としてコンテナに設定されます。
具体的には今回の動作確認ではCOPILOT_SNS_TOPIC_ARNSという環境変数に{"sample-sns":"arn:aws:sns:ap-northeast-1:xxxxxxxx:demo-app-develop-send-message-api-sample-sns"}のようなJSON文字列が設定されました。
単純にARNが設定されるのではなく、JSON文字列であることに注意が必要です。パースしてARNを取り出さなければなりません。

サブスクライバーをデプロイ

次にSQSからメッセージを取り出して処理するサブスクライバーをデプロイします。
今回デプロイしてみたアプリケーションはパブリッシャー同様GitLabに置いてあります。receive-message-batchで確認できます。
ほぼメインメソッドしかない超簡易的な実装です。

Service作成

copilot svc init -a demo-app -n receive-message-batch -t "Worker Service"

receive-message-batchというServiceをWorker Serviceタイプでinitします。

次にcopilotがプロジェクト内に作成したmanifestのsubscribeセクションを次のように記載します。

subscribe:
  topics:
    - name: sample-sns
      service: send-message-api

Worker Serviceのmanifestについてのドキュメント

上記のような書き方をすることでsend-message-apiServiceのsample-snsトピックをサブスクライブします。つまり、先程デプロイしたServiceが送信するメッセージを受け取れるようになります。

参考のためにmanifest全体を折りたたみ中に記載しておきます。

manifest.yml
name: receive-message-batch
type: Worker Service

image:
  build: Dockerfile

cpu: 256
memory: 512
count: 1
exec: true

subscribe:
  topics:
    - name: sample-sns
      service: send-message-api

command: java -jar receive-message-batch-all.jar

デプロイ

copilot svc deploy -a demo-app -e environment -n receive-message-batch

でデプロイします。demo-appApplicationのdevelopEnvironmentにreceive-message-batchというServiceがデプロイされます。

demo-app-develop-receive-message-batch-EventsQueue-yyyyyyのような名称のSQSが追加されます。さらに、SNSのへのサブスクリプション、必要なポリシーなども自動で作成されます。

SNSと似たように、SQSの名称は
${Application}-${Environment}-${Service}-EventsQueue-${ランダム文字列}
のように命名され、一意になるということのようです。
パブリッシャーにおけるSNS同様にSQSのURIは環境変数経由で取得できるため、具体的にどのような名前のSQSが作成されたかなどは基本的に意識する必要はありません。

環境変数に格納されるSNSのARN

作成されたSQSのARNは環境変数としてコンテナに設定されます。
具体的には今回の動作確認ではCOPILOT_QUEUE_URIという環境変数にhttps://sqs.ap-northeast-1.amazonaws.com/xxxxxx/demo-app-develop-receive-message-batch-EventsQueue-yyyyyyのような文字列が設定されました。今回はJSONではありません。

Worker Serviceの挙動

勝手な思い込みでしかないのですが、当初私は Worker Service はSQSにメッセージが送信されるたびに起動されてメッセージを処理し、それ以外のタイミングでは起動しないものだと思っていました。
例えばlambdaだとそのような起動の仕方が可能だったはずです。
しかし、実際の Worker Service はそのようなものではなく、基本的にコンテナは常駐し、SQSをポーリングしメッセージがあるか確認する処理などは自前で実装する必要があります。
どのような実装が良いのかはよくわかりませんでしたが、一旦while文で無限ループさせ、SQSをポーリングするようにしました。(この部分です

動作確認

ここまでの手順でパブリッシャーとサブスクライバーがデプロイされ、SNS・SQSを介して連携するようになりました。

今回私はサンプルとしてsend-message-api(パブリッシャー)、receive-message-batch(サブスクライバー)をデプロイしています。

今回の場合、次のように動作確認しました。

パブリッシャーへPOSTリクエスト

パブリッシャーは Load Balanced Web Service としてSNSへメッセージを送信するWebアプリケーションをデプロイしているので、

curl -X POST -H "Content-Type: application/json" -d '{"message":"java"}'  http://${デプロイしたLoad Balanced Web Serviceに紐づくALBのDNS名}/send-message-api/send

のようにPOSTリクエストします。

SNSからSQSへとメッセージの伝達

先述のPOSTリクエストでパブリッシャーはリクエストボディ取得してSNSへ送信します。(この部分です
Worker Serviceデプロイ時に作成されたSQSはSNSをサブスクリプションしているので、SQSへメッセージが渡ります。

Worker ServiceはSQSをポーリングしているため、メッセージがSQSに入ると、それを取得し、処理します。今回のreceive-message-batchは処理といってもログ出力(この部分です)するだけですが、メッセージがあると、そのメッセージをログ出力します。

これで2つのサービスがSNS・SQSを介して非同期的にコミュニケーションできていることが確認できました。

その他追加で試したこと

キューの細かい設定

Worker Service で最小の設定として

subscribe:
  topics:
    - name: sample-sns
      service: send-message-api

のような記載をmanifestに書きました。この場合SQSはデフォルト設定で作成されます。
しかし、Worker Serviceに関するドキュメントにはキューのカスタマイズ設定も紹介されています。
目一杯設定すると次のようになります。

subscribe:
  topics:
    - name: sample-sns
      service: send-message-api
  queue:
    # 可視性タイムアウト
    timeout: 45s
    # メッセージ保持期間
    retention: 71h
    # 配信遅延
    delay: 30s
    dead_letter:
      # デッドレターキュー 最大受信数
      tries: 5

コメントに書いている項目を設定できるようです。
「可視性タイムアウト」とはなにか、などは前述のCopilotのドキュメントおよびSQSのドキュメント(キューパラメータの設定(コンソール)など)が参考になります。

サブスクライバーが複数いる場合

1つのパブリッシャーに対して複数のサブスクライバーがいるパターンも試しました。
別の Worker Service をcopilot svc init -t "Worker Service"で作成し、manifestに全く同じように、

subscribe:
  topics:
    - name: sample-sns
      service: send-message-api

と記載してデプロイするだけです。
すると、このサービス用にもう一つSQSが作成され、SNSへのサブスクリプションが設定されます。
パブリッシャーがSNSへメッセージを送信すると両方のSQSにメッセージが送られ、ほぼ同時に2つの Worker Service が同じメッセージを受信して処理を実施します。

FIFOキューについて

現在SQSのFIFOキューには対応していなさそうです。
しかし、下記のIssueを見ると近いうちに対応されるのかもしれません。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?