メインコンテンツへスキップ
友田 陽大
ECS on Fargate 本番運用
AWS
ECS
Fargate
オートスケーリング
SQS
Application Auto Scaling
可観測性
コスト最適化

ECS on Fargate オートスケーリング完全ガイド:ターゲット追跡・ステップ・SQSバックログパターンを本番品質で設計する

ECS on Fargateのオートスケーリングを体系化。ターゲット追跡・ステップ・スケジュールの使い分けから、SQSバックログ・パー・タスクによるワーカースケーリングのカスタムメトリクス実装まで、Terraformと実コードで解説。

公開日
読了時間
18分
著者
友田 陽大
シェア

「desiredCountを手で変えるのは限界がある。でもオートスケールの設定を雑にすると、逆にフラッピングして不安定になる」——ECS on Fargateを本番に持ち込むとき、必ずこの局面が来ます。

私は決済基盤(本番二重課金0件)でSQS駆動の冪等ワーカーをFargate上で運用し、木材流通B2B SaaSではAPI Gateway → NLB → ALB → ECS on Fargateの上に221本のAPIエンドポイントを本番稼働させてきました。どちらも「速く増やし、慎重に減らす」という非対称スケーリングの考え方と、ワークロードに合ったメトリクス選択が安定の核心でした。

本稿はECS on Fargate 本番運用ガイドの続編として、オートスケーリング設計に特化します。HTTPサービスとSQSワーカーという2つの代表的なワークロードで、それぞれ最適な設計を実コード付きで体系化します。


なぜ手動 desiredCount では限界があるか

手動でdesired_countを調整する運用には3つの根本的な限界があります。

  1. 反応が遅い:人間がアラートに気づき、Terraformを適用するまでにスパイクは終わっているか、すでにSLAを割っているかのどちらかです。
  2. 縮小を忘れる:増やしたタスクを減らし損ねると、コストが静かに膨らみ続けます。
  3. SQS長が読めない:キューにメッセージが溜まっていても、CPUは動いていないため気づけません。

Application Auto Scalingは「計測値が閾値を超えたらdesiredCountを動かす」という仕組みをポリシーとして宣言し、ECSサービスコントローラに委譲します。あなたがやることは目標値と上下限を決めるだけです。


仕組み:Application Auto Scaling が ECS の desiredCount を動かす

ECSのオートスケーリングは、AWS Application Auto Scalingというサービスが担います。これは単独のサービスで、ECS以外にDynamoDB・Aurora・Lambda・SageMakerなども同じAPIで扱います。ECS固有の話は3つの要素に整理されます。

スケーラブルターゲット

まず「何をスケールするか」を登録します。これがスケーラブルターゲットです。

resource "aws_appautoscaling_target" "app" {
  service_namespace  = "ecs"                         # ECS専用の名前空間
  scalable_dimension = "ecs:service:DesiredCount"    # 操作するのはdesiredCount
  resource_id        = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.app.name}"
  min_capacity       = 2   # 最低タスク数(下限。0にするとアイドルゼロが可能)
  max_capacity       = 20  # 最大タスク数(上限)
}

resource_idの形式はservice/<cluster_name>/<service_name>です。スペルミスしてもTerraformのapplyは通ってしまいますが、スケーリングが一切機能しなくなります。applyした後はaws application-autoscaling describe-scalable-targetsで実際に登録されたか確認するのを習慣にしましょう。

スケーリングポリシー

次に「いつ・どう動かすか」をポリシーで定義します。大きく3種類あります。

ポリシー種別判断基準主な用途
ターゲット追跡目標メトリクス値を維持定常サービス(CPU・リクエスト数)
ステップスケーリングCloudWatchアラームの段階で増減幅を変えるバースト対応・非線形負荷
スケジュールスケーリング時刻ベースでmin/max/desiredを変更既知のピーク(業務時間・キャンペーン)

クールダウン

