3
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?

OU情報出力作業をトイル削減する

Last updated at Posted at 2025-10-27

はじめに

私は株式会社GENEROSITYのSREです。

日々の業務では複数アカウントに対する同時操作やOU(Organizational Unit)に応じた操作が必要になることが多々あります。
AWS OrganizationsにはCSVエクスポート機能がありますが、毎回必要な情報を欲しい形に加工するのが手間でした。

そこで定期的にOU情報を取得し、必要な情報のみをJSON形式で出力するシステムを構築しましたので共有します。

前提条件

弊社の環境は以下のような状況です。

  • 常時100を超えるAWSアカウントを運用している
  • アカウントは月に数個から十数個のペースで作成される
  • すべてのアカウントはOrganizations管理されており、いずれかのOUに属している
  • 情報取得用アカウントと取得対象アカウントは同一で、Organizationsの情報取得権限が委譲されている

システム概要

構築したシステムの概要は以下の通りです。

  • EventBridge:定期的(毎週月曜日00:00)にターゲットのLambdaを実行
  • Lambda:OU情報取得および作成したJSONをS3へアップロード処理を実行

システム構築手順

以下の手順に従ってシステムを作成いたします。今回はすべて同一アカウント内にシステムを構築しています。

1. IAMロールの準備

システムに必要な2つのIAMロールを事前に準備します。

EventBridge実行ロール

Lambda実行権限を持つポリシーをアタッチしたロールを作成します。

evnetbridge-role.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": [
                "arn:aws:lambda:ap-northeast-1:{アカウントID}:function:{任意のLambda関数名}:*",
                "arn:aws:lambda:ap-northeast-1:{アカウントID}:function:{任意のLambda関数名}"
            ]
        }
    ]
}

Lambda実行ロール

Organizations情報取得、S3オブジェクトアップロード、ログ出力権限を持つポリシーをアタッチしたロールを作成します。

