はじめに

AWS Cost Categoriesは、AWS利用料をルールに基づいてカテゴリ分類しコスト状況を把握・整理できる機能です。
アカウント、タグ、サービスなどに基づいてルールを定義できます。機能の詳細は公式ドキュメントをご覧ください。

この記事では、AWS Cost Categoriesによる分類を、AWS Organizationsの「組織単位(OU)」に自動的に同期するアプローチを紹介します。


*図1: Cost Categoriesの利用(マネジメントコンソールから手動設定)


*図2: OrganizationsのOU階層でコスト可視化

仕組み

アプローチの詳細

  • 自動化の必要性: OUの構成変更やアカウント追加するたびにCost Categoriesを手動更新するのは手間がかかり、ミスも発生しやすいです。直接OUに紐づける設定は2025年9月時点では存在しません
  • 実装: Cost Categoriesのルールは直接OUに紐付けができないため、簡単なスクリプトで以下のロジックを実装します:

  1. ListAccountsを使用してすべてのアカウントを一覧表示
  2. 各アカウントに対してListParentsを使用してOUパスを特定
  3. 特定したOUパス名に相当するコストカテゴリ名をアカウントを割り当て。OU階層はパラメータで指定したdepth の深度まで細分化。
  4. Cost Categories上に、OUパス名のカテゴリにアカウントIDのリストをDimensions にマッピングするルールを作成


図3: 今回構築する構成

  • 実行: Python3.x/Boto3を使用。AWS CloudShellなどの環境からの実行を想定。スケジュールされたLambda実行にも対応可能(パラメータ入力の変更と実行制限の考慮が必要)。

スクリプトの機能

  • 階層カテゴリ: Level1OU-Level2OU-...のような名前を作成
  • 深度制御: depth引数で粒度を設定

注意点

  • このスクリプトはサンプルです。本番環境で使用する前に十分にテストしてください。
  • 必要なOrganizations APIとCost Explorer APIにアクセスするため、管理アカウント上での実行を前提としています。
  • 最小権限の原則: 管理者権限でスクリプトを実行しないでください。 最小限の必要な権限のみを持つ専用のIAMロールを作成し、そのロールを使用して実行してください(例:CloudShell、EC2、Lambda)。

スクリプト

スクリプトはGitHubで公開しています。

リポジトリ:
https://github.com/shu85t/PutOuCostCategory

前提条件

  • Python: バージョン3.9以降
  • Boto3: Python用AWS SDK(pip install boto3)。最新バージョンを使用してください
  • AWSアカウント: AWS Organizationの管理アカウントへのアクセス

実行環境

  • このスクリプトは、必要なIAM権限(下記参照)を持つAWS Organizationの管理アカウント上で実行する必要があります。
  • 管理アカウント上でAWS CloudShellを利用するのがわかりやすい例です。
  • 必要な権限を持つ実行ロールが提供されていれば、管理アカウント内のEC2インスタンスやAWS Lambda関数でも実行できます。

使用方法

コマンドライン実行

python3 put_ou_cost_category.py <CostCategoryName> <EffectiveStartMonth> <Depth>

