こんにちは、フロントエンドエンジニアの尾崎です。 6月のボーナスでロボット掃除機(Xiaomi)とコードレス掃除機(makita)を購入し、在宅勤務がますます快適になってきました。
さて、今回の記事では、テックタッチで AWS Step Functions をどのように活用しているかをお伝えしていきます。
記事の前半では、Step Functions の概要を説明しながらテックタッチでの利用方法について、 記事の後半では、Serverless Framework との組み合わせ方や本番運用してみてわかった Tips など、より実践的な内容に触れていきます。
AWS Step Functionsとは何か
ビジネスロジックから独立したワークフローを定義して、実行状態を管理しやすくするしくみです。 「複数の Lambda 関数を連続して呼び出していく機構を手軽に作れるもの」というイメージがわかりやすいです。
複数の Lamba 関数を連続して実行していく機構でありがちな、途中の Lambda 関数でエラーが発生した場合にリトライするには?処理がどこまで進行したかをすぐに確認する方法は?といった問題を解消してくれます。
処理順序の管理
関数(タスク)どうしの依存関係を Step Functions に任せることができ、開発者はビジネスロジックの実装に集中できるようになるのが大きなメリットです。
「ワークフローの管理」と「個別の関数」を切り離して考えることができるので、関数の実行基盤をほか実装に切り替える(例:Lambda → ECS)、処理と処理の間にキューイングを挟む、といったこともやりやすくなります。
AWSサービスとの連携
Lambda だけではなく、 SNS、Dymano DB への読み書き、ECS、SQS、CodeBuild など、AWSの各種サービスとのintegrationが豊富です。
Step Functions の定義から直接サービスを呼び出すことができます(AWS SDK や API を呼び出す必要はなく、JSON に直接書けるのがポイントです)
// SQSにメッセージを作成するステップの例 "Send message to SQS": { "Type": "Task", "Resource": "arn:aws:states:::sqs:sendMessage.waitForTaskToken", "Parameters": { "QueueUrl": "https://sqs.us-east-2.amazonaws.com/123456789012/myQueue", "MessageBody": { "Message": "Hello from Step Functions!", "TaskToken.$": "$$.Task.Token" } }, "Next": "NEXT_STATE" }
ワークフロー、ステップについて
主なステップの種類は以下の通りです。
- タスク実行
- 各タスクにエラーをキャッチする条件やリトライロジックを設定できます。
- 条件分岐
- Input の内容によって、どのステップを実行するかを決定するためのステップです。
- 待機
- 次のステップに進むまでに指定された期間もしくは指定された期限まで処理を一時停止します。
- 並列実行
- 複数のステップを並列に実行し、結果を集約できます。
- これまではあらかじめ並列数を定義しておく必要がありましたが、2019年のアップデートで動的に並列数を指定できるようになりました。
ワークフローが State machine 図として可視化され、タスクの実行状況を一目で把握できます。
ユースケースの例
- AWS Glue や EMR,SageMaker などと連携して、データ処理フローを構築する
- CodeBuild や ECS と連携して、CI/CD などの IT オートメーション
- 動画を分割して並行エンコーディングするワークフロー
今回のブログ記事のために AWS の公式ドキュメントでユースケースを調べたのですが、コカ・コーラやロイターなどの企業でも Step Functions がビジネスロジックの中心で使われていることがわかり、興味深かったのでリンクを紹介しておきます。
※33分付近からロイター社の事例(英語動画)
テックタッチでの利用方法
ここからは、実際テックタッチでどのように AWS Step Functions を活用しているかお伝えしていきます。
技術的なチャレンジ
テックタッチでは Web でのインタフェースに加えて、異なるプラットフォーム(Chrome, Firefox など)に複数のブラウザ拡張機能を通じてプロダクトを提供しています。 また、エンタープライズ向け SaaS で見られるしくみですが、顧客が自らテックタッチのバージョンアップをすることが可能になっています。
ここで少しやっかいなのは、ブラウザ拡張機能のバージョンアップにはプラットフォームの審査を通過する必要があるので、プラットフォームやブラウザ拡張の種類によってバージョンアップにかかる時間が異なってきます。
更に、すべての拡張機能を一律でバージョンアップ処理をできず、拡張機能 A のアップデートが完了してから拡張機能 B のアップデートを開始する、という依存関係が存在します。 さらに、将来的に対応プラットフォームが増えることでより複雑なフローになっていくことも考慮する必要がありました。
上記の制約のもと、顧客ごとのバージョンアップを適切に管理していく必要がありました。
アップデートプロセスの具体例:
1. テックタッチの新バージョンリリース
2. 顧客αが新バージョンにバージョンアップ、拡張機能 A を審査提出
3. 顧客βが新バージョンバージョンアップ、拡張機能 A が審査提出
4. 顧客αの拡張機能 A が審査を通過、拡張機能 B を審査提出
これまでの課題
Step Functions を導入する以前は、「審査状況を問い合わせ、変更があれば DB に記録&後続の処理を行う」巨大なバッチを一定間隔で実行していましたが、以下のような問題を抱えていました。
- 通常、バージョンアップは顧客ごとには 1 ヵ月 1 回以下の頻度であるにもかかわらず、単一のバッチを高頻度で実行していました。
- 顧客の数が増えるにつれて、バッチ実行にかかる時間が増えていってしまいます。
Step Functionsを使う
上記の問題を解決するため、アップデートプロセスを管理するための新しいマイクロサービスを AWS Step Functions を使って実装することにしました。
- バージョンアッププロセスが開始、API Gateway 経由で Step Functions のワークフローが execute されます
- Step Functions が複数の Lambda 関数を逐次実行し、プラットフォームへの申請や審査状況のチェックを行う
- 万が一、審査がリジェクトされた場合などは SNS、AWS Chatbot を通じて Slack にメッセージを送信します
- Cloudwatch Event を経由して Step Functions の実行ステータスを Slack にメッセージを送信します
- ワークフローが開始、異常終了、正常終了したタイミングでメッセージを送信します
前述のように、Step Functions 内の処理に Lambda 関数だけではなく、SNS への Publish を簡単に組み込むことができました。
AWS Step Functions を使ったことで、以下のようなメリットがありました。
- 顧客ごとにバージョンアッププロセスが走るので、バッチ処理よりもコストを抑えられました。
- 顧客ごとのバージョンアップ状況を把握しやすくなりました。
- 万が一、外部 API が不通になったケースなど、エラーが発生した場合は AWS Step Functions にビルトインされているしくみでリトライができるので、運用コストが減りました。
Serverless Frameworkで快適AWS生活
上記のしくみを実現するためのフレームワークとして、テックタッチではServerless Frameworkを利用しています。
Serverless Framework の詳細な説明はここでは省きますが、複数の Lambda 関数や API Gateway, SNS, SQS などの AWS リソースを定義、デプロイ、管理するためのたいへん便利なフレームワークです。
Serverless Framework には、機能を拡張するための各種プラグインが提供されており、その中にStep Functions用のプラグインがあります。*1
Serverless Frameworkとプラグインを使ってよかったこと
- 環境変数や IAM Role の設定から Lambda 関数や API Gateway の定義まで、serverless.yml という設定ファイルにまとめられるので見通しがよくなります。
- Step Functions のステートマシン定義を JSON ではなくて YAML で書くことができます。コメント書ける!最高!*2
- Lambda 関数を Node.js + TypeScript で書く場合、Webpack の設定テンプレートを含んだ一式がすぐに利用可能です。(そのほかの言語でも各種templateが用意されています)
- Serverless な構成にする上で、「こういうことやりたい」という組み合わせがプラグインとして用意されていることが多いので、非常に助かります。
- 例:API Gateway のエンドポイントにカスタムドメインを利用できる serverless-domain-manager
Step Functionsを使っていく上で学んだTips
最後に実践編として、いくつか実際に Step Functions を使いながら学んだ Tips をお伝えします。
タスクのInput / Output
実際に Step Functions を使って開発し始めると、タスク間の入出力の定義の方法にいくつかバリエーションがあり、少しわかりづらい*3と感じることがありました。
そのときにたいへん参考になったのが Classmethod 様のこちらの記事です。
具体的な例と図で説明されており、非常にわかりやすいので、input / output で迷ったら一読されることをお勧めします。
個別のエラーをキャッチしてリトライロジックを分ける例
たとえば、外部 API に接続する Lambda 関数が、"429 Too many request"というレスポンスを受け取った場合、リトライの間隔を通常より長めに設定するケースを考えてみます。
Lambda 関数ではこのようにエラーを throw する axios の interceptor を定義しておきます。
// api-client.ts const axiosInstance = axios.create() axiosInstance.interceptors.response.use( response => { // 通常の場合は何もしない return response }, error => { if (error.response.status === 429) { const customError = new CustomError( 'TooManyRequestError', JSON.stringify(error) ) throw customError } else { return Promise.reject(error) } } )
Step Functions の定義で、以下のようなタスク定義をしておきます。
# serverless.yml Communicate with outer API Task: Type: Task Resource: arn:aws:lambda:ap-northeast-1:00000000:function:communicate-with-outer-api Next: Next-example-task # リトライのロジックを複数記述できる Retry: - ErrorEquals: - TooManyRequestError IntervalSeconds: 60 MaxAttempts: 30 BackoffRate: 1.3 - ErrorEquals: - States.ALL IntervalSeconds: 5 MaxAttempts: 3
TooManyRequestError
が発生した場合は、60 秒間隔で 30 回リトライ(ただし、2 回目は 60 秒 * 1.3、3回目は60秒 * 1.32..。のように待ち時間を Backoff していく)、
それ以外のエラーの場合は 5 秒間隔で 3 回リトライ、というような柔軟な設定が実現できます。
まとめ
この記事では、SQS の概要や一般的なユースケースから、テックタッチでの実際の利用方法、運用しながら学んだ Tips についてお伝えしてきました。
AWS の各種サービスを組み合わせて Serverless なワークフローを組み上げる際に非常に有用なサービスだと感じました。
開発時に使うツールとして Serverless Framework を取り上げましたが、 AWS 本家のサポートツール(AWS Toolkit, AWS Step Functions Local)も進化しているようですので、用途に合わせて使い分けていきましょう。
*1:main contributor は日本の堀家さんです。ありがとうございます! horike37 (Takahiro Horike) · GitHub
*2:とは言いながら、AWS からVS Code向けの開発支援ツールがリリースされているので、一長一短かもしれません。 Serverless Framework の Step Functions プラグインから JSON を export して、それを AWS Toolkit で検証する、という流れがよいのかも?(未検証)
*3:ちなみに、私は Parallel タスクの Output が配列になるということに気付くまでにしばらく時間がかかり、数時間溶かしたことがあります。参考:AWS公式