Dev Study
AWSサービスの内部原理 コース

80. スケジューラは差分を埋め続ける制御ループである — desired countと現実のずれが仕事のリスト

ECSサービスの中心には「望ましい状態」と「現実」を突き合わせて差分を埋め続ける1つのループがあります。この回では、そのループが何を観測し・どう差分を計算し・どんな順序で行動するのかを、上から下へたどる図で追っていきます。

① 命令ではなく宣言で動く差分ループ

宣言desiredCount = 3(望ましい状態)
あなたが書くのはこれだけ。手順ではなく、あるべき姿の宣言
① 観測現実に今いくつRUNNINGか数える → 現状 = 2
② 差分desired 3 − 現実 2 = +1 不足
③ 行動設計図(タスク定義)から1つ起動
終わったら永遠に①へ戻る(ループ)
  • 公式:「If a task in a service stops, the scheduler launches a new task to replace it. This process continues until your service reaches your desired number of tasks」
  • 差分が +1 なら起動、−1 なら停止、0 なら何もしない
  • タスクの失敗・停止も基盤(インスタンス)の故障も、次の観測で「差分」として現れ、同じループが埋める。公式:「if the underlying infrastructure fails, the service scheduler reschedules a task」
  • CS基礎ではこれを制御ループ / リコンシリエーション(reconciliation)と呼ぶ。Kubernetesも全く同じ原理(desired stateとactual stateを突き合わせるcontroller loop)で動く

スケジューラは手順を実行する存在ではなく、宣言と現実の差分を検出して埋め続ける制御ループそのもの。「タスクが死んだら誰が復旧を命じるのか」という問いの答えは「誰も命じない。次の観測で差分が現れ、ループが勝手に埋める」。

② 置き換えの原則 — 先に代わり、あとで撤去

UNHEALTHY判定コンテナ/ターゲットグループのヘルスチェック失敗
置換タスクは健康か?
余裕あり(通常)先に置換タスクを起動(start first)
余裕なし不健康なタスクを1つずつランダムに止めて場所を空けてから置換を起動
minimumHealthyPercentが下限の枷
全部が健康になった後
HEALTHY不健康な方を止める
UNHEALTHY不健康な置換か既存の不健康のどちらかを止めて総数をdesiredCountに合わせる
超過分を間引く総数 > desiredCount なら健康なタスクをランダムに止めて数を合わせる
  • maximumPercent = RUNNING/STOPPING/PENDING状態のタスク数の上限。desiredCountに対する割合で切り捨て。REPLICAの既定は200%(DAEMONは100%)
  • 例: desiredCount=4, max=200% なら4つ起動してから4つ止められる
  • 公式:「the service scheduler will first start a replacement task」
  • 公式:「If the maximumPercent parameter limits the scheduler from starting a replacement task first, the scheduler will stop an unhealthy task one at a time at random to free up capacity」
  • 「先に代わりを起動してから古い方を止める」はローリングアップデートの余剰キャパシティと同じ発想。max=200%は「一時的に定員の倍まで許すから無停止で入れ替えられる」という宣言

置き換えの既定は「先に代わり、あとで撤去」——この順序が可用性を守る。資源不足のときだけ「1つ空けてから起動」に退避し、決着後に超過分の健康なタスクをランダムに間引いて、最終的に総数をdesiredCountぴったりに収束させる。

③ 起動失敗のスロットル — あえてリトライを遅くする

起動を試みる差分 +1 を検出してタスク起動
人間が原因を直してサービスを更新
試行を減速起動の試行を意図的に遅くする
イベント発報service event messageで人間に知らせる
通常速度に復帰resumes normal scheduling behavior
  • 発動条件は「RUNNING状態に入らずに停止した」場合。正常起動後に落ちたケースとは区別される
  • 公式:「If a task is stopped without having entered a RUNNING state, the service scheduler starts to slow down the launch attempts and sends out a service event message.」
  • 目的は公式:「prevents unnecessary resources from being used for failed tasks before you can resolve the issue」
  • API Gateway編・メッセージング編で見たバックオフ(再試行は間隔を空ける)の再登場。バックオフは通信に限らず「失敗し続ける行動全般」に効く一般原理

