0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Terraformで構築するAWS FinOps基盤:予算超過・異常検知・月次レポート完全自動化

0
Posted at

はじめに

開発における「プラットフォームのコスト管理」その意識付けをチームに持って貰いたい。また、インフラ運用に組み込みたいと考え本施策を導入しました。

AWSリソースは簡単に構築できる反面、気づかないうちにコストが肥大化するリスク(消し忘れやオーバースペック)があります。そこで今回「コスト抑制・削減」とは別に、開発のスピードを落とさずにコストに対する当事者意識を組織に根付かせる(FinOpsを推進する)ため、今回はTerraformを用いて 「AWSコストの可視化と異常検知」を自動化する基盤を構築しました。

本記事では具体的な実装手法と、IaC(Terraform)運用における実践的なハマりポイント・Tipsを紹介します。

実装したコスト管理施策(FinOps 3つの施策)

今回は以下の3つの施策を実装。結果はSlackに集約・通知するとしました。

  1. 前月の確定請求額通知 (Bill Report)

    • 概要: 毎月最初の平日に、前月の確定請求額(USDおよびJPY概算)とサービス別内訳を自動通知
    • 期待する結果: 開発チーム全体が「自分たちのシステムがいくらかかっているか(固定費)」を定期的に把握できるように。その結果、コストに対する当事者意識の芽生えを期待
  2. コスト異常検知・通知 (Cost Anomaly Detection)

    • 概要: 機械学習により、通常と異なるパターンのコスト増(今回は$10以上)を検知したら即時アラート
    • 期待する結果: リソースの消し忘れ や バグによる意図しないAPIコール急増などによる「想定外の課金」を数時間以内に検知し、実害を最小限に抑える
  3. 予算超過検知・通知 (AWS Budgets)

    • 概要: アカウントの月次予算に対し、実績が90%、または予測が100%を超えた段階で警告
    • 期待する結果: 月の途中でペースオーバーに気づくことができ、月末を待たずに対策を打つ

AWS FinOps構成図.png

☕️ [余談] Slack通知にグラフ画像を添付しようとしていました

パッと見で状況が分かるよう当初の設計案では、以下のようなフローでSlack通知に円グラフ(コストのサービス別割合)の画像を添付する予定でした。

画像生成には、パラメータを渡すだけでグラフ画像を生成してくれる便利な外部サービス「QuickChart(Chart.jsベースの画像生成API)」の利用を検討し、検証環境では実際にグラフ付きの通知(※下図参考)を実装しました。

QuickChart_sample.png

しかし、本番導入にあたってこの機能は意図的に除外しました。

【理由:セキュリティリスクの排除】
QuickChartのパブリックAPIを利用する場合、確定請求額やサービス名といった「自社の機密情報(インフラの内部構成やコスト)」を外部の第三者サーバーに送信する必要があります。
「見栄えを良くするため」だけに、セキュリティ要件やログの保持期間がコントロールできない外部の無償APIに機密データを流すことは、企業コンプライアンスの観点でリスクが高いと判断しました。

もし今後グラフ描画を要件として組み込む場合は、外部APIに頼るのではなく、AWS環境内にQuickChartのOSS版コンテナ(Docker)を自前でホスティングするか、AWSネイティブなCost Explorerのレポート機能をSlack連携する方針をとるべきだと考えています。


実装のポイントとTerraformのTips

今回の実装は単一のAWSアカウントだけでなく「マルチアカウント&Terraform Workspace環境」で安全に運用できるよう設計しています。構築の中で見つけた、いくつかの重要なTipsを共有します。

0. [前提] マルチアカウント利用に関しまして

⚠️下記前提のもと当該FinOps設計、Terraform定義をしております。

  • prd(本番環境) は、 AWSアカウントA に構築
  • それ以外(stg含む)は AWSアカウントB に構築

1. アカウント共通リソースの「二重作成」を防ぐWorkspace制御

BudgetsやAnomaly DetectionはAWSアカウント全体にかかるグローバルな設定です。これを dev, stg, prd といった環境(Workspace)ごとに愚直に terraform apply してしまうと、リソースが重複作成され、アラートが何重にも飛んでくることになります。

これを防ぐため、特定のWorkspaceを「代表(リーダー)」として設定し、条件分岐でリソースを作成する設計にしました。

  • Workspace別フローチャート
  • Terraform該当箇所
locals {
  finops_manager_workspaces = var.target_workspaces # 配列["stg", "prd"]格納

  # 現在のWorkspaceと比較判定 (true/false)
  is_finops_manager         = contains(local.finops_manager_workspaces, local.environment)
}