スケーリングアクション後に次のアクションを抑制する待機時間です。スケールアウトは短く、スケールインは長く——これが唯一の定石です。スパイク直後に縮めて再び慌てる「フラッピング」を防ぐためです。


ターゲット追跡(Target Tracking):目標値を宣言して任せる

最もシンプルで、ほとんどのHTTPサービスはこれで十分です。

事前定義メトリクス

ECSサービスに対して使える事前定義メトリクスは3つです(公式)。

predefined_metric_type計測対象いつ使うか
ECSServiceAverageCPUUtilizationサービス内タスクのCPU平均使用率(%)CPUバウンドな処理(演算・エンコード)
ECSServiceAverageMemoryUtilizationサービス内タスクのメモリ平均使用率(%)メモリバウンドな処理(大量データ展開)
ALBRequestCountPerTargetALBターゲット1台あたりのリクエスト数HTTPトラフィックに線形追従したい

選び方の原則:ボトルネックになっているリソースを使う。分からなければContainer Insightsで実測してから決める。CPU/メモリとリクエスト数を両方設定するとより安全(どちらかがトリガーになった時点でスケールアウトされる)。

完全な Terraform 例(CPU + ALBRequestCountPerTarget)

# --- スケーラブルターゲット(1回定義すれば複数ポリシーを紐付けられる) ---
resource "aws_appautoscaling_target" "app" {
  service_namespace  = "ecs"
  scalable_dimension = "ecs:service:DesiredCount"
  resource_id        = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.app.name}"
  min_capacity       = 2
  max_capacity       = 20

  depends_on = [aws_ecs_service.app]  # サービスが先に存在していること
}

# --- CPU ターゲット追跡 ---
resource "aws_appautoscaling_policy" "cpu_tt" {
  name               = "cpu-target-tracking"
  policy_type        = "TargetTrackingScaling"
  service_namespace  = aws_appautoscaling_target.app.service_namespace
  resource_id        = aws_appautoscaling_target.app.resource_id
  scalable_dimension = aws_appautoscaling_target.app.scalable_dimension

  target_tracking_scaling_policy_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ECSServiceAverageCPUUtilization"
    }
    target_value       = 60.0  # 60%を維持。70〜80%は高すぎてバースト余裕がなくなる
    scale_out_cooldown = 30    # スケールアウトは速く(秒)
    scale_in_cooldown  = 300   # スケールインは慎重に(秒)
    disable_scale_in   = false # スケールインも自動で行う(コスト管理)
  }
}

# --- ALBリクエスト数 ターゲット追跡 ---
resource "aws_appautoscaling_policy" "alb_tt" {
  name               = "alb-request-count-target-tracking"
  policy_type        = "TargetTrackingScaling"
  service_namespace  = aws_appautoscaling_target.app.service_namespace
  resource_id        = aws_appautoscaling_target.app.resource_id
  scalable_dimension = aws_appautoscaling_target.app.scalable_dimension

  target_tracking_scaling_policy_configuration {
    predefined_metric_specification {
      predefined_metric_type = "ALBRequestCountPerTarget"
      # ALBとターゲットグループのリソースラベルが必要
      resource_label = "${aws_lb.main.arn_suffix}/${aws_lb_target_group.app.arn_suffix}"
    }
    target_value       = 1000  # タスク1台あたり1000 req/min を目標
    scale_out_cooldown = 30
    scale_in_cooldown  = 300
  }
}

resource_labelALBRequestCountPerTargetだけで必要な指定です。フォーマットは<load-balancer-arn-suffix>/<target-group-arn-suffix>で、aws_lbaws_lb_target_groupリソースのarn_suffix属性で取れます。

target_value の選び方

  • CPU:60〜70%が一般的。80%以上に設定するとバーストの余白がなく、スケールアウトが間に合わない。
  • ALBリクエスト数:ローカルまたはステージング環境でタスク1台あたりの処理可能リクエスト数を計測し、その6〜7割を目標にする。憶測で設定せず計測ファースト

