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

Control Tower環境でConfigのコストを最適化しよう

1
Posted at

はじめに

少し古い記事ですが、下記AWSブログにて、Control Tower環境でConfigの設定をカスタマイズするソリューション「aws-control-tower-config-customization」が紹介されています。

具体的なカスタマイズ項目は、リソースごとの記録有無と記録頻度です。Configはリソースの変更記録回数が多ければ多いほどコストがかかるため、変更頻度が高いリソースがある場合は、このソリューションで記録有無や記録頻度を調整することでコストを抑えることができます。

今回は、このソリューションについて書いていきたいと思います。

Control Towerによって作成されたリソースの取り扱い

基本的には、Control Towerによって作成されたリソースを、ControlTowerを通さず変更してはいけません

(翻訳) AWS Control Towerによって作成されたリソース(管理アカウント、共有アカウント、メンバーアカウント内のリソースを含む)を変更または削除しないでください。これらのリソースを変更すると、ランディングゾーンの更新やOUの再登録が必要になる場合があり、変更によりコンプライアンス報告が不正確になる可能性があります。
(引用元:https://docs.aws.amazon.com/controltower/latest/userguide/getting-started-guidance.html)

(翻訳) SCPなどのAWS Control Towerリソースを変更した場合、またはConfig RecorderやAggregatorなどのAWS Configリソースを削除した場合、AWS Control Towerは制御機能が設計どおりに動作することを保証できなくなります。
(引用元:https://docs.aws.amazon.com/controltower/latest/userguide/control-limitations.html)

「aws-control-tower-config-customization」はControl Towerの機能ではありません。

では、なぜこのソリューションはControl Towerによって制御されているConfigの設定を変更していいのでしょうか?

下記は冒頭で引用したAWSブログから抜粋です。

  • 各管理対象アカウントのAWSControlTowerExecutionロールを利用して、AWS Control Tower が管理するすべてのリージョンの AWS Config レコーダーリソースタイプを更新します。
  • このソリューションは、Landing Zone の更新やアカウントの管理など、特定の管理操作のために AWS Control Tower が生成するライフサイクルイベントを活用します。
  • この方法により、既存の AWS Control Tower の設定、将来の Landing Zone の更新、そしてアカウント管理機能への影響を回避できます。

つまり下記のような理由なのだと読み取りました。

  • AWSControlTowerExecutionロールを利用することにより、Control Tower操作範囲から逸脱しない
  • Control Towerのライフサイクルイベントに合わせて動作する

あとは、利用者視点からだと、AWS公式のソリューションであること、という点が非常に大きいです。

ソリューションの鮮度

AWS公式と言いましたが、aws-samplesはあくまでサンプルなので、AWSが動作を保証してくれるものではありません。リポジトリの更新が無い状態で何年も経過しているような場合は、現在も有効なソリューションであるかどうかを自身で確認する必要があります

このソリューションがAWSブログで最初に紹介された日付は2022/8/5ですが、 2025/11/15にv1.1.0、2025/11/16にv2.0.0がリリースされており、現在もアクティブなソリューションとして更新されていることがわかります。

アーキテクチャー

下図はAWSブログからの引用です。サーバーレスで構成されているため、このソリューション自体のコストは低く抑えられると推測できます。

image.png

使ってみた

設計方針

ガバナンスの観点では、Configに対応しているリソースの変更は、基本的にはすべて記録するのが望ましいと思います。とは言え、あまりにも変更頻度が多いとコストが跳ね上がります。そこで、変更頻度が多くコスト影響が出るリソースのみ取得頻度を落とすことを考え、下記の設計で進めてみます。

  • カスタマイズから除外するアカウントをリストアップする (*)
  • Control Towerの共有アカウント(管理アカウント、ログアーカイブアカウント、監査アカウント)は除外する (*)
  • IAMはControl Towerホームリージョンでのみ記録対象とし、頻度は日次とする (*)
  • 変更頻度が高く、コスト影響が大きそうなリソースの取得頻度を日次とする
  • いずれのリソースも取得対象外にはしない

(*) ソリューションのデフォルト値=推奨設定

できないこと

このソリューションでも下記のようなより細かいカスタマイズをすることはできません。

  • アカウントごとのカスタマイズ
    • (例) AアカウントだけSecurityGroupリソースの記録を除外する、ということはできない。
  • リージョンごとのカスタマイズ
    • (例) バージニアリージョンだけSecurityGroupリソースの記録を除外する、ということはできない。

できるのは、対象になったアカウントすべてに対して一律で、一部リソースの記録を除外したり、記録頻度を日次にするリソースを指定したりすることです。

デプロイ構成

運用を考えると、ソリューションのテンプレートファイルはリポジトリ管理が望ましいと思います。
今回は、CloudFormationの Git同期 を利用してソリューションを展開してみます。
GitプロバイダーとしてGitHub、GitLab、BitBucket等に対応していますが、今回はGitHubを利用します。
デプロイ先は管理アカウントのControl Towerホームリージョンです。

絵にすると以下のようなイメージです。

image.png

設定の流れ

  1. GitHub接続
  2. スタックデプロイメントファイル作成
  3. GitHubにコミット
  4. IAMロール作成
  5. CloudFormationスタック作成

1. GitHub接続

まず、AWSとGitHubの接続を作成します。

image.png

GitHubを選択します。

image.png

対象のGitHubアカウントを選択します。

image.png

新しいアプリをインストールをクリックします。(インストール済みの場合は左側から既存のアプリを選択)

image.png

対象のGitHubアカウントを選択します。

image.png

インストール先のリポジトリを選択します。

image.png

接続をクリックします。

image.png

選択したGitHubリポジトリとAWSの接続が作成できました。

image.png

2. スタックデプロイメントファイル作成

スタックデプロイメントファイルでは下記を指定します。

  • CloudFormationテンプレートファイルのパス
  • CloudFormationテンプレートに渡すパラメータ値
  • CloudFormationスタックに適用するタグ

先述の設計方針を踏まえ、以下の記述としました。

deployment.yaml
template-file-path: template.yaml

parameters:
  CloudFormationVersion: 1
  # AccountSelectionMode: EXCLUSION ※デフォルト
  ExcludedAccounts: "['111111111111', '222222222222', '333333333333']" # ダミー
  # ConfigRecorderStrategy: EXCLUSION ※デフォルト
  ConfigRecorderExcludedResourceTypes: ''
  # ConfigRecorderDefaultRecordingFrequency: CONTINUOUS ※デフォルト
  ConfigRecorderDailyResourceTypes: ''

tags:
  Environment: 'test'

ExcludedAccountsに実際は管理アカウント、ログアーカイブアカウント、監査アカウントを設定しました。
デフォルトの部分は指定不要ですが、わかりやすいようにコメントアウトの形で載せました。
タグにはとりあえず適当な値を入れてみました。

なお、このスタックデプロイメントファイルは、Git同期の過程で自動生成することもできます。
今回は事前に作成してGitHubリポジトリに配置しておくことにしました。

3. GitHubにコミット

作成したデプロイメントファイルdeployment.yamlと、aws-samplesから取得したCloudFormationテンプレートtemplate.yamlをGitHubリポジトリにコミットします。

image.png

4. IAMロール作成

ロールは2種類作成します。

  • GitHub同期サービスロール
  • CloudFormationサービスロール

GitHub同期サービスロール

GitHubからCodeConnection経由でCloudFormationを操作するのに利用されるロールです。
ドキュメント を参考にして、下記のCloudFormationテンプレートでIAMロールを作成しました。

テンプレート
AWSTemplateFormatVersion: '2010-09-09'
Description: IAM Role for CloudFormation GitHub Sync

Parameters:
  SystemAlias:
    Type: String
    Description: System alias for the role name

  Env:
    Type: String
    Description: Environment name

  CodeConnectionId:
    Type: String
    Description: ID of the CodeConnections connection

Resources:
  GitHubSyncRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub '${SystemAlias}-${Env}-github-sync-role'
      Description: Role for CloudFormation GitHub Sync to deploy templates
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Sid: CfnGitSyncTrustPolicy
            Effect: Allow
            Principal:
              Service: cloudformation.sync.codeconnections.amazonaws.com
            Action: sts:AssumeRole
            Condition:
              StringEquals:
                aws:SourceAccount: !Ref AWS::AccountId
              ArnLike:
                aws:SourceArn: !Sub 'arn:${AWS::Partition}:codeconnections:${AWS::Region}:${AWS::AccountId}:connection/${CodeConnectionId}'
      Policies:
        - PolicyName: CloudFormationGitHubSyncPolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Sid: SyncToCloudFormation
                Effect: Allow
                Action:
                  - cloudformation:CreateChangeSet
                  - cloudformation:DeleteChangeSet
                  - cloudformation:DescribeChangeSet
                  - cloudformation:DescribeStackEvents
                  - cloudformation:DescribeStacks
                  - cloudformation:ExecuteChangeSet
                  - cloudformation:GetTemplate
                  - cloudformation:ListChangeSets
                  - cloudformation:ListStacks
                  - cloudformation:ValidateTemplate
                Resource: '*'
              - Sid: PolicyForManagedRules
                Effect: Allow
                Action:
                  - events:PutRule
                  - events:PutTargets
                Resource: '*'
                Condition:
                  StringEquals:
                    events:ManagedBy:
                      - cloudformation.sync.codeconnections.amazonaws.com
              - Sid: PolicyForDescribingRule
                Effect: Allow
                Action: events:DescribeRule
                Resource: '*'

CloudFormationサービスロール

CloudFormationがリソースを作成・更新・削除するのに利用するロールです。
今回はAdministratorAccessをアタッチしたロールを作成して利用することにします。

本番環境では権限設計は慎重に行う必要があります。

サービスロールを指定すると、CloudFormation はスタックで実行されるすべてのオペレーションに対して、そのロールを常に使用します。スタックの作成後に、スタックにアタッチされたサービスロールを削除することはできません。このスタックで操作を実行する権限を持つ他のユーザーは、iam:PassRole 権限の有無にかかわらず、このロールを使用できます。ユーザーが持つべきではないアクセス権限がロールに含まれる場合、ユーザーのアクセス権限を非意図的にエスカレーションできてしまいます。
(引用元:https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/using-iam-servicerole.html )

5. CloudFormationスタック作成

マネコンからスタックを作成していきます。
「Gitから同期」を選択します。

image.png

今回はデプロイメントファイルを事前に作成済みのため、「リポジトリに自分のファイルを提供しています」を選択します。

image.png

GitHubを選択し、先ほど作成したCodeConnectionを指定します。

image.png

GitHubにコミットしてあるデプロイメントファイル名を指定します。

image.png

GithHubがAWSへ接続するときに利用するロールを指定します。

image.png

CloudFormationがリソースを操作するときに利用するロールを指定します。

image.png

スタックを作成してしばらく待つと、各種ステータスが正常な状態になりました。

image.png

デプロイメントファイルで指定したタグはスタックに設定されています。

image.png

パラメータを変更してみる

変更内容

パラメータを下記のように変更してみます。

deployment.yaml (変更点のみ抜粋)
    ExcludedAccounts: "['111111111111', '222222222222', '999999999999']" # 1~,2~は実在アカウント、9~はダミー
    ConfigRecorderExcludedResourceTypes: 'AWS::EC2::NetworkInterface'
    ConfigRecorderDailyResourceTypes: 'AWS::EC2::SecurityGroup'

除外リソースと、記録頻度を日次とするリソースを具体的に指定しました。また、わたしが試した環境では、除外アカウントに指定したアカウントしか存在しなかった(全アカウントがカスタマイズ対象外だった)ため、除外アカウントのうちの一つ(上記の例では999999999999)だけダミーにして、カスタマイズ対象としました。

プルリクエスト

プルリクエストを作成して少し待つと、コメントが書き込まれました。

image.png

画像のテーブル部分は下記のとおりです。

Action LogicalResourceId PhysicalResourceId ResourceType Replacement Scope Attribute Name RequiresRecreation BeforeValue AfterValue AttributeChangeType
Modify ConsumerLambda config-custom-stack-ConsumerLambda-odXcWKbm28v7 AWS::Lambda::Function False Properties Properties Environment Never AWS::EC2::SecurityGroup Modify
Properties Environment Never AWS::EC2::NetworkInterface Modify
Properties Environment Never AWS::EC2::NetworkInterface Modify
Properties Environment Never AWS::EC2::SecurityGroup Modify
Modify ProducerLambda config-custom-stack-ProducerLambda-1Hxh1Ii7WOep AWS::Lambda::Function False Properties Properties Environment Never ['111111111111', '222222222222', '333333333333'] ['111111111111', '222222222222', '999999999999'] Modify
Properties Environment Never ['111111111111', '222222222222', '333333333333'] ['111111111111', '222222222222', '999999999999'] Modify

変更が入るリソースはLambda関数2つで、いずれも環境変数の値が変更されます。ConsumerLambdaでは除外リソースや日次記録対象リソース、ProducerLambdaではアカウントIDの値が変わることがわかりますが、どの環境変数名が変わるかまでは読み取れないようです。

プルリクエストをマージすると、スタックが更新されました。

変更状況を確認

具体的にどの環境変数が更新されたかをマネコン上で確認してみます。

ProducerLambda

image.png

ConsumerLambda

Before
image.png

After
image.png

それぞれのLambda関数は構成図でいうとここです。

image.png

ProducerLambdaは、ControlTowerによって作成されたConfigをデプロイするStackSetsから、ConfigがデプロイされているアカウントIDとリージョンを読み取り、SQSに送信します。
ConsumerLambdaは、SQSをトリガーとして動作し、メンバーアカウントのAWSControlTowerExecutionロールを使って、環境変数に設定されたカスタマイズ値をもとにメンバーアカウントのConfig設定を更新します。

カスタマイズ対象アカウントの設定を確認したところ、なぜか変わっていません。

image.png

ProducerLambdaと、ProducerLambdaをキックするEventBridgeのメトリクスを確認したところ、動作した形跡がありませんでした。

Configカスタマイズのトリガー

ProducerLambdaをキックするEventBridgeのイベントパターンは下記のとおりで、ランディングゾーン変更やアカウント作成をハンドリングするようになっています。

{
  "source": ["aws.controltower"],
  "detail-type": ["AWS Service Event via CloudTrail"],
  "detail": {
    "eventName": ["UpdateLandingZone", "CreateManagedAccount", "UpdateManagedAccount", "ResetLandingZone"]
  }
}

今回行った操作はConfigカスタマイズソリューションのパラメータ変更なので、イベントをハンドリングすることは当然できません。

パラメータ変更を反映させる方法

パラメータにCloudFormationVersionというものがあり、これを変更すればよさそうです。

CloudFormationVersion
 説明: スタックの更新を強制し、ソリューションを再実行するためのバージョン番号
 タイプ: 文字列
 デフォルト:1
 使用法: すべてのアカウントでソリューションを強制的に再実行する必要がある場合は、この値を増やします。

バージョンを2に変更してみます。

deployment.yaml (変更点のみ抜粋)
    CloudFormationVersion: 2

今度は正しくConfigの設定が変更されました。

image.png

なお、IAMの記録頻度が変わっているのは、今回デプロイメントファイルで明示的に指定していなかった下記パラメータのデフォルト値が適用されたためです。(上記画面はホームリージョンのものです)

ConfigRecorderDailyGlobalResourceTypes
 説明: コントロールタワーのホームリージョンで毎日記録するグローバルリソースタイプのコンマ区切りリスト
 タイプ: 文字列
 デフォルト:AWS::IAM::Policy,AWS::IAM::User,AWS::IAM::Role,AWS::IAM::Group
 使用方法: 重複を避けるため、グローバルリソース(IAM、CloudFrontなど)はホームリージョンにのみ記録されます。
 注: これらのリソースは、コントロールタワーのホームリージョンの毎日の記録にのみ自動的に追加されます。

なぜバージョンを変えるとLambdaが動作するのか

CloudFormationテンプレートの下記の部分でカスタムリソースが定義されています。

CloudFormationVersionを変更することで、ServiceTokenに指定されたProducerLambdaを動作させることができます。

なお、ProducerLambdaへのイベント入力として下記のようにFunctionNameやVersionが渡されますが、ProducerLambdaでこれらの値は使用されない作りになっています。

イベント抜粋
  "ResourceProperties":
    {
      "ServiceToken": "arn:aws:lambda:ap-northeast-1:111111111111:function:config-custom-stack-ProducerLambda-1Hxh1Ii7WOep",
      "FunctionName": "config-custom-stack-ProducerLambda-1Hxh1Ii7WOep",
      "Version": "2",
    }

CloudFormationVersionパラメータはString型なので数値以外も指定できますが、このソリューションを運用していく上では数字をインクリメントしていくのがよさそうです。
※FunctionNameはそもそも不要な気がします。

管理アカウントのConfigは対象?

管理アカウントは対象外です。

そもそもControl TowerはメンバーアカウントのConfigを有効化しますが、管理アカウントのConfigは管理対象外です。
そして、メンバーアカウントのConfig有効化はAWSControlTowerBP-BASELINE-CONFIGというControl Towerによって作成されるStackSetsによって行われます。

「aws-control-tower-config-customization」ではカスタマイズの対象アカウントまたは対象外アカウントを指定できますが、ProducerLambdaがAWSControlTowerBP-BASELINE-CONFIGに含まれるスタックインスタンスを読み取ることで、対象とするアカウントの全量を決めています。

さいごに

「aws-control-tower-config-customization」は、Control Towerを利用する場合、コスト最適の観点では必須の機能だと思います。AWSから公開されているので信頼できますし、容易に導入してコストを最適化できるので、良いソリューションだと感じました。

弊社では一緒に働く仲間を募集中です!

現在、様々な職種を募集しております。
カジュアル面談も可能ですので、ご連絡お待ちしております!

募集内容等詳細は、是非採用サイトをご確認ください。

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