📖

re:Invent 2024: AWS FargateでSaaSマルチテナンシーを簡素化

2024/01/01に公開

はじめに

海外の様々な講演を日本語記事に書き起こすことで、隠れた良質な情報をもっと身近なものに。そんなコンセプトで進める本企画で今回取り上げるプレゼンテーションはこちら!

📖 AWS re:Invent 2024 - Simplifying multi-tenancy with SaaS applications on AWS Fargate (SVS329)

この動画では、AWS Fargateを使用したSaaS構築について、長年の経験を持つNathan Peckが解説しています。SaaSの成功に不可欠な4つの柱として、「必要な時にソフトウェアが利用できる」「データが安全に保護される」「必要な新機能が追加される」「ビジネスが長期的に継続する」を挙げ、それぞれについて具体的な実装方法を説明しています。特にAWS FargateとAmazon ECSを活用したContainer分離による可用性の確保、FireLensによる監査ログの実装、Spotインスタンスを活用したコスト最適化など、実践的な知見が豊富に語られています。また、SaaSの価格設定においてBasic層とPremium層のバランスを取ることの重要性や、メンテナンス負担を軽減するための具体的な方法論も提示されています。
https://www.youtube.com/watch?v=M7tNpEnnnhk
※ 動画から自動生成した記事になります。誤字脱字や誤った内容が記載される可能性がありますので、正確な情報は動画本編をご覧ください。
※ 画像をクリックすると、動画中の該当シーンに遷移します。

re:Invent 2024関連の書き起こし記事については、こちらのSpreadsheet に情報をまとめています。合わせてご確認ください!

本編

AWS Fargateを使用したSaaS構築:信頼関係の4つの柱

Thumbnail 0

みなさん、こんにちは。Nathan Peckと申します。本日は、AWS Fargateを使用したSaaSの構築についてお話しできることを嬉しく思います。私にはSaaSアプリケーションを構築してきた長い経験があります。ソフトウェア業界でのキャリアは、いくつかのSaaSスタートアップで働くことから始まりましたが、残念ながらそれらは全て失敗に終わりました。しかし、自分でSaaSを立ち上げようとした経験と、様々なSaaSスタートアップでの経験から、貴重な教訓を学ぶことができました。その後、しばらくソーシャルメディア業界で働いた後、明らかに非常に大規模なSaaSサービスを提供しているAmazonに入社しました。

Thumbnail 60

今日は、コンテナを使用したインフラストラクチャ上でのSaaSの構築、特にコンピューティング層としてAWS Fargateを使用する場合について、これまでの経験から得た知見を共有したいと思います。 この講演は、お客様との信頼関係の構築という観点で構成しています。なぜなら、Software as a Serviceの成功にとって、お客様との信頼関係が最も重要だと考えているからです。スタートアップであれ大企業であれ、SaaS分野で長年の経験があろうとこれから始めようとしていようと、信頼関係を築くための4つの重要な柱があります。第一に「必要な時にソフトウェアが利用できると信頼できること」、第二に「データが安全に保護されていると信頼できること」、第三に「必要な新機能が追加されると信頼できること」、第四に「ビジネスが長期的に継続すると信頼できること」です。

SaaSの可用性:ダウンタイムの影響を最小限に抑える戦略

Thumbnail 90

まず、「必要な時にソフトウェアが利用できると信頼できること」というアイデアについてお話ししましょう。これは非常に重要です。なぜなら、お金を払ってサービスを利用するお客様は、必要な時にいつでもそのサービスがオンラインで利用できることを期待するからです。これは、無料で利用できるソーシャルメディアとは全く異なります。ソーシャルメディアの場合、ダウンタイムが発生しても深刻な影響はありません。ユーザーは文句を言うかもしれませんが、無料サービスなのでダウンタイムがあっても結局は戻ってきます。しかしSaaSは全く違います。お客様はお金を払っているので、ダウンタイムが発生すると、サービスの価値に見合わないと判断して、しばしばサブスクリプションをキャンセルしてしまいます。

Thumbnail 130

可用性について考える時、Amazonを含む様々な規模の企業で働いた経験から得た一つの考え方は、可用性は二元的なものではなく、スペクトラムだということです。可用性の極端な状態として、全てがダウンしていて何も機能していない状態(ウェブサイトやアプリケーションを開こうとしても全ての機能が使えない状態)から、全てが完璧に機能している状態(ウェブサイトが素早く読み込まれ、全ての機能が動作する状態)まであります。しかし現実的には、特にAmazon Web Servicesのような多数のサービスやAPIを提供している大企業では、完全に右端の状態になることはありません。どこかで常に何かが壊れているものですが、ほとんどの場合、それに気付くことすらありません。

Thumbnail 180

ダウンタイムの影響を最小限に抑えることは非常に重要です。複数のテナントを持つサービスには、考慮すべき多くの要因があります。無料プランのユーザーか、少額の支払いユーザーか、高額の支払いユーザーかを考慮する必要があります。また、彼らが実行しているワークロードのレベル(非常に小規模か大規模か)も重要です。ダウンタイムの影響を最小限に抑えようとする際には、テナント間で共有されているリソースの問題、特定の顧客と同じ場所に配置された他のテナントに可用性の問題を引き起こす可能性のあるノイジーネイバーの問題、予測不可能なアクティビティパターンや難しいスケーリングポリシーなども考慮しなければなりません。

Thumbnail 240