制御ループは「速く回せば良い」わけではない。素朴なループなら「不足→起動→即失敗→また即起動」を高速で回して資源を燃やし尽くすところを、スロットルで試行間隔を空け、イベントで人間に知らせる——無限高速リトライで資源を溶かさない抑制もループ設計の一部。

④ スケジューリング戦略 — 指定数維持か、各インスタンスに1つか

schedulingStrategy未指定ならREPLICA
desiredの定義がそもそも違う
REPLICA指定数をクラスター全体で維持
既定でAZをまたいで分散配置
DAEMON各activeコンテナインスタンスに正確に1つずつ
Fargate非対応
数を宣言desiredCountでN個と宣言する
インスタンス数=数desiredCount不要。置ける先が増えれば自動で1つ増える
  • 公式 REPLICA:「places and maintains the desired number of tasks across your cluster. By default, the service scheduler spreads tasks across Availability Zones.」
  • 公式 DAEMON:「deploys exactly one task on each active container instance that meets all of the task placement constraints ... there is no need to specify a desired number of tasks」
  • 公式:「Fargate tasks do not support the DAEMON scheduling strategy.」Fargateはインスタンスという概念をユーザーに見せないので当然
  • DAEMONはログ収集やモニタリングのエージェント(全ホストに1つ欲しいもの)に向く

REPLICAは「合計N個をどこかに保て」、DAEMONは「各マシンに1個ずつ貼れ」。前者は数を宣言し、後者は「インスタンスの数」がそのままdesiredになる。Fargateではインスタンスが見えないのでDAEMONは使えない。

⑤ 配置戦略 — random / spread / binpack

placementStrategyN個のタスクをどのインスタンスに置くか
EC2起動タイプで意味を持つ(FargateはAWSが場所を管理)。最大4ルールまで組み合わせ可
binpackの狙い
random空いている候補にランダムに置く
spread指定軸(field)で均等に分散
field: instanceId / host / attribute:ecs.availability-zone
binpack指定資源の「残り最少」のインスタンスに詰める
field: cpu / memory
丸ごと空きを作る半端な空きを埋め、丸ごと空いたインスタンスを落としてコスト削減
  • 公式 random:「randomly places tasks on available candidates」
  • 公式 spread:「spreads placement across available candidates evenly based on the field parameter」
  • 公式 binpack:「places tasks on available candidates that have the least available amount of the resource ... if you binpack on memory, a task is placed on the instance with the least amount of remaining memory but still enough to run the task」
  • binpackは教科書に載るビンパッキング問題(限られた容器に荷物を無駄なく詰めるNP困難な最適化問題)の名が、そのままAPIのenum値になっている
  • spreadがAZにタスクを散らすのは、DynamoDB編でハッシュ関数がキーをパーティションに散らして偏りを防いだのと同じ「均等分散で偏りを避ける」発想

spreadは可用性のため散らし、binpackはコストのため詰める——正反対の目的が同じ「配置戦略」の枠に同居している。binpackは名前どおりビンパッキング問題の実装で、「設定値の名前は、その裏にある問題を指している」ことがあると教えてくれる。

⑥ AZリバランス — 偏りも差分とみなす

AZリバランス設定availabilityZoneRebalancing(新しめの設定)
ENABLEDにすると
DISABLED既定。偏りは放置
ENABLEDAZ間の偏りを自動で直す
偏り=差分AZ障害の復旧後などに片側AZへ寄ったままにならないよう、ループが再配置して均す
  • 新規サービス: 値を指定しなければ既定はDISABLED(執筆時=2026年に現行記述を確認済み)
  • 既存サービス: 指定しなければ既存値を維持、過去にも未設定ならDISABLED
  • 公式:「Indicates whether the service uses Availability Zone rebalancing. The valid values are ENABLED and DISABLED.」
  • REPLICAは既定でAZ分散するが、障害復旧やスケール後に偏りが残ることがある。ENABLEDはその「偏りという差分」もループの仕事に加える設定

ENABLEDにすると、ループが監視する「あるべき姿」に「AZ間でバランスが取れていること」が加わる——制御ループは、何を差分とみなすかを設定で拡張できる。タスクの死・基盤故障・デプロイ・AZの偏り、起きることは千差万別でも対処するのはたった1つの制御ループであり、この型はKubernetesをはじめ現代のオーケストレーションが共有する骨格そのものだ。

公式ドキュメントで詳しく ↗