ステップスケーリング:段階的に増減幅を変える

バーストが激しいワークロードや「ちょっとした超過は小幅に、大幅な超過は一気に増やしたい」という非線形な需要にはステップスケーリングが適合します。

ターゲット追跡との使い分け

観点ターゲット追跡ステップスケーリング
設定の複雑さ低い(目標値だけ)高い(アラーム + ステップ定義)
スケール量の制御AWS自動計算自分で段階を定義
向いているケース定常的なHTTPサービスバースト・非線形・精密な制御が必要な場合
組み合わせ単独でOKターゲット追跡と共存も可能

ステップスケーリングはCloudWatchアラームと連動します。アラームが「ALARM状態」になるとスケールアウト、「OK状態に戻る」ときにスケールインのポリシーを定義します。

# CloudWatchアラーム(スケールアウトトリガー)
resource "aws_cloudwatch_metric_alarm" "cpu_high" {
  alarm_name          = "ecs-cpu-high"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 2
  metric_name         = "CPUUtilization"
  namespace           = "AWS/ECS"
  period              = 60
  statistic           = "Average"
  threshold           = 70.0
  dimensions = {
    ClusterName = aws_ecs_cluster.main.name
    ServiceName = aws_ecs_service.app.name
  }
  alarm_actions = [aws_appautoscaling_policy.step_out.arn]
}

# ステップスケールアウトポリシー
resource "aws_appautoscaling_policy" "step_out" {
  name               = "step-scale-out"
  policy_type        = "StepScaling"
  service_namespace  = aws_appautoscaling_target.app.service_namespace
  resource_id        = aws_appautoscaling_target.app.resource_id
  scalable_dimension = aws_appautoscaling_target.app.scalable_dimension

  step_scaling_policy_configuration {
    adjustment_type          = "ChangeInCapacity"  # 絶対値で変える(他にPercentChangeInCapacityも可)
    cooldown                 = 60
    metric_aggregation_type  = "Average"

    step_adjustment {
      # CPU 70〜80%: +2タスク
      metric_interval_lower_bound = 0
      metric_interval_upper_bound = 10
      scaling_adjustment          = 2
    }
    step_adjustment {
      # CPU 80%超: +5タスク(バースト対応)
      metric_interval_lower_bound = 10
      scaling_adjustment          = 5
    }
  }
}

metric_interval_lower_boundupper_boundは、アラームの閾値からの差分(ブリーチ量)で指定します。「閾値70%に対してCPUが73%」なら差分は+3——0〜10のステップに入ります。


スケジュールスケーリング:ピークを先読みする

毎朝9時の業務開始、月次のキャンペーン、バッチ前のウォームアップなど、いつ負荷が来るかわかっている場合はスケジュールスケーリングで先回りします。

# 平日9時にスケールアウト(JST = UTC+9、なのでUTCは0時)
resource "aws_appautoscaling_scheduled_action" "scale_up_business_hours" {
  name               = "scale-up-business-hours"
  service_namespace  = aws_appautoscaling_target.app.service_namespace
  resource_id        = aws_appautoscaling_target.app.resource_id
  scalable_dimension = aws_appautoscaling_target.app.scalable_dimension
  schedule           = "cron(0 0 ? * MON-FRI *)"  # UTC 0:00 = JST 9:00

  scalable_target_action {
    min_capacity = 5   # ピーク時の下限を引き上げる
    max_capacity = 30  # ピーク時の上限を広げる
  }
}

# 平日21時に縮小(JST = UTC 12:00)
resource "aws_appautoscaling_scheduled_action" "scale_down_off_hours" {
  name               = "scale-down-off-hours"
  service_namespace  = aws_appautoscaling_target.app.service_namespace
  resource_id        = aws_appautoscaling_target.app.resource_id
  scalable_dimension = aws_appautoscaling_target.app.scalable_dimension
  schedule           = "cron(0 12 ? * MON-FRI *)"  # UTC 12:00 = JST 21:00

  scalable_target_action {
    min_capacity = 2   # 夜間の下限に戻す
    max_capacity = 20  # 夜間の上限に戻す
  }
}