インフラストラクチャレベルでこれらの問題すべてに対処する鍵は、分離(Isolation)にあります。分離によって、システムの一部が利用できない状態になっても、他の部分は稼働し続けることができます。目指すべきは、一部の顧客がダウンタイムを経験している間でも、他の顧客の信頼を高められる状態です。つまり、一部の顧客が影響を受けていても、顧客ベース全体での信頼度は上昇し続けることができるのです。これは、モノリシックアプリケーションとサービス分割型アプリケーションの重要な違いの一つです。現在、Microservicesからモノリシックアプリケーションへ回帰する傾向が見られます。これは理解できることで、Microservicesが行き過ぎた時期があったためです。多くの人々が必要以上にMicroservicesを実装し、複雑さゆえに失敗しがちな複雑なアーキテクチャを作り出していました。しかし、すべての顧客が一度に信頼を失うような完全なモノリスを構築することと、分散サービスを構築することの間には、バランスポイントがあると考えています。

Thumbnail 320

新しいコードをどのように分離するかを考えることは重要です。 今朝読んだレポートによると、コード開発の速度に最も影響を与えるのは、コードを書く行数ではありません。Generative AIや経験豊富な開発者がいても、最大の課題は変更失敗率です。つまり、加えた変更によって新しいバグや問題、ダウンタイム、そしてシステム内の様々なサービスの障害がどの程度発生するかということです。

Thumbnail 410

デプロイメント時の新しいコードの分離は非常に重要で、Amazon Elastic Container Serviceのようなコンテナオーケストレーションシステムでは、これがプラットフォームの中核的な概念として組み込まれています。コンテナオーケストレーションを始める多くの人は、すべてのコンテナを最新のタグでコンテナレジストリにプッシュし、サービスを再起動して最新バージョンを実行するという基本的なアプローチから始めます。しかし、これはコンテナオーケストレーションの本来の使い方ではありません。コンテナイメージはバージョン管理が可能で、Amazon ECS内でデプロイメントをグループ化する中核的な概念であるタスク定義もバージョン管理されたリソースです。

Thumbnail 450

タスク定義の複数のバージョンを持つことができ、Amazon ECS update service APIコールを使用して、新しいバージョンのコンテナを含む新しいバージョンのタスク定義をサービスにロールアウトするようAmazon ECSに指示することができます。Amazon ECSは、このアップデートをダウンタイムなしでロールアウトできるように設計されています。例えば、Elastic Load Balancerの背後にアプリケーションタスクが3つあり、最初はすべてバージョン1だとします。新機能やパフォーマンスの変更、バグ修正のためにバージョン1からバージョン2に移行する場合、 Amazon ECSは古いタスクと並行して、新しいバージョンのタスクの別コピーを起動することでトラフィックの中断を防ぎます。その後、古いタスクからトラフィックを徐々に排出し、新しいタスクに移行していきます。

Thumbnail 480

このプロセスはロードバランサーとの連携により自動的に行われ、インテリジェントに動作します。古いタスクから新しいタスクへのトラフィック移行中に、 タスクの1つがヘルスチェックに失敗したり、クラッシュしたり、Amazon CloudWatchのメトリクスの1つ(応答時間や4xx検証エラーの発生率など)が急上昇したりした場合、それを検知し、タスク定義の以前のバージョンに自動的にロールバックする機能があります。これが新しいコードを分離する一つの方法で、トラフィックを徐々にシフトさせながらロールアウトし、ロールアウト中に問題が検出された場合は、即座に前のバージョンにロールバックできます。新しいタスクが起動し、健全な状態を維持し、少なくとも1〜2分間安定していることが確認されるまで、システムは以前のバージョンのタスクをシャットダウンしません。

SaaSのセキュリティ:テナントデータの分離と保護

Thumbnail 540

大規模なSaaSでは、異なるインフラ要件を持つ様々なユーザー層が必ず存在することになります。Basic層のテナント、おそらく無料層のユーザーは、すべて共有プールのインフラを使用します。これは、1つのアプリケーションインスタンスが数百、あるいは数千の接続テナントにサービスを提供し、すべてのテナントがそのアプリケーションのリソースを共有している状態を意味します。Webページであれ、APIリクエストであれ、すべてのリクエストは同じ共有コンピューティングリソースプールから処理されます。一方、スペクトルの反対側には、Premium層のクライアントがいます。これらは非常に大規模なテナントで、セキュリティ上の理由か、BasicやAdvanced層のユーザーよりもはるかに高額な料金を支払っているため、専用のインフラを必要とします。彼らには完全にプロビジョニングされた専用スタックが提供されます。

中間に位置するAdvanced層のユーザーは、最上位の料金は支払っていないものの、無料層でもない中間層の有料顧客です。このような場合、SaaSシステム全体のコンポーネントの中には、共有テナンシーのものと、特定のテナント専用のものが混在することがあり、これは主にデータストレージに関連します。これらのプールの1つを分離することも可能です。

Thumbnail 640

これは、Amazonが広範に使用しているインフラストラクチャです。私がAWSで過去8年近く携わってきたすべてのサービスがCellular Architectureを採用しており、これは特定のCellが故障した際のダウンタイムの影響を最小限に抑えられるため、非常に重要です。ご覧のように、本番環境のデプロイメントは1つではなく、実際にはProduction-1、2、3の3つあります。私が関わってきた大規模サービスの多くでは、20、40、場合によっては数百のCellがあり、その across に顧客が分散されています。この考え方は、1つのCellが故障しても、そのCellに属する顧客の割合にのみ影響が及ぶというものです。

