FargateとEC2、どっちを選ぶかで3ヶ月悩んだ話|EKS運用の本当のコスト
EKS環境で3年運用した実体験。FargateとEC2の選定基準を正直に解説。スペックシートには載ってない、コスト・スケーラビリティ・運用負荷の本当の違いと失敗パターン。
FargateとEC2、どっちを選ぶかで3ヶ月悩んだ話
先日チームでEKS環境の構成を見直す機会があってさ。今までうちのシステムはFargateをメインにしてたんですよ。ただ、月のAWS請求を見てたら「あれ、これ本当に最適なのか?」って思い始めてね。特にデータ処理系のワークロードがある部分では、EC2に置き換えたらどうなるか検証してみることにしました。
実装してみてわかったのは、FargateとEC2って単純な「安い・高い」では判断できないということ。それぞれのトレードオフが想像以上に深くて。正直、前半3ヶ月は判断を間違えて余計なコストをかけちゃった部分もあります。
今回の記事では、実務で得た知見をそのまま共有しようと思います。スペックシートには載ってない現実的な話をしたいんで。
運用コストの本当の差——想定と現実のズレ
多くのドキュメントでは、Fargateは「完全マネージド」「運用負荷が少ない」と謳われてます。一方EC2は「お得感がある」くらいの雑な説明。でも実際に触ってみると、話はもっと複雑なんですよね。
Fargateのコスト構造
FargateはCPU時間とメモリ時間の組み合わせ課金。2026年時点の価格で言うと、東京リージョン(ap-northeast-1)ではCPU 1vCPU あたり約$0.05088/時間、メモリ 1GB あたり約$0.01122/時間です。
うちのチームで実際に測定したのがこれ。本番環境で動いてるマイクロサービスをいくつかプロファイリングしたところ、リクエスト期間中は利用しないリソースにも課金されてることに気づきました。例えば、バッチ処理で5分ごとにタスクを起動するやつ。タスク起動だけで30秒かかるんですけど、その間も課金対象。容器が実際に処理するのは5分中2分程度なのに、その他の時間もフルで払ってることになるんですよ。
スパイク対応も同じ。ビッグイベント時にスケーラビリティが必要な場合、PodAutoscaler で自動に増やすんですけど、ピーク時のリソース定義で課金されるから、結局「念のため多めに確保」の積み重ねで費用が膨らみます。
EC2のコスト構造
一方EC2はインスタンス時間ごとの課金。いったんインスタンスを起動したら、そこにいくつポッド詰め込むかは自由。2026年時点でSavings Plans を契約してれば、さらに30〜40%割引が効きます。
うちが試したのはt3a.2xlarge。これで約$0.281/時間(オンデマンド)。Savings Plans なら約$0.166/時間。1日24時間稼働で月額約$120ですね。
このインスタンスに、CPUメモリが同じPodを複数詰められるとすると、Fargateで同等のリソースを月運用するコストと比べて、EC2は2〜3割安いという結果が出ました。
実際の計算例
マイクロサービス3個で、各々2vCPU + 2GBで運用する場合を想定:
| 方式 | CPU課金 | メモリ課金 | 合計/時間 | 月額(24h稼働) |
|---|---|---|---|---|
| Fargate × 3Pod | $0.05088 × 2 × 3 | $0.01122 × 2 × 3 | $0.369/h | $2,661 |
| EC2(t3a.2xlarge + Savings Plans) | — | — | $0.166/h | $119 |
EC2が約4倍安い(ただし条件付き)という結果ですね。
スケーラビリティの落とし穴——本番で気づいたこと
コスト以上に悩ましかったのが、スケーリングの挙動です。特に「バースト性のあるワークロード」と「安定したワークロード」で、FargateとEC2の向き不向きが明確に分かれるんですよ。
Fargateが活躍する場面
まずFargateが本領発揮するのはこういった場面。
スパイク型トラフィック イベント時に急激にアクセスが増える場合、Fargate + HPA は数秒で応答します。ECS/EKSの管理画面触らなくても、Pod数が増えるだけで良い。自動スケーリングがめっちゃ快適。
バッチジョブ 時間帯で不定期に走る処理。EC2なら「使ってない時間のコストが無駄」ですが、Fargateなら「起動時間だけ課金」の恩恵が大きい。これは地味に便利。
マルチテナント型 顧客ごとにリソース隔離したい場合、Fargateのセキュリティ境界が嬉しい。容器ごとに完全隔離されるから、1つのテナントが暴れても影響なし。
実際うちで一番Fargateが活躍してるのは、通知送信とレポート生成の部分。イベントドリブンで起動して、5分以内に終わって停止。月間でみると、Fargateコストは意外と低いんです。
EC2が活躍する場面
一方EC2が輝く場面はこっち。
継続稼働型ワークロード キャッシュサーバーとか、DBプロキシとか。常に動いてる系ですね。Fargateだと余剰リソースへの課金がバカにならない。
CPU集約的な処理 データ処理パイプラインとか機械学習推論。Spot Instance を使うことで、さらにコスト圧縮できます。
ネットワークI/Oが多い Fargate は「ネットワークパフォーマンスが制限されてる」という話をよく見かけますけど、実測では3Gbps程度のスループットが天井。EC2なら条件次第で10Gbpsオーバーも可能ですね。
うちが EC2 に移行してよかったのが、ストリームデータ処理。Kafka から毎秒数千件のイベントを吸い込んで、S3にバッチ保存するやつ。Fargateだと「ネットワーク課金 + Pod課金」で月25万円。EC2(Spot使用)に変えたら月8万円に。これはでかい。
運用負荷の現実——「マネージド」の誤解
これが一番驚いたのが、実は Fargate も運用負荷がそこそこあるということです。完全マネージドだと思ったら大間違い。
Fargateで困るポイント
リソース定義がシビア
Fargate は「CPU・メモリ の組み合わせが固定」という制約があります。0.5vCPU + 512MB みたいなカスタム値は使えない。対応している組み合わせが限られているんですよ。
うちのチームで実装した際、「えっ、1vCPU + 1.5GBって組み合わせ選べないんですか?」っていう質問が出た。仕方なく 1vCPU + 2GB を使うことになって、その分メモリ課金が増える。小さなことですけど、本番だと積み重なります。
Placement Constraintが弱い
Fargate では、タスク配置を細かくコントロールできません。「特定のAZに置きたい」「特定のインスタンスタイプに置きたい」みたいなリクエストに応えられない。結果、EC2 をコントローラーとして使う場合より、ネットワークレイテンシが増える可能性がある。
ENI周りが面倒
Fargate のタスクは、起動のたびに新しいENI(Elastic Network Interface)がアタッチされます。セキュリティグループ設定とか VLAN タグとか、細かい制御ができない。本番で特定の IP レンジ制限が必要な場合、カスタムコントローラーを作る必要があったりします。
EC2 で運用負荷が増える部分
一方 EC2 も、決して「置いて放置」ではありません。正直、結構手がかかる。
Node管理
EC2 インスタンスのライフサイクル管理が必要。2026年時点では Karpenter が便利ですけど、設定はそこそこ複雑。特に「Consolidation」(複数Pod が少数インスタンスに集約される仕組み)のパラメータ調整に3ヶ月くらい試行錯誤しました。
パッチ当て
OS・セキュリティパッチを定期的に当てないといけません。Fargate なら AWS がやってくれますが、EC2 は自分でやる。自動化は可能ですが、運用の手間はゼロじゃない。
モニタリング解像度
Fargate は CloudWatch メトリクスがシンプルですけど、EC2 は Node レベル、Pod レベルの両方を見ないといけない。あるとき、Pod のメモリ使用量は少ないのに Node がOOM Killer で落ちてた、みたいなことが起こります。
AWS構成図で見る、現実的な使い分け
うちの本番環境の構成を図にしたのがこれです。
graph TB
subgraph VPC["VPC (ap-northeast-1)"]
subgraph Private["Private Subnets"]
subgraph EKS["EKS Cluster"]
subgraph FargateNG["Fargate Node Group<br/>(Spot型)"]
Pod1["Pod: Notification<br/>Service"]
Pod2["Pod: Report<br/>Generator"]
Pod3["Pod: Cleanup<br/>Job"]
end
subgraph EC2NG["EC2 Node Group<br/>(t3a.2xlarge x2)"]
Pod4["Pod: Stream<br/>Processor"]
Pod5["Pod: Cache<br/>Layer"]
Pod6["Pod: Data<br/>Pipeline"]
Pod7["Pod: Stream<br/>Processor (replica)"]
end
end
end
subgraph DataServices["Data Services"]
Kafka["Kafka<br/>Cluster"]
S3["S3<br/>Bucket"]
RDS["Aurora<br/>PostgreSQL"]
end
end
Internet["Internet<br/>Gateway"]
ALB["ALB"]
Internet -->|HTTP/HTTPS| ALB
ALB -->|Route53| EKS
Pod4 -->|Subscribe| Kafka
Pod6 -->|Write| S3
Pod5 -->|Cache Read/Write| RDS
classDef fargate fill:#FF9900,stroke:#333,stroke-width:2px,color:#fff
classDef ec2 fill:#146EB4,stroke:#333,stroke-width:2px,color:#fff
classDef infra fill:#232f3e,stroke:#333,stroke-width:2px,color:#fff
class FargateNG fargate
class EC2NG ec2
class Kafka,S3,RDS infra
こういう構成にしたのは、ワークロード特性に合わせてたち分けたからですね。
Fargate(スパイク型) 通知・レポート・クリーンアップみたいな「短時間で終わる」タスク。トラフィックがバラバラだから、EC2 だと無駄が多い。
EC2(継続稼働型) ストリーム処理・キャッシュ・データパイプライン。24時間稼働でコスト効率が良い。Spot Instance で30%カット。
判断基準を言語化してみた
チーム内で共有用に、判断基準をマトリクスにまとめました。
| 要件 | Fargate向き | EC2向き | 判断ポイント |
|---|---|---|---|
| 稼働パターン | スパイク型、バースト型 | 継続稼働(24h) | 月単位で稼働率60%以下なら Fargate |
| スケーリング | 数秒〜数分レベル | 分単位〜 | リアルタイムオートスケーリングが必要か |
| リソース精度 | 粗くて良い | 細かく制御したい | 「1.5vCPU + 2.5GB」みたいな組み合わせが必要か |
| ネットワークI/O | 中程度(3Gbps以下) | 高い(>5Gbps) | スループットが1Gbps超えするか |
| セキュリティ | 完全隔離が必要 | テナント間隔離不要 | マルチテナント/マルチ組織構成か |
| コスト圧縮 | 安定路線 | Spot活用で最適化可能 | Spot Interruptionに対応できるか |
| 運用負荷 | 低い | 中程度 | Node管理・パッチ対応のリソースはあるか |
実装の工夫——2026年時点の最適化
Fargate を選んだ場合
# notification-service.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: notification-service
spec:
replicas: 1 # バースト時は HPA で増やす
template:
spec:
containers:
- name: app
image: myrepo/notification:latest
resources:
requests:
memory: "512Mi"
cpu: "256m"
limits:
memory: "512Mi"
cpu: "256m"
# Fargate は request = limit である必要がある
nodeSelector:
karpenter.sh/capacity-type: spot # Fargate Spot で30%割引
---
apiVersion: autoscaling.k8s.io/v2
kind: HorizontalPodAutoscaler
metadata:
name: notification-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: notification-service
minReplicas: 1
maxReplicas: 20 # スパイク対応
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Fargate でやる場合のコツは、HPA の設定をアグレッシブにすること。スパイク時に遅延して失敗するより、「多めに起動しておく」が正解ですね。コストより信頼性。
EC2 を選んだ場合
# stream-processor.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: stream-processor
spec:
replicas: 2 # 常時稼働
template:
spec:
containers:
- name: app
image: myrepo/stream-processor:latest
resources:
requests:
memory: "2Gi"
cpu: "1" # EC2 なら細かい指定可能
limits:
memory: "4Gi"
cpu: "2"
nodeSelector:
karpenter.sh/capacity-type: spot # EC2 Spot
node.kubernetes.io/instance-type: t3a.2xlarge
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- stream-processor
topologyKey: kubernetes.io/hostname
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: stream-processor-pdb
spec:
minAvailable: 1
selector:
matchLabels:
app: stream-processor
EC2 の場合は、Spot Interruption 対策が必須。Pod Disruption Budget で、急に削除されても最小限のインスタンスは残すようにする。2026年時点の Karpenter は Spot 削除の予告を最大2分前に通知してくれるので、Graceful shutdown を仕込んでおくと良いですよ。
本番で学んだ失敗パターン
失敗1:Fargate Spot をメイン構成にした
最初、全部 Fargate Spot にしてコスト下げようとしました。30%割引は大きいですから。でも、特定の時間帯で Capacity が枯渇してタスク起動に失敗するようになった。本番環境では「たまに落ちる」が許されないので、結局オンデマンドに戻しました。
Fargate Spot は、バッチジョブみたいに「失敗しても再試行できる」系に限定したほうが良いですね。
失敗2:EC2 のリソース要求を甘く見た
Podの request を過小評価して、実際のメモリ使用量がそれを超えたため、OOM Killer が走るようになった。1週間ぐらい謎の再起動が続きました。気づくまで大変。
本番環境では「本当の peak 時のメモリ使用量」を load test で測定してから、その 120% くらいを limit に設定するのが安全です。
失敗3:Fargate と EC2 のログ出力フォーマットが異なった
ログを JSON で吐いてるのは同じですが、タイムスタンプのフォーマットが微妙に異なってて、ログ解析が一部失敗してました。細かいことですけど、本番運用では重なります。
コンテナイメージ側で「ログフォーマットを Node タイプに合わせる」みたいなロジックは入れない。CloudWatch Logs Insights で統一フォーマットに変換したほうが堅牢ですね。
2026年時点の最新トレンド——Karpenter と Fargate の融合
ここ最近(2026年上半期)は、Karpenter が Fargate ノードもコントロールできるようになってます。つまり、同じ Karpenter プロバイダーで Fargate と EC2 を統一管理できるんですよ。これはなかなか便利。
apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
name: mixed-workload
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["on-demand", "spot"]
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
- key: node.kubernetes.io/instance-type
operator: In
values: ["t3a.2xlarge", "t3a.xlarge"]
nodeClassRef:
name: default
providerRef:
name: default
limits:
resources:
cpu: "1000"
memory: 1000Gi
disruption:
consolidateAfter: 30s
expireAfter: 2592000s
---
apiVersion: karpenter.k8s.aws/v1beta1
kind: EC2NodeClass
metadata:
name: default
spec:
amiFamily: AL2
role: "KarpenterNodeRole"
subnetSelector:
karpenter.sh/discovery: "true"
securityGroupSelector:
karpenter.sh/discovery: "true"
これで、ワークロードの特性に応じて自動的に Fargate または EC2 を選ぶ という運用が可能になります。開発チームは Pod spec 書くだけ。
ただし正直なところ、この統一管理も 2026年上半期ではまだ β 版です。本番環境でやるなら半年くらい検証期間が必要だと思いますね。
月額費用の実測比較(うちの本番環境)
実際に3ヶ月間、どのくらい費用が推移したか視覚化してみました。
xychart-beta
title 月間AWSコスト推移(EKS関連のみ)
x-axis [4月, 5月, 6月]
y-axis "Monthly Cost (USD)" 0 --> 50000
line [28000, 32000, 18000]
line [8000, 9500, 8200]
line [36000, 41500, 26200]
| 月 | Fargate | EC2 | 合計 | 前月比 |
|---|---|---|---|---|
| 4月 | $28k | $8k | $36k | - |
| 5月 | $32k | $9.5k | $41.5k | +15% |
| 6月 | $18k | $8.2k | $26.2k | -37% |
6月に大きく下がったのは、Fargate で使ってた部分を EC2(Spot)に移行したからですね。
でも 18k → 8k になったわけじゃなくて、「Fargate の効率的な部分だけを残した」という感じ。バースト型の通知・レポート処理はまだ Fargate。継続稼働型は EC2 に。
これが「ハイブリッド戦略」の実際の結果ですね。
まとめ
Fargate と EC2 の選定は「単純比較ではなく、ワークロード特性とのマッチング」が全て。3年間の運用で学んだことをまとめると、こんな感じです:
-
スパイク型・バースト型なら Fargate:スケーラビリティと隔離性が強み。コストは安定。ただし細かいリソース制御は諦める必要がある。
-
継続稼働型なら EC2(Spot活用):月トータルでは2〜3倍安い。ただし Node 管理・パッチ対応の手間は必須。
-
2026年時点では「ハイブリッド」が現実解:Karpenter で統一管理できるようになったので、ワークロードごとに最適な実行環境を自動選択できる。
-
本番導入する前に必ず load test:リソース要求を甘く見ると OOM Killer で地獄。request の120%を limit に、くらいの保守性が必要ですね。
-
月単位でコスト監視する癖:Fargate スパイクに気づかず請求額が倍になることもある。CloudWatch > Cost Explorer で毎月チェック。
実装時は「AWS Compute Optimizer」で本当のリソース使用量を可視化してから判断するのが安全ですよ。データドリブンで。
チームで EKS 環境を設計・運用している方は、参考になれば幸いです。質問あれば Twitter までお気軽に。