引数

  1. (必須): 作成または更新するCost Categoriesの名前(例:OrganizationStructureOUHierarchy
  2. (必須): YYYY-MM形式の有効開始月(例:2025-04)。スクリプトはAPI呼び出しでこの月の初日(UTC午前0時)を使用します
  3. (必須): カテゴリを作成するOU階層の最大深度を指定する整数(1以上)

* 1: Rootと第1レベルOUのカテゴリを作成
* 2: Root、第1レベルOU、第2レベルOU(例:OU1-OU1A)のカテゴリを作成
* アカウントは常に指定された深度までの最も深いカテゴリに割り当てられます

ログレベル設定

LOG_LEVEL環境変数を使用してログの詳細度を制御できます。デフォルトはINFOです。

# 例: DEBUGログで実行
export LOG_LEVEL=DEBUG
python3 put_ou_cost_category.py MyOUCategory 2025-04 2

# または一時的に1つのコマンドで
LOG_LEVEL=DEBUG python3 put_ou_cost_category.py MyOUCategory 2025-04 2

IAM権限

このスクリプトを実行するIAMプリンシパル(ユーザーまたはロール)には以下の権限が必要です:

必要なアクション:

  • organizations:ListAccounts
  • organizations:ListParents
  • organizations:DescribeOrganizationalUnit
  • organizations:ListRoots
  • ce:ListCostCategoryDefinitions
  • ce:CreateCostCategoryDefinition
  • ce:UpdateCostCategoryDefinition

実行例

サンプルOU構造での動作例を紹介します。

サンプルOU構造

Root
├── Management Account
│
├── Management OU
│ ├── Management Tool Account1
│ └── Management Tool Account2
│
├── Sandbox OU (直接アカウントなし)
│
├── Security OU
│ ├── Audit Account
│ └── Log Archive Account
│
└── SDLC OU
├── Dev OU
│ └── Workload Dev Account
└── Stg OU
└── Workload Staging Account

depth=1での結果

コマンド:

python3 put_ou_cost_category.py OUStructure 2025-01 1

結果:

depth=2での結果

コマンド:

python3 put_ou_cost_category.py OUStructure 2025-01 2

結果:

OU階層に沿ったカテゴリ分類を実現することができました


図4: 実際のソリューション実行結果とコスト分析画面

スクリプトの動作原理

組織構造の取得

該当コード

# --- Core Functions ---
def get_organization_structure(max_depth):
""" Retrieves Org structure assigning accounts to deepest category up to max_depth. Returns dict or None. Propagates exceptions. """
logger.info(f"Fetching organization structure (assigning accounts to deepest path up to depth {max_depth})...")
...
  1. list_accountsを使用してすべてのアカウントを一覧表示することから開始
  2. 各アカウントについて、list_parents APIを使用してRootまでの親OUの完全なパスをトレース
  3. describe_organizational_unitを使用してOU名を取得(効率向上のためget_ou_nameでローカルキャッシュ)
  4. OUパスと提供されたmax_depthに基づいて、最終的なカテゴリ名(例:L1OU-L2OU)を構築
  5. 重要なのは、各アカウントが深度制限内での最も深いOU配置を表す単一のカテゴリに一意に割り当てられること
  6. 最終的に、生成されたカテゴリ名をキー、対応するアカウントIDのリストを値とするPython辞書を返す

Cost Categoriesルールの構築

該当コード

def build_cost_category_rules(org_structure):
""" Builds rule list. Skips categories with empty accounts. Propagates exceptions. """
logger.info("Building Cost Categories rules...")
...
  1. 入力辞書(カテゴリ名とアカウントリストのマッピング)を受け取り、アカウントを持つ各カテゴリをAPI用にフォーマットされたルールオブジェクトに変換
  2. 実際にアカウントが割り当てられたカテゴリに対してのみ生成されたルールを含むリストを返す

Cost Categoriesの設定

該当コード

def put_cost_category(cost_category_name, rules, default_value, effective_start_iso_str):
""" Creates/Updates Cost Categories. Returns True on success. Raises exceptions on failure. Logs raw parameters. """
  1. まずfind_cost_category_arnを使用して指定されたcost_category_nameのCost Categoriesが既に存在するかチェック。結果に基づいて、create_cost_category_definitionまたはupdate_cost_category_definition API呼び出しに必要なパラメータを準備
  2. API呼び出し前に、ドキュメント化された制限に対してルール数とルールあたりのアカウント数の事前チェックを実行
  3. 検証に合格した場合、適切なAPI呼び出し(作成または更新)を実行してAWSで変更を適用

参考資料

今回はPythonでコードを書き、AWSへのアクセスはboto3を使用しています。参考としてSDKドキュメントのリンクを記載します。

余談ですが、APIで定義されている引数やレスポンスの仕様を公式ドキュメントで読んでおくと、AWSサービス自体の理解、動作、様々なオプションを把握するのに役立ちます。
あわせてCloudWatchメトリクスでとれる情報、クォータ制限にも目を通しておくと非機能要件として留意すべき観点も学べます。

まとめ

AWS Cost CategoriesをAWS Organizations OU構造と自動的に同期する方法を提供します。
これによりOU分類という断面でコスト把握・分析ができるようになりました。