3つのCellの場合、影響率は33%となります。しかし、これは一見高く見えるものの、33%のユーザーが障害を経験している間でも、66%、つまり過半数の顧客は引き続きサービスとの信頼関係を築ける良好な体験を得られることを意味します。舞台裏の魔法をちょっとのぞいてみると、AWSから障害通知が出て、ソーシャルメディアで「AWSがダウンしている」という投稿を目にすることがあります。Amazon APIやAWS APIにアクセスしようとして失敗したという人がいる一方で、「私は何も問題なかった」という人もいます。これは通常、Cellular Architectureが理由です。おそらく1つのCellで何らかの障害が発生しましたが、他のCellには影響が及んでいないのです。

Thumbnail 730

ただし、Cell内では依然としてNoisy Neighbor問題が存在します。これは、テナントごとに異なる使用パターンがあるため、課題となっています。あるテナントは非常に高いリクエスト量を持つ一方で、別のテナントは低いリクエスト量かもしれません。また、何らかの理由で100倍のコンピューティングリソースを必要とするテナントもいるかもしれません - 例えば、他のテナントが1メガバイトのファイルを送信している中、このテナントは時々100ギガバイトのファイルをドロップすることがあります。さらに、通常は普通のリクエスト量でサービスを利用しているテナントが、突然100万倍にスパイクすることもあります。

Thumbnail 780

では、このような問題にどう対処すればよいのでしょうか?SaaSシステムにおいて、突然暴走するテナントや急激なリソース消費を引き起こすテナントが、他のテナントに影響を与えることをどのように防げばよいのでしょうか?その鍵となるのがThrottlingです。しかも単一層のThrottlingではなく、3層のThrottlingが必要となります:アクティビティタイプによるThrottling、コンピュート消費量によるThrottling、そして基盤となるリソースレベルでのThrottlingです。

Thumbnail 800

私が好んで実装する第一のThrottlingは、フロントエンドでのServerlessなThrottlingです。これは多くのAmazonサービスで実装されているのを目にすることができます。クォータが設定されており、利用可能なクォータを確認できる専用のクォータコンソールまで用意されています。すべてのAWSサービスには、呼び出し可能なレートと、サービスの呼び出しを継続するためのトークンがToken Bucketに補充されるリフレッシュレートが設定されています。例えば、Amazon API Gatewayでは、Custom Authorizerを設定してテナントごとの識別を行い、そのテナントのIDに基づいて、APIリクエストに応じた特定のレートでトークンを消費していきます。右上に示されているように、このAPIには1秒あたり50リクエスト、バースト値50、1日あたり1,000リクエストというクォータが設定されています。これは、無料の基本プランユーザーがシステムを乱用したり、APIの背後にある重いリソースを大量に呼び出したりすることを制限する一つの方法と言えます。

Thumbnail 870

また、個々のサービスが実際に消費できるリソース量もThrottlingする必要があります。私がSaaSの開発を始めた頃は、サーバー上で単純にPHPプロセスを動かすのが一般的でした。異なるテナントからのリクエストがあると、バックグラウンドでPHPプロセスが起動し、これらのプロセスはすべて同じ共有アーキテクチャ上で実行されていました。これは問題でした。なぜなら、CPUリソースやメモリリソースの分配方法がオペレーティングシステムに委ねられていたからです。そのため、あるテナントが突然使用量を急増させ、大量のCPUとメモリを消費し始めると、同じマシン上の他のテナントが必要とするリソースが不足してしまう事態が発生していました。

私は初期の頃、このPHPサービスの問題と何度も戦ってきました。Containerはこの問題の解決に役立ちます。Containerの素晴らしい点の一つは、特定のテナントに許可したいCPUとメモリの量に基づいて、Container単位でCPUとメモリの制限を設定できることです。この講演で取り上げているAWS Fargateでは特に細かな制御が可能で、CPUの4分の1と1ギガバイトのメモリという単位まで設定できます。これにより、1つのCPUで4つのテナントにサービスを提供することができます。各テナントに独自のプロセスを与えたい場合や、プロセス内でテナントをグループ化したい場合でも、CPUを4分の1ずつ分割して分離することで、あるCPUの4分の1で処理されているテナントが他のテナントからCPUを奪うことができないようにすることができます。

Thumbnail 970

AWS Fargateの背後で動作しているこの技術は、Micro-VMです。AWS Fargateは、各Containerタスクを、指定したサイズの独立したMicro-VM内で起動します。これにより、急激なリソース使用を引き起こすお客様の影響を極めて限定的に抑えることができます。例えば、ここではCellular Architectureが採用されており、Production-0とProduction-1という2つのCellがありますが、そのCell内にも異なるContainerが存在します。Production-0では、そのCell内のすべての顧客が明らかにサービスを高負荷で使用しており、95%、95%、95%、99%という使用率に達しています。これは問題となる可能性があります。Production Cell 0で遅延が発生する可能性がありますが、他のCellには影響を与えることはできません。

Thumbnail 1060

右側のProduction-1では、そのセル内の異なるタスクが互いに分離されているのが分かります。あるテナントが1つのコンテナ内のサービスを使用してCPU使用率を95%まで急上昇させたとしても、他のコンテナには影響が及びません。他のコンテナは25%と65%で動作しており、これは高品質なサービスを提供するのに十分な余裕があります。これは、コンテナやAWS Fargate、Amazon ECSクラスターを使用してアーキテクチャをセグメント化し、分離する方法についてのイメージを掴んでいただけたのではないでしょうか。そして、Rolling Deploymentの活用もお忘れなく。

SaaSの機能開発:メンテナンス負担を軽減し、イノベーションを加速

Thumbnail 1080