スケジュールスケーリングとターゲット追跡は共存できます。ピーク時間帯にはmin_capacityを引き上げてウォームな状態を保ち、ターゲット追跡がさらに細かく増減を調整するという組み合わせが本番でよく機能します。


SQS 駆動ワーカーのスケーリング:「バックログ・パー・タスク」パターン

ここからが本稿の核心です。HTTPサービスとは全く異なる設計が必要です。

なぜ CPU ではダメか

SQSワーカーは「キューにメッセージがあれば処理し、なければ待つ」という構造です。キューが空でもワーカーは起動したまま待機しているため、CPU使用率はほぼゼロになります。逆に、大量のメッセージが積まれていてもワーカーがIO待ち主体の処理(外部API呼び出し・DB書き込みなど)をしていれば、CPU使用率は低いままです。

つまりCPUと処理待ちメッセージ数の間に相関がないのです。CPU追跡でSQSワーカーをスケールするのは、ガソリン残量ではなくエンジン回転数で燃料補給タイミングを決めるようなものです。

AWS 推奨:バックログ・パー・タスク

AWSが公式に推奨するパターンは、**「バックログ・パー・タスク(Backlog Per Task)」**です(Scaling based on Amazon SQS、コンセプトはEC2 Auto ScalingのドキュメントですがECS Application Auto Scalingでも同じ原則が適用されます)。

計算式はシンプルです。

バックログ・パー・タスク = ApproximateNumberOfMessagesVisible ÷ RunningTaskCount

これを**目標バックログ(Target Backlog)**に向けてターゲット追跡します。目標バックログの設定値は、許容レイテンシから逆算します。

目標バックログの算出(例)

以下は架空の例として、計算方法を示します。実際の値はワークロードの計測から求めてください。

例(illustrative values — 実計測値ではありません):
  - 1メッセージあたりの平均処理時間:5秒
  - タスク1台あたりの同時処理数(concurrency):1(シングルスレッドワーカー)
  - タスク1台が1分間に処理できるメッセージ数:60秒 ÷ 5秒 = 12件/分
  - 許容メッセージ滞留時間(最大レイテンシ目標):1分

  → 目標バックログ = 許容レイテンシ(秒) ÷ 1メッセージ処理秒
                   = 60秒 ÷ 5秒
                   = 12

  つまり「タスク1台あたり最大12件のバックログを目標に追跡する」と設定する。
  バックログが36件あればタスクを3台に、120件なら10台に増やす、という挙動になる。

0除算問題の扱い

RunningTaskCountが0のとき(アイドルでmin_capacity=0に縮んでいる状態)に除算するとゼロ除算になります。この場合、メッセージ数そのものを目標値として使うか、RunningTaskCountを最低1として扱うかのどちらかで対処します。カスタムメトリクスの発行ロジックで吸収するのが最も安全です。

カスタムメトリクスの発行

SQS関連のメトリクス(ApproximateNumberOfMessagesVisible)はCloudWatchに自動で届きますが、RunningTaskCountとの比率(バックログ・パー・タスク)は自分で計算してCloudWatchカスタムメトリクスとして発行する必要があります。

TypeScript(EventBridge Schedulerで定期実行するLambda)の例

import {
  CloudWatchClient,
  PutMetricDataCommand,
} from "@aws-sdk/client-cloudwatch";
import {
  SQSClient,
  GetQueueAttributesCommand,
} from "@aws-sdk/client-sqs";
import {
  ECSClient,
  DescribeServicesCommand,
} from "@aws-sdk/client-ecs";

const cw = new CloudWatchClient({});
const sqs = new SQSClient({});
const ecs = new ECSClient({});

const QUEUE_URL = process.env.QUEUE_URL!;
const CLUSTER = process.env.ECS_CLUSTER!;
const SERVICE = process.env.ECS_SERVICE!;
const NAMESPACE = "Custom/ECS";
const METRIC_NAME = "BacklogPerTask";

