AWSコスト集計自動化システム設計書
概要
このドキュメントは、AWS UnblendedCostを基準として、事業部(オンライン診療/在宅/AmazonConnect/共通)× アカウントごとのコストを月次で自動集計し、S3に出力するシステムの設計書です。
さらに、代理店割引後のアカウント別コストを手動で入力して再按分する機能も提供します。
注: 代理店割引後のコストは専用ポータルでのみ確認可能であり、API等での機械的な取得が難しいため、月次で手動入力する運用とします。
目的
背景・課題
現在、AWSコストの集計と事業部別の配分は手作業で行われており、以下の課題が存在します:
集計作業の属人化と工数負担
- 月次で時間のかかる手作業
- 担当者が不在の場合に集計が滞る
再現性と監査性の欠如
- 手作業のため計算ミスのリスク
- 過去月の再集計が困難
目指す状態
このシステムにより、以下の状態を実現します:
自動化による工数削減
- 月次集計作業の短縮
透明性の高いコスト配分
- GitHubで管理されたカテゴリマスタによる一貫した判定
再現性と監査性の確保
- 過去月のデータを同じロジックで再集計可能
何を実現するか
AWSの UnblendedCost を元に、以下を月次で自動集計し、S3に出力する:
1. 事業部 × アカウント別のコスト集計
以下の軸でコストを分類・集計:
対象環境・アカウント:
- 本番環境: 967691968827
- ステージング環境: 301608970378
- 開発環境: 900176301532
- インフラ開発環境: 853790572692 ※特殊処理: 全コストを「共通その他」(100%)に計上
- AmazonConnect環境: 913831226605
- 共通環境: 770217130318
- 保健師環境: 211125417886
- AmazonChimeプロトタイプ: 339712701386
- R&D環境: 226458723831
- 外部連携アカウント: 866741171210
事業部軸:
- オンライン診療事業部(online)
- 在宅事業部(tob: to business)
- AmazonConnect(connect)
- 共通(common)
アウトプット: 事業部 × アカウントのマトリクス形式でコスト配分
2. 「その他」コストのECSネットワーク通信量による按分
入力データ:
- ECS Container Insights から NetworkRxBytes/TxBytes を取得
処理:
- タグなしリソースや共通リソースの「その他」コストを算出
- ネットワーク通信量の比率に基づき、事業部別に按分
アウトプット: 按分前後のコスト内訳と按分根拠
3. 代理店割引後の再按分機能
入力データ:
- 代理店ポータルから手動取得したアカウント別実コスト(円建て)
処理:
- Cost Explorerベースで算出した事業部別割合を維持
- 代理店割引適用後のアカウント合計コストで再配分
アウトプット: 割引適用後の事業部別コスト(円建て)
成果物
Phase 1: monitoring_ce_YYYYMMDD_HHmmss.csv
- Cost Explorerベースの集計結果(USD建て、事業部別割合)
- タグ付きリソースのみ集計
- 実行日時付き
- パス:
output/phase1/YYYY/MM/result/ - 含まれる情報:
- 事業部別コストと割合
- Cost Explorer合計(コメント行)
- 内部計算合計(コメント行)
- 差異(コメント行)
- タグなしコスト(コメント行)
Phase 1: debug/ 配下の中間ファイル群
service_tag_breakdown_*.csv: サービスタグごとのコスト詳細- 通常環境:
サービスタグ,コスト(USD),事業部,カテゴリ - amazon-connect環境:
TAG:Service,TAG:Name,使用したタグ値,コスト(USD),事業部,カテゴリ
- 通常環境:
aws_service_breakdown_*.csv: AWSサービスごとのコスト詳細categorization_details_*.csv: 分類結果の詳細ecs_network_details_*.csv: ECSネットワーク転送量の詳細ecs_allocation_details_*.csv: ECS按分の詳細(直接コスト、按分比率、按分結果、計算式)- パス:
output/phase1/YYYY/MM/debug/
Phase 1.5: manual_allocation_YYYYMMDD_HHmmss.csv
- タグなしリソースの手動配分結果
- Phase 1の結果 + 手動入力分の最終割合
- パス:
output/phase1_5/YYYY/MM/result/
3-1. Phase 1.5: debug/ 配下の中間ファイル群
untagged_resources_categorization_*.csv: タグなしリソース分類詳細(リソースID、コスト、事業部、マッチパターン)resource_pattern_matching_*.csv: パターンマッチング統計(パターン別のマッチ数とコスト合計)untagged_ecs_allocation_*.csv: タグなしリソースのECS按分詳細(事業部別コスト、按分比率、按分結果)- パス:
output/phase1_5/YYYY/MM/debug/
Phase 2: monitoring_adjusted_YYYYMMDD_HHmmss.csv
- 代理店割引適用後の再配分結果(JPY建て)
- 実行日時付き
- パス:
output/phase2/YYYY/MM/DD/result/
Slack通知
- 集計完了通知
- 差分アラート
- エラー通知
- タグなしコスト通知(Phase 1.5への手動入力を促す)
前提・方針
コスト指標・対象
- 使用するコスト指標: UnblendedCost
- Savings Plan: なし
- Tax: 集計対象外
- Cost Explorerと実際の請求額の差分:
- 差分は許容し、「事業部ごとの配分割合」が一貫していることを重視する
- 手動で代理店割引後のコストで再按分するため
データ取得・期間
- Cost Explorer API: GetCostAndUsage APIでコストデータを取得
- データ反映期間: 最長48時間(2日)
- 月次集計は毎月2日に実行(前月データが確実に反映済み)
- 期間の定義: AWSのAPIの仕様(UTC基準)に準拠する
- タグの取得: Cost Explorer APIから
TAG:Serviceでグループ化して取得- 注意: Cost Explorer APIは
Service$プレフィックス付きで返すため、コード内で除去する
- 注意: Cost Explorer APIは
ECS環境
- 起動タイプ: Fargate
- Container Insights: 有効化必須
- ECSネットワークメトリクスの取得に必要
- 各クラスターで有効化されていること
- Namespace:
ECS/ContainerInsightsを使用 - ネットワークメトリクス:
NetworkRxBytes(受信)とNetworkTxBytes(送信)の合計値を使用- 按分基準は双方向の通信量合計とする
- メトリクス保持期間:
- 1時間間隔(Period=3600秒): 455日間保持
- 過去データの分析には1時間間隔を使用
- 必須Dimensions:
ServiceName: ECSサービス名ClusterName: ECSクラスター名- 両方の指定が必要(どちらか一方では取得不可)
カテゴリ・タグ・ネットワーク
- TAG:Service の運用ルール: 別途定めたルールに従う(本設計書では詳細に触れない)
- カテゴリ判定ルール: GitHubで管理(
config/categories.yaml)- 対象カテゴリ(4種類):
- オンライン診療事業部(online)
- 在宅事業部(tob: to business)
- AmazonConnect(connect)
- 共通(common)
- 管理主体: SREチーム
- 変更は「変更した月から適用」とし、過去分への遡及は行わない
- 対象カテゴリ(4種類):
- 未分類コスト(共通その他):
- いずれのカテゴリにもマッチしない行は、**「共通その他」**として吸収する
- 新規リソース検知:
- 未分類コストが発生した場合、詳細(TAG:Service名、AWS Service名、金額)をSlackに通知
- 通知内容には、カテゴリマスタ更新が必要かどうかの判断材料を含める
- 未分類比率が高い場合(例: アカウント総コストの10%超)は警告レベルで通知
- カテゴリマスタ更新フロー:
- Slack通知で未分類リソースを確認
config/categories.yamlをPRで更新- マージ後、GitHub Actions の手動ワークフロー(
workflow_dispatch)で対象月を再集計 - 更新された結果がS3に再出力される
- ECSネットワーク按分の判定:
- ECSサービスのTAG:ServiceまたはECSサービス名を
config/categories.yamlのルールでマッチング - 判定されたカテゴリ(online/tob/connect/common)に基づいてネットワーク按分を実施
- 別ファイル(
ecs-network-categories.yaml)は不要:categories.yamlで統一管理
- ECSサービスのTAG:ServiceまたはECSサービス名を
タグなしリソースの扱い
背景:
- 当初はCost Explorer APIからタグなしリソースも自動で分類する設計だったが、Cost Explorer APIではリソースID(ARN等)の取得に制約があるため、現在は手動運用に変更
- 将来的な自動化方針: AWS CUR(Cost and Usage Report)のS3エクスポートを活用してリソースID単位のコスト取得を実現予定
現在の処理方法(Phase 1):
- タグなしリソース(
Service$またはNoTag)はスキップ untagged_costとして別途記録し、結果CSVにコメント行として出力- 中間ファイル(
debug/)には含まれない(タグ付きリソースのみ出力)
Phase 1.5(手動配分)の運用:
- Phase 1実行後、タグなしコスト金額を確認
- Megazone Hyper Billingからタグなしリソースの一覧CSV (
billing.exported.csv) をダウンロード - S3の指定パスにアップロード:
output/phase1_5/YYYY/MM/no_tag_resource_list/billing.exported.csv - GitHub Actionsで
workflow_dispatchを実行 - Phase 1.5スクリプト (
phase1_5_step1_allocate_untagged_resources.py) がCSVを読み込み、リソースIDパターンマッチングで事業部を判定 - Phase 1の結果に加算して最終割合を算出し、
output/phase1_5/YYYY/MM/result/に出力
Phase 1.5の入力ファイルフォーマット (Megazone Hyper Billing形式):
ym,projectName,usageAccountId,productCode,TAG:Service,TAG:Name,resourceId,unblendedCost
2025-11,p82516-220391,967691968827,AmazonS3,,,arn:aws:s3:::fd-platform-logs,123.45
2025-11,p82516-220391,967691968827,AWSLambda,,,arn:aws:lambda:ap-northeast-1:123456789012:function:online-notification,45.67
2025-11,p82516-220391,967691968827,AmazonEC2,,,i-0abcdef1234567890,234.56必須カラム:
- ym: 年月(例:
2025-11) - projectName: プロジェクト名
- usageAccountId: AWSアカウントID
- productCode: AWSサービス名(例:
AmazonS3,AWSLambda) - TAG:Service: Serviceタグの値(タグなしリソースの場合は空白)
- TAG:Name: Nameタグの値(タグなしリソースの場合は空白)
- resourceId: リソースID(ARN、リソース名、インスタンスID等)
- unblendedCost: コスト(USD)
処理:
- ResourceIdのパターンマッチングで事業部を自動判定
- マッチしない場合は
commonに分類
Phase 2での処理:
- Phase 1.5の結果(タグ付き + タグなし の最終割合)を使用して代理店割引後の再配分を実施
将来の自動化(CURベース):
- **CUR(Cost and Usage Report)**をS3にエクスポート
- CURにはリソースID(ARN等)とコストが含まれる
- Phase 1の処理を以下のように変更予定:
- Cost Explorer API: タグ付きリソースのコストを集計
- CUR S3エクスポート: タグなしリソースのリソースIDとコストを取得
- リソースIDパターンマッチング(既に実装済み)で事業部を自動判定
- タグ付き + タグなし を統合して最終結果を出力
- Phase 1.5は不要になる
実装済みのリソースID判定ロジック:
_categorize_by_resource_id()メソッドが実装済み- パターン例(一部抜粋):
- online事業部: mental-, online-, dr-, chronic-, orca, pf-account, fd-video 等
- tob事業部: cp-front, bsys-front, fd-sys, fd-mwaa, movacal, fdm 等
- connect事業部: amazon-connect, connect-, ccp-, amazonconnect 等
- common: fd-platform, fd-redash, cost-monitoring 等
- 詳細は
.github/config/categories.yamlを参照
丸め・レート・誤差
- 丸め・端数処理:
- USD: 小数点以下2桁で丸め(例:
11576.72) - JPY: 小数点以下2桁で丸め(例:
1620740.93) - 割合: 内部計算は小数点以下4桁精度(例:
0.5639)、表示は2桁(例:56.39%)
- USD: 小数点以下2桁で丸め(例:
- 為替レート:
- USD→JPYのレートは、GitHub Actionsの環境変数として指定する(例:
FX_RATE_USD_JPY=140.0) - レートを変更したい場合は、ワークフローのenvを修正して反映する
- USD→JPYのレートは、GitHub Actionsの環境変数として指定する(例:
- 代理店割引による差分:
- AWSとの契約には代理店が入り、割引が適用されるため、Cost Explorerの数値と実際の請求額に差異が発生する
- この差分は許容し、事業部ごとの按分割合が一貫していることを重視する
- 月次自動実行時は Cost Explorer の数値をもとに算出
- 代理店割引後のコスト取得方法:
- 代理店の専用ポータルでのみ確認可能
- API等での機械的な取得が難しい
- 運用: 月次で人間が専用ポータルから取得し、GitHub Actions(
workflow_dispatch)経由で手動入力 - 入力後、事業部別割合を使って自動再按分
- Cost Explorerのアカウント合計と内部計算した合計がズレた場合:
- 計算結果と差分をまとめてSlackに通知するだけとし、自動補正は行わない
- 対応要否は人間が判断する
システムアーキテクチャ
インフラ全体構成
処理フロー
カテゴリマスタと判定ルール
カテゴリファイル(GitHub管理)
GitHubリポジトリ内にカテゴリファイルを配置します。環境によって異なるファイルを使用します。
通常環境用: config/categories.yaml
対象アカウント: amazon-connect環境(913831226605)以外の全アカウント
判定方法:
- TAG:Serviceまたはリソース名に対して、
patternsリストの文字列部分一致で判定 - 最初にマッチしたカテゴリを採用
- どのカテゴリにもマッチしない場合は「共通その他」
構造イメージ(クリックして展開)
departments:
- key: "online"
name: "オンライン診療事業部"
- key: "tob"
name: "在宅事業部"
- key: "connect"
name: "AmazonConnect"
- key: "common"
name: "共通"
categories:
# オンライン診療事業部
- name: "mental-appointment"
department: "online"
patterns:
- "mental-appointment"
- "mental-"
- name: "online-karte"
department: "online"
patterns:
- "online-karte"
- "karte-"
- name: "online-patient-mypage"
department: "online"
patterns:
- "online-patient-mypage"
- "patient-mypage"
# 在宅事業部
- name: "cp-front"
department: "tob"
patterns:
- "cp-front"
- "cp-"
- name: "bsys-front"
department: "tob"
patterns:
- "bsys-front"
- "bsys-"
# 共通
- name: "fd-redash"
department: "common"
patterns:
- "fd-redash"
- "redash"
# AmazonConnect
- name: "amazon-connect"
department: "connect"
patterns:
- "amazon-connect"
- "connect-"
# 以下同様にカテゴリを定義...フィールド説明:
name: カテゴリの識別名department: 所属事業部(online/tob/connect/common)patterns: マッチングパターンのリスト(文字列部分一致)
判定ロジック(amazon-connect環境以外):
- Cost Explorerから取得した
TAG:Service(プレフィックス除去済)またはリソース名を取得 - categories.yamlの順番に各カテゴリの
patternsと照合 - いずれかのパターンに部分一致すれば、そのカテゴリの
departmentを採用 - 例:
online-karte-service→"online-karte"パターンにマッチ →department: "online"online-db-instance→"online-karte"パターンにマッチ →department: "online"cp-front-api→"cp-front"パターンにマッチ →department: "tob"
amazon-connect環境用: config/amazon-connect-categories.yaml
対象アカウント: amazon-connect環境(913831226605)のみ
判定方法:
- TAG:ServiceまたはTAG:Nameあり: 通常環境と同じpatternsベースで判定
- タグなし: AWS Service名で
aws_servicesと照合して判定 - どちらにもマッチしない: 「共通その他」
構造イメージ(クリックして展開)
departments:
- key: "online"
name: "オンライン診療事業部"
- key: "connect"
name: "AmazonConnect"
- key: "common"
name: "共通"
# タグありの場合の通常ルール(通常環境と同じ)
categories:
- name: "fd-system"
department: "common"
patterns:
- "fd-system"
- name: "debt-manager"
department: "online"
patterns:
- "debt-manager"
- name: "lp"
department: "online"
patterns:
- "lp"
- name: "member-service"
department: "online"
patterns:
- "member-service"
- name: "amazon-connect-tagged"
department: "connect"
patterns:
- "amazon-connect"
- "connect-"
# タグなしAWS Service判定(完全一致)
- name: "amazon-connect-services"
department: "connect"
aws_services:
- "Amazon Connect"
- "Contact Center Telecommunications (service sold by AMCS, LLC)"
- "Contact Lens for Amazon Connect"
- "Amazon Transcribe"
- "CloudWatch Events"
- "AWS Glue"
- "AWS Lambda"
- "Amazon API Gateway"
- "Amazon DynamoDB"
- "EC2 - Other"
- "Amazon Elastic Compute Cloud - Compute"
- "Amazon Kinesis"
- "Amazon Kinesis Firehose"
- "Amazon Lightsail"
- name: "tob-services"
department: "tob"
aws_services:
- "Amazon ElastiCache"
- "Amazon Simple Email Service"
- "CodeBuild"
- name: "online-services"
department: "online"
aws_services:
- "AWS Amplify"
- "AWS Backup"
- "AWS CodePipeline"
- "AWS Config"
- "AWS WAF"
- "AWS Secrets Manager"
- "Amazon Elastic Load Balancing"
- "Amazon Elastic File System"
- "Amazon GuardDuty"
- "Amazon Inspector"
- "Amazon Virtual Private Cloud"
- name: "common-services"
department: "common"
aws_services:
- "Tax"
- "AWS Certificate Manager"
- "AWS CloudTrail"
- "AWS Cost Explorer"
- "AWS Key Management Service"
- "AWS Payment Cryptography"
- "AWS Security Hub"
- "AWS Step Functions"
- "Amazon CloudFront"
- "Amazon DevOps Guru"
- "Amazon EC2 Container Registry (ECR)"
- "Amazon Location Service"
- "Amazon Macie"
- "Amazon Route 53"
- "Amazon Simple Notification Service"
- "Amazon Simple Queue Service"
- "Claude Sonnet 4.5 (Amazon Bedrock Edition)"フィールド説明:
categories: カテゴリ定義リストname: カテゴリ名department: 所属事業部(online/tob/connect/common)patterns: TAG:ServiceまたはTAG:Nameの部分一致パターン(タグ判定用)aws_services: AWS Service名の完全一致リスト(タグなし判定用)
判定ロジック(amazon-connect環境 913831226605):
- TAG:ServiceまたはTAG:Nameあり:
categoriesセクションのpatternsで判定(通常環境と同じ)- 例:
TAG:Service=fd-system→fd-systemカテゴリ - 例:
TAG:Name=debt-manager-production→debt-managerカテゴリ
- 例:
- タグなし:
aws_servicesで判定- 例: AWS Service=
Amazon Connect→amazon-connect-servicesカテゴリ →connect事業部 - 例: AWS Service=
Amazon Pinpoint→online-servicesカテゴリ →online事業部 - 例: AWS Service=
AWS Amplify→online-servicesカテゴリ →online事業部
- 例: AWS Service=
- どちらにもマッチしない: 「共通その他」
オンライン診療事業部カテゴリ例
以下のサービスをカテゴリマスタに定義:
- mental-appointment
- mental-appointment-bff
- mental-interview
- mental-notification
- mental-reserve-admin
- mental-reserve-patient
- chronic-api
- online-karte-front
- online-karte-service
- online-karte-websocket
- online-patient-mypage
- online-patient-mypage-cdn
- online-video
- debt-manager
- eligibility-verification
- fd-office-connection
- payment-service
- payment-service-bff
- ai-triage
- fd-app-bff
- fd-app-webview-cdn
- retool-bastion
- retool-debt-manager
- retool-self-hosted
- retool-self-hosted-jobs-runner
- pharme-ctop
- dr-announcement
- dr-announcement-bff
- dr-app-bff
- dr-community-bff
- dr-gas-bff
- dr-karte-bff
- dr-shift
- dr-shift-bff
- dr-site-bff
- dr-work-record
- dr-work-record-bff
- dr-worker-information
- dr-worker-information-bff
- dr-community
- member-service
- osaka-gas
- online
- lp
注: 在宅事業部、共通、AmazonConnect のカテゴリも同様に categories.yaml に記述
ECSネットワーク按分の判定方法
判定ロジック:
- ECSサービスのTAG:Serviceまたはサービス名を
categories.yamlのルールでマッチング - マッチしたカテゴリ(online/tob/connect/common)に基づいてネットワークbytesを集計
例:
online-karte-service→ categories.yamlでonlineと判定 →online_bytesに加算cp-front-service→ categories.yamlでtobと判定 →tob_bytesに加算fd-redash-service→ categories.yamlでcommonと判定 →other_ratioに影響
ルール適用の優先順位
- カテゴリ判定ルールは、カテゴリファイルに記述された順に評価
- 最初にマッチしたカテゴリを採用
- マッチしなかった行は「共通その他」に集計
集計ロジック
ステップ1: 基礎配分(CEベース)
1.1 対象月の決定
- GitHub Actionsのジョブ内で
target_month(例:2025-08)を決定 - 自動実行(スケジュール)は「実行日の前月」を対象とする
1.2 アカウント合計(母数)取得
Cost Explorer APIから UnblendedCost を取得:
aws ce get-cost-and-usage \
--region us-east-1 \
--time-period Start="2025-08-01",End="2025-09-01" \
--granularity MONTHLY \
--metrics UnblendedCost \
--group-by Type=DIMENSION,Key=LINKED_ACCOUNTここで得た値を account_total_cost_ce[AccountId] とする。
重要: Cost Explorer APIは us-east-1 リージョンでのみ利用可能
1.3 TAG:Service別コスト取得
Cost Explorer APIから TAG:Service でグループ化したコストを取得:
通常環境(amazon-connect以外):
aws ce get-cost-and-usage \
--region us-east-1 \
--time-period Start="2025-08-01",End="2025-09-01" \
--granularity MONTHLY \
--metrics UnblendedCost \
--group-by Type=DIMENSION,Key=LINKED_ACCOUNT Type=TAG,Key=Serviceamazon-connect環境(913831226605)専用:
aws ce get-cost-and-usage \
--region us-east-1 \
--time-period Start="2025-08-01",End="2025-09-01" \
--granularity MONTHLY \
--metrics UnblendedCost \
--group-by Type=DIMENSION,Key=LINKED_ACCOUNT Type=TAG,Key=Service Type=TAG,Key=Nameamazon-connect環境での処理:
- TAG:ServiceとTAG:Nameの両方を同時に取得
- 優先順位: TAG:Service → TAG:Name(TAG:Serviceが空の場合はTAG:Nameを使用)
- APIレスポンス例:
['913831226605', 'Service$', 'Name$debt-manager-production']- TAG:Serviceが空 (
Service$) の場合、TAG:Name (debt-manager-production) を使用
- TAG:Serviceが空 (
取得データ:
- アカウントID × TAG:Service のコスト(通常環境)
- アカウントID × TAG:Service × TAG:Name のコスト(amazon-connect環境)
- タグ未設定リソースは
$または空文字列としてまとめられる
重要な注意点(プレフィックス処理): Cost Explorer APIは、TAGでグループ化した際にService$というプレフィックスを付けて返します。
API返り値の例:
{
"Keys": ["967691968827", "Service$online-karte-service"],
"Metrics": {"UnblendedCost": {"Amount": "1234.56", "Unit": "USD"}}
}処理が必要:
- 返り値:
"Service$online-karte-service" - 処理後:
"online-karte-service"←Service$プレフィックスを除去 - categories.yamlとのマッチングには、プレフィックスを除去した値を使用
1.4 インフラ開発環境の特殊処理
対象アカウント: 853790572692(インフラ開発環境)
インフラ開発環境は、インフラのテスト・検証専用環境であり、事業部別のコスト按分が不要です。 そのため、以下の特殊処理を適用します:
処理内容:
- 直接コスト: 全事業部で
$0に設定 - その他ベースコスト: アカウント全体のコスト(100%)
- ECSネットワーク按分: スキップ(按分比率
other=100%) - 最終コスト: 全額を「共通その他」として計上
理由:
- インフラ開発環境は特定の事業部に紐づかない
- 定期的にリセットされる環境であり、事業部別配分が意味を持たない
- コスト集計の簡素化と正確性の向上
注意: 他の環境(本番、ステージング、開発等)には通常の按分ロジックが適用されます
1.5 カテゴリ判定
Cost Explorerから取得した各コスト行について、カテゴリファイルのルールを適用し振り分け:
判定方法(amazon-connect環境以外、かつ インフラ開発環境以外):
使用ファイル: config/categories.yaml
- TAG:Serviceあり: TAG:Serviceの値(プレフィックス除去後)を
categories.yamlのpatternsで文字列部分一致判定- 例:
online-karte-service→online-パターンにマッチ → オンライン診療事業部
- 例:
- TAG:Serviceなし: リソース名(例: ECSサービス名、RDSインスタンス名)を
categories.yamlのpatternsで文字列部分一致判定- 例:
online-db-instance→online-パターンにマッチ → オンライン診療事業部
- 例:
- いずれにもマッチしない: **「共通その他」**として吸収
判定方法(amazon-connect環境 913831226605):
使用ファイル: config/amazon-connect-categories.yaml
判定優先順位:
TAG:ServiceまたはTAG:Nameあり →
categoriesセクションのpatternsで部分一致判定- 例:
TAG:Service=fd-system→fd-systemカテゴリ →tob事業部 - 例:
TAG:Name=debt-manager-production→debt-managerカテゴリ →online事業部 - TAG:Serviceが空でTAG:Nameがある場合、TAG:Nameを使用
- 例:
タグなし + CloudWatch/ECS/RDS/S3 → Phase 1.5送り(Phase 1ではスキップ)
- 対象AWS Service:
Amazon CloudWatchAmazon Elastic Container ServiceAmazon Relational Database ServiceAmazon Simple Storage Service
- これらはPhase 1.5でECS按分により事業部に配分
- 対象AWS Service:
タグなし + その他AWS Service →
categoriesセクションのaws_servicesで完全一致判定- 例: AWS Service=
Amazon Connect→amazon-connect-servicesカテゴリ →connect事業部 - 例: AWS Service=
Amazon API Gateway→amazon-connect-servicesカテゴリ →connect事業部 - 例: AWS Service=
Amazon DynamoDB→amazon-connect-servicesカテゴリ →connect事業部
- 例: AWS Service=
いずれにもマッチしない → **「共通その他」**として吸収
振り分け先:
- オンライン診療事業部
- 在宅事業部
- AmazonConnect
- 共通
- 共通その他(未分類)
1.6 事業部×アカウント別の直接コスト
振り分け済みの行を (usageAccountId, department) 単位で UnblendedCost を合計。 これを 直接コスト とする。
direct_cost_online[account]
direct_cost_tob[account]
direct_cost_connect[account]
direct_cost_common[account]1.7 「その他ベース」コストの算出
アカウントごとに:
other_base_cost[account] =
account_total_cost_ce[account]
- direct_cost_online[account]
- direct_cost_tob[account]
- direct_cost_connect[account]注: 共通系(共通 + 共通その他)は other_base の中に含まれる扱い
インフラ開発環境(853790572692)の場合: other_base_cost = account_total_cost_ce(アカウント全体の100%)
1.8 ECSネットワークbytesの集計
CloudWatch Container Insightsから NetworkRxBytes(受信)と NetworkTxBytes(送信)を ECSサービス単位で Sum 集計(対象月):
# NetworkRxBytes(受信)の取得
aws cloudwatch get-metric-statistics \
--namespace ECS/ContainerInsights \
--metric-name NetworkRxBytes \
--dimensions Name=ServiceName,Value=online-karte-service Name=ClusterName,Value=online-karte-cluster \
--start-time 2025-08-01T00:00:00Z \
--end-time 2025-09-01T00:00:00Z \
--period 3600 \
--statistics Sum
# NetworkTxBytes(送信)の取得
aws cloudwatch get-metric-statistics \
--namespace ECS/ContainerInsights \
--metric-name NetworkTxBytes \
--dimensions Name=ServiceName,Value=online-karte-service Name=ClusterName,Value=online-karte-cluster \
--start-time 2025-08-01T00:00:00Z \
--end-time 2025-09-01T00:00:00Z \
--period 3600 \
--statistics Sum重要な設定:
- Namespace:
ECS/ContainerInsightsを使用(AWS/ECSではネットワークメトリクスが取得できない) - Dimensions:
ServiceNameとClusterNameの両方を必須で指定 - メトリクス:
NetworkRxBytesとNetworkTxBytesの両方を取得し合計- 按分基準: 受信と送信の双方向通信量の合計値を使用
- Period:
3600秒(1時間間隔)を使用- 過去データ(最大455日前まで)の取得に対応
- 5分間隔(300秒)は63日間のみ保持
- 日次間隔(86400秒)は15日間のみ保持
前提条件:
- ECSクラスターでContainer Insightsが有効化されていること
- Fargateタスクが実行されていること
カテゴリ判定:
- ECSサービスのTAG:Serviceまたはサービス名を
categories.yamlのルールでマッチング - 判定されたカテゴリ(online/tob/connect/common)ごとにネットワークbytesを集計
- 以下を算出:
online_bytes[account]
tob_bytes[account]
total_ecs_bytes[account]インフラ開発環境(853790572692)の場合: ECS按分をスキップ
1.9 ネットワーク比率の算出
online_ratio[account] = online_bytes[account] / total_ecs_bytes[account]
tob_ratio[account] = tob_bytes[account] / total_ecs_bytes[account]
other_ratio[account] = 1.0 - online_ratio[account] - tob_ratio[account]注:
total_ecs_bytesが 0 の場合は、按分せず「その他ベース」を全て「共通その他」に振るother_ratioは、オンライン系・在宅系のどちらにも分類されないECSネットワークの割合(fd-redash等の共通系サービス)
インフラ開発環境(853790572692)の場合:
online_ratio = 0
tob_ratio = 0
connect_ratio = 0
other_ratio = 1.0 # 100%1.10 「その他」の按分
アカウントごとに、ネットワーク比率に基づいて「その他ベース」コストを按分:
other_for_online = other_base_cost[account] * online_ratio[account]
other_for_tob = other_base_cost[account] * tob_ratio[account]
other_rest = other_base_cost[account] * other_ratio[account]直接コストに加算して最終コストを算出:
final_cost_online[account] = direct_cost_online[account] + other_for_online
final_cost_tob[account] = direct_cost_tob[account] + other_for_tob
final_cost_connect[account] = direct_cost_connect[account] (その他は按分しない)
final_cost_common_other[account] = other_rest + direct_cost_common[account]計算例(クリックして展開)
# 直接コスト
direct_cost_online[account] = 10,000 USD
direct_cost_tob[account] = 5,000 USD
# その他ベースコスト
other_base_cost[account] = 3,000 USD
# ネットワーク比率
online_ratio[account] = 0.6 # 60%
tob_ratio[account] = 0.3 # 30%
other_ratio[account] = 0.1 # 10%
# 按分計算
other_for_online = 3,000 * 0.6 = 1,800 USD
other_for_tob = 3,000 * 0.3 = 900 USD
other_rest = 3,000 * 0.1 = 300 USD
# 最終コスト
final_cost_online = 10,000 + 1,800 = 11,800 USD
final_cost_tob = 5,000 + 900 = 5,900 USD
final_cost_common_other = 300 USD按分の内訳:
other_for_online: オンライン系ECSネットワーク比率で按分された「その他」コストother_for_tob: 在宅系ECSネットワーク比率で按分された「その他」コストother_rest: 上記以外の「その他」コスト(共通系ECSや按分対象外)→「共通その他」へ
1.11 JPY換算
環境変数 FX_RATE_USD_JPY を参照し:
# USD → JPY 換算
final_cost_*.jpy = round(final_cost_*.usd * FX_RATE_USD_JPY, 2)
# 例: FX_RATE_USD_JPY = 140.0
# final_cost_online.usd = 11576.72
# final_cost_online.jpy = round(11576.72 * 140.0, 2) = 1620740.801.12 事業部別割合の計算
アカウントごとの合計:
# アカウントごとの総コスト(USD)
total_account_cost_ce[account] = (
final_cost_online[account].usd +
final_cost_tob[account].usd +
final_cost_connect[account].usd +
final_cost_common_other[account].usd
)
# 事業部別割合(4桁精度)
dept_ratio[account][department] = round(
final_cost_dept[account].usd / total_account_cost_ce[account],
4
)
# 例:
# final_cost_online = 17862.65
# total_account_cost_ce = 20528.09
# dept_ratio[online] = round(17862.65 / 20528.09, 4) = 0.87021.13 出力(CEベース結果)
S3にCSV形式で保存:
S3ディレクトリ構造:
s3://cost-monitoring-monthly-${env}/
└── output/
└── phase1/
└── YYYY/
└── MM/
└── DD/
├── result/ # メイン結果
│ └── monitoring_ce_YYYYMMDD_HHmmss.csv
└── debug/ # 中間ファイル(検証用)
├── service_tag_breakdown_YYYYMMDD_HHmmss.csv
├── aws_service_breakdown_YYYYMMDD_HHmmss.csv
├── categorization_details_YYYYMMDD_HHmmss.csv
└── ecs_network_details_YYYYMMDD_HHmmss.csvメイン結果ファイル:
output/phase1/YYYY/MM/result/monitoring_ce_YYYYMMDD_HHmmss.csv- 実行日時(JST)を含めることで、1日に複数回実行しても一意なファイル名になる
中間ファイル(デバッグ用):
debug/service_tag_breakdown_*.csv: サービスタグごとのコスト詳細debug/aws_service_breakdown_*.csv: AWSサービスごとのコスト詳細debug/categorization_details_*.csv: 分類結果の詳細debug/ecs_network_details_*.csv: ECSネットワーク転送量の詳細
例:
s3://cost-monitoring-monthly-production/output/phase1/2025/08/03/result/monitoring_ce_20250803_090512.csvs3://cost-monitoring-monthly-production/output/phase1/2025/08/03/result/monitoring_ce_20250803_143022.csv(再実行)
出力カラム:
事業部別コスト詳細(USD建て、事業部 × アカウントの明細):
事業部,アカウント,アカウントID,年月,料金(USD),料金割合
オンライン診療事業部,本番環境,967691968827,2025-08,17862.65,0.8702
在宅事業部,本番環境,967691968827,2025-08,69.41,0.0034
AmazonConnect,本番環境,967691968827,2025-08,0.00,0.0000
共通その他,本番環境,967691968827,2025-08,2596.04,0.1264
オンライン診療事業部,ステージング環境,301608970378,2025-08,10951.61,0.9439
在宅事業部,ステージング環境,301608970378,2025-08,650.53,0.0561
AmazonConnect,ステージング環境,301608970378,2025-08,0.00,0.0000
共通その他,ステージング環境,301608970378,2025-08,0.00,0.0000注: Phase 1(Cost Explorerベース)の結果はUSD建てのみ、JPY列は含まない
1.14 Cost Explorer合計との乖離チェック
total_account_cost_ce と Cost Explorer から取得した account_total_cost_ce を比較し:
- 許容しきい値は設けず、単に「差分」の一覧を作ってSlackに通知する
ステップ2: 代理店割引後コストの再配分(手動)
2.1 目的
AWSとの契約には代理店が入り、割引が適用されるため、Cost Explorerの数値と実際の請求額に差異が発生します。
代理店の**専用ポータルから確認できる実際の請求額(割引適用後)**をもとに、事業部別のコストを再計算する機能です。
データ取得の制約:
- 代理店割引後のコストは専用ポータルでのみ確認可能
- API等での自動取得が難しい
- そのため、月次で手動入力する運用フローを採用
2.2 入力方法
GitHub Actions の workflow_dispatch を使い、以下を入力して実行:
入力パラメータ:
target_month(例:2025-08)- 対象月(YYYY-MM形式)account_cost_json(文字列)- アカウント別の割引適用後コスト(JSON形式)
フォーマット(固定):
{
"967691968827": 0,
"301608970378": 0,
"900176301532": 0,
"853790572692": 0,
"913831226605": 0,
"770217130318": 0,
"211125417886": 0,
"339712701386": 0,
"226458723831": 0,
"866741171210": 0
}入力方法:
- アカウントIDは固定(全10アカウント)
- 各アカウントの値(0の部分)に代理店割引適用後のコスト(円建て)を手動で入力
- 例:
"967691968827": 2800000→ 本番環境のコストが2,800,000円
値の意味:
967691968827: 本番環境301608970378: ステージング環境900176301532: 開発環境853790572692: インフラ開発環境913831226605: AmazonConnect環境770217130318: 共通環境211125417886: 保健師環境339712701386: AmazonChimeプロトタイプ226458723831: R&D環境866741171210: 外部連携アカウント
注意:
- ステップ1で算出された事業部別割合を使用して再配分するため、ステップ1が先に実行されている必要がある
2.3 ロジック
- ステップ1で出力した
monitoring_ce.csvをS3から取得 dept_ratio[account][department]をロード- ユーザー入力の
account_cost_jsonをパースし、adjusted_total_jpy[account]を得る - アカウントごとに事業部別コストを再計算:
# 代理店割引適用後のコスト再配分
adjusted_cost_dept[account][department].jpy = round(
adjusted_total_jpy[account] * dept_ratio[account][department],
2
)
# 例:
# adjusted_total_jpy["967691968827"] = 2800000(代理店割引適用後)
# dept_ratio["967691968827"]["online"] = 0.8702
# adjusted_cost_online = round(2800000 * 0.8702, 2) = 2436560.00端数調整:
- 各事業部の合計とアカウント総コストの差分(端数)を計算
- 差分が発生した場合は、最も金額の大きい事業部に加算/減算
再配分された結果を
monitoring_adjusted_YYYYMMDD_HHmmss.csvとして S3 に保存
S3ディレクトリ構造:
s3://cost-monitoring-monthly-${env}/
└── output/
└── phase2/
└── YYYY/
└── MM/
└── DD/
└── result/ # メイン結果
└── monitoring_adjusted_YYYYMMDD_HHmmss.csv- パス:
s3://cost-monitoring-monthly-${env}/output/phase2/YYYY/MM/DD/result/monitoring_adjusted_YYYYMMDD_HHmmss.csv - 実行日時(YYYYMMDD_HHmmss)を含める
- 例:
s3://cost-monitoring-monthly-production/output/phase2/2025/08/15/result/monitoring_adjusted_20250815_143022.csv
- 生成完了を Slack に通知(対象月・合計金額・割引率などを添えて)
運用フロー
月次コスト集計の流れ
Phase 1: 自動集計(タグ付きリソースのみ、毎月2日)
- GitHub Actions が自動実行(Cost Explorerベース)
- S3に
output/phase1/YYYY/MM/result/monitoring_ce_YYYYMMDD_HHmmss.csvを出力 - S3に中間ファイル(デバッグ用)を
output/phase1/YYYY/MM/debug/に出力 - Slack通知でチームに完了を報告(タグなしコストがある場合は通知に含める)
実行日の根拠:
- Cost Explorerのデータ反映: 最長48時間(2日)
- 月初から2日後であれば、前月の全データが確実に反映済み
Phase 1完了後のSlack通知:
- 事業部別コスト集計結果のサマリー
- CSVファイル添付(スレッド): Phase 1の詳細結果CSV
- 作業者はこのCSVをダウンロードし、スプレッドシートにインポートします
- 次のアクション: スプレッドシートでタグなしリソースの按分作業を実施
重要: Slack通知とCSVファイルの一致:
- Slack通知の事業部別割合とCSVファイルの割合は完全一致します
- 通知とCSV両方で「内部計算合計(タグ付きリソースのみ)」を分母として使用
- Cost Explorer合計は参考値として別途表示
- これにより、Slack通知を見るだけでCSVファイルの内容が正確に把握できます
注意: Phase 1ではタグ付きリソースのみを集計します。タグなしリソースは現在、スプレッドシートで手動按分しています。
現行運用: スプレッドシートによる手動按分
Phase 1完了後、非エンジニア(経理・財務担当者)がスプレッドシートを使用してタグなしリソースの按分を行います。
運用フロー:
Phase 1結果CSVの取得
- Slack通知のスレッドに添付されたCSVファイルをダウンロード
- ファイル名例:
monitoring_ce_20251223_020240.csv
スプレッドシートにPhase 1結果をインポート
- 事業部別コスト(タグ付きリソースのみ)
- 各事業部の按分比率
Megazone Hyper Billingからタグなしリソース一覧を取得
- Cost Pivot画面で保存済みフィルタ
no_tag_resource_id_{環境名}を適用 - タグなしリソースのID、コスト一覧をCSVエクスポート
- Cost Pivot画面で保存済みフィルタ
スプレッドシートにタグなしリソースリストをインポート
- リソースID、コスト、AWSサービスなど
按分結果の自動計算
- スプレッドシートの計算式により、以下が自動計算されます:
- タグなしリソースの事業部別按分
- Phase 1結果とタグなしリソースの合算
- 最終的な事業部別割合
- スプレッドシートの計算式により、以下が自動計算されます:
(将来対応)AWS総額・為替レート入力
- 現在はスコープ外
- 実装後、事業部ごとの最終金額(JPY)が自動計算される予定
詳細手順:
Phase 1.5(自動化版)との比較:
| 項目 | スプレッドシート手動按分(現行) | Phase 1.5自動化版(廃止) |
|---|---|---|
| 実行者 | 非エンジニア(経理・財務) | エンジニア(SRE) |
| データ取得 | SlackからCSVダウンロード | S3からCSVダウンロード |
| 按分計算 | スプレッドシート計算式 | Pythonスクリプト |
| GitHub Actions | 不要 | 必要 |
| アクセス権限 | Slack, Megazone, スプレッドシート | S3, GitHub, AWS CLI |
| 作業工数 | 中(手動インポート) | 低(自動実行) |
| 柔軟性 | 高(手動調整可能) | 低(スクリプト修正必要) |
Phase 1.5: タグなしリソースの手動配分(廃止、参考資料として保持)
⚠️ 重要: Phase 1.5は廃止されました(2025年12月)
廃止理由と背景:
- Phase 1.5は当初、エンジニアがS3とGitHub Actionsを使用してタグなしリソースを按分する設計でした
- しかし、実際の運用では**非エンジニア(経理・財務担当者)**がコスト按分作業を実施します
- 非エンジニアはS3やGitHub Actionsへのアクセス権限がなく、操作も困難です
- そのため、スプレッドシートベースの手動按分に運用を変更しました
現行運用:
- Phase 1完了後、結果CSVがSlack通知にスレッド添付されます
- 作業者はSlackからCSVをダウンロードし、スプレッドシートにインポートします
- Megazone Hyper Billingからタグなしリソース一覧を取得し、スプレッドシートで按分計算を行います
- 詳細手順: 月次AWSコスト按分作業手順
このセクションを残す理由:
- 将来、運用が安定しエンジニアリソースが確保できた場合、自動化を再実装する可能性があります
- その際の設計資料・実装参考として、Phase 1.5の仕様を保持します
- 既に実装されたコード(ワークフロー、スクリプト)も削除せず保持します
以下、Phase 1.5の設計仕様(参考資料):
概要: Phase 1.5は、Cost Explorer APIで取得できないタグなしリソースのコストを、Megazone Hyper BillingからダウンロードしたCSVを使用して事業部別に按分する機能です。
対象リソース:
TAG:Serviceタグがないリソース(develop/staging/production環境)TAG:ServiceANDTAG:Nameタグがないリソース(amazon-connect環境)- 主な対象AWSサービス: CloudWatch Logs, ECS, RDS, S3
Phase 1との関係:
| 項目 | Phase 1 | Phase 1.5 |
|---|---|---|
| データソース | Cost Explorer API | Megazone Hyper Billing CSV |
| 対象リソース | タグ付きリソース | タグなしリソース |
| 判定方法 | タグベース | リソースIDパターンマッチング |
| 実行頻度 | 月次自動実行 | 月次手動トリガー |
前提条件:
- Phase 1が正常に完了していること
- タグなしコストが存在すること(Phase 1の結果CSVのコメント行で確認)
- Megazone Hyper BillingからCSVをダウンロード済みであること
手順:
Phase 1の結果確認:
bash# Phase 1の結果CSVをダウンロード aws s3 cp s3://cost-monitoring-monthly-develop/output/phase1/2025/11/result/monitoring_ce_20251105_*.csv . # CSVの末尾を確認 tail monitoring_ce_20251105_*.csv # 未配分コスト(タグなしリソース): $1234.56 USD # 未配分コスト(タグなしリソース): ¥172838.40 JPYMegazone Hyper BillingからCSVをダウンロード:
詳細手順: タグなしリソースアップロード手順書
保存済みフィルタを適用:
- develop/staging/production環境:
no-tag-resource-id(TAG:Serviceが空) - amazon-connect環境:
no-tag-resource-amazon-connect(TAG:Service AND TAG:Nameが両方空)
- develop/staging/production環境:
CSVフォーマット(Megazone Hyper Billing):
csvym,projectName,usageAccountId,productCode,TAG:Service,TAG:Name,resourceId,unblendedCost 2025-11,p82516-220391,900176301532,AmazonCloudWatch,,,"arn:aws:logs:ap-northeast-1:900176301532:log-group:/aws/ecs/containerinsights/debt-manager-cluster/performance",123.45 2025-11,p82516-220391,900176301532,AmazonS3,,,fd-platform-logs-bucket,45.67 2025-11,p82516-220386,913831226605,AmazonCloudWatch,,,"arn:aws:logs:ap-northeast-1:913831226605:log-group:/aws/lambda/connect_spreadsheet",0.04- カラム説明:
ym: 年月(YYYY-MM)projectName: Megazoneプロジェクト名usageAccountId: AWSアカウントIDproductCode: AWSサービス名(AmazonCloudWatch, AmazonECS, AmazonRDS, AmazonS3)TAG:Service: Serviceタグ(空の場合あり)TAG:Name: Nameタグ(空の場合あり、amazon-connect環境のみ使用)resourceId: リソースID/ARNunblendedCost: コスト(USD)
S3にアップロード:
bash# Megazoneからダウンロードしたファイルをそのままアップロード(リネーム不要) aws s3 cp billing.exported.csv \ s3://cost-monitoring-monthly-develop/output/phase1_5/2025/11/no_tag_resource_list/billing.exported.csv注: S3パスには
DD(日付)がなく、YYYY/MM/の後に直接no_tag_resource_list/となります。Phase 1.5ワークフローを手動実行:
- GitHub Actionsで
{environment}@cost-monitoring-monthly-untag-resourcesワークフローを実行 - パラメータ:
target_month: 2025-11(空欄の場合は前月を自動選択)
ワークフロー名の規則:
- Phase 1:
{environment}@cost-monitoring-monthly.yml(タグ付きリソース、自動実行) - Phase 1.5:
{environment}@cost-monitoring-monthly-untag-resources.yml(タグなしリソース、手動実行)
- GitHub Actionsで
Phase 1.5の処理内容: Phase 1.5スクリプト(
phase1_5_step1_allocate_untagged_resources.py)は以下を実行します:ステップ1: Phase 1結果の読み込み
s3://{bucket}/output/phase1/{year}/{month}/result/monitoring_ce_*.csv
ステップ2: Megazone CSVの読み込み
s3://{bucket}/output/phase1_5/{year}/{month}/no_tag_resource_list/billing.exported.csv
ステップ3: リソースIDパターンマッチングで事業部判定
- カテゴリファイル(
categories.yamlまたはamazon-connect-categories.yaml)のパターンと照合 - 判定例:
arn:aws:logs:.../log-group:/aws/lambda/connect_spreadsheet→connectパターンマッチ → connect部門connect-5c94a06cd76f→connectパターンマッチ → connect部門arn:aws:logs:.../log-group:/aws/ecs/.../debt-manager-cluster/...→debt-managerパターンマッチ → online部門
ステップ4: common コストをECS按分
- パターンマッチできないリソース →
commonに分類 - ECSサービスのネットワーク転送量(NetworkRxBytes + NetworkTxBytes)を取得
- ネットワーク転送量比率で
commonコストを事業部別に按分
ステップ5: Phase 1結果と統合
- Phase 1のコスト + Phase 1.5のコスト = 最終コスト
- 事業部別割合を再計算
結果確認:
bash# Phase 1.5の結果CSVをダウンロード aws s3 cp s3://cost-monitoring-monthly-develop/output/phase1_5/2025/11/result/manual_allocation_20251105_*.csv . # 内容確認: タグ付き + タグなし の最終割合が記載されている cat manual_allocation_20251105_*.csvPhase 2へ進む:
- Phase 1.5の結果を確認後、Phase 2(代理店割引後の再配分)を実行
- Phase 2スクリプトは自動的にPhase 1.5の結果を読み込む
S3ディレクトリ構造(Phase 1.5):
s3://cost-monitoring-monthly-${env}/
└── output/
├── phase1/ # Phase 1: タグ付きリソース
│ └── YYYY/
│ └── MM/
│ └── result/
│ └── monitoring_ce_YYYYMMDD_HHmmss.csv
│
└── phase1_5/ # Phase 1.5: タグなしリソース
└── YYYY/
└── MM/
├── no_tag_resource_list/ # 入力(手動アップロード)
│ └── billing.exported.csv # Megazoneからダウンロードしたファイル
├── result/ # 出力(スクリプト生成)
│ └── manual_allocation_YYYYMMDD_HHmmss.csv
└── debug/ # デバッグ用中間ファイル
├── untagged_resources_categorization_*.csv
├── resource_pattern_matching_*.csv
└── untagged_ecs_allocation_*.csvカテゴリファイルの使い分け:
| 環境 | カテゴリファイル | タグなし判定条件 |
|---|---|---|
| develop/staging/production | config/categories.yaml | TAG:Serviceが空 |
| amazon-connect | config/amazon-connect-categories.yaml | TAG:Service AND TAG:Nameが両方空 |
amazon-connect専用カテゴリファイルを使用する理由:
- amazon-connect環境では
TAG:ServiceとTAG:Nameの両方を使用するタグ戦略を採用 - Amazon Connect専用リソースの識別に
TAG:Service=amazon-connectとTAG:Name={リソース名}の組み合わせが必要 - オンライン診療事業部のリソースも同一アカウントに混在するため、より詳細な判定が必要
リソースID判定ロジックの優先順位:
- カテゴリファイルは上から順に評価される
- 最初にマッチしたパターンの事業部が採用される
- 設計原則:
- 具体的なパターンを上位に配置(例:
amazon-connect→connect) - 汎用的なパターンを下位に配置(例:
online→fd-) - 事業部ごとにグルーピング
- 具体的なパターンを上位に配置(例:
注:
- Phase 1.5は廃止されました(2025年12月): 非エンジニアがS3/GitHub Actionsにアクセスできないため、スプレッドシートベースの手動按分に変更
- 現行運用: Slackから Phase 1結果CSVをダウンロードし、スプレッドシートで按分計算
- 将来: CURベースの自動化実装後は、Phase 1でタグ付き+タグなしを統合処理予定
Phase 2: 代理店割引後の再配分(手動、毎月中旬)
代理店ポータルからコスト確認:
- 担当者が代理店の専用ポータルにログイン
- 対象月のアカウント別請求額を確認
- アカウントID と 円建てコスト をメモ
GitHub Actionsで手動再配分:
cost-monitoring-manual-reallocationワークフローを実行(workflow_dispatch)- 対象月(YYYY-MM形式)を入力
- アカウント別コストをJSON形式で入力(固定フォーマット、全10アカウント):json
{ "967691968827": 0, "301608970378": 0, "900176301532": 0, "853790572692": 0, "913831226605": 0, "770217130318": 0, "211125417886": 0, "339712701386": 0, "226458723831": 0, "866741171210": 0 } - 各アカウントの値(0の部分)を代理店ポータルで確認した実際の金額(円建て)に置き換える
- 例:
"967691968827": 2800000→ 本番環境のコストが2,800,000円 - 例:
"913831226605": 1500000→ AmazonConnect環境のコストが1,500,000円
- 例:
結果確認:
- S3に
output/phase2/YYYY/MM/DD/result/monitoring_adjusted_YYYYMMDD_HHmmss.csvが出力される - Slack通知で再配分完了を確認
- 必要に応じてスプレッドシート等に転記
- S3に
重要: 代理店ポータルのデータ取得が機械的に難しいため、この手動ステップを運用フローに組み込んでいます。将来的にAPIが提供された場合は自動化を検討します。
Phase 3: 未分類リソース対応(随時)
未分類リソース検出:
- 月次集計時に未分類コストが検出された場合、Slack通知が送信される
- 通知には未分類リソースの詳細(TAG:Service、AWS Service名、金額)が含まれる
カテゴリマスタ更新:
- 未分類リソースの所属事業部を確認
config/categories.yamlを更新するPRを作成- レビュー後、mainブランチにマージ
手動再集計:
- GitHub Actions の
cost-monitoring-recalculateワークフローを実行 - 対象月を指定して再集計
- 更新されたカテゴリルールが適用される
- GitHub Actions の
結果確認:
- S3の結果CSVが更新される
- Slack通知で未分類コストの減少を確認
判断基準:
- 未分類コストが$100未満の場合: 対応は任意(次月まで様子見も可)
- 未分類コストが$100以上、または10%以上の場合: 速やかに対応を推奨
インフラ構成
IAM Role(OIDC認証)
ロール名: aws-cost-monitoring-github-actions-role
信頼ポリシー: GitHub Actions OIDC プロバイダー
必要な権限:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ce:GetCostAndUsage"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"cloudwatch:GetMetricStatistics"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::cost-monitoring-monthly-${env}/output/phase1/*",
"arn:aws:s3:::cost-monitoring-monthly-${env}/output/phase2/*"
]
}
]
}S3バケット構成
バケット名: cost-monitoring-monthly-${env}
環境別バケット例:
- 本番環境:
cost-monitoring-monthly-production - ステージング環境:
cost-monitoring-monthly-staging - 開発環境:
cost-monitoring-monthly-develop - インフラ開発環境:
cost-monitoring-monthly-infra-dev
ディレクトリ構成:
cost-monitoring-monthly-${env}/
└── output/
├── phase1/ # Phase 1: Cost Explorerベースの自動集計
│ └── YYYY/ # 年(例: 2025)
│ └── MM/ # 月(例: 08)
│ └── DD/ # 日(例: 03)
│ ├── result/ # メイン結果
│ │ └── monitoring_ce_YYYYMMDD_HHmmss.csv
│ └── debug/ # 中間ファイル(検証用)
│ ├── service_tag_breakdown_YYYYMMDD_HHmmss.csv
│ ├── aws_service_breakdown_YYYYMMDD_HHmmss.csv
│ ├── categorization_details_YYYYMMDD_HHmmss.csv
│ └── ecs_network_details_YYYYMMDD_HHmmss.csv
└── phase2/ # Phase 2: 代理店割引後の再配分
└── YYYY/ # 年(例: 2025)
└── MM/ # 月(例: 08)
└── DD/ # 日(例: 15)
└── result/ # メイン結果
└── monitoring_adjusted_YYYYMMDD_HHmmss.csv命名規則: Phase 1 出力:
output/phase1/YYYY/MM/result/monitoring_ce_YYYYMMDD_HHmmss.csv: Cost Explorer APIベースの集計結果- 例:
s3://cost-monitoring-monthly-production/output/phase1/2025/08/03/result/monitoring_ce_20250803_090512.csv - 実行日時(JST)を含めることで、1日に複数回実行しても一意
- 例:
output/phase1/YYYY/MM/debug/: 中間ファイル(デバッグ・検証用)service_tag_breakdown_*.csv: サービスタグごとのコスト詳細aws_service_breakdown_*.csv: AWSサービスごとのコスト詳細categorization_details_*.csv: 分類結果の詳細ecs_network_details_*.csv: ECSネットワーク転送量の詳細
Phase 2 出力:
output/phase2/YYYY/MM/DD/result/monitoring_adjusted_YYYYMMDD_HHmmss.csv: 代理店割引適用後の再配分結果- 例:
s3://cost-monitoring-monthly-production/output/phase2/2025/08/15/result/monitoring_adjusted_20250815_143022.csv - 実行日時(YYYYMMDD_HHmmss)を含める
- 例:
セキュリティ設定:
- ✅ バージョニング: 有効(全履歴保持)
- 誤って上書きした場合でも過去バージョンを復元可能
- ✅ サーバーサイド暗号化: AES256(SSE-S3)
- コストデータを暗号化して保存
- ✅ パブリックアクセスブロック: 有効
- 外部からのアクセスを完全にブロック
- ✅ アクセス制限: IAM Roleベースのアクセス制御
- GitHub Actions専用のIAM Roleのみアクセス可能
ライフサイクルルール:
LifecycleRules:
- Id: "TransitionToIA"
Status: Enabled
Transitions:
- Days: 365
StorageClass: STANDARD_IAライフサイクル詳細:
- 0-365日: STANDARD(標準ストレージ)
- 最近のデータは頻繁にアクセスされる可能性が高い
- 365日後: STANDARD_IA(低頻度アクセスストレージ)に自動移行
- 1年以上前のデータはアクセス頻度が低いため、コスト削減
Slack通知設定
通知先チャンネル: #squad-sre-noti-saas-status-dev
Webhook URL: GitHub Secretsで管理(SLACK_WEBHOOK_URL)
通知内容:
- 集計完了通知:
- 対象月、アカウント別合計金額、事業部別割合
- 差分アラート:
- Cost Explorer合計と計算結果の差分
- 未分類コストの詳細(TAG:Service名、AWS Service名、金額)
- エラー通知:
- API呼び出し失敗、データ取得エラー、計算エラー
- 未分類リソース通知:
- カテゴリマスタにマッチしないリソースの詳細
- 未分類比率が10%以上の場合は警告レベル
GitHub Actions / 運用
ワークフロー構成
月次自動集計ワークフロー
ファイル: .github/workflows/cost-monitoring-monthly.yml
トリガー:
schedule: 毎月2日 09:00 JST(0 0 2 * *- 毎月2日 00:00 UTC)- Cost Explorerのデータ反映期間(最長48時間)を考慮
処理:
- Cost Explorer API / CloudWatch から対象月のデータ取得
- ステップ1のロジック実行
- 結果CSV を S3 に保存
- 成功 or 失敗を Slack に通知
環境変数:
env:
AWS_REGION: ap-northeast-1
FX_RATE_USD_JPY: "150.0" # 為替レート(必要に応じて変更)手動再配分ワークフロー
ファイル: .github/workflows/cost-monitoring-manual-reallocation.yml
トリガー: workflow_dispatch
入力:
inputs:
target_month:
description: '対象月 (YYYY-MM形式)'
required: true
type: string
account_cost_json:
description: '代理店割引後のアカウント別コスト (JSON形式)'
required: true
type: string処理:
- S3からCEベース結果の割合を読み込み
- 手動入力のアカウント別コストで再配分
- S3に adjusted 結果を保存
- Slack通知
カテゴリマスタ更新後の再集計ワークフロー
ファイル: .github/workflows/cost-monitoring-recalculate.yml
トリガー: workflow_dispatch
目的:
- カテゴリマスタ(
config/categories.yaml)更新後、過去月のデータを再集計 - 未分類リソースが検出された際の対応フロー
入力:
inputs:
target_month:
description: '再集計する対象月 (YYYY-MM形式)'
required: true
type: string処理:
- 最新の
config/categories.yamlを使用 - Cost Explorer API / CloudWatch から指定月のデータを再取得
- ステップ1のロジックを実行(新しいカテゴリルールを適用)
- 結果CSV を S3 に新規ファイルとして出力(実行時刻を含むファイル名で保存)
- Slack通知(再集計完了、未分類コストの変化を含む)
注意: 既存ファイルを上書きせず、新しいタイムスタンプ付きファイルとして保存されます
利用シーン:
- 未分類リソース通知を受けて、カテゴリマスタを更新した後
- カテゴリルールの誤りを修正した後
失敗時の挙動
- リトライ(一定回数)を試みる
- それでもダメなら ジョブを失敗状態 とし、エラー内容を含んだ Slack 通知を送る
- 自動での補正や再試行ループは行わず、人間が状況を確認して再実行する
出力形式
monitoring_ce_YYYYMMDD_HHmmss.csv(CEベース結果)
ファイル名例: monitoring_ce_20250803_090512.csv
事業部別コスト詳細(USD建て、事業部 × アカウントの明細):
事業部,アカウント,アカウントID,年月,料金(USD),料金割合
オンライン診療事業部,本番環境,967691968827,2025-08,17862.65,0.8702
在宅事業部,本番環境,967691968827,2025-08,69.41,0.0034
AmazonConnect,本番環境,967691968827,2025-08,0.00,0.0000
共通その他,本番環境,967691968827,2025-08,2596.04,0.1264
オンライン診療事業部,ステージング環境,301608970378,2025-08,10951.61,0.9439
在宅事業部,ステージング環境,301608970378,2025-08,650.53,0.0561
AmazonConnect,ステージング環境,301608970378,2025-08,0.00,0.0000
共通その他,ステージング環境,301608970378,2025-08,0.00,0.0000注: Phase 1(Cost Explorerベース)の結果はUSD建てのみ、JPY列は含まない
monitoring_adjusted_YYYY-MM_YYYYMMDD_HHmmss.csv(再配分結果)
ファイル名例: monitoring_adjusted_2025-08_20250815_143022.csv
事業部別コスト詳細(JPY建て、代理店割引適用後の事業部 × アカウントの明細):
事業部,アカウント,アカウントID,年月,料金(JPY),料金割合
オンライン診療事業部,本番環境,967691968827,2025-08,2436560.00,0.8702
在宅事業部,本番環境,967691968827,2025-08,9520.00,0.0034
AmazonConnect,本番環境,967691968827,2025-08,0.00,0.0000
共通その他,本番環境,967691968827,2025-08,353920.00,0.1264
オンライン診療事業部,ステージング環境,301608970378,2025-08,1200000.00,0.9439
在宅事業部,ステージング環境,301608970378,2025-08,71332.00,0.0561
AmazonConnect,ステージング環境,301608970378,2025-08,0.00,0.0000
共通その他,ステージング環境,301608970378,2025-08,0.00,0.0000注: Phase 2(代理店割引適用後)の結果はJPY建てのみで出力、料金割合はPhase 1で算出した値と同一
セキュリティとIAM権限
GitHub Actions → AWS へのアクセス
- 認証方式: OIDC + IAM Role
- 付与権限(最小権限):
- 出力先S3書き込み
- CloudWatchメトリクス読み取り
- Cost Explorer API 読み取り
IAMポリシー例
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:PutObjectAcl"
],
"Resource": [
"arn:aws:s3:::cost-monitoring-${env}",
"arn:aws:s3:::cost-monitoring-${env}/*"
]
},
{
"Effect": "Allow",
"Action": [
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"ce:GetCostAndUsage",
"ce:GetDimensionValues",
"ce:GetTags"
],
"Resource": "*"
}
]
}データ取り扱い
- 集計結果(特に円ベースのコスト情報)は S3のみに保存 し、GitHub Artifacts やリポジトリには保存しない
- S3バケットは
cost-monitoring-${env}など環境ごとに分離し、アクセスを社内の必要なメンバーに限定する
OIDC認証フロー
1. GitHub Actions Workflow起動
↓
2. OIDC Tokenの取得(GitHub)
↓
3. AWS STS AssumeRoleWithWebIdentity
↓
4. 一時的な認証情報の取得
↓
5. Cost Explorer API / CloudWatch API / S3 の呼び出し通知設定
Slack通知
通知タイミング
- 月次集計完了時
- CE合計との差分検出時
- 未分類リソース(共通その他)検出時
- 手動再配分完了時
- エラー発生時
通知内容例(月次集計完了)
{
"attachments": [
{
"color": "good",
"title": "AWS Cost Monitoring - 月次集計完了 (2025年08月)",
"fields": [
{
"title": "対象月",
"value": "2025-08",
"short": true
},
{
"title": "出力ファイル",
"value": "monitoring_ce_20250803_090512.csv",
"short": true
},
{
"title": "オンライン診療",
"value": "¥2,250,000 (70.61%)",
"short": true
},
{
"title": "在宅",
"value": "¥525,000 (16.48%)",
"short": true
}
],
"footer": "GitHub Actions",
"ts": 1693814400
}
]
}通知内容例(CE合計との差分)
{
"attachments": [
{
"color": "warning",
"title": "AWS Cost Monitoring - CE合計との差分検出",
"fields": [
{
"title": "アカウント",
"value": "967691968827",
"short": true
},
{
"title": "CE合計",
"value": "$21,246.90",
"short": true
},
{
"title": "内部計算合計",
"value": "$21,250.00",
"short": true
},
{
"title": "差分",
"value": "$3.10 (0.015%)",
"short": true
}
],
"text": "差分が検出されました。確認が必要な場合は手動で調査してください。"
}
]
}通知内容例(未分類リソース検出)
{
"attachments": [
{
"color": "warning",
"title": "AWS Cost Monitoring - 未分類リソース検出",
"text": "カテゴリマスタに登録されていないリソースが検出されました。必要に応じて `config/categories.yaml` を更新してください。",
"fields": [
{
"title": "対象月",
"value": "2025-08",
"short": true
},
{
"title": "未分類コスト合計",
"value": "$1,234.56 (6.2%)",
"short": true
},
{
"title": "未分類リソース詳細",
"value": "• TAG:Service=new-service-name: $856.20\n• AWS Service=Amazon API Gateway: $234.50\n• TAG:Service=experimental-app: $143.86",
"short": false
},
{
"title": "対応方法",
"value": "1. リソースの所属事業部を確認\n2. `config/categories.yaml` にルールを追加\n3. GitHub Actions で手動再集計を実行",
"short": false
}
],
"footer": "GitHub Actions",
"ts": 1693814400
}
]
}通知条件:
- 未分類コストが$100以上の場合
- または、未分類コストの割合がアカウント総コストの10%以上の場合
設定方法
- GitHub Secretsに
SLACK_WEBHOOK_URLを設定 - ワークフローが自動的にSlack通知を送信
拡張性と今後の改善
Phase 1: 現在の実装(完了予定)
- ✅ Cost Explorer API / CloudWatch からのデータ取得
- ✅ カテゴリマスタによる事業部判定
- ✅ ECSネットワーク按分
- ✅ Cost Explorerベースの月次集計
- ✅ 代理店割引後の手動再配分
- ✅ 未分類リソース検知・通知
- ✅ カテゴリマスタ更新後の手動再集計
- ✅ S3への出力
- ✅ Slack通知
Phase 2: 計画中
- 📋 Datadog連携によるコストダッシュボード
- 📋 AWS Budgetsとの統合
- 📋 Cost Anomaly Detectionとの連携
- 📋 未分類リソースの自動カテゴリ推定(機械学習ベース)
Phase 3: 将来的な拡張
- 🔮 RI/Savings Plans利用率レポート(将来的に導入した場合)
- 🔮 リソース最適化提案
- 🔮 予測分析とアラート
- 🔮 カスタムダッシュボードAPI
今後のタスク・課題
短期(実装待ち)
1. Phase 1.5スクリプトの実装 ✅ 完了
優先度: 高 完了日: 2025年12月
実装済みタスク:
- [x]
phase1_5_step1_allocate_untagged_resources.pyスクリプト作成 - [x]
phase1_5_step2_notify_slack.pySlack通知スクリプト作成 - [x] Phase 1結果の読込処理
- [x] S3から
billing.exported.csv(Megazone Hyper Billing形式) の読込処理 - [x] リソースIDパターンマッチングによる自動判定
- [x] タグなしcommonのECS按分処理
- [x] タグ付き+タグなし統合と最終割合再計算
- [x] S3への出力(
output/phase1_5/YYYY/MM/result/) - [x] デバッグファイル出力(分類詳細、パターンマッチング統計、ECS按分詳細)
- [x] GitHub Actions workflow作成(
develop@cost-monitoring-monthly-untag-resources.yml)- パラメータ: target_month(空欄時は前月自動選択)
実装ファイル:
- スクリプト:
.github/scripts/cost_monitoring_monthly/phase1_5_step1_allocate_untagged_resources.py - Slack通知:
.github/scripts/cost_monitoring_monthly/phase1_5_step2_notify_slack.py - ワークフロー:
.github/workflows/develop@cost-monitoring-monthly-untag-resources.yml
入力ファイル仕様:
- パス:
s3://{bucket}/output/phase1_5/YYYY/MM/no_tag_resource_list/billing.exported.csv - フォーマット: Megazone Hyper Billing形式(UsageDate, ItemDescription, UnblendedCost, ResourceId)
2. CURベースのタグなしリソース自動分類機能の実装
優先度: 中 期限: CUR設定完了後
背景:
- Cost Explorer APIではリソースIDの取得に制約がある
- CUR(Cost and Usage Report)のS3エクスポートにはリソースID(ARN等)が含まれる
- リソースID自動分類ロジックは実装済み(
_categorize_by_resource_idメソッド)
タスク:
Step 1: インフラ設定(Terraform)
- [ ] CUR用S3バケットの作成(各環境)
- バケット名:
fd-cur-export-${environment} - ライフサイクルポリシー: 90日後に削除(コスト削減)
- バージョニング: 無効
- 暗号化: AES-256(S3管理)
- バケットポリシー: CURサービスからの書込み許可
- バケット名:
- [ ] CURのS3エクスポート設定(AWS Billing Console)
- レポート名:
cost-usage-report-${environment} - データ範囲: リソースID含む詳細データ(
INCLUDE_RESOURCE_IDS) - 更新頻度: Daily
- 圧縮形式: Parquet推奨(Athenaとの相性が良い)
- レポートバージョニング: 既存レポートを上書き
- 統合: Athena統合を有効化
- レポート名:
- [ ] Athena用S3バケットの作成
- バケット名:
fd-athena-query-results-${environment} - ライフサイクルポリシー: 30日後に削除
- バケット名:
- [ ] Athena Workgroupの作成
- Workgroup名:
cost-monitoring-${environment} - クエリ結果の場所:
s3://fd-athena-query-results-${environment}/ - データ使用量制御: 最大クエリサイズ 10GB
- Workgroup名:
- [ ] Glue Crawlerの設定(オプション: CURデータのスキーマ自動更新用)
- Crawler名:
cur-crawler-${environment} - データソース:
s3://fd-cur-export-${environment}/ - 実行スケジュール: Daily
- IAMロール: Glue実行用ロール
- Crawler名:
Step 2: アプリケーション実装
- [ ] CUR読込モジュールの実装(
cur_reader.py)- Athenaクエリ実行機能
- タグなしリソースのフィルタリング(
WHERE resource_tags['Service'] IS NULL OR resource_tags['Service'] = '') - リソースIDとコストの抽出
- Parquetファイルの直接読込(boto3 + PyArrow)
- [ ] Phase 1スクリプトの修正(
monthly_cost_analysis.py)- Cost Explorer API: タグ付きリソースのみ
- CUR: タグなしリソースのリソースIDとコスト
- リソースID自動分類ロジックの適用(既存の
_categorize_by_resource_idを使用) - 統合処理(タグ付き + タグなし)
- [ ] GitHub Actionsワークフローの修正
- Athena実行権限の追加(IAMロール)
- S3読取り権限の追加(CURバケット、Athenaクエリ結果バケット)
- [ ] テスト実行と結果検証
- infra-dev環境でテスト
- タグ付き・タグなしコストの合計値検証
- リソースIDパターンマッチングの精度確認
- [ ] Phase 1.5の廃止(不要になる)
- ワークフロー削除
- Runbookをアーカイブ
- 設計書の更新
メリット:
- タグなしリソースも完全自動化
- 運用工数の削減
- リソースID単位の詳細なコスト分析が可能
中期(機能改善)
3. Slack通知の充実
優先度: 中
タスク:
- [ ] タグなしコスト通知の追加(Phase 1.5への誘導)
- [ ] 未分類リソース通知の改善
- [ ] コスト異常値の自動検知と通知
- [ ] 前月比の変化率通知
4. Phase 1.5 WebUI化
優先度: 低
タスク:
- [ ] タグなしコスト配分のWebUI作成
- [ ] 過去の配分履歴表示
- [ ] ワンクリックでPhase 1.5実行
- [ ] リソースID自動サジェスト機能
長期(運用改善)
5. カテゴリマスタの自動更新提案
優先度: 低
タスク:
- [ ] 新規サービスの自動検知
- [ ] カテゴリ判定の学習・提案
- [ ] PRの自動作成
6. コスト予測機能
優先度: 低
タスク:
- [ ] 過去データからの予測モデル構築
- [ ] 月末コスト見込の自動算出
- [ ] 予算超過アラート
既知の制約・課題
1. タグなしリソースの手動運用
影響: 運用工数増加、リアルタイム性の低下 暫定対応: Phase 1.5で手動CSV入力 根本対応: CURベースの自動化(今後のタスクで対応予定)
2. Cost Explorer APIの制約
制約: リソースID取得に制限 影響: タグなしリソースの自動分類ができない 対応策: CURのS3エクスポートで補完
3. 代理店割引後コストの手動入力
影響: 完全自動化できない 制約: 代理店ポータルにAPI未提供 対応策: 将来API提供時に自動化
4. Cost Explorerデータの反映遅延
影響: 月初2日まで集計不可 制約: AWS仕様(最長48時間) 対応策: 運用スケジュールに組込済み
5. ECS以外のネットワーク按分
影響: Lambda, Fargate以外は按分対象外 制約: Container Insightsメトリクス限定 対応策: 現状は「その他」扱い、将来的にCloudWatch Logsで補完検討
更新履歴
| 日付 | 更新内容 | 更新者 | 所属チーム |
|---|---|---|---|
| 2025-11-20 | 初版作成 | 大賀 | SRE |