ECSサービスでのコスト最適化とキャパシティープロバイダー戦略

に公開

ECSでサービスからタスクを起動するとき、基本的には

タスクスペック x 個数 x 時間

がベースの課金ラインとなる。

この場合たとえばタスクが2つ起動していると

タスクスペック x 2 x 時間

となるわけだ。これでスケールして3になると

タスクスペック x 3 x 時間

となる。この辺のことは前回行ったClaude Codeで計算させる事も可能である(結果は精査する必要があるだろうが)

https://zenn.dev/catatsumuri/articles/410375d6cb816e


通常運用 (2タスク) | $20.7あたりに注目

通常はこのように倍々になるが...

Fargateサービスでタスクを組んでいる場合、通常はタスクの消滅上等で組み上げることになるだろう。その場合全てのタスクが絶対息してないといけない、という事もない状態となるはずだ。ここで登場するのがFargate Spotタスクである。

Fargate Spotタスクとは

以下にFargate Spotタスクの説明を記す

  • AWS Fargate の割引版。オンデマンド Fargate より 最大70%安い
  • AWS 内部の「余剰キャパシティ」を使う仕組み。
  • 余剰がなくなると タスクが中断(強制停止) される可能性がある。

https://aws.amazon.com/jp/fargate/pricing/#Fargate_Spot_Pricing_for_Amazon_ECS


強制停止させられている例

これは実は(実は?)EC2でもスポットインスタンスというのがあって使った事がある人はご存知かもだが、それのFargate版である。30〜70% くらいの割引で使えるため使い方によっては非常にコスパよく並列環境を手に入れられる事が期待できる。

冒頭で書いた通り、Fargateは1つ死んでもあんま問題が起き辛い構成を通常取るので、常時オンデマンド(スポットではない)のタスクを上げておくよりスポットを予備的に上げておくとコスパが良くなると理解できるだろう。この場合たとえば2タスクの場合

2つをオンデマンド(スポットの対義語)にすると

タスクスペック x 2 x 時間

であるが1つをオンデマンド、1つをスポットとするなら

タスクスペック x 1 x 時間
タスクスペック x 1 x0.7(最大) x 時間

となり2に対して1.3くらいのコストで動作させられる「かもしれない」期待が生じてくる。これはタスクをどんどん増やしたときに、たとえば2オンデマの料金

タスクスペック x 1 x 時間
タスクスペック x 1 x0.7(最大) x 時間
タスクスペック x 1 x0.7(最大) x 時間
タスクスペック x 1 x0.7(最大) x 時間

とすると 1 + 0.3x3 = 1.9となり、同じ値段で4タスク動作させられる「かもしれない」ということになるとなけば検討する余地しかないだろう。

なお、空きというのはリージョンとAZに依る。たとえばap-northeast-1aが忙しくなったら1aはしばらく空きがないだろうが、1c1dは空いているということもあるので、そっちで起動できるようにしておくという戦略も取れる。従って、可能な限りAZを確保したVPCを作成しておいた方がうまく行きやすいだろう。

実際の設定方法を見てみる

ここまでの記事はCDKに特化していたが、今回に関してはAWSマネージメントコンソールを主体に使っていく事にする(最後にCDKでの設定も書く)。ただしECSサービスを起動する必要があるからタスク定義は適当なものを用意しておくこと。あとクラスターを1つ用意しておかないとそもそも使えないですね。

クラスターからサービスを作成する

キャパシティープロバイダー戦略は基本的にサービスに設定するものであるので、サービスの作成UIでこれをセットしていくことになる。まずはサービスを作成を押してみよう。


サービスを手動作成する画面

適当に設定したりして、少しスクロールすると


コンピューティング設定 (- アドバンスト)

このようなのが見える

ここでコンピューティングオプションとして「キャパシティープロバイダー戦略」と「起動タイプ」があり起動タイプが既定の選択となっている(かつてはそうでもなかった時代があったと記憶、、まあ左にあるものを優先したいのが通常のUIなんだろうし)

UIの確認

横文字すぎて日本語として成立していない気もする、が、まあそれはともかくUIを確認してみよう


キャパシティープロバイダー戦略を選択した画面

ここは 「カスタムを使用」を選択する。すると


プロバイダーの設定

「ベース」や「ウエイト」とかいうまた意味のわからんそうな単語が出てくるが、これは後で徹底解説するので、まずは「キャパシティープロバイダー」の選択肢を確認する


ドロップダウンにてFARGATE_SPOTが見える

ここでFARGATE_SPOTが選択できる。これをセットすることで先述のFargate Spotが発動できるようになる。

ここではまず、オンデマンドとスポットを1:1で分割するようにしてみよう。


ウエイトを1:1で分割して必要カウントを2にしている

これでサービスを作成する。ここではspot-testという名前でサービスを作成する事とした。


ここではspot-testという名前にした

サービスおよびタスクの起動確認

この時点でサービスの必要タスク数2に応じ、2タスク起動してくる


2タスク起動

この時点で必要なタスク数: desiredCountは充足しているが、オンデマンドなのかスポットなのかはこのUIでは分からない。これはUI列を追加する事で表示が可能である。


キャパシティープロバイダ列を追加

このように表示列を増やせば

以上のようにどのようになっているかwebからも理解しやすくなる。

あるいはawscli

複雑なキャパシティープロバイダー戦略

そもそもベースとウエイトとは何だろうか、も含めて以下に様々な構成で起動してみた例を書く。

今の構成

キャパシティープロバイダー ベース ウェイト
FARGATE 0 1
FARGATE_SPOT 0 1