export async function handler(): Promise<void> {
  // 1) SQS の可視メッセージ数を取得
  const sqsRes = await sqs.send(
    new GetQueueAttributesCommand({
      QueueUrl: QUEUE_URL,
      AttributeNames: ["ApproximateNumberOfMessages"],
    }),
  );
  const visibleMessages = parseInt(
    sqsRes.Attributes?.ApproximateNumberOfMessages ?? "0",
    10,
  );

  // 2) ECS の Running タスク数を取得
  const ecsRes = await ecs.send(
    new DescribeServicesCommand({ cluster: CLUSTER, services: [SERVICE] }),
  );
  const runningCount = ecsRes.services?.[0]?.runningCount ?? 0;

  // 3) バックログ・パー・タスクを計算(0除算を安全に処理)
  //    runningCount=0 のときはメッセージ数をそのまま発行し、
  //    スケールアウトが起動するようにする
  const backlogPerTask =
    runningCount > 0 ? visibleMessages / runningCount : visibleMessages;

  console.log({ visibleMessages, runningCount, backlogPerTask });

  // 4) CloudWatch カスタムメトリクスへ発行
  await cw.send(
    new PutMetricDataCommand({
      Namespace: NAMESPACE,
      MetricData: [
        {
          MetricName: METRIC_NAME,
          Value: backlogPerTask,
          Unit: "Count",
          Dimensions: [
            { Name: "ClusterName", Value: CLUSTER },
            { Name: "ServiceName", Value: SERVICE },
          ],
        },
      ],
    }),
  );
}

このLambdaをEventBridge Scheduler で1分ごとに実行します。CloudWatchのカスタムメトリクスの解像度は最小1分なので、これで十分です。

Bash(シェルスクリプトで手動確認・デバッグ用)

#!/usr/bin/env bash
set -euo pipefail

QUEUE_URL="${QUEUE_URL:?QUEUE_URL not set}"
CLUSTER="${ECS_CLUSTER:?ECS_CLUSTER not set}"
SERVICE="${ECS_SERVICE:?ECS_SERVICE not set}"
REGION="${AWS_REGION:-ap-northeast-1}"

# SQS 可視メッセージ数
VISIBLE=$(aws sqs get-queue-attributes \
  --queue-url "$QUEUE_URL" \
  --attribute-names ApproximateNumberOfMessages \
  --query 'Attributes.ApproximateNumberOfMessages' \
  --output text \
  --region "$REGION")

# ECS Running タスク数
RUNNING=$(aws ecs describe-services \
  --cluster "$CLUSTER" \
  --services "$SERVICE" \
  --query 'services[0].runningCount' \
  --output text \
  --region "$REGION")

if [[ "$RUNNING" -gt 0 ]]; then
  BACKLOG=$(echo "scale=2; $VISIBLE / $RUNNING" | bc)
else
  BACKLOG="$VISIBLE"
fi

echo "visible=$VISIBLE running=$RUNNING backlog_per_task=$BACKLOG"

# CloudWatch に発行
aws cloudwatch put-metric-data \
  --namespace "Custom/ECS" \
  --metric-name "BacklogPerTask" \
  --value "$BACKLOG" \
  --unit "Count" \
  --dimensions "Name=ClusterName,Value=$CLUSTER" "Name=ServiceName,Value=$SERVICE" \
  --region "$REGION"

Terraform:カスタムメトリクスを使ったターゲット追跡

resource "aws_appautoscaling_target" "worker" {
  service_namespace  = "ecs"
  scalable_dimension = "ecs:service:DesiredCount"
  resource_id        = "service/${aws_ecs_cluster.main.name}/${aws_ecs_service.worker.name}"
  min_capacity       = 0   # アイドル時はゼロに縮む(コスト最適化)
  max_capacity       = 50  # 上限は処理能力と許容コストから設定
}