lambda-role.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:ap-northeast-1:{AWSアカウントID}:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:ap-northeast-1:{AWSアカウントID}:log-group:/aws/lambda/{任意のLambda関数名}:*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "organizations:ListRoots",
                "organizations:ListOrganizationalUnitsForParent",
                "organizations:ListChildren",
                "organizations:ListAccounts",
                "organizations:DescribeAccount"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::{S3バケット名}/*"
        }
    ]
}

2. Lambdaの作成

処理の流れは以下のようにシンプルな構成になっています。

  1. OUの必要情報を取得
  2. 親OUから最下層の子OUまで情報を再帰的に取得
  3. 必要情報を加工してJSONオブジェクトを生成
  4. JSONオブジェクトをS3へアップロード
index.mjs
import { OrganizationsClient, ListRootsCommand, ListOrganizationalUnitsForParentCommand, ListChildrenCommand, DescribeAccountCommand } from "@aws-sdk/client-organizations";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
import { format, addHours } from "date-fns";

const client = new OrganizationsClient({ region: "ap-northeast-1" });
const s3Client = new S3Client({ region: "ap-northeast-1" });

// S3バケット名(環境に応じて変更してください)
const BUCKET_NAME = '{任意のS3バケット名}';

// 再帰的にOUとアカウントを取得する関数
const getOUHierarchy = async (parentId, depth = 0) => {
    let result = [];

    // OU一覧を取得
    const { OrganizationalUnits } = await client.send(new ListOrganizationalUnitsForParentCommand({ ParentId: parentId }));

    for (const ou of OrganizationalUnits) {
        console.log(`${"  ".repeat(depth)}OU: ${ou.Name} (${ou.Id})`);

        // OU内のアカウントをページネーションで取得
        let accounts = [];
        let nextToken;

        do {
            const { Children, NextToken } = await client.send(new ListChildrenCommand({
                ParentId: ou.Id,
                ChildType: "ACCOUNT",
                NextToken: nextToken
            }));

            // 子アカウント情報を取得
            for (const child of Children) {
                const accountInfo = await client.send(new DescribeAccountCommand({ AccountId: child.Id }));
                console.log(`${"  ".repeat(depth + 1)}- Account: ${accountInfo.Account.Name} (ID: ${accountInfo.Account.Id})`);
                accounts.push({
                    Name: accountInfo.Account.Name,
                    Id: accountInfo.Account.Id
                });
            }

            nextToken = NextToken;
        } while (nextToken);

        // 再帰的に子OUを取得
        const subOUs = await getOUHierarchy(ou.Id, depth + 1);

        result.push({
            OUName: ou.Name,
            OUId: ou.Id,
            Accounts: accounts,
            SubOUs: subOUs
        });
    }

    return result;
};

// Lambdaハンドラー
export const handler = async () => {
    try {
        // Root IDを取得
        const { Roots } = await client.send(new ListRootsCommand({}));
        const rootId = Roots[0].Id;
        console.log(`Root ID: ${rootId}`);

        // Root OU配下の階層構造を取得
        const hierarchy = await getOUHierarchy(rootId);

        console.log("Final Structure:", JSON.stringify(hierarchy, null, 2));

        // 現在の日時を取得してJSTに変換
        const jstDate = addHours(new Date(), 9);
        const fileName = `${format(jstDate, 'yyyyMMddHH')}_organizations_info.json`;

        // S3にアップロードするオブジェクトを作成
        const putObjectParams = {
            Bucket: BUCKET_NAME,
            Key: fileName,
            Body: JSON.stringify(hierarchy, null, 2),
            ContentType: 'application/json'
        };

        // S3にアップロード
        const uploadResult = await s3Client.send(new PutObjectCommand(putObjectParams));
        console.log(`File uploaded successfully to S3: ${uploadResult.ETag}`);

        return {
            statusCode: 200,
            body: JSON.stringify({
                message: 'Organizations info successfully exported',
                fileName: fileName
            })
        };

    } catch (error) {
        console.error("Error fetching Organizations data:", error);
        throw error;
    }
};

作成したLambda関数には、先ほど準備したLambda実行ロールをアタッチしてください。

3. EventBridge Schedulerの作成

定期実行のためのEventBridge Schedulerを設定します。

スケジュール設定

毎週月曜日00:00(UTC)に実行するよう、以下のCron式を設定します。

0 0 ? * MON *

Cron式の構成:

  • 分:0
  • 時間:0
  • 日:?(任意)
  • 月:*(毎月)
  • 曜日:MON(月曜日)
  • 年:*(毎年)

ターゲット設定

  1. ターゲットに先ほど作成したLambda関数を指定
  2. 先ほど作成したEventBridge実行ロールをアタッチ

これでシステムの構築は完了です!

出力結果のサンプル

システムが実行されると、以下のようなJSON形式のファイルがS3にアップロードされます。

ou-info.json
[
  {
    "OUName": "ExceptionsOU",
    "OUId": "ou-0000-01234567",
    "Accounts": [
      {
        "Name": "Account1",
        "Id": "000011112222"
      },
      {
        "Name": "Account2",
        "Id": "111122223333"
      }
    ],
    "SubOUs": []
  },
  {
    "OUName": "Workloads",
    "OUId": "ou-0000-12345678",
    "Accounts": [],
    "SubOUs": [
      {
        "OUName": "Development",
        "OUId": "ou-0000-23456789",
        "Accounts": [
          {
            "Name": "Account3",
            "Id": "222233334444"
          }
        ],
        "SubOUs": []
      }
    ]
  }
]

運用上のメリット

このシステムを導入することで、以下のようなメリットを感じています。

  • 作業時間の削減:毎回手動でOU情報を取得・加工する必要がなくなった
  • 情報の鮮度向上:定期的に最新情報が自動取得されるため、古い情報での作業ミスが減った
  • 即座の情報取得:必要に応じてLambdaを直接実行することで、リアルタイムでの情報取得も可能

おわりに

定期出力されることで、毎回OU情報を手動取得する手間が省け、業務効率が大幅に改善されました。
現時点の最新情報を取得したい場合には、Lambdaを直接実行することで即座に情報を取得することも可能です。

これからも日々のちょっとした繰り返し業務を自動化し、トイル削減に努めていきます。

同じような課題をお持ちの方々の参考になれば幸いです。
最後までお読みいただき、ありがとうございました!

3
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
3
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?