これでオンデマとスポット 1:1 に分割されているはずだ

では

キャパシティープロバイダー ベース ウェイト
FARGATE 1 1
FARGATE_SPOT 0 1

これでdesiredを3にすると?


オンデマンド2のスポット1

これは「まずベース1」に基いてオンデマンドが配置され、残りの2をウエイト1:1で分割するという指示である。


では別の例

キャパシティープロバイダー ベース ウェイト
FARGATE 2 1
FARGATE_SPOT 0 1

これでdesiredを4にすると?


オンデマ3のスポット1

これは4タスクをまずベースの2に充足し残りを1:1で分割する

逆にすると、もちろん逆になる

キャパシティープロバイダー ベース ウェイト
FARGATE 0 1
FARGATE_SPOT 2 1

とかならオンデマ1のスポット3になるだろう


desiredCountがベース未満のときは?

たとえばdesiredCount:2

キャパシティープロバイダー ベース ウェイト
FARGATE 0 1
FARGATE_SPOT 3 0

だと要求2に対しベースが3となって要求より多い数値になっているがこれは通る


スポット2が充足されている

ではdesiredCount:3


スポット3が充足されている

ではdesiredCount:4


スポット3が充足された後weightに基いてオンデマが1

となる。もう大体わかったと思うけど、ベースは「最低保証タスク数」でありdesiredCountがベース未満でも問題ないのであるが、desiredCountが上昇してもまずベースから満たそうとするベースが充足されたあとでweightに応じて振り分けられる。

具体的な戦略

以下に、いろいろと戦略を考えてみよう。この時の分配においては、待機するタスクの数と、増えるタスクを考える必要があるかもしれない。そもそもスケールを考えないのであればdesiredCountをうまいこと割り切れるようにウエイトでセットすれば終わり

コスト最適化型(安さ優先)

キャパシティープロバイダー ベース ウェイト
FARGATE 1 0
FARGATE_SPOT 0 1
  • 最低 1 タスクはオンデマンド(高信頼)。
  • 残りはすべてスポット。
  • 2タスク以上のスケールは全てスポットに回る

「サービスが全部死ぬのはまずいから 1 台は確保。でも基本は安く回したい」ケース。

安定重視型(信頼性優先)

キャパシティープロバイダー ベース ウェイト
FARGATE 2 1
FARGATE_SPOT 0 1
  • 最低 2 タスクは必ずオンデマンド。
  • それ以上スケールするときは FARGATE と SPOT に半々。
  • 効果は最低3タスク以上からスケールアウトするとき

「最低限の冗長性はオンデマンドで確保して、増分はコスト抑制」ケース。

均等分散型(ハイブリッド)

キャパシティープロバイダー ベース ウェイト
FARGATE 0 1
FARGATE_SPOT 0 1
  • 「特に片寄せ理由がなく、コストと信頼性をバランスしたい」ケース。
  • ただしスポットの空きがないと急速なスケールアウトには追い付かない可能性もある

オンデマンドがメイン、本番可用性を担保しつつSPOTを1つだけ常時稼働させる

キャパシティープロバイダー ベース ウェイト
FARGATE 0 1
FARGATE_SPOT 1 0
  • スケールは全部オンデマンド
  • desiredCountを2以上にしないとspotだけで動作するのでプロダクションでは相当やばいというかどう考えてもアウト。2以上で等分にしつつ述びるのはオンデマ

戦略設計の考え方

  • Base → 生き残りライン(最低限の可用性をどこに置くか)
  • Weight → コストと信頼性のバランス(どちらに寄せたいか)
  • たとえば2、3タスク撒いても全部スポットとかは流石にやばい
  • 1タスクは最低オンデマにして置いておく場合でも2タスク目をスポットで何となく置いておくと保険的な可用性が確保できる、かも
    • 少なくとも2タスクオンデマにするなら1オンデマの2スポットとかにすると同じような値段で反応の良さが得られる「かも」

以上は待機状態の考え方だが、スケールアウトするときは速さが欲しいとなるとスポットの空きがなくて詰まるとかは困るとかあるかもしれないし、結構ドカンと上げてドカンと下げる方式の場合はもうスポットが入ってても何でもいいからガツガツ上げたいとかいうのもあるだろう(その場合もオンデマンドを混ぜた方がいいとは思うけど)。こういうのは環境によって結構異なるのであとは経験しかない。

まあとはいえ、こういうのは技術の差こそ出るもののプロジェクトによっては札束で殴る(要するに全部オンデマでぶん回す)のが正解の事もあるので、一律にどうのこうの言うのが難しい領域である。ここにはベストプラクティスなんてのは存在しないのだ。コストカットして褒められるかどうかってのは、どうなんでしょうかねえ、、インフラ技術者の影の努力なんて評価されるかどうかは見る人が見ないとわかんないっすからねえ。

CDKでやる場合

https://github.com/catatsumuri/cdktest

まずはここにECSのサービス作ってtaskを2つ起動するようなサンプルがあるので、これを使うとして

キャパシティープロバイダー ベース ウェイト
FARGATE 0 1
FARGATE_SPOT 0 1

これをセットしてみよう。せっかくなのでclaude code使ってみますか


やらせている

とかやったらコスト算出「機能」を付けてきたので

現在のECSの料金を再計算してください

とプロンプトしたら


コスト再計算の結果

となった。コードの差分は

https://github.com/catatsumuri/cdktest/pull/12


コード差分

デプロイを引くと


1:1分割

ただしくスポットとオンデマンドが分かれてくれます

Discussion