module "budgets" {
  source = "./modules/budgets"
  
  # trueなら作成、falseなら作成しない
  count  = local.is_finops_manager ? 1 : 0 
  # ...
}

2. 【罠】SNS Topicポリシーの「完全上書き」トラップの回避

AWS Chatbotへの連携としてSNS Topicを利用しますが、Terraformの aws_sns_topic_policy リソースは既存のポリシーを完全に上書きしてしまいます。
マネジメントコンソールから手動でSNSを作成した際に自動付与される「自アカウントに対するデフォルトのフルアクセス権限」が消し飛んでしまうため、以下のように data "aws_iam_policy_document" 内でデフォルト設定を明記した上で、各種サービス(Budgets等)からの許可をマージする必要がありました。

data "aws_iam_policy_document" "sns_topic_policy" {
  # 1. デフォルトのSNSアクセスポリシー(コンソール作成時と同等の必須権限)
  statement {
    sid    = "__default_statement_ID"
    effect = "Allow"
    principals {
      type        = "AWS"
      identifiers = ["*"]
    }
    actions = ["SNS:Publish", "SNS:RemovePermission", "SNS:SetTopicAttributes", "SNS:DeleteTopic", "SNS:ListSubscriptionsByTopic", "SNS:GetTopicAttributes", "SNS:AddPermission", "SNS:Subscribe"]
    resources = [aws_sns_topic.main.arn]
    condition {
      test     = "StringEquals"
      variable = "AWS:SourceOwner"
      values   = [data.aws_caller_identity.current.account_id] # 自アカウント制限
    }
  }

  # ココから権限追加
  # 2. Budgets用ポリシー
  statement {
    sid    = "AllowBudgetsToPublish"
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["budgets.amazonaws.com"]
    }
    actions   = ["SNS:Publish"]
    resources = [aws_sns_topic.main.arn]
  }

  # 3. Cost Anomaly Detection用ポリシー
  statement {
    sid    = "AllowAnomalyDetectionToPublish"
    effect = "Allow"
    principals {
      type        = "Service"
      identifiers = ["costalerts.amazonaws.com"]
    }
    actions   = ["SNS:Publish"]
    resources = [aws_sns_topic.main.arn]
  }
}

3. Lambdaのコードとインフラのライフサイクル分離

前月請求額を通知するLambda関数はPythonで記述していますが、「コードのちょっとした修正でTerraformの差分が出てほしくない」と考えlifecycle ブロックの ignore_changes を活用し、インフラ基盤とアプリケーションコードの関心事を分離しています。初回だけTerraformでデプロイし、以降のコード修正はコンソールや別パイプラインから行えるようにしています。

resource "aws_lambda_function" "reporter" {
  # ...
  lifecycle {
    ignore_changes = [
      filename,
      source_code_hash,
    ]
  }
}

4.【重要】Python 3.14とAWS Providerのバージョン指定

AWS Lambdaで最新の python3.14 を使用する場合、TerraformのAWS Providerのバージョンは v6.21.0 以上である必要がありました。古いバージョン(~> 5.x等)を指定しているとバリデーションエラーになるため、注意が必要です。

実装用IaC(テンプレート)の公開

今回構築したFinOpsモジュールのTerraformコード一式を、GitHubにてOSSテンプレートとして公開しました。
必須変数(SlackのチャンネルIDなど)をご自身の環境に合わせて調整するだけで、すぐにSlackへのコスト通知基盤が構築できるかと思います。
(⚠️但し、前提条件に記載したとおりマルチアカウント用であることにご注意ください)

🔗 terraform-aws-finops-slack-notice

まとめと今後の展望

FinOps基盤をIaC化(module化)したことで新規PJでプラットフォームを構築する際にも、コマンド一発で標準的なコスト監視の仕組みが導入できるようになったかと思います。

今後は「タグベースでのプロジェクト別・機能別のコスト配分予算の設定1」や、「Anomaly Detectionの検知結果をトリガーにした自動修復(Auto-Remediation)2」といった施策にて、よりコスト最適化に寄与出来ればと考えています。

この記事が、クラウドコスト管理に悩むエンジニアやインフラ担当者の参考になれば幸いです。

  1. 例えば、コスト配分タグを利用し環境毎のコストを対象とする等

  2. 例えば、Lambdaの無限ループによる課金急増が発生した場合、EventBridgeにて検知および自動的に該当Lambdaの「同時実行数(Concurrency)」を 0 に設定し、それ以上課金されないようにストップをかける等

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?