次は、信頼性の重要な柱である「データの安全性確保」について見ていきましょう。これは非常に重要です。なぜなら、私がサービスの料金を支払っているのに、データが漏洩してしまうと、SaaSの所有者として訴訟のリスクにさらされる可能性があるからです。これは深刻な問題になり得ます。 ここでも、Microservicesはセキュリティ分離の重要な柱となります。Microservicesの利点は、システムの異なる部分が特定のタイプのデータにのみアクセスでき、システム全体のデータにはアクセスできないように構築できることです。これにより、脆弱性の影響を最小限に抑えることができます。例えば、あなたのサービスの1つがハッキングされ、誰かがすべてのセキュリティ層を突破して、最悪のケース、つまりコンテナ内でリモートコード実行を可能にしてしまったとしましょう。もちろん、これは深刻な状況ですが、ハッカーがすでにシステムに侵入してどんなコマンドでも実行できる状況で、その影響を制限するために何ができるでしょうか?

Thumbnail 1130

Thumbnail 1170

まず第一に、Microservicesを実行する際には、悪意のあるインターネットトラフィックから保護するためのファイアウォールが必要です。これはAmazon ECSとAWS Fargateが標準で対応している機能で、Amazon Virtual Private Cloudをコアコンセプトとしてサポートしています。クラスター内で実行されている各タスク、各コンテナは、VPC内に独自のネットワークインターフェースを持っています。つまり、独自のIPアドレスを持ち、独自のセキュリティグループを持つことができるのです。これにより、実行しているすべてのものの間で、非常に細かいアクセス制御を構築することができます。 ここでは例として、Webユーザーがパブリックに公開されているApplication Load Balancerと通信できるようになっています。このロードバランサーは、WebサイトやアプリケーションからのインターネットトラフィックのIngress(入り口)として機能します。そのロードバランサーは、タスクの1つのElastic Network Interfaceと通信することが許可されています。これはおそらく、Webサイトのタスクで、Webページのサーバーサイドレンダリングを行い、ブラウザに返すものでしょう。

このWebサイトタスクのネットワークインターフェースは、別のタスクとも通信できますが、ロードバランサーからその別のタスクへのトラフィックは拒否され、その別のタスクはWebサイトと通信することはできません。この別のタスクは、Webサイトがサーバーサイドレンダリングの一部としてデータを取得する必要のあるAPIかもしれません。この内部APIがパブリックインターネットやロードバランサーからアクセスされる理由はなく、また、この特定のバックエンド内部サービスがWebサイトと通信する理由もありません。このように、外部および内部トラフィックから保護する細かいアクセス制御とIngressルールを構築し、どのサービスが他のサービスと通信できるかを方向性を持って制御することができます。

Thumbnail 1260

AWSを長く使用している方なら、IAMルールについてご存知でしょう。これは、実行中のものやAWSアカウントとやり取りするものに、AWS APIを呼び出す権限を与える方法です。これは非常に細かく設定することができます。私の経歴の初期、Amazon EC2インスタンスで実行する場合、EC2インスタンスにロールを付与する方法しかありませんでした。問題は、EC2インスタンス上で10〜15の異なるサービスを実行している場合、そのインスタンスに付与されるロールが、そのインスタンス上で実行されているすべてのものの上位集合になってしまうことでした。例えば、WebタスクとAPIタスクがあり、WebタスクがS3バケットと通信する必要があり、APIタスクがDynamoDBテーブルと通信する必要がある場合、そのEC2インスタンスにバケットとDynamoDBテーブルの両方と通信する権限を与えなければなりませんでした。

これは問題があります。なぜなら、誰かがEC2インスタンスに侵入してリモートコード実行を行った場合、そのEC2インスタンスがアクセスできるすべてのリソースにアクセスできてしまうからです。コンテナとAmazon ECSのルールを使用することで、これを制限することができます。コンテナでは、1つのコンテナとして実行されるWebタスクには、Amazon S3バケットと通信できる独自のIAMロールがあり、APIタスクにはDynamoDBと通信できる独自のIAMロールがありますが、お互いのロールは見えません。これにより、さらなる保護層が追加されます。誰かが1つのタスクでリモートコード実行を行ったとしても、ホストシステムに侵入して別のコンテナのIAMロールにアクセスするには、コンテナからの脱出を行う必要があります。

Thumbnail 1400

AWS Fargateの場合、これらのタスクを別々のVMで個別に実行していれば、マイクロVMは互いに非常に良く分離されているため、コンテナ脱出のリスクはありません。私のタスクの1つが完全に侵害されたとしても - ハッカーが侵入してリモートコード実行を達成し、そのタスク内で望むことを何でもできたとしても - AWS Fargateで実行されている他のタスクは完全に分離されたままです。それらのIAMロールは秘密に保たれ、分離されているため、アクセスを許可されているリソースも保護されています。

Thumbnail 1450

次に、テナントデータの分離について説明しましょう。先ほど、インフラストラクチャのプールについて話しましたが、データもプール化することができます。テナントデータを分離するには3つのレベルがあります。第1レベルでは、システム内のすべてのテナントのデータを含む注文テーブルがあります - SaaSで提供されるすべての人のための1つのテーブルです。第2レベルはセルラーアーキテクチャで、各セルごとにテーブルがあります。第3レベルはプレミアム層のクライアント向けで、各クライアントが専用のテーブルを持ち、他のクライアントのデータは含まれません。

これは非常に脆弱なコードの例です。フェッチされるユーザーIDが実際に特定のテナント組織のメンバーであることの検証を追加し忘れています。この場合、悪意のあるユーザーのEveは、orders/aliceというリクエストを入力してAliceの注文を取得しようとし、AliceがSaaSで提供される全く別の組織やテナントに属している可能性があるにもかかわらず、彼女の注文を見ることができてしまいます。これは非常に問題です - もしこれが明るみに出れば、別の組織の誰かが彼らのデータにアクセスできたということで、Aliceとその組織全体の信頼を失うことになります。これは大きな問題です。

