【レポート】マイクロサービスをサーバーレスでデザインしよう #reinvent #SRV310
はじめに
本記事は AWS re:Invent 2017 のセッション 「SRV310-R - [REPEAT] Designing Microservices with Serverless」のレポートです。
スピーカー
- David Nasi - Senior Product Manager-Tech, AWS
- Nik Khilnani - Sr. Director of Platform, Digital Products Technology, National Geographic Partners
概要
When designing microservices there are a number of things to think about. Just for starters, the bounds of their functionality, how they communicate with their dependencies, and how they provide an interface for their own consumers. Serverless technologies such as AWS Lambda change paradigms around code structure, usage of libraries, and how you deploy and manage your applications. In this session, we show you how by combining microservices and serverless technologies, you can achieve the ultimate flexibility and agility that microservices aim for, while providing business value in how serverless greatly reduces operational overhead and cost.
In addition, National Geographic will share how it built its NG1 platform using a serverless, microservices architecture. The NG1 platform provides National Geographic consumers with content personalized to their preferences and behaviors in an intuitive, easy-to-use way on smartphones.
レポート
マイクロサービスとは
まずはじめに、マイクロサービスについて確認しておきましょう。マイクロサービスは、軽量で独立したサービスの集合によって構築されるべきだとしています。それぞれのサービスは自身がデータを持っており、スケーラブルで、耐障害性を備えている必要があります。
なぜマイクロサービスか?
ビジネス上のゴールを、設計の軸へ変換したときに、その設計を最も満たしやすいのがマイクロサービスだからです。
- ビジネス上のゴール
- 市場投入へまでの時間を短縮する
- チームが、自分たちがよく知っている境界づけられたコンテキストでオーナーシップを持つ
- 設計の軸
- 弾力性:障害を分離し、全体の可用性を向上させる
- 拡張性:水平に、独立してスケールできる
- 継続的デプロイ:頻繁に変更するようにすれば、失敗によるコストが安く済む
では、サーバーレスがマイクロサービスへどのように関与するか?
- サーバーレスのアプローチは、マイクロサービスの設計原則とベストプラクティスに沿っています
- アプローチの中には、独創的なものもあります
- それ以外は、伝統的なアプローチよりもはるかに簡単です
- サーバーレスは銀の弾丸ではありません
- 設計原則について、サーバーレスの視点から見ていきましょう
高可用性
- AWS Lambda は、高可用性のために複製・冗長となるような設計です
- さらなる耐障害性のために、リージョンごとにマイクロサービスを配置することもできます
マルチリージョンサーバーレスアプリケーションの例
レイテンシベースのヘルスチェック
ヘルスチェック用のエンドポイントが ヘルス状でない場合、 Amazon Route 53 は レイテンシレコードを次の最適なものに選び直します。これは、マルチリージョンでのアプリケーションに有用です。
スロットリング
下流のあるAPIをつぶさないように、スロットルを設定してください。
API Gateway での 利用計画
以下のことをコントロールするために利用計画を作成します:
- スロットリング: 全体的なリクエスト割合(1秒あたりの平均)とバースト容量
- 割り当て: 1日、1週間、1ヶ月あたりに受付可能なリクエスト数
- API/ステージ: アクセス可能なAPIおよびAPIステージ
スロットリングによるエラーを AWS X-Ray でデバッグする
DynamoDB や Rekognition, Lambda をサービスマップとして持つ X-Ray を例示します。
タイムアウトとリトライを設定する
- Lambda でタイムアウトを設定することができます
- レスポンスが必要なタイプは同期実行としてください
- リトライをクライアントに要求する場合は、HTTPコード429を返すようにしてください
- レスポンスが不要な場合、非同期実行としてください
- メッセージが実行されるまでキューイングされたままです
- エクスポネンシャルバックオフによる自動リトライ
- 失敗したイベントのための Dead Letter Queue
- ファンクションごとに DLQ を有効にして、SQSかSNSと繋いでください
- DLQ にエラーを送信できない場合、DLQのエラーメトリクスが更新されます
参考: * AWS でのエラーの再試行とエクスポネンシャルバックオフ - アマゾン ウェブ サービス
より高度なオーケストレーションに Step Functoons を使う
拡張性:Lambda Function のスケールについて
拡張性を考慮するにあたっては、サーバーレスの要となる Lambda Function について詳しく見ておきましょう。
ウォームスタートと、コールドスタートを考慮する必要があります。
コールドスタートはAWSによって最適化されますが、ウォームスタートの場合は最適化がアプリケーションレイヤに依存します。
コールドスタートの場合にとれる策は:
- デプロイパッケージのサイズを最小にします
- 例えば本当に使っているライブラリだけ切り出して使うなど
- 依存関係の複雑さを減らします
一方、ウォームスタートの場合にとれる策は:
- グローバルスコープで変数を遅延評価します
- あらゆるところで、必要なければ読み込まないようにします
継続的デプロイ
それぞれについて、何が良いプロセスでしょうか?
- 解釈しやすいこと
- 変更が頻繁で小さいこと
- 変更による影響の範囲
- 自動デプロイ
「解釈しやすいこと」については、AWS Serverless Application Model を使うのが良いです。テンプレートを使ってサーバーレスアプリケーションの状態を管理できます。
「変更が頻繁で小さいこと」については、 SAM Local を使うことでサーバーレスアプリケーションをローカルでテストできます。これで、修正した分だけテストを行い、SAMを使ってデプロイすることもできます。
「変更による影響に範囲」については、Lambda Alias に対して重み付けを行い、トラフィックを2バージョンに分配させる という手段を使ってください。これは Lambda の新機能であり、後日発表します。CLIがどのようなものかを記載しておきます。
「自動デプロイ」については、CodePipeline を使うのが良いです。例えば以下のようなフローを構築します:
- コードリポジトリにコードをプッシュします
- AWS CodeBuild を使ってテストおよびパッケージングを行います
- SAM テンプレートを使ってスタックを更新するよう、CloudFormation のアクションを使います
- 環境変数をLambdaに渡します
- 環境ごとにテストを行います
設計の軸に対するサーバーレスの機能を一覧にします
- 弾力性
- もともと備わった弾力性と可用性がある
- オーケストレーションが利用できる
- 拡張性
- 自動でスケールする
- 作業ごとに支払いが発生する
- 継続的デプロイ
- デプロイのモデルと理解がしやすい
- ビジネスロジックだけ考えれば良い
- ローカルでテストできる
- 自動化されたプロセスによって安全性を確保できる
まとめ
マイクロサービスアーキテクチャを構築する上で必要となる要件を整理しつつ、それに対してサーバーレスがどのように応えるのかを、AWSのサービス群を実装例として示していました。
個人的には、 Lambda Function のウォームスタートとコールドスタートそれぞれに対する対策が具体的でためになりました。ウォームスタートの場合は資源が再利用されることを考慮して、なるべくメモリに保持している情報を使いまわすようにしたいのと、必要になるまでは変数など初期化しないということを守れば良さそうですね。