resource "aws_appautoscaling_policy" "worker_backlog" {
  name               = "sqs-backlog-per-task"
  policy_type        = "TargetTrackingScaling"
  service_namespace  = aws_appautoscaling_target.worker.service_namespace
  resource_id        = aws_appautoscaling_target.worker.resource_id
  scalable_dimension = aws_appautoscaling_target.worker.scalable_dimension

  target_tracking_scaling_policy_configuration {
    customized_metric_specification {
      metric_name = "BacklogPerTask"
      namespace   = "Custom/ECS"
      statistic   = "Average"
      dimensions {
        name  = "ClusterName"
        value = aws_ecs_cluster.main.name
      }
      dimensions {
        name  = "ServiceName"
        value = aws_ecs_service.worker.name
      }
    }
    target_value       = 12    # 上の計算例で求めた目標バックログ
    scale_out_cooldown = 60    # キュー急増への応答を速く
    scale_in_cooldown  = 300   # 処理しきるまでの余裕を持って縮小
    disable_scale_in   = false
  }
}

min_capacity=0 の起動遅延

min_capacity=0(アイドルゼロ)にすると、タスクがゼロの状態からスケールアウトするとき、Fargateのタスク起動時間(ENI割り当て・イメージpull・アプリ起動込みで概ね数十秒)が加わります。この「コールドスタート」期間はメッセージが処理されないので、許容レイテンシに対して起動時間が無視できない場合はmin_capacity=1以上にすることを検討してください。決済基盤のワーカーでは厳しいレイテンシSLAがあったためmin_capacity=1を維持しました。


アンチパターンとトラブルシューティング

フラッピング(増減の繰り返し)

症状:スケールアウト直後にスケールインし、またスケールアウトするサイクルが止まらない。

原因scale_in_cooldownが短すぎる。スケールアウト後の負荷が落ち着く前にスケールインが走り、また負荷が上がる。

対処scale_in_cooldownscale_out_cooldownの数倍(最低300秒)に設定する。disable_scale_in = trueを一時的に使うのもフラッピング診断には有効ですが、コスト管理ができなくなるため本番では使わない。

ヘルスチェック猶予を忘れる

スケールアウトで新しいタスクが起動し、ALBに登録されてもアプリの初期化が終わっていない間はヘルスチェックが失敗します。health_check_grace_period_secondsを設定しないと、起動中のタスクが即座に不健全扱いで落とされ、スケールアウトがデスマーチになります

resource "aws_ecs_service" "app" {
  # ...
  health_check_grace_period_seconds = 30  # アプリ起動時間に応じて調整
}

また、タスク定義のhealthCheck.startPeriodも忘れずに設定します(ピラー記事の実装①参照)。

スケールインで処理中タスクを殺さない

スケールインが発生すると、ECSはタスクにSIGTERMを送ります。SQSワーカーがメッセージを処理中にSIGTERMを受け、stopTimeout(既定30秒・最大120秒)を過ぎてSIGKILLで殺されると、処理中のメッセージが中断されてvisibility timeoutまでリトライ不能になります。

正しい対処は3点セットです。

  1. SIGTERMハンドラを実装する(新規メッセージの受信を止め、処理中のメッセージを捌き切る)。
  2. stopTimeoutを処理完了に十分な時間に設定する(最大120秒。5秒かかるメッセージを1件処理している最中にSIGTERMが来る最悪ケースを想定して設定)。
  3. 冪等性を担保する(万が一SIGKILL後に再配信されても二重処理しない)。
// SQS ワーカーの SIGTERM ハンドリング(概念例)
let isShuttingDown = false;

process.on("SIGTERM", () => {
  console.log("SIGTERM received: stopping new message consumption");
  isShuttingDown = true;
  // 処理中のメッセージが完了するのを待ち、stopTimeout内にexitする
});

