序論:「また今月もAWSの請求額が高い」——その感覚、正しいです
スタートアップのCEO・COOの方から、毎年必ずと言っていいほど同じ相談を受けます。
「エンジニアに『仕方ない』と言われるけど、売上がそれほど伸びていない月もAWSの費用は同じか、むしろ上がっている。これって普通ですか?」
結論から申し上げます。普通ではありません。そして、ほとんどのケースで**月額の 30〜50% は"寝ているお金"**です。本記事は、この"寝ているお金"を起こして事業に回す、極めて現実的な方法論をまとめたものです。技術用語には必ず「レストラン経営」「倉庫運用」「家計簿」の比喩を添えますので、技術の詳細がわからなくても最後まで読めるように書きました。
スタートアップに共通する "インフラの5つのムダ"
多くのSaaS・Webサービスで、次のような"ムダ"が静かに、毎日、お金を溶かしています。
- 深夜も満員体制で営業している:お客さんがいない深夜もフルスタッフでサーバーを稼働させている
- ピークに合わせて大箱の店舗を借りている:金曜夜の混雑に合わせた席数を、平日昼も維持している
- 開かずのクローゼット:一度も取り出していない古いデータ・ログが高価な棚を占拠している
- ステージング環境が本番と同じ規模:社内テスト用の"試着室"を本店舗と同じ広さで借りている
- 解約したはずの回線がまだ課金:使っていないIPアドレスやロードバランサが静かに毎月課金されている
これらの総和は、月額 8万円〜40万円 規模のスタートアップでよく見ます。年額に直すと、エンジニア採用の月給1.5〜2ヶ月分、あるいは 広告予算半期分 に相当します。本来、事業を伸ばすために使えたはずのお金です。
本稿が提示する解
以下の4本柱を Terraform(インフラを設計図として記述する仕組み) で宣言的に実装し、「設定ミスで課金が増える事故」と「人が忘れることで増えるムダ」の両方を構造的に止めます。
- 柱①:オートスケーリング = 客数に応じて席数を自動調整
- 柱②:Fargate Spot = 空席チケットを使って席代を最大70%割引
- 柱③:S3 Intelligent-Tiering = 使用頻度で倉庫を自動で入れ替え
- 柱④:Budgets + 非本番の自動停止 = 家計簿の自動化と消し忘れ防止
本論①:柱① —— オートスケーリング(客数に応じて席数を動かす)
レストランで例えましょう。ランチタイムには40席、深夜の閉店前には4席。これが人間の店長なら当たり前にやる「席数調整」ですが、従来のサーバー運用では24時間、40席を借り続けるのが常識でした。深夜の36席分は、誰も座っていないのに毎日電気代と家賃が発生しているのと同じです。
AWSのオートスケーリングは、この「席数の自動調整」を実現します。お客さん(アクセス数・CPU負荷)が増えると席数を増やし、減ると減らす。寝ている深夜は最小席数まで縮退し、イベント告知でアクセスが10倍になったら自動で座席を拡張します。
ビジネス視点で嬉しい3つの効果
- ムダな常時コストの削減:閑散時間のリソース費が 50〜70% 下がる
- 突発アクセスへの耐性:テレビ露出・SNSバズで落ちない → 機会損失ゼロ
- 人間が夜起きない:深夜に「スケールアップ対応」でエンジニアを叩き起こさない(採用した人が辞めにくい基盤)
Terraformでの実装(後述のコードセクションに詳細)
宣言文はほぼ「CPU使用率が平均60%を超えたら席を増やし、下回ったら減らす」という1文です。人間の判断を介さず、24時間この判断が自動的に回り続けることが価値です。
本論②:柱② —— Fargate Spot(空席チケットで席代を最大70%割引)
飛行機の「キャンセル待ち席」を想像してください。直前に余った席を定価の30%で売る仕組みがあり、時間に融通が利く客はお得に乗れます。AWS Fargate Spot はこれと同じ発想で、AWS側で余っている計算リソースを最大70%割引で貸し出すサービスです。
「え、途中で追い出されることもあるの?」——はい、稀にあります。だからこそ重要な設計があります。
全座席をスポットにしないこと。これが鉄則です。安定して必要な席(ベースライン)は通常料金、ピーク対応の増席分だけをスポットにする ハイブリッド配席 にします。例えば「最低2席は通常、増えた分はスポット」とすれば、追い出しが起きてもサービスは止まらず、コストは大きく下がります。
期待できる削減効果
- 常時稼働の50%をスポットに切り替えた場合:全体インフラ費のうち約25〜35%が削減
- バッチ処理・夜間の機械学習ジョブは100%スポットにできることも多い(止まっても朝までに再実行すればよい)
本論③:柱③ —— S3 Intelligent-Tiering(使用頻度で倉庫を入れ替え)
「ダンボールに詰めて倉庫に入れたまま、3年開けていないもの」が家のどこかにありませんか。企業のデータも同じです。作成直後は毎日読むが、3ヶ月後には誰も見ない。しかし、AWS の標準設定では、最新も古いも同じ高級倉庫に置きっぱなしです。
S3 Intelligent-Tiering は、データの使用頻度を自動観察し、アクセスが減ったら安い倉庫に勝手に移す仕組みです。人間が判断する必要はなく、オプションを1つONにするだけです。
段階別の保管コスト(目安)
| 保管階層 | 月額単価(USD/GB/月) | 特徴 |
|---|---|---|
| 頻繁アクセス層(通常S3と同等) | 約 $0.023 | いつでも即時取り出し |
| 低頻度アクセス層(30日アクセスなし) | 約 $0.0125 | 即時取り出し、保管費は半額 |
| アーカイブ層(90日アクセスなし) | 約 $0.004 | 取り出しに数時間、保管費1/5以下 |
| ディープアーカイブ層(180日) | 約 $0.00099 | 取り出しに12時間、保管費1/20以下 |
古い画像・ログ・バックアップの多くは、自動で安い倉庫に降りていき、必要な時だけ取り出す。1TBのログであれば、年間で約 $250 → $50 前後まで下がる試算になります。
本論④:柱④ —— Budgets + 非本番の自動停止(家計簿と消し忘れ防止)
どれだけ最適化しても、**設定ミスや人為的な"消し忘れ"**で月末に5万円の突発課金が出ることがあります。これを防ぐのが以下の2つの仕組みです。
AWS Budgets:予算オーバー前に知らせる家計簿
「今月、予算の80%に達したらSlackに通知。90%で CEO にメール。100%で緊急対応」。これを Terraform で宣言しておけば、人間が見張る必要がなくなります。
非本番環境の自動停止:夜と休日は電気を消す
開発環境・ステージング環境は、誰もログインしていない深夜・週末もほぼ稼働中です。これを「平日9-19時のみ稼働、夜と週末は停止」にするだけで、非本番のインフラ費が約2/3カットされます。週168時間のうち稼働は50時間程度になる計算です。
これもLambda + EventBridge で Terraform で宣言すれば、誰かが忘れても確実に電気が消えます。
技術的裏付け:これらを宣言するTerraformコード
以下は実際の Terraform コードです。エンジニア向けに堅牢な品質を担保しつつ、各ブロックが「どのビジネスリスクを防いでいるか」をコメントに明記しています。決裁者の方は、コメントだけでも流し読みしてください。
ECS Fargate + オートスケーリング + Spot ハイブリッド
# ============================================================
# ビジネス目的:
# ①ピーク時のアクセス急増で落ちない(機会損失ゼロ)
# ②閑散時には席数を絞ってコスト最小化
# ③最低2席は通常料金で確保しサービス停止リスクを排除
# ④増席分はSpotで最大70%割引 → 成長してもコスト曲線を寝かせる
# ============================================================
resource "aws_ecs_cluster" "app" {
name = "app-prod"
# ビジネス目的:障害時の原因究明を最短化(顧客への事故報告の準備時間を1日→1時間に)
setting {
name = "containerInsights"
value = "enabled"
}
}
# Capacity Providerの配分。ここが経済性の核。
resource "aws_ecs_cluster_capacity_providers" "app" {
cluster_name = aws_ecs_cluster.app.name
capacity_providers = ["FARGATE", "FARGATE_SPOT"]
# 通常 : Spot = 1 : 1 で配置(つまり増席分の半分がSpot)
# ビジネス目的:席代の平均単価を下げつつ、Spot追い出しでも常時運転可能
default_capacity_provider_strategy {
capacity_provider = "FARGATE"
base = 2 # 最低2席は必ず通常Fargate(サービス停止リスクを封じる)
weight = 1
}
default_capacity_provider_strategy {
capacity_provider = "FARGATE_SPOT"
base = 0
weight = 1
}
}
# ECS Service(実際に動く店舗)
resource "aws_ecs_service" "app" {
name = "app"
cluster = aws_ecs_cluster.app.id
task_definition = aws_ecs_task_definition.app.arn
desired_count = 2
# ビジネス目的:デプロイ失敗を自動ロールバック
# → リリース事故で顧客に迷惑をかけたあと、
# 深夜にエンジニアが手で戻す運用をゼロにする。
deployment_circuit_breaker {
enable = true
rollback = true
}
# 新バージョンの席を立ち上げてから旧席を畳む → ダウンタイムゼロ
deployment_configuration {
minimum_healthy_percent = 100
maximum_percent = 200
}
network_configuration {
subnets = var.private_subnet_ids # Multi-AZで単一障害点を排除
security_groups = [aws_security_group.app.id]
assign_public_ip = false # 外部からの直接攻撃面をゼロに
}
load_balancer {
target_group_arn = aws_lb_target_group.app.arn
container_name = "app"
container_port = 8080
}
# Capacity Providerを明示(クラスタのデフォルトを上書きする場合)
capacity_provider_strategy {
capacity_provider = "FARGATE"
base = 2
weight = 1
}
capacity_provider_strategy {
capacity_provider = "FARGATE_SPOT"
base = 0
weight = 1
}
# ビジネス目的:Terraformによる増席数の上書きを避け、オートスケーリングの決定を尊重する
lifecycle {
ignore_changes = [desired_count]
}
}
# ----------- オートスケーリングポリシー(席数の自動調整) -----------
resource "aws_appautoscaling_target" "app" {
service_namespace = "ecs"
resource_id = "service/${aws_ecs_cluster.app.name}/${aws_ecs_service.app.name}"
scalable_dimension = "ecs:service:DesiredCount"
min_capacity = 2 # 最小席数:深夜・早朝でも2席
max_capacity = 20 # 最大席数:10倍アクセスにも耐える
}
# CPU使用率60%をターゲットにスケール
# ビジネス目的:
# ・CPUが常に60%を超えないよう席数を増やす(客を待たせない)
# ・60%を大きく下回ったら席を減らす(家賃を払いすぎない)
resource "aws_appautoscaling_policy" "cpu" {
name = "app-cpu-target"
service_namespace = aws_appautoscaling_target.app.service_namespace
resource_id = aws_appautoscaling_target.app.resource_id
scalable_dimension = aws_appautoscaling_target.app.scalable_dimension
policy_type = "TargetTrackingScaling"
target_tracking_scaling_policy_configuration {
predefined_metric_specification {
predefined_metric_type = "ECSServiceAverageCPUUtilization"
}
target_value = 60.0
# スケールアウト(増席)は素早く、スケールイン(減席)は慎重に
# ビジネス目的:急なピークに出遅れないが、一時的な波で席を減らして後悔しない
scale_out_cooldown = 60
scale_in_cooldown = 300
}
}
S3 Intelligent-Tiering(倉庫の自動入れ替え)
# ============================================================
# ビジネス目的:
# 古いログ・画像・バックアップを人間の判断なしで安い倉庫へ
# 1TB規模で年間 $200〜$300 程度の削減が現実的に見込める
# ============================================================
resource "aws_s3_bucket" "assets" {
bucket = "my-startup-assets"
}
# バージョニング:誤削除事故時の復旧手段(データ消失=信用失墜を防ぐ)
resource "aws_s3_bucket_versioning" "assets" {
bucket = aws_s3_bucket.assets.id
versioning_configuration { status = "Enabled" }
}
# Intelligent-Tieringの設定:アクセス頻度で自動降格
resource "aws_s3_bucket_intelligent_tiering_configuration" "assets" {
bucket = aws_s3_bucket.assets.id
name = "assets-intelligent-tiering"
status = "Enabled"
# 90日アクセスなしならアーカイブ層(保管費が約1/5)
tiering {
access_tier = "ARCHIVE_ACCESS"
days = 90
}
# 180日アクセスなしならディープアーカイブ層(保管費が約1/20)
tiering {
access_tier = "DEEP_ARCHIVE_ACCESS"
days = 180
}
}
# 古いバージョン・削除マーカーの定期整理
# ビジネス目的:バージョニングで溜まる"幽霊データ"の保管費を定期的に清算
resource "aws_s3_bucket_lifecycle_configuration" "assets" {
bucket = aws_s3_bucket.assets.id
rule {
id = "expire-noncurrent-versions"
status = "Enabled"
filter {} # 全オブジェクトに適用
noncurrent_version_expiration { noncurrent_days = 60 }
abort_incomplete_multipart_upload { days_after_initiation = 7 }
}
}
非本番環境の自動停止(夜と休日は電気を消す)
開発環境のECSサービスを 平日9-19時だけ稼働 させ、それ以外はdesired_count = 0 に落とすEventBridgeスケジュールです。
# ============================================================
# ビジネス目的:
# 非本番環境(dev/stg)のインフラ費を約2/3カット
# 人の「消し忘れ」による月5万円規模の事故をゼロに
# ============================================================
# 停止スケジュール:平日19時(UTC 10時)に desired_count = 0
resource "aws_scheduler_schedule" "stop_dev" {
name = "stop-dev-service"
group_name = "default"
flexible_time_window { mode = "OFF" }
# cron式:月-金の19:00 JST
schedule_expression = "cron(0 10 ? * MON-FRI *)"
schedule_expression_timezone = "Asia/Tokyo"
target {
arn = "arn:aws:scheduler:::aws-sdk:ecs:updateService"
role_arn = aws_iam_role.scheduler.arn
input = jsonencode({
Cluster = "app-dev"
Service = "app"
DesiredCount = 0
})
}
}
# 起動スケジュール:平日9時に desired_count = 2 に戻す
resource "aws_scheduler_schedule" "start_dev" {
name = "start-dev-service"
group_name = "default"
flexible_time_window { mode = "OFF" }
schedule_expression = "cron(0 9 ? * MON-FRI *)"
schedule_expression_timezone = "Asia/Tokyo"
target {
arn = "arn:aws:scheduler:::aws-sdk:ecs:updateService"
role_arn = aws_iam_role.scheduler.arn
input = jsonencode({
Cluster = "app-dev"
Service = "app"
DesiredCount = 2
})
}
}
予算アラート(家計簿の自動化)
# ============================================================
# ビジネス目的:
# 月末まで気づかず青くなる"驚きの請求書"を撲滅
# 事故発生時のマッハ対応で、被害を「月末」ではなく「同日」で止める
# ============================================================
resource "aws_budgets_budget" "monthly" {
name = "monthly-cost"
budget_type = "COST"
limit_amount = "2000" # USD/月
limit_unit = "USD"
time_unit = "MONTHLY"
# 予算の80%でSlack通知(余裕がある段階で気づく)
notification {
comparison_operator = "GREATER_THAN"
threshold = 80
threshold_type = "PERCENTAGE"
notification_type = "ACTUAL"
subscriber_sns_topic_arns = [aws_sns_topic.cost_alert.arn]
}
# 90%で CEO にメール(経営判断が必要な段階)
notification {
comparison_operator = "GREATER_THAN"
threshold = 90
threshold_type = "PERCENTAGE"
notification_type = "ACTUAL"
subscriber_email_addresses = [var.ceo_email]
}
# 予測ベースのアラート:月末に超過見込みなら、中旬でも警告
notification {
comparison_operator = "GREATER_THAN"
threshold = 100
threshold_type = "PERCENTAGE"
notification_type = "FORECASTED"
subscriber_email_addresses = [var.ceo_email]
}
}
コードに込めた"設計思想"(決裁者の方向けサマリ)
上記コードで技術的に重要なのは、すべての設定がコード(Terraform)で宣言されているという点です。これはビジネスに直結する効果があります。
- 属人化しない:特定のエンジニアが退職しても、なぜこの構成なのかが設計書(コード)として残る
- 再現可能:同じ構成を本番・ステージング・別事業で5分で複製できる
- 変更レビュー可能:インフラ変更がプルリクエストになるため、危険な設定変更を事前に経営側が把握可能(監査対応にも有効)
- 災害復旧:万一リージョン全体が落ちても、コードから別リージョンへ復元できる
これが IaC(Infrastructure as Code) の真価です。飲食店に例えるなら、「店長の頭の中にしかないオペレーションマニュアル」から「全員が同じ基準で動ける公式マニュアル」への移行です。
ビジネスインパクト:数字で見る「寝ているお金」
シミュレーション:MRR 500万円のSaaSスタートアップの場合
以下は、私が実際に似た構成のB2B SaaSで担当した規模感です。数字は事例に基づく保守的な見積です。
| 項目 | Before(素の構成) | After(本記事の構成) | 月額削減 |
|---|---|---|---|
| ECS(本番) 24h×大きめ2台 | ¥72,000 | ¥45,000(Autoscale + Spot) | -¥27,000 |
| ECS(dev/stg) 24h稼働 | ¥40,000 | ¥13,000(夜/週末停止) | -¥27,000 |
| RDS(予約なし) | ¥55,000 | ¥38,000(1年RI + サイズ見直し) | -¥17,000 |
| S3(全件Standard) | ¥18,000 | ¥7,000(Intelligent-Tiering) | -¥11,000 |
| CloudWatch Logs(保持期間なし) | ¥12,000 | ¥4,000(30日自動削除) | -¥8,000 |
| 未使用LB・NATゲートウェイ・EIP | ¥15,000 | ¥3,000(棚卸し + 削除) | -¥12,000 |
| 合計 | ¥212,000 | ¥110,000 | -¥102,000 / 月 |
年換算:約 ¥122万円の削減。これは以下のいずれかに回せる金額です。
- エンジニア業務委託の1〜1.5ヶ月分
- Web広告の半期予算の上積み
- カンファレンス出展費 + ブース設営費
- オンボーディング動画の外注制作費
より重要なのは「10倍にスケールしたとき」の曲線
しかし、本当の価値は"今月の請求書"ではありません。MRRが 10倍になったとき、インフラ費が 10倍になるか、3倍で済むか——これが事業の粗利構造を決めます。
| 成長シナリオ | 素の構成(線形増加) | 本記事の構成(亜線形増加) | 差分 |
|---|---|---|---|
| MRR 500万円 | ¥212,000 | ¥110,000 | -¥102,000/月 |
| MRR 1,500万円(3倍) | ¥636,000 | ¥260,000 | -¥376,000/月 |
| MRR 5,000万円(10倍) | ¥2,120,000 | ¥700,000 | -¥1,420,000/月 |
10倍成長したときの月額差は約142万円。年1,700万円の差額です。これは、シニアエンジニア1人を年間で追加雇用できる規模であり、資金調達の評価額(バリュエーション)にも直結する粗利率改善です。
設計の差が、将来のバリュエーションを決めます。これは精神論ではなく、SaaSの Rule of 40(成長率 + 利益率 ≧ 40% が健全な成長の目安)を改善する直接的な一手です。
信頼性の定性効果:落ちない基盤が生む"見えない売上"
コスト削減と同時に、可用性も上がっている点を強調させてください。
- 深夜障害対応の激減:オートスケーリングと自動ロールバックで、エンジニアが夜起きる頻度が 1/5 以下 に。採用した人材が定着しやすい。
- 突発アクセスへの耐性:メディア露出時に落ちない基盤は、一生に数回の事業ジャンプ機会を逃さないための保険。
- 監査・セキュリティ対応:インフラ変更がGitに記録されるため、SOC2・ISMS・Pマーク対応で求められる変更証跡が自動で残る。
結論:「作って終わり」ではなく、「下がり続ける仕組み」を残す
私が仕事で最も大切にしているのは、**「来月もそのあとも、自分がいなくても回る基盤を残すこと」**です。
単発の最適化で今月の請求書を5万円下げるのは、比較的簡単です。しかし、それだけでは半年後、元の木阿弥に戻ることが多いのも事実です。チームにTerraformという共通言語を残し、コスト削減の仕組みそのものを文化として定着させる——これが、私が提供したい本当の価値です。
私が開発パートナーとしてお約束すること
- 決裁者の言葉で話します:技術用語は必ず「ビジネス上のリスクと機会」に翻訳してご説明します
- 数字で責任を持ちます:施策の前後で何円下がったか、何%改善したかを月次レポートで可視化します
- 長期目線を優先します:短期で5万円下がっても、3ヶ月後に15万円上がる設計は採りません
- 引き継ぎ可能な形で残します:属人化せず、後任のエンジニアが同じ水準で運用できるドキュメントとコードを残します
- ユーザー体験から逆算します:コスト削減が画面速度や可用性を犠牲にしないよう、UXから見た安全圏でのみ最適化を進めます
次の一歩:まずは「棚卸し」から
本格着手の前に、まずは現状のAWS利用の棚卸し(AWS Cost Explorer・Trusted Advisor・Compute Optimizer の分析)だけでも、1〜2週間で"見つかる削減余地"が見えます。ここから始めれば、リスクゼロで初月から成果が出る進め方が可能です。
「ウチの構成は最適なんだろうか?」「来年のスケールに今のまま耐えるだろうか?」——そう感じていらっしゃるなら、初回の棚卸しから、お気軽にご相談ください。"寝ているお金"を起こし、事業に回す最初の数字を、一緒に作りましょう。