Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

AWS Configのコストを賢く抑える実践ガイド

By Nikhil PawarDec 23, 20248 min read

このページはEnglishDeutschEspañolFrançaisItalianoPortuguêsでもご覧いただけます。

Photo by SuPatMaN from Shutterstock

はじめにお伝えしておきたいのは、AWS Configは非常に優れたサービスだということです。ご存じない方のために補足すると、AWS Configはリソースの構成変更を継続的に評価・監視・記録できるサービスです。すべての機能をここで列挙はしませんが、詳細はこちらをご覧ください。AWS Configの料金ページには課金の仕組みが説明されており、理解を助ける具体例も掲載されています。要点をまとめると、AWS Configの料金は、記録された構成項目の数、有効化したAWS Configルールの評価回数、アカウント内のコンフォーマンスパックの評価回数によって決まり、加えてS3ストレージやSNSなどの追加コストも発生します。インフラが頻繁に変化する環境では、AWS Configのコストが一気に跳ね上がることもあります。本記事では、AWS Configのコスト構造を分解しながら、コストを抑えるためのいくつかの戦略と、想定外のコスト急増を調査するヒントを紹介します。

注 — プライバシーポリシーに配慮し、デモには筆者の検証用AWS環境を使用しています。Configデータの量はそれほど多くありませんが、ここで紹介するアプローチを理解するには十分です。

  • 記録された構成項目(Configuration Items) — AWS Configダッシュボード(image1)から記録された構成項目を確認できます。同じメトリクスはCloudWatch Metrics(image2)でも参照可能です。

image1 — 記録された構成項目 — AWS Configダッシュボード

image2 — 記録された構成項目 — AWS CloudWatch Metrics

  • ただ、これだけでは情報量としては心もとないですよね。とはいえ、これらの記録件数はアカウント内の動きによって、1日に数百件から数千件まで変動します。何が記録されているかをさらに掘り下げるため、設定済みのS3バケット(image3)に格納されたAWS Configの記録データをAWS Athenaで分析してみました。S3バケットの設定先は、AWS Config > Settings > Delivery Methodの項目から確認できます。

image3 — AWS Configレコードの配信先として使用しているS3

  • S3を開くと、AWS Configから配信された複数のJSONファイルが、組織ID、アカウントID、リージョン、月、日付などの単位で整理されているのが分かります(下図)。ファイルサイズはAWS Configが記録した項目数に応じて変動します。

S3に配信されたConfigファイル

  • 試しにこれらのファイルを開いて中身を確認してみました。例として最初の2ファイルを取り上げます。