Thumbnail 1510

では、これをどのように修正するのでしょうか?このダイアグラムは非常に複雑で多くの動く部分がありますが、高レベルで説明します。なぜなら、これはAmazonが実際にあなたのデータを保護し、マルチテナント分離を行う方法だからです。リクエストが入ってくると、そのリクエストにはIDが付属しています。Amazon Cognito User Poolは、これらのIDを保存して検証する1つの方法です。検証の一部として、そのIDを認可し、Amazon API Gatewayに渡します。Amazon API Gatewayは、このユーザーのIDを知っているため、その知識を使用して、この特定のアプリケーションに与えられる通常のロールを取得し、リクエストが処理される間、権限が制限された別のIAMロールにダウンスコープします。

Thumbnail 1580

先ほど、APIのデプロイメントやAPIコンテナが、DynamoDBテーブルへのアクセスを許可する特定のIAMロールを持つことができるという話をしました。実はこれをもっと細かく制御することができます。こちらがAmazon ECSタスクロールの例です。これは非常にハイレベルなIAMルールで、Ordersテーブル全体に対してDynamoDBのクエリアクションを許可しています。つまり、Ordersテーブルから好きなものを取得できるということです。一方、テナントスコープのロールはより制限的で、フェッチされるすべての値のキーが、特定のテナント組織の名前である「acme」という文字列で始まることを確認する条件が付いています。

Thumbnail 1610

コードで見るとこのようになります。コードは非常によく似ていますが、実はこのコードにもまだ脆弱性があります。ユーザーIDが特定のテナント組織に属しているかどうかの検証を一切行っていないのです。しかし、最初の段階でロールをダウンスコープするToken Vending Machineを実装したおかげで、このコードは実際には安全です。データベースからデータを検索する際、テナントIDとオーダー検索を指定する方法でしか検索できず、そのテナントIDはIAMロールによって検証されているからです。

このコードには脆弱性がありますが、Eveがデータベースからデータを取得しようとした場合でも、同じ脆弱性が存在します。EveはAliceのデータを取得するための適切なクエリを生成することはできますが、それがDynamoDBレイヤーまで到達しても、DynamoDBはAliceがAcme組織に属していないことを検知してリクエストを拒否します。また、AliceのOrg情報は変更できず、Token Vending Machineの一部として、Eveのデータのフェッチをブロックします。このアプローチは非常に有用です。なぜなら、プログラマーは誰でもミスを犯すものですし、大規模なSaaSシステムには数多くのクエリやエンドポイントが存在するからです。バリデーションの追加を忘れた場合でも、別のレイヤーで保護する必要があります。

Thumbnail 1710

では、攻撃者が突破して、他のユーザーのデータを検索することに成功した場合はどうなるでしょうか。AWS FargateとAmazon ECSによるコンテナオーケストレーションは、そのような場合でも助けになります。Amazon ECS FireLensを使用すれば、システム内で発生するすべての事象を監査することができます。ここでは、EveがAliceの注文データを取得しようとした悪意のあるリクエストを見ることができます。先ほど実装したバリデーションにより、401ステータスコードが返されているのがわかります。この監査により、関係する識別情報を確認することができます。特定のリソースに対して401が生成されたこと、そしてそれを生成したのがEveであることがわかります。そして、Eveは他人のシステムにハッキングして情報を盗もうとする悪意のあるユーザーだとわかったので、このユーザーをBANすることにしましょう。

Amazon ECS FireLensの設定を検討することをお勧めします。私がFireLensを気に入っている理由の1つは、ログにメタデータを付加してそのデータを検索できることです。テナントIDなどのカスタムデータや、どのクラスター、どのセルがリクエストを処理したのかといったECSのメタデータ、さらにはタスク定義のバージョンや実際にリクエストを処理したタスクなども確認できます。攻撃者がシステムを攻撃した場合、何をしたのか、何を試みたのかを判断するために必要なすべてのデータを手に入れることができます。

やるべきことについて説明しましょう。

SaaSの長期的存続:インフラコストの最適化と収益性の確保

3番目の柱に移りましょう:「必要な新機能が追加されることを信頼できる」です。これはどのSaaSにとっても非常に重要です。なぜなら、お金を払っているお客様は開発のスピードを求めているからです。通常、特定のサービスを提供しているベンダーは1社だけではなく複数存在し、お客様は新機能の提供状況に基づいてサービスを比較する傾向があります。もし、あるサービスが停滞していて新機能が追加されていないのを見ると、より活発に開発が行われ、より多くの機能が構築されている競合サービスではなく、なぜこのサービスを使い続けるべきなのかと疑問に思い始めます。

Thumbnail 1860

では、機能を構築する能力に関する信頼をどのように維持できるでしょうか?ここでは、その背後にある考え方について高いレベルで説明しましょう。その考え方とは、オーバーヘッドが少なければ少ないほど、機能構築に使える時間が多くなるということです。ここに2人の開発者がいます。開発者は日々の仕事の一部として、学習、メンテナンス、機能の実装といった活動を行う必要があります。多くのソフトウェア開発組織が直面する残念な現実の1つは、機能を実装しようとすると、円グラフの一部が犠牲になってしまうことです。多くの場合、学習の部分が犠牲になります。組織が大規模な締め切り対応に追われると、ソフトウェア開発者は学習を継続できなくなります。スキルを最新に保つことができず、組織全体が古いテクノロジーを使い続け、最新のテクノロジーを知る人がいなくなり、学習意欲の高い優秀な開発者たちは競合他社に流出してしまいます。

