App Runner vs ECS Fargate:Pulumiコードから暴く「真のコストと運用」のトレードオフ
テックリードの私たちが新しいマイクロサービスを立ち上げる際、必ず直面する究極の二者択一がある。それが AWS App Runner と Amazon ECS Fargate だ。
「とりあえずサーバーレスが良いからApp Runnerで」
「いや、将来の拡張性を考えてFargate一択だ」
そんな思考停止の議論は今日で終わりにしよう。コード(Pulumi)の行数、状態管理、そして毎月のAWS請求書を極限まで分析すれば、両者の本質的なトレードオフは一目瞭然となる。
今回は、Infrastructure as Code(IaC)としてPulumiを駆使し、両者の実力を引き出すための判断基準と実践的なコード設計を徹底解説する。
—
1. コード量と抽象度の決定的な差:Pulumiで比較する構造
まず、両者をプロビジョニングする際の「コードの複雑性」を比較する。
App Runnerはインフラの抽象度が極めて高く、ECS Fargateは自由度と引き換えに設定のボイラープレート(定型コード)が増える。
App Runner:圧倒的なミニマリズム
App RunnerのPulumiコードは、VPC接続やロードバランサーの配線を意識する必要がほとんどない。数行の定義で「セキュアなHTTPSエンドポイントを持つコンテナ稼働環境」が手に入る。
ECS Fargate:圧倒的な制御力と引き換えの記述量
一方、Fargateは「Cluster」「Task Definition」「Service」「VPC」「Subnet」「Security Group」「Target Group」「ALB Listener」……と、ネットワークとコンテナランタイムの全レイヤーを明示的に配線しなくてはならない。
ここで重要なのは、「コード量が少ない = 優秀」ではないという点だ。抽象度が高いということは、AWS側のブラックボックスな挙動に依存するということである。
—
2. コストとトラフィックの境界線:いつどちらを選ぶべきか?
コンテナ基盤選定の最大のドライバーは「コスト」だ。ここで、財務的なシミュレーションをコードの挙動ベースで行う。
App Runnerのコスト構造
- リクエスト非処理時(インスタンス一時停止中): CPU 0.025vCPU / メモリ 50MB あたり約 $0.007/時間(待機料金)
- リクエスト処理時(オートスケーリング有効時): 選択したスペックに応じた従量課金
【致命的な落とし穴】
App Runnerは「完全にゼロ円(スケールインして完全停止)」にはならない。インスタンスがアイドル状態でも、待機コスト(Provisioned instance)が発生する。夜間や週末に完全にトラフィックが途絶える社内ツールであっても、微小なベースコストが常時発生し続けることを忘れてはならない。
ECS Fargateのコスト構造
- 常時起動(Provisioned): 確保した vCPU / メモリに対して1秒単位の課金。
- Fargate Spotの活用: Spotインスタンスを使えば最大70%オフになるが、中断耐性(Graceful Shutdown)の実装が強制される。
🎯 決定的な使い分けの判断基準
1. App Runnerを選ぶべきケース:
- トラフィックが予測不能、またはバースト傾向にある。
- インフラ管理に割くマンパワーがなく、CI/CDからデプロイまでを秒速で完結させたい。
- VPC内リソースへの厳格なプライベートアクセス(データベース等)が初期段階では不要、またはApp RunnerのVPC Connectorで要件が満たせる。
2. ECS Fargateを選ぶべきケース:
- 24時間365日、一定のベースロードがあり、リソースを完全にチューニング(予約)したい。
- 既存のVPCアーキテクチャ(AWS PrivateLink、複雑なセキュリティグループ間通信)への組み込みが必須。
- Fargate Spotを組み合わせてコストを極限まで最適化したい。
—
3. 実践:Pulumi (TypeScript) による実装とスケーリング設計
ここからが本題だ。チーム全体の生産性を底上げするため、Pulumiを用いた堅牢な実装パターンを見ていこう。
開発スピードを加速させる前提環境
- 言語: TypeScript (型安全によるリファクタリングの爆速化)
- 神プラグイン: `pulumi-language-nodejs` と IDE(VSCode)の “Pulumi” 拡張機能。これによってエディタ上でリソースの依存関係やスタックの出力(Outputs)がリアルタイムで可視化される。
- チーム共有ルール: スタック設定(環境変数やSecrets)は必ず `pulumi config` を使用し、プレーンテキストをGitにコミットしない。機密情報は `pulumi config set –secret` で暗号化(AWS KMS / Pulumi Service Secret Backend)を強制する。
—
パターンA:App Runnerの実装コード(ミニマムかつモダン)
import as pulumi from “@pulumi/pulumi”;
import as aws from “@pulumi/aws”;
// App Runnerサービスの定義
// 秘訣: ゼロダウンタイムデプロイを担保するため、ヘルスチェックのタイムアウトは余裕を持たせる
const appRunnerService = new aws.apprunner.Service(“my-app-runner”, {
serviceName: “api-service”,
sourceConfiguration: {
autoDeploymentsEnabled: true,
imageRepository: {
imageIdentifier: “123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest”,
imageRepositoryType: “ECR”,
imageConfiguration: {
port: “8080”,
runtimeEnvironmentVariables: {
NODE_ENV: “production”,
},
},
},
},
// スケーリング設定のベストプラクティス: 同時接続数を絞り、素早いスケールアウトを促す
instanceConfiguration: {
cpu: “1024”, // 1 vCPU
memory: “2048”, // 2 GB
},
autoScalingConfigurationArn: new aws.apprunner.AutoScalingConfiguration(“as-config”, {
autoScalingConfigurationName: “high-throughput-as”,
maxConcurrency: 100, // 1インスタンスあたりの最大同時リクエスト数
maxSize: 25, // 最大スケール数
minSize: 1, // 最小常時起動数(コスト最適化のため1にするが、完全ゼロにはならない)
}).arn,
});
// エンドポイントの公開
export const appRunnerUrl = appRunnerService.serviceUrl;
—
パターンB:ECS Fargateの実装コード(本番対応の堅牢な設計)
Fargateはコード量が膨らむため、モジュール化(コンポーネント化)が必須となる。ここではコアな定義に絞って示す。
import as pulumi from “@pulumi/pulumi”;
import as aws from “@pulumi/aws”;
import as awsx from “@pulumi/awsx”; // AWS公式の高度な抽象化ライブラリ
// 1. ECSクラスター
const cluster = new aws.ecs.Cluster(“my-fargate-cluster”);
// 2. Fargate用Application Load Balancer (ALB)
const alb = new awsx.lb.ApplicationLoadBalancer(“my-fargate-alb”, {
// 既存のVPCを指定する想定
});
// 3. Fargate Service with AutoScaling (AWSXを活用したクリーンな記述)
const fargateService = new awsx.ecs.FargateService(“my-fargate-service”, {
cluster: cluster.arn,
desiredCount: 2, // 可用性を担保するため最小2タスク
taskDefinitionArgs: {
container: {
image: “123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest”,
cpu: 512,
memory: 1024,
portMappings: [{ containerPort: 8080, targetGroup: alb.defaultTargetGroup }],
environment: [
{ name: “NODE_ENV”, value: “production” }
],
},
},
});
// スケーリング設定のベストプラクティス (Application Auto Scaling)
// CPU使用率が70%を超えたらスケールアウト
const scalingTarget = new aws.appgated.Target(“ecs-scaling-target”, {
// 省略: Application Auto Scalingのターゲットとポリシーの紐付け
// 実務では CPU / Memory の複合メトリクス、または ALBのRequestCountPerTarget をトリガーにするのが鉄則
});
export const albEndpoint = alb.loadBalancer.dnsName;
—
4. テックリードが実践するチーム開発のルールと設定ファイル構成
複数人でPulumiプロジェクトを運用する場合、野放図なコードはカオスを生む。以下のベストプラクティス構成をプロジェクトの標準として強制せよ。
推奨ディレクトリ構成
.
├── Pulumi.yaml # プロジェクト定義(ランタイム、 descripción)
Pulumi.dev.yaml # 開発環境用設定(暗号化済みセキュア変数含む)
Pulumi.prod.yaml # 本番環境用設定
├── index.ts # エントリーポイント(リソースのオーケストレーション)
├── config/
│ └── index.ts # 設定値のバリデーションと型安全なエクスポート
└── components/ # 再利用可能なカスタムコンポーネント(VPC、ECS基盤など)
└── fargateApp.ts
設定値の安全管理:`config/index.ts` のイディオム
環境変数をコードのあちこちで `process.env` から直接取得するな。必ずPulumiのConfigオブジェクトを経由させ、起動時にバリデーションを行え。
// config/index.ts
import as pulumi from “@pulumi/pulumi”;
const config = new pulumi.Config();
export const environment = config.require(“environment”); // dev or prod
export const minInstances = config.getNumber(“minInstances”) ?? 1;
export const dbPassword = config.requireSecret(“dbPassword”); // 暗号化を強制的におこなう
—
まとめ:プロフェッショナルとしての選択
- App Runner は、インフラの複雑性を極限まで排除し、アプリケーションコードのデリバリー速度を最優先したい場合に採用する最強のツールである。ただし、アイドルコストの発生メカニズムを理解して設計すべし。
- ECS Fargate は、トラフィックの予測がつき、既存のVPCトポロジやSpotインスタンスによるコスト最適化など「細部を完全にコントロールしたい」プロフェッショナルのための基盤である。
どちらを選ぶにせよ、Pulumiを用いることで、そのインフラストラクチャは単なる「設定の山」から、バージョン管理され、テストされ、型安全に守られた「洗練されたソフトウェア」へと昇華する。
チームの生産性を限界まで高めるアーキテクチャを、今日のコードから始めよう。