nikhilpawar@MacBookPro Downloads % cat xxxxxxxxxx_Config_us-east-1_ConfigHistory_AWS__AppConfig__DeploymentStrategy_20241029T013419Z_20241029T013419Z_1.json
{"fileVersion":"1.0","configurationItems":[{"relatedEvents":[],"relationships":[],"configuration":{"Id":"AppConfig.AllAtOnce","ReplicateTo":"NONE","GrowthType":"LINEAR","Description":"Quick","DeploymentDurationInMinutes":0,"GrowthFactor":100.0,"FinalBakeTimeInMinutes":10,"Name":"AppConfig.AllAtOnce","Tags":[]},"supplementaryConfiguration":{},"tags":{},"configurationItemVersion":"1.3","configurationItemCaptureTime":"2024-10-29T01:34:19.420Z","configurationStateId":1730165659420,"awsAccountId":"xxxxxxxxxx","configurationItemStatus":"ResourceDiscovered","resourceType":"AWS::AppConfig::DeploymentStrategy","resourceId":"AppConfig.AllAtOnce","resourceName":"AppConfig.AllAtOnce","ARN":"arn:aws:appconfig:us-east-1:xxxxxxxxxx:deploymentstrategy/AppConfig.AllAtOnce","awsRegion":"us-east-1","availabilityZone":"Regional","configurationStateMd5Hash":""},{"relatedEvents":[],"relationships":[],"configuration":{"Id":"AppConfig.Canary10Percent20Minutes","ReplicateTo":"NONE","GrowthType":"EXPONENTIAL","Description":"AWS Recommended","DeploymentDurationInMinutes":20,"GrowthFactor":10.0,"FinalBakeTimeInMinutes":10,"Name":"AppConfig.Canary10Percent20Minutes","Tags":[]},"supplementaryConfiguration":{},"tags":{},"configurationItemVersion":"1.3","configurationItemCaptureTime":"2024-10-29T01:34:19.546Z","configurationStateId":1730165659546,"awsAccountId":"xxxxxxxxxx","configurationItemStatus":"ResourceDiscovered","resourceType":"AWS::AppConfig::DeploymentStrategy","resourceId":"AppConfig.Canary10Percent20Minutes","resourceName":"AppConfig.Canary10Percent20Minutes","ARN":"arn:aws:appconfig:us-east-1:xxxxxxxxxx:deploymentstrategy/AppConfig.Canary10Percent20Minutes","awsRegion":"us-east-1","availabilityZone":"Regional","configurationStateMd5Hash":""},{"relatedEvents":[],"relationships":[],"configuration":{"Id":"AppConfig.Linear20PercentEvery6Minutes","ReplicateTo":"NONE","GrowthType":"LINEAR","Description":"AWS Recommended","DeploymentDurationInMinutes":30,"GrowthFactor":20.0,"FinalBakeTimeInMinutes":30,"Name":"AppConfig.Linear20PercentEvery6Minutes","Tags":[]},"supplementaryConfiguration":{},"tags":{},"configurationItemVersion":"1.3","configurationItemCaptureTime":"2024-10-29T01:34:19.625Z","configurationStateId":1730165659625,"awsAccountId":"xxxxxxxxxx","configurationItemStatus":"ResourceDiscovered","resourceType":"AWS::AppConfig::DeploymentStrategy","resourceId":"AppConfig.Linear20PercentEvery6Minutes","resourceName":"AppConfig.Linear20PercentEvery6Minutes","ARN":"arn:aws:appconfig:us-east-1:xxxxxxxxxx:deploymentstrategy/AppConfig.Linear20PercentEvery6Minutes","awsRegion":"us-east-1","availabilityZone":"Regional","configurationStateMd5Hash":""},{"relatedEvents":[],"relationships":[],"configuration":{"Id":"AppConfig.Linear50PercentEvery30Seconds","ReplicateTo":"NONE","GrowthType":"LINEAR","Description":"Test/Demo","DeploymentDurationInMinutes":1,"GrowthFactor":50.0,"FinalBakeTimeInMinutes":1,"Name":"AppConfig.Linear50PercentEvery30Seconds","Tags":[]},"supplementaryConfiguration":{},"tags":{},"configurationItemVersion":"1.3","configurationItemCaptureTime":"2024-10-29T01:34:19.475Z","configurationStateId":1730165659475,"awsAccountId":"xxxxxxxxxx","configurationItemStatus":"ResourceDiscovered","resourceType":"AWS::AppConfig::DeploymentStrategy","resourceId":"AppConfig.Linear50PercentEvery30Seconds","resourceName":"AppConfig.Linear50PercentEvery30Seconds","ARN":"arn:aws:appconfig:us-east-1:xxxxxxxxxx:deploymentstrategy/AppConfig.Linear50PercentEvery30Seconds","awsRegion":"us-east-1","availabilityZone":"Regional","configurationStateMd5Hash":""}]}%                                                  nikhilpawar@MacBookPro Downloads % cat xxxxxxxxxx_Config_us-east-1_ConfigHistory_AWS__AppConfig__DeploymentStrategy_20241029T013419Z_20241029T013419Z_1.json | jq
{
  "fileVersion": "1.0",
  "configurationItems": [\
    {\
      "relatedEvents": [],\
      "relationships": [],\
      "configuration": {\
        "Id": "AppConfig.AllAtOnce",\
        "ReplicateTo": "NONE",\
        "GrowthType": "LINEAR",\
        "Description": "Quick",\
        "DeploymentDurationInMinutes": 0,\
        "GrowthFactor": 100.0,\
        "FinalBakeTimeInMinutes": 10,\
        "Name": "AppConfig.AllAtOnce",\
        "Tags": []\
      },\
      "supplementaryConfiguration": {},\
      "tags": {},\
      "configurationItemVersion": "1.3",\
      "configurationItemCaptureTime": "2024-10-29T01:34:19.420Z",\
      "configurationStateId": 1730165659420,\
      "awsAccountId": "xxxxxxxxxx",\
      "configurationItemStatus": "ResourceDiscovered",\
      "resourceType": "AWS::AppConfig::DeploymentStrategy",\
      "resourceId": "AppConfig.AllAtOnce",\
      "resourceName": "AppConfig.AllAtOnce",\
      "ARN": "arn:aws:appconfig:us-east-1:xxxxxxxxxx:deploymentstrategy/AppConfig.AllAtOnce",\
      "awsRegion": "us-east-1",\
      "availabilityZone": "Regional",\
      "configurationStateMd5Hash": ""\
    },\
    {\
      "relatedEvents": [],\
      "relationships": [],\
      "configuration": {\
        "Id": "AppConfig.Canary10Percent20Minutes",\
        "ReplicateTo": "NONE",\
        "GrowthType": "EXPONENTIAL",\
        "Description": "AWS Recommended",\
        "DeploymentDurationInMinutes": 20,\
        "GrowthFactor": 10.0,\
        "FinalBakeTimeInMinutes": 10,\
        "Name": "AppConfig.Canary10Percent20Minutes",\
        "Tags": []\
      },\
      "supplementaryConfiguration": {},\
      "tags": {},\
      "configurationItemVersion": "1.3",\
      "configurationItemCaptureTime": "2024-10-29T01:34:19.546Z",\
      "configurationStateId": 1730165659546,\
      "awsAccountId": "xxxxxxxxxx",\
      "configurationItemStatus": "ResourceDiscovered",\
      "resourceType": "AWS::AppConfig::DeploymentStrategy",\
      "resourceId": "AppConfig.Canary10Percent20Minutes",\
      "resourceName": "AppConfig.Canary10Percent20Minutes",\
      "ARN": "arn:aws:appconfig:us-east-1:xxxxxxxxxx:deploymentstrategy/AppConfig.Canary10Percent20Minutes",\
      "awsRegion": "us-east-1",\
      "availabilityZone": "Regional",\
      "configurationStateMd5Hash": ""\
    },\
    {\
      "relatedEvents": [],\
      "relationships": [],\
      "configuration": {\
        "Id": "AppConfig.Linear50PercentEvery30Seconds",\
        "ReplicateTo": "NONE",\
        "GrowthType": "LINEAR",\
        "Description": "Test/Demo",\
        "DeploymentDurationInMinutes": 1,\
        "GrowthFactor": 50.0,\
        "FinalBakeTimeInMinutes": 1,\
        "Name": "AppConfig.Linear50PercentEvery30Seconds",\
        "Tags": []\
      },\
      "supplementaryConfiguration": {},\
      "tags": {},\
      "configurationItemVersion": "1.3",\
      "configurationItemCaptureTime": "2024-10-29T01:34:19.475Z",\
      "configurationStateId": 1730165659475,\
      "awsAccountId": "xxxxxxxxxx",\
      "configurationItemStatus": "ResourceDiscovered",\
      "resourceType": "AWS::AppConfig::DeploymentStrategy",\
      "resourceId": "AppConfig.Linear50PercentEvery30Seconds",\
      "resourceName": "AppConfig.Linear50PercentEvery30Seconds",\
      "ARN": "arn:aws:appconfig:us-east-1:xxxxxxxxxx:deploymentstrategy/AppConfig.Linear50PercentEvery30Seconds",\
      "awsRegion": "us-east-1",\
      "availabilityZone": "Regional",\
      "configurationStateMd5Hash": ""\
    }\
  ]
}
  • これらのJSONファイルを手作業やスクリプトで読み解くのはかなり骨が折れます。最適な手段は、もう一つの強力なサービスであるAWS Athenaの活用です。
  • Athenaテーブルの作成 — AWS Configデータ用のAthenaテーブルを作成する際は、対象期間やデータの範囲に応じてクエリを柔軟に組み立てられます。テーブル作成の構文は、分析したい内容によって次のように変わります。
  • 1日分のスナップショット
  • 複数日にまたがるデータ
  • 特定の月または年
  • 過去のConfigデータすべて