Thumbnail 1960

そのため、円グラフの学習の部分は維持する必要があります。機能の実装は他のSaaSとの競争力を維持するために重要です。では、円グラフのどの部分を犠牲にできるでしょうか?それはメンテナンスです。しかし、間違った方法でメンテナンスを犠牲にすると、システムの不安定性、古いバージョンの残存、脆弱性のあるバージョンの使用、そして今にも崩壊しそうなシステムという結果になってしまいます。私たちは、より多くのものを構築してもメンテナンスの負担が増加しないような方法を考え始める必要があります。一般的には、階段状の上昇が見られます。ピンク色の階段状の線は機能開発を示しており、システムが複雑になり、より多くの機能が追加され、構築されるものが増えると、メンテナンスが必要なシステムの規模も大きくなります。残念ながら、これは通常、メンテナンス負担が指数関数的に増加することを意味します。

Thumbnail 2010

私たちはこの指数関数的な増加から脱却したいと考えています。初期のセットアップ負担があり、システムを構築した後、より多くの機能を構築しても、メンテナンス負担が対数的に収束してフラットになるような状態を目指しています。これにより、メンテナンス負担の継続的な増加なしに開発を続けることができます。AWS Fargateはそのための優れたツールです。AWS Fargateは、従来のコンテナオーケストレーションシステムで必要とされていた多くのメンテナンス負担を軽減してくれます。オペレーティングシステムへのパッチ適用、Amazon ECSエージェントの更新など、これは典型的なものです - すべてのオペレーティングシステムにはパッチが必要で、すべてのソフトウェアには更新が必要です。また、バックグラウンドで実行する必要のあるAmazon EC2インスタンスの数や、管理が必要なEC2インスタンスの世代もあります。通常、re:Inventのたびに、AWSはより高速で安価な新しいインスタンスを発表するので、最新のアーキテクチャインフラストラクチャに追従し続ける必要があります。

最後に重要なのが、コンテナのパッチ適用についてです。実際のコードの中で、依存しているパッケージやランタイムに脆弱性が発見され、パッチを当てる必要が出てくることがあります。理想的なのは、すべてを管理する必要のあるEC2モデルから、Fargateへと移行することです。Fargateでは、自分のコードとコンテナの中身だけを管理すればよく、脆弱性やパッチ適用は自分のコードに対してのみ行えばいいからです。これにより、開発者は保守の負担から解放され、その時間をアプリケーションの機能開発やコードの改善に費やすことができます。

Thumbnail 2090

また、初期セットアップの負担を軽減する方法も数多くあります。Amazon ECSのセットアップは、他のコンテナオーケストレーションシステムと同様に複雑で難しい場合があります。しかし、事前に用意されたパターンが用意されています。まだ使ったことがない方は、AWS Cloud Development Kitを強くお勧めします。

AWS Cloud Development Kitは、開発者が好みのプログラミング言語でインフラストラクチャをプロビジョニングできるツールです。開発者は一般的に、YAML、CloudFormation、Terraformなどの異なるシステムを学んでインフラストラクチャをデプロイするよりも、慣れ親しんだプログラミング言語を使い続けたいと考えています。このシステムでは、この場合JavaScriptで書かれたAPIを使ってデプロイすることができます。JavaScriptの開発者であれば、このTypeScript/JavaScriptの馴染みのあるパターンを使って、負荷分散されたFargateサービスをデプロイすることができます。

Thumbnail 2150

私が特に気に入っている事前構築されたパターンの一つが、AWS CDK containers ECS service extensionsです。これは、インフラストラクチャを構築するためにチェーンでつなげることができるカスタムプログラミング拡張機能を構築するためのモジュラーシステムを提供します。ここでは、基本的にはタスク定義であるサービス記述を使用し、そこにさまざまな拡張機能を追加していることがわかります。これらの拡張機能は、組織内のプログラマーが開発し、インフラストラクチャのツールとしてソフトウェア組織内で共有することができます。これにより、インフラストラクチャに携わるすべての開発者が、馴染みのあるインターフェースと社内のコード共有プラットフォームを通じて、インフラストラクチャのパターンを共有・連携できるようになります。デプロイのたびに一からインフラストラクチャを構築する必要がなくなるのです。

Thumbnail 2210

さて、最後の柱について話しましょう:「あなたのビジネスが長期的に存続すると信頼できること」です。これは恐らく最も難しい課題かもしれません。そしてここには多くの要因が関係してきます。これらすべてをクラウドプロバイダーとそのサービスだけで解決することはできません。マーケティングや開発者のコストなど、SaaSが資金を使い果たして失敗する原因は様々です。先ほど述べたように保守の負担を減らすことができれば、開発とメンテナンスのコストを下げることができます。ここでは、時間とともに膨らんでSaaSを破綻させる可能性のある、もう一つの要因であるインフラストラクチャの負担について話したいと思います。

AWS Fargateを活用したSaaSのコスト削減戦略

Thumbnail 2260

SaaSを構築する理想的なシナリオは、コストスケールとパフォーマンスがテナントの消費と常に同期している状態です。新規顧客を獲得し、時間とともにテナントが成長したり、あるいはチャーンアウトで減少したりする中でも、コストスケールとパフォーマンスは同期を保ち続けます。2本の線の間のギャップである利益マージンを少し確保しながら、テナント数が増えるにつれて、この2本の線を同期させ続け、そのパーセンテージマージンが時間とともに成長することで、より多くの収益を上げることができるはずです。

