はじめに
こんにちは!今回は個人的な資格学習の計画や実績管理のために利用していた自前ホストのRedmineを、AWS LightsailからECS Fargate + Aurora Serverless v2の構成に移行し、「使う時だけ起動する」究極のゼロスケール環境を構築したお話です。
インフラ基盤はすべてTerraformで構成しており、コストの最適化にとどまらず、日々の運用保守(OSアップデート、バックアップ等)の手間を大きく削減することができました。個人の検証環境やツールの維持費用を極限まで抑えたい方の参考になれば幸いです。
背景と抱えていた課題
元々、私は個人の学習時間をEVM(アーンド・バリュー・マネジメント)値で可視化する自作プラグインを開発し、AWS Lightsailの一番安いインスタンス(月額約3.5ドル)にRedmineをホストして運用していました。


月額約600円と決して高い金額ではないのですが、いくつか運用上の課題(モチベーション)がありました。
- コストの割に稼働率が低い: 個人利用のpovo(月5GBで1,100円)などと比較すると、1日1時間未満しかアクセスしないサービスに月額600円は相対的に高く感じてしまう。Lightsailは停止中も課金されるため、クラウドの醍醐味である「使った分だけ」の従量課金にしたかった。
- プラグイン修正のアジリティが低い: 少しの修正でもZIPで固めてSSH接続し、再配置してサービスを再起動する手間がかかっており、GitにPushしたら即反映されるCI/CD構成が欲しかった。
- OS・ミドルウェアのサポート切れ: Lightsailのプリインストール環境は便利ですが、OS(Debian 11)やRedmineのバージョンが古くなっても自動更新されません。手動での検証・移行は面倒です。
- バックアップ取得の面倒さ: 過去に取得した11個の資格学習実績が入っており消えると悲しいのですが、インスタンス丸ごとのバックアップは手間なので、コンピュートとデータを分離してデータだけを簡単・自動にバックアップしたかった。
今回の要件(諦めたこと・割り切ったこと)
上記の課題を解決するため、「個人利用だからこそ可能な割り切り」を徹底しました。これが後述の極端なアーキテクチャ構成を可能にしています。
- 常時起動は不要(1日の利用は1時間未満)
- アクセス時のコールドスタートによる起動遅延(2〜3分)は許容する
- チケットと学習時間の記録のみ行い、ファイル保存の機能は不要
- 特定端末(個人のPCとiPhone)からのみアクセスできればよい
- 構築の手間はいくらかけてもよいが、金は極力かけない
アーキテクチャ構成と選択のポイント
これらを踏まえ、インフラ構成は以下のように決定しました。
-
コンピュート:
ECS Fargate- (AppRunnerは更新頻度の懸念、LambdaはWebAdapterの導入が面倒、EKSは学習コストが高いので除外)
-
データベース:
Aurora Serverless v2- (最小ACUが0になったため、アクセスがない時は完全に課金がストップし、自動バックアップも完備)
-
デプロイメント:
CodePipeline + CodeBuild- (Gitからソースを取得し、自作プラグインを含んだカスタム・イメージをビルドしてECRへPush)
全体アーキテクチャ構成図
構築の中で工夫したこと・ハマったこと
要件を満たすためにいくつか超えなければならない壁がありました。
① ALBが高すぎる問題の解決
通常、Fargate上のコンテナにアクセスさせる場合、前段にALB(Application Load Balancer)を配置して固定エンドポイント化します。しかし、ALBは設置するだけで月額約17ドル(約3000円)固定で発生します。これでは本末転倒です。
そこで、API Gateway (HTTP API) の VPC Link と ECS Service Discovery (AWS Cloud Map) を組み合わせる構成を採用しました。これにより、API Gatewayの固定エンドポイントからVPC内のFargateタスクの動的IPを名前解決し、直接アクセスすることが可能になります。
▼ Terraformでの設定イメージ(抜粋)
# VPC Linkの作成
resource "aws_apigatewayv2_vpc_link" "redmine" {
name = "redmine-vpc-link"
security_group_ids = [aws_security_group.apigw.id]
subnet_ids = module.vpc.public_subnets
}
# HTTP API Proxy統合の作成
resource "aws_apigatewayv2_integration" "redmine" {
api_id = aws_apigatewayv2_api.redmine.id
integration_type = "HTTP_PROXY"
integration_uri = aws_service_discovery_service.redmine.arn # Cloud MapのARNを指定
integration_method = "ANY"
connection_type = "VPC_LINK"
connection_id = aws_apigatewayv2_vpc_link.redmine.id
}
② Fargateの自動起動・自動停止の実現(ゼロスケール機能)
使わない時に確実にFargateのタスクを0にし、アクセスがあった時だけ1に増やす仕組みが必要です。
はじめは「専用のエンドポイントを叩いてLambdaをキックし、タスク数を増減させる」ことも考えましたが、停止し忘れると課金され続けるリスクがありました。
そこで、API Gatewayへのアクセス時に記録される CloudWatch Metricsの「Count」や「5xx(タスク0時のエラー数)」メトリクス を活用しました。
タスクが0の状態でアクセスすると一時的に503エラー等になりますが、そのエラー回数(メトリクス)の発生をCloudWatch Alarmで検知し、Target Tracking機能であるApplication Auto Scalingの「Step Scaling」を発火させてタスク数を1に増やします(スケールアウト)。逆に、一定時間アクセス(Count)が0であればタスク数を0に戻します(スケールイン)。
ここでハマったのが、API Gatewayはアクセスがない時に「データポイント0」が出力されるのではなく「データなし(No Data)」となるため、うまくアラームが状態推移しないことでした。
解決策として、Metric Mathの FILL(raw, 0) を利用して意図的に0のデータポイントを作り出すことで、正常に0へのスケールインを判定できるようにしました。
▼ Terraformでの設定イメージ(抜粋)
# スケールイン用のアラーム
resource "aws_cloudwatch_metric_alarm" "scale_in" {
alarm_name = "redmine-scale-in-alarm"
comparison_operator = "LessThanThreshold"
evaluation_periods = var.idle_time # 一定時間のアイドル待機
threshold = 1
alarm_actions = [aws_appautoscaling_policy.scale_in.arn]
# API GatewayのCountメトリクスを取得
metric_query {
id = "raw"
metric {
metric_name = "Count"
namespace = "AWS/ApiGateway"
# ... 中略 ...
}
}
# FILL関数を使ってNo Dataを0として扱う
metric_query {
id = "filled"
expression = "FILL(raw,0)"
return_data = true
}
}
③ ボットやクローラーによる不要な起動と課金の防止
ゼロスケール構築後に問題が発覚しました。アクセスしていない時間帯にもFargateが起動していたのです。
原因は、登録したドメインに対するクローラーや脆弱性スキャンボットからの無作為なアクセスでした。このアクセスによってメトリクスが上がり、オートスケーリングが発火していました。
対策として以下の3つを検討しました。
- WAFの導入: 最低でも月額約600円かかるため断念。
- Lambda Authorizer: 実装が面倒な上、何を以て認証とするか検討が必要で却下。
- リソースポリシー(IP/UA制限): 自身のアクセス元IPは動的であり、厳格な制御が難しいため却下。
最終的に行き着いたのが、API GatewayのmTLS(相互TLS認証・クライアント証明書認証) の導入です。
個人で利用するPC(Windows)とiPhone(Safari)にクライアント証明書をインストールしておくだけで、証明書を持たない不特定多数のボットのアクセスをAPI Gatewayのフロントレイヤーで弾き落とすことができます。
これにより無駄なAPI呼び出しがバックエンド(メトリクス)に到達せず、不要なコンテナ起動を完全にシャットアウトできました。
▼ Terraformでの設定イメージ(抜粋)
resource "aws_apigatewayv2_domain_name" "redmine" {
domain_name = var.apigw_fqdn
domain_name_configuration {
certificate_arn = data.aws_acm_certificate.redmine.arn
endpoint_type = "REGIONAL"
security_policy = "TLS_1_2"
}
# mTLS(相互TLS認証)の設定
mutual_tls_authentication {
truststore_uri = "s3://${aws_s3_object.truststore.id}"
}
}
初回アクセスから実際に起動するまでの時間(5/23追記)
①2026-05-21T12:38:55.453Z ブラウザでエンドポイントにアクセス(API Gatewayログより)
②2026-05-21T12:39:57.999Z ECS ServiceのDesiredTaskがCW Alarmに0->1に変更(ECSイベントログ)
③2026-05-21T12:40:42.454Z Redmine起動(Redmineの標準出力)
④2026-05-21T12:41:35.447Z Redmineサービス開始(Redmineの標準出力)
⑤2026-05-21T12:41:39.713Z ブラウザでRedmineにはじめてアクセス
起動時間(①-⑤)は約2分45秒でした。うんまぁ個人利用にしては許容できるレベルだと思います。
実際コスト(5/23追記)
- 1回あたりの利用料(目安): 約 $0.03 (約 5円)
- 1ヶ月(30日)の合計: $0.03 × 30日 = $0.9(約 150円)
毎日発生している微小なコスト(青いバー)の正体は、主にAurora Serverless v2の利用量です。データベースのコンピュート(ACU)自体はアクセスがない時は完璧に「0」にスケールインしているため、文字通り「自分が使った数十円分だけを支払う」というクラウドの本質を極限まで体現することができました。

※その他FargateやAPI Gateway等のコストも発生していることが想定されますが、グラフに表示される最小単位以下だと考えられます。
おわりに
Lightsailという固定インスタンスの呪縛から解き放たれ、Fargate + Aurora Serverless v2へ移行した結果、日次で少額しか発生しない「真の使った分だけ課金」基盤に変貌しました。もちろんバックアップは全自動ですし、OSの更新管理からも解放されました。
API GatewayのVPC LinkやmTLS認証を活用するなど、少しピーキーな構成ではありますが、「自分しか使わない」という要件を極めることで、クラウドの恩恵を最大限活かすことができる面白いユースケースになったと思います。
みなさんも個人のちょっとしたツールをサーバーレスで運用する際、ぜひ今回の構成を参考にしてみてください!