注 — 以下のクエリ内の「xxxxxxxxxx」はご自身のAWSアカウントIDに置き換えてください。また、全データをスキャンするのではなく、必要な期間のみを対象にすることをおすすめします(Athenaの料金)。

1. 1日分のスナップショット

1.1:- Athenaテーブルの作成: 例:aws_config_table_single_day_2024_11_08

CREATE EXTERNAL TABLE aws_config_table_single_day_2024_11_08 (
  fileversion string,
  configSnapshotId string,
  configurationitems ARRAY<STRUCT<
    configurationItemVersion: STRING,
    configurationItemCaptureTime: STRING,
    configurationStateId: BIGINT,
    awsAccountId: STRING,
    configurationItemStatus: STRING,
    resourceType: STRING,
    resourceId: STRING,
    resourceName: STRING,
    ARN: STRING,
    awsRegion: STRING,
    availabilityZone: STRING,
    configurationStateMd5Hash: STRING,
    resourceCreationTime: STRING
  >>
)
ROW FORMAT SERDE 'org.apache.hive.hcatalog.data.JsonSerDe'
LOCATION 's3://config-bucket-xxxxxxxxxxxx/AWSLogs/xxxxxxxxxxxx/Config/us-east-1/2024/11/8';

1.1:- Athenaテーブル — 1日分のスナップショット