Thumbnail 2300

しかし、現実はもっと複雑です。一般的に、ユーザーには異なるティアが存在し、最も多くのユーザーが基本ティアに属しています。カタログサイズを見ると、下部のサーモンピンク色は各ティアの顧客数を示しています。基本ティアは誰もが無料で使用したいため、かなり大きくなります。プレミアム価格を支払うAdvancedティアのユーザーは常に少数です。しかし、基本ティアユーザーのインフラコストが、Advancedティアユーザーと比べて不釣り合いに高くなる可能性があり、テナント収入が非常に小さくなって、Advancedティアユーザーが基本ティアユーザー全体のコストを何とか賄っているような状況になることがあります。これにより、SaaSのバランスが完全に崩れ、Advancedティアユーザーを1人失うだけでSaaS全体の安定性が損なわれ、急速に損失を出す可能性があります。

Thumbnail 2370

サービスのマージンを考える際は、ソフトウェア開発やエンジニアリング組織全体のバーンレートだけでなく、システムに接続された各テナントのマージンを見て、そのテナントが使用しているリソースと、そのテナントに請求している金額との関係を考慮することが重要です。これにより、価格モデルやテナントに提供している機能の実装に不均衡がないかを判断することができます。

一部の機能が他の機能と比べて劇的にコストがかかることに驚くかもしれません。例えば、Generative AIは素晴らしい機能で誰もが気に入っているかもしれませんが、実際に請求している金額よりもはるかに多くのコストがかかることが判明するかもしれません。すると突然、そのGenerative AI機能を使用している基本ティアユーザー全員が、想定以上のコストを消費してしまうことになります。

インフラコストを見てみましょう。ここでは、Cellとその中のContainerがあり、それぞれがテナントがシステムを使用する際に実際に消費されるリソースを追跡しています。テナントの利用状況を最も低いレベル、つまりContainer レベルで確認し、そのContainerに対するリクエストとリソース消費の関係を見る必要があります。

Thumbnail 2470

Thumbnail 2510

それでは、セットアップの方法についてご説明します。AWS Fargateは、Amazon ECSを通じて継続的にテレメトリーデータを生成します。このテレメトリーは、先ほどお話したAmazon FireLensで送信されるアプリケーションログだけではありません。Container InsightsとAmazon EventBridgeもあります。これにより、Amazon ECSがコンテナから収集した情報やテレメトリー情報から生成される実際の生のイベントを取得することができます。具体的にはこのようになります - これが出力されるイベントの例で、収集されているすべての統計情報が表示されています。サンプルタスクを実行すると、これは実際にはストレステスト用のベンチマークツールであるstress-ngですが、そのすべてのデータをCloudWatchに送信し、CloudWatch Log Insightsを使用してすべての数値を分析することで、特定のテナント、クラスター内の特定のサービス、あるいはアーキテクチャ全体の特定のセルで異常に高い消費が発生している箇所を見つけることができます。

Thumbnail 2560

このセッションはAWS Fargateに関するものですので、Amazon EC2の料金モデルとAWS Fargateの料金モデルの違いを理解することが重要です。EC2インスタンスを起動する場合、そのEC2インスタンス上でコンテナが実際に実行されているかどうかに関係なく、EC2インスタンスが持つキャパシティに対して料金を支払います。16コアと64ギガバイトのメモリを持つ大きなEC2インスタンスを起動した場合、実行している間ずっとその料金を支払い続けることになります。一方、Fargateでは、各CPUコアと各ギガバイトのメモリに対してEC2の料金と比べてやや高い料金を支払いますが、コンテナが停止すると料金の発生も停止します。時には3つのコンテナを実行し、時には2つ、時には1つというように、コンテナの上の空いているスペースがEC2と比較したときのコスト削減分となります。

Thumbnail 2630

AWS Fargateを使用してSaaSを構築する理想的な方法は、イベント駆動型および非同期の作業を活用することです。コンテナを実行している限り、作業がなくても一定のコストがかかります。しかし、AWS Fargateではコンテナが停止した瞬間に料金の発生が停止します。実行している作業をバッチ処理にまとめて、作業を実行して停止するという方法が可能かどうか検討してみましょう。AWS Fargateには起動時の遅延がありますが、これは時間とともに短縮されており、今後も短縮され続けるでしょう。例えばLambdaを見ると、起動時の遅延が年々劇的に減少していることがわかります。AWS Fargateの場合、一般的に約30秒の起動遅延がありますが、起動後はCPUコアの全リソースを使用して膨大な作業を処理することができます。すべての作業を処理し終えたら、シャットダウンして料金の発生を停止できます。SaaSの特定の部分を非同期で実行し、キューに入れておいて、インフラを起動してキューに溜まったタスクを処理し、その後シャットダウンするという方法が可能かどうか検討してみましょう。

インフラをシャットダウンして、再度バックログが蓄積されるのを待つというこのアプローチは、SaaSのコストを最適化する優れた方法となり得ます。これは一般的に、ジョブの完了時間に厳密なSLAがあるSaaSでない限り、顧客にそれほど大きな影響を与えません。十分に大規模なSaaSでは、インフラの一部を遅延させることができ、10分後にリンクをメールで送信する、あるいはジョブの完了まで5〜10分かかる可能性があると顧客に伝えれば、一般的に受け入れられます。これは通常、バックグラウンドで作業をバッチ処理し、インフラを効率的に使用できるだけの作業量が蓄積されるまで待機しているためです。

Thumbnail 2760