async function pollMessages(): Promise<void> {
  while (!isShuttingDown) {
    const messages = await receiveMessages();
    for (const msg of messages) {
      await processMessage(msg);      // 冪等な処理
      await deleteMessage(msg);       // 正常終了後にのみ削除
    }
  }
  console.log("Worker gracefully stopped");
  process.exit(0);
}

冪等な非同期処理の詳細な実装パターンはSQS・Lambda・EventBridgeの冪等非同期処理ガイドを、回路遮断・リトライの設計はリトライ・バックオフ・サーキットブレーカーを参照してください。

スケーリング設定が効いているか確認する

# スケーラブルターゲットの確認
aws application-autoscaling describe-scalable-targets \
  --service-namespace ecs \
  --query 'ScalableTargets[*].{Resource:ResourceId,Min:MinCapacity,Max:MaxCapacity}'

# スケーリングアクティビティ(直近のスケール履歴)
aws application-autoscaling describe-scaling-activities \
  --service-namespace ecs \
  --resource-id "service/<cluster>/<service>" \
  --max-results 10

スケーリング履歴を定期的に確認し、フラッピングや想定外のスケールイン・アウトが起きていないかをモニタリングに組み込みます。OpenTelemetry × ECS の可観測性と合わせてダッシュボードに載せておくと、スケーリング挙動の異常に素早く気づけます。


本番リリース前チェックリスト

  • スケーラブルターゲットがdescribe-scalable-targetsで正しく登録されているか確認した
  • min_capacitymax_capacityはビジネス要件(コスト上限・SLA)から決めたか
  • scale_out_cooldown < scale_in_cooldown(非対称)になっているか
  • CPUターゲット追跡のtarget_valueは計測ベースか(60〜70%が目安)
  • ALBRequestCountPerTargetを使う場合、resource_labelが正しく設定されているか
  • SQSワーカーはCPU追跡ではなくバックログ・パー・タスクを使っているか
  • カスタムメトリクス発行Lambdaは1分ごとに実行され、CloudWatch上でデータポイントが確認できるか
  • min_capacity=0の場合、コールドスタート遅延を許容レイテンシと照合したか
  • health_check_grace_period_secondsを設定し、起動時の誤検知による強制終了を防いでいるか
  • SIGTERMハンドラを実装し、stopTimeout内に処理を完了できるか検証したか
  • スケールイン後もメッセージの二重処理が起きないよう冪等性を担保しているか
  • スケーリングアクティビティのログを可観測性ダッシュボードに組み込んだか
  • コスト最適化視点でFargate Spotとの組み合わせをコスト最適化ガイドで確認したか

まとめ

ECS on Fargateのオートスケーリングは、「メトリクスを正しく選ぶ」「非対称クールダウンでフラッピングを防ぐ」「グレースフルシャットダウンと連動させる」の3点が本番品質の土台です。

  • HTTPサービス:ターゲット追跡(CPU + ALBRequestCountPerTarget)で十分。バーストがある場合はステップスケーリングを追加する。
  • SQSワーカー:CPUは使わない。バックログ・パー・タスクをカスタムメトリクスで発行し、許容レイテンシから逆算した目標値でターゲット追跡する。
  • 共通:SIGTERMハンドリング・stopTimeout・冪等性の三点セットなしにスケールインは安全に運用できない。

私が一人 × 生成AIで決済基盤とB2B SaaSを本番運用してきた経験から言えるのは、「ツールを正しく使えば、少人数でも世界水準のインフラは手の届く場所にある」ということです。オートスケーリングの設計・見直し・トラブルシューティングについてご相談があれば、お気軽にどうぞ。

友田

友田 陽大

経済産業大臣賞 受賞プロダクト開発者。TypeScript + Python + AWS で、SaaS・業界DX・ 実用レベルの生成AI(RAG)を、要件定義からインフラ・運用まで一人で完遂します。

この記事で解説した技術の適用事例

環境分野のサーバーレス決済プラットフォーム(フルスタック開発・決済信頼性レイヤーを主導)

ケーススタディを見る