1.2:- 合計レコード数のカウント: (任意。冒頭で確認したCloudWatchメトリクスと件数が一致するかをチェックできます。)

SELECT
  COUNT(1) AS record_count
FROM
  default.aws_config_table_single_day_2024_11_08
CROSS JOIN
  UNNEST(configurationitems) AS t(configurationItem)
WHERE
  configurationItem.resourceType IS NOT NULL

1.2:- 1日分の合計レコード数

1.3:- リソースタイプ別の変更回数を取得: 次のクエリを使うと、構成変更が最も多く発生しているリソースタイプを洗い出せ、AWS Configのモニタリングにおけるコスト要因を特定できます。

SELECT
  configurationItem.resourceType,
  COUNT(configurationItem.resourceId) AS NumberOfChanges
FROM
  default.aws_config_table_single_day_2024_11_08
CROSS JOIN
  UNNEST(configurationitems) AS t(configurationItem)
GROUP BY
  configurationItem.resourceType
ORDER BY
  NumberOfChanges DESC;

1.3:- リソースタイプと記録された変更回数

1.4:- リソースIDの取得: 変更回数の多いリソースタイプを、さらに個別のリソース単位までドリルダウンできます。 例えば、先ほどのクエリではAWS:EC2:Subnetが最も多くの構成変更を抱えていることが分かりました。次のクエリを使えば、どのサブネットIDが変更頻度を押し上げているのかを特定できます。

SELECT
  configurationItem.resourceType,
  configurationItem.resourceId,
  COUNT(configurationItem.resourceId) AS NumberOfChanges
FROM
  default.aws_config_table_single_day_2024_11_08
CROSS JOIN
  UNNEST(configurationitems) AS t(configurationItem)
GROUP BY
  configurationItem.resourceType,
  configurationItem.resourceId
ORDER BY
  NumberOfChanges DESC

1.4:- 特定されたリソースID

ここまでで、何が起きているのかをかなりクリアに把握できるはずです。同じクエリパターンは別の期間にもそのまま応用できます。月単位のトレンド、特定の日付範囲、Config履歴全体のいずれを分析する場合でも、アプローチはそのままに、時間パラメータだけを変更すれば対応可能です。

2. 特定の月

2.1:- Athenaテーブルの作成: 例:aws_config_table_november_2024

CREATE EXTERNAL TABLE aws_config_table_november_2024 (
  fileversion string,
  configSnapshotId string,
  configurationitems ARRAY<STRUCT<
    configurationItemVersion: STRING,
    configurationItemCaptureTime: STRING,
    configurationStateId: BIGINT,
    awsAccountId: STRING,
    configurationItemStatus: STRING,
    resourceType: STRING,
    resourceId: STRING,
    resourceName: STRING,
    ARN: STRING,
    awsRegion: STRING,
    availabilityZone: STRING,
    configurationStateMd5Hash: STRING,
    resourceCreationTime: STRING
  >>
)
ROW FORMAT SERDE 'org.apache.hive.hcatalog.data.JsonSerDe'
LOCATION 's3://config-bucket-xxxxxxxxxxxx/AWSLogs/xxxxxxxxxxxx/Config/us-east-1/2024/10/';