特にコスト削減の方法についてお話ししたいと思います。これらは、インフラの使用方法に関係なく、一律の削減が可能です。これらの一律削減は、消費するキャパシティのクラスに基づいて適用されます。X86 Intelベースのプロセッサは長年使用されてきた標準ですが、GravitonのARMベースプロセッサの使用を検討することをお勧めします。なぜなら、より高速であるだけでなく、実際に安価でもあるからです。価格性能比が実際の価格よりも低い理由は、GravitonコアがIntelプロセッサと比較してより多くの作業を処理できるためです。これは、Gravitonではハイパースレッディングによってコアの全能力を得られるのに対し、Intelでは、vCPUとして購入しているものは実際にはハイパースレッドであり、ほとんどの場合コアの半分に相当するためです。

Spotは、AWSデータセンター内に大量の予備キャパシティが存在する場合のプロビジョニングモデルです。これは、キャパシティのバーストに備えて確保されているものですが、空いたままにしておくのではなく、Spotキャパシティとして提供することで誰かに販売したいと考えています。このキャパシティは、必要な時にはいつでも取り戻される可能性があります。他のお客様が大規模なオンデマンドタスクを起動した場合、Spotは回収されてオンデマンドに対応するために使用されます。これにより、より低価格で多くのタスクを起動することが可能になります。例えば、AWS Fargateの標準料金では、1つのオンデマンドタスクを起動する価格で、3つの追加のSpotタスクを起動することができます。

Thumbnail 2890

Amazon ECSでは、次のような仕組みで動作します。 Capacity Providerという概念が組み込まれており、1つのCapacity Providerだけでなく、複数のCapacity Providerを分散戦略として使用してサービスをプロビジョニングすることができます。私の場合、AWS Fargateのオンデマンドで2つのタスクを起動し、AWS Fargate Spotで4つのタスクを起動するという戦略を設定しました。1から6タスクまでスケールアップする際、2つのタスクがオンデマンドに、4つのタスクがSpotに分散されました。

このような構成を検討する理由は、SaaSテナントの基本的なニーズに常に対応できるようにしたいからです。私の場合、SaaSテナントの基本的なニーズを満たすには、常に最低2つのタスクが実行されている必要があると判断しました。しかし、より低いレイテンシーでサービスの品質を向上させたいと考えています。顧客のリクエスト数が急増した場合や、アプリケーションコードにバグがあってコンテナがクラッシュして再起動が必要になった場合に備えて、バックグラウンドで余分なキャパシティを実行しておきたいのです。この余分なキャパシティにSpotを使用することで、4つのSpotタスクはオンデマンドタスクほどのコストがかからないため、3タスク強の価格で6タスクを実行することができます。Spotの回収は頻繁には発生しませんが、回収が発生した場合でも、SaaSのお客様の基本的なトラフィックには対応できます。Webページの読み込みやAPIリクエストの応答が若干遅くなる可能性はありますが、ダウンタイムは発生しません。

まとめ:AWS FargateとAmazon ECSを用いたSaaS運用の4つの柱

このアプローチは、大幅に削減された価格で多くの余分なキャパシティを確保する方法として、非常に重要なモデルです。では、Amazon ECSとAWS Fargateを使用してSaaSを運用するための各柱とそのヒントをまとめてみましょう。

Thumbnail 3030

まず可用性に関して、重要なポイントは、AWS Fargateがコンテナ同士を分離することで、あるコンテナでのスパイクや問題が他に影響を与えることを防いでいるということです。また、Amazon ECSを使用することで、異なるコードのデプロイメントを簡単に分離することができます。さらに、Amazon ECSではゼロダウンタイムのロールアウトや、サーキットブレーカーによる迅速で正確なロールバックを実装することができます。これらすべてが連携して、変更を加えても可用性が低下しない、高度に分離された安全なシステムを構築することができます。

Thumbnail 3090

セキュリティに関して、AWS Fargateはすべてのタスクに独自のネットワークインターフェースを提供し、タスク間のネットワークアクセスの詳細なグラフを構築することができます。また、コンテナごとにユニークな認証情報をローテーションさせることができ、IAM認証情報の範囲を個々のテナントに限定することで、テナントが誤って許可されていないデータにアクセスすることを防ぐことができます。

残りの2つの柱は、アジリティと価格設定です。アジリティについては、Fargateが運用上のオーバーヘッドの多くを処理することで、作業を簡素化します。また、サーバーレスでバージョンレスのオーケストレーターなので、アップグレードやパッチ適用が不要です。これを、定期的に新しいメジャーバージョンがリリースされるKubernetesと比較してみてください。ここで新しく登場したものについて触れておきたいのですが、それはECS Automatic Modeです。ぜひチェックしてみてください。これは興味深いサービスで、私も気に入っています。Amazon ECSとAWS Fargateが長年持っていた同様のアジリティを、ECSでも実現できるようになると考えています。

Thumbnail 3160

最後は価格モデルです。これは少なくともインフラ側で、私たちのサービスが経営破綻しないようにする方法です。AWS Fargateを使用することでEC2キャパシティの無駄を避けることができ、インフラコストを削減するための様々なコスト削減戦略を提供します。今日お話しした内容は以上です。4つの柱とそれに関するヒントをご紹介しました。この内容が皆さんのお役に立てば幸いです。ご質問がありましたら、お気軽に声をかけてください。


※ こちらの記事は Amazon Bedrock を利用することで全て自動で作成しています。
※ 生成AI記事によるインターネット汚染の懸念を踏まえ、本記事ではセッション動画を情報量をほぼ変化させずに文字と画像に変換することで、できるだけオリジナルコンテンツそのものの価値を維持しつつ、多言語でのAccessibilityやGooglabilityを高められればと考えています。

Discussion