2.1:- Athenaテーブル — 1か月分のデータ

2.2:- 合計レコード数のカウント: 手順1.2(合計レコード数のカウント)を参考に、テーブル名のみ書き換えてください。

2.2:- 1か月分の合計レコード数

2.3:- リソースタイプ別の変更回数を取得: 手順1.3(リソースタイプ別の変更回数を取得)を参考に、テーブル名のみ書き換えてください。

2.3:- リソースタイプと記録された変更回数

2.4:- リソースIDの取得: 手順1.4(リソースIDの取得)を参考に、テーブル名のみ書き換えてください。

2.4:- 特定されたリソースID

3. すべてのConfigデータ

3.1:- Athenaテーブルの作成: 例: aws_config_table_all

CREATE EXTERNAL TABLE aws_config_table_all (
  fileversion string,
  configSnapshotId string,
  configurationitems ARRAY<STRUCT<
    configurationItemVersion: STRING,
    configurationItemCaptureTime: STRING,
    configurationStateId: BIGINT,
    awsAccountId: STRING,
    configurationItemStatus: STRING,
    resourceType: STRING,
    resourceId: STRING,
    resourceName: STRING,
    ARN: STRING,
    awsRegion: STRING,
    availabilityZone: STRING,
    configurationStateMd5Hash: STRING,
    resourceCreationTime: STRING
  >>
)
ROW FORMAT SERDE 'org.apache.hive.hcatalog.data.JsonSerDe'
LOCATION 's3://config-bucket-XXXXXXXXXXXX/AWSLogs/XXXXXXXXXXXX/Config/us-east-1/';

3.1:- Athenaテーブル — すべてのConfigデータ

3.2:- 合計レコード数: 手順1.2(合計レコード数のカウント)を参考に、テーブル名のみ書き換えてください。

3.2:- 合計レコード数

3.3:- リソースタイプ別の変更回数を取得: 手順1.3(リソースタイプ別の変更回数を取得)を参考に、テーブル名のみ書き換えてください。

3.3:- リソースタイプと記録された変更回数

3.4:- リソースIDの取得: 手順1.4(リソースIDの取得)を参考に、テーブル名のみ書き換えてください。

3.4:- 特定されたリソースID

4. 複数日にまたがるデータ

4.1 Athenaテーブルの作成: このシナリオでは、2024/11/7から2024/11/10までの期間を対象に、パーティション射影(partition projection)を使ってAthenaテーブルをパーティション化します。期間は必要に応じて調整してください。

CREATE EXTERNAL TABLE aws_config_table_period_2024_11_07to2024_11_10 (
  fileversion string,
  configSnapshotId string,
  configurationitems ARRAY<STRUCT<
    configurationItemVersion: STRING,
    configurationItemCaptureTime: STRING,
    configurationStateId: BIGINT,
    awsAccountId: STRING,
    configurationItemStatus: STRING,
    resourceType: STRING,
    resourceId: STRING,
    resourceName: STRING,
    ARN: STRING,
    awsRegion: STRING,
    availabilityZone: STRING,
    configurationStateMd5Hash: STRING,
    resourceCreationTime: STRING
  >>
)

PARTITIONED BY (`year` string,`month` string,`day` string)
ROW FORMAT SERDE 'org.apache.hive.hcatalog.data.JsonSerDe'
LOCATION 's3://config-bucket-xxxxxxxxxxxx/AWSLogs/xxxxxxxxxxxx/Config/us-east-1'
TBLPROPERTIES (
  'projection.enabled'='true',
  'projection.year.interval'='1',
  'projection.year.range'='2024,2024',
  'projection.year.type'='integer',
  'projection.month.interval'='1',
  'projection.month.range'='10,11',
  'projection.month.type'='integer',
  'projection.day.interval'='1',
  'projection.day.range'='7,10',
  'projection.day.type'='integer',
  'storage.location.template'='s3://config-bucket-xxxxxxxxxxxx/AWSLogs/xxxxxxxxxxxx/Config/us-east-1/${year}/${month}/${day}/ConfigHistory/')

4.1:- Athenaテーブル — 指定期間

4.2:- 合計レコード数:

SELECT result.configurationitemcapturetime,
         count(result.configurationitemcapturetime) AS NumberOfChanges
FROM
    (SELECT regexp_replace(configurationItem.configurationItemCaptureTime,
         '(.+)(T.+)', '$1') AS configurationitemcapturetime
    FROM default.aws_config_table_period_2024_11_07to2024_11_10
    CROSS JOIN UNNEST(configurationitems) AS t(configurationItem)
    WHERE "$path" LIKE '%ConfigHistory%'
            AND configurationItem.configurationItemCaptureTime >= '2024-11-05T%'
            AND configurationItem.configurationItemCaptureTime <= '2024-11-12T%') result
GROUP BY  result.configurationitemcapturetime
ORDER BY  result.configurationitemcapturetime

4.2:- 合計レコード数

4.3:- リソースタイプ別の変更回数を取得:

SELECT configurationItem.resourceType,
       configurationItem.resourceId,
       COUNT(configurationItem.resourceId) AS NumberOfChanges
FROM default.aws_config_table_period_2024_11_07to2024_11_10
CROSS JOIN UNNEST(configurationitems) AS t(configurationItem)
WHERE "$path" LIKE '%ConfigHistory%'
        AND configurationItem.configurationItemCaptureTime >= '2024-11-05T%'
        AND configurationItem.configurationItemCaptureTime <= '2024-11-12T%'
GROUP BY  configurationItem.resourceType, configurationItem.resourceId
ORDER BY  NumberOfChanges DESC

### or ###

SELECT configurationItem.resourceType,
       configurationItem.resourceId,
       CAST(COUNT(configurationItem.resourceId) AS INTEGER) AS NumberOfChanges
FROM default.aws_config_table_period_2024_11_07to2024_11_10
CROSS JOIN UNNEST(configurationitems) AS t(configurationItem)
WHERE "$path" LIKE '%ConfigHistory%'
      AND configurationItem.configurationItemCaptureTime >= '2024-11-05T%'
      AND configurationItem.configurationItemCaptureTime <= '2024-11-12T%'
GROUP BY configurationItem.resourceType, configurationItem.resourceId
ORDER BY NumberOfChanges DESC

4.3:- リソースタイプと記録された変更回数

4.4:- リソースIDの取得

SELECT configurationItem.resourceId,
       configurationItem.resourceType,
       COUNT(configurationItem.resourceId) AS NumberOfChanges
FROM default.aws_config_table_period_2024_11_07to2024_11_10
CROSS JOIN UNNEST(configurationitems) AS t(configurationItem)
WHERE "$path" LIKE '%ConfigHistory%'
      AND configurationItem.configurationItemCaptureTime >= '2024-11-05T%'
      AND configurationItem.configurationItemCaptureTime <= '2024-11-12T%'
GROUP BY
     configurationItem.resourceId,
     configurationItem.resourceType
ORDER BY NumberOfChanges DESC

4.4:- 特定されたリソースID

  • これで、対象期間に記録項目が多かったリソースIDとリソースタイプが特定できました。

2023年6月以降、AWS Configではリソースタイプ単位で記録対象から除外できるようになりました。これを活用すれば、リソースタイプごとの設定を見直してチューニングしたり、特定の日に発生した想定外のコスト急増を調査したりできます。

記録対象リソース

もう一つ押さえておきたいのが、データ保持期間です。こちらも運用ニーズに合わせて調整可能です。

保持期間

まとめると、AWS Configはパワフルなサービスですが、特に変化の激しい環境ではコストが一気に膨らむこともあります。構成項目の記録状況の分析、AWS Athenaを活用した詳細な調査、構成記録の除外設定の活用、データ保持期間のきめ細やかな管理といった戦略を組み合わせれば、堅牢なインフラの可視性を保ちながら、AWS Configのコストを細かい粒度で把握できるようになります。ポイントは、モニタリングをやめることではなく、「何を」「どう」追跡するかを最適化することです。リソースの変更パターンを理解し、必要なものだけを選んで記録することで、網羅的なモニタリングとコスト効率の両立が可能になります。

AWS Configをさらに使いこなしたい方や、私たちのサービスにご関心のある方は、お気軽にご連絡ください。お問い合わせはこちらからどうぞ。