はじめに:AWSパラメータシート作成の「元」を手軽に作る
CS1の石井です。
AWSリソースの各種パラメータは、プロジェクトごとに設定の経緯や設計があり、それらは「パラメータシート」として文書化されていることと思います。
しかし、ゼロからこの膨大なパラメータシートを作成しようとすると、AWSの広大なリソースを前にして途方に暮れてしまうことも少なくありません。これは多くのエンジニアが抱える共通の課題でしょう。
そこで本記事では、Cloudformationの機能である IaCジェネレーター でリソース情報を取得、AIにマークダウン形式で出力してもらうという手法で、手間を大幅に削減する試みを紹介します。
このアプローチが皆さんのプロジェクトの一助になれば幸いです。
構想:AIへのインプットをどう取得するか
AIが対象AWSアカウントにログインし、全リソースを自動で調査して文字起こしする、という方法は理想的ですが、実装のハードルが高いです。
そのため、今回は「ユーザーが一度AWSリソースの情報を取得し、それをAIにインプットして文字起こししてもらう」というプロセスを採用しました。
AWSリソースの情報の取得方法
AIにインプットするAWSリソースの情報取得ですが、候補としては以下のような手法が考えられます。
- マネジメントコンソールで画面を目視確認
- 作成したリソースに対してdescribe系コマンドを実行
- AWS Configでリソース情報を収集
- IaCジェネレーターを使用する
上2つは現実的ではないため除外。AWS Configは有効化されていないアカウントがある可能性を考慮し、今回は最も手軽で網羅性の高い CloudFormationのIaCジェネレーター を採用することにしました。
IaCジェネレータとは
IaCジェネレーターは、デプロイ済みのAWSリソースの状態からCloudFormationやCDKテンプレートを生成してくれるAWS公式のサービスです。
以前はFormer2といったサードパーティツールがありましたが、ついにAWSから公式機能が提供されました。
AWSアカウントのすべてのリソースをスキャンでき、必要に応じてAWSのリソースタイプで絞り込むことも可能です。 今回は、特定のリソースタイプで絞り込む方法をAWS CLIから実行します。
サンプルとなるAWSリソースのデプロイ
まずはIaCジェネレーターで出力する対象となるリソースを作成します。
本記事では、後でフィルタリングしやすいように、すべてのリソースに ptag: true というタグ情報が付与されるよう、以下のECSサンプルをデプロイしました。
※補足:筆者の環境では手頃なサンプル用リソースがなかったので、CDKでリソースをデプロイしています。
CDKの場合、IaCジェネレータを使わずとも synth したタイミングで cdk.out にテンプレートが格納されます。 そのため、AIに対するインプット情報であればそちらのファイルを使うのが適切です。
本記事ではCDKで作成したリソースを、マネジメントコンソールで手動作成したリソースと見立て、IaCジェネレーターで再度CloudFormationテンプレートを作成しています。
import * as cdk from 'aws-cdk-lib'; import { Construct } from 'constructs'; import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; export class GenStack extends cdk.Stack { constructor(scope: Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'MyFargateService', { publicLoadBalancer: true, taskImageOptions: { image: ecs.ContainerImage.fromRegistry('public.ecr.aws/nginx/nginx:alpine-slim'), containerPort: 80, }, }); cdk.Tags.of(this).add('ptag', 'true'); } }
IaCジェネレータでCloudFormationを生成
AWS公式の手順は以下となります。
アカウントをスキャンし、デプロイ中のリソース情報を集めた上でCloudFormationのテンプレートを生成するという流れです。 また、スキャン対象は特定のリソースタイプを明記すればスキャン対象を狭めることができます。
本記事ではリソースタイプを明記しスキャン対象を狭めた上でスキャンする段取りで作業を進めます。全体の流れを整理すると以下となります。
- config.jsonにスキャン対象とするリソースタイプを明記
- config.jsonに明記されたリソースタイプを対象にアカウントに対してスキャン実行
- スキャンされた結果からCloudFormationテンプレートを生成する
取得するリソースのタイプを記載する
私の環境では先ほど「GenStack」というECSのサンプルをデプロイしました。 このスタックで生成したリソースタイプを 「フィルターの役割を持つ config.json」 に記載すると以下のようになります。
$ cat config.json [ { "Types": [ "AWS::EC2::EIP", "AWS::EC2::InternetGateway", "AWS::EC2::NatGateway", "AWS::EC2::Route", "AWS::EC2::RouteTable", "AWS::EC2::SecurityGroup", "AWS::EC2::SecurityGroupEgress", "AWS::EC2::SecurityGroupIngress", "AWS::EC2::Subnet", "AWS::EC2::SubnetRouteTableAssociation", "AWS::EC2::VPC", "AWS::EC2::VPCGatewayAttachment", "AWS::ECS::Cluster", "AWS::ECS::Service", "AWS::ECS::TaskDefinition", "AWS::ElasticLoadBalancingV2::Listener", "AWS::ElasticLoadBalancingV2::LoadBalancer", "AWS::ElasticLoadBalancingV2::TargetGroup", "AWS::IAM::Policy", "AWS::IAM::Role", "AWS::Lambda::Function", "AWS::Logs::LogGroup" ] } ]$ $
今回はCDKでデプロイしているため、CDKが生成したテンプレートからリソーススタイプを絞り込むことができましたが
手動作成しているリソースなどがある場合、抜け漏れがないように AWS::*::*
といったアスタリスク指定が良いかと思います。
スキャン実行
以下のコマンドでスキャンを開始します。
$ aws cloudformation start-resource-scan --scan-filters file://config.json --region ap-northeast-1 { "ResourceScanId": "arn:aws:cloudformation:ap-northeast-1:968841012693:resourceScan/5e5d51a2-15c3-4b1f-ad2c-8eaa12f77b23" } $
スキャンIDだけ帰ってきました。スキャンは10-20分ほどかかるため待機します。 状態は先ほど生成したスキャンIDを対象にdecribeすれば状態を確認できます。
以下のサンプルでは"Status": "IN_PROGRESS",となっており、これがCOMPLETEになるまで待ちます。
$ aws cloudformation describe-resource-scan --region ap-northeast-1 --resource-scan-id arn:aws:cloudformation:ap-northeast-1:968841012693:resourceScan/5e5d51a2-15c3-4b1f-ad2c-8eaa12f77b23 { "ResourceScanId": "arn:aws:cloudformation:ap-northeast-1:968841012693:resourceScan/5e5d51a2-15c3-4b1f-ad2c-8eaa12f77b23", "Status": "IN_PROGRESS", "StartTime": "2025-07-06T22:58:38.977000+00:00", "PercentageCompleted": 1.0, "ResourceTypes": [], "ScanFilters": [ { "Types": [ "AWS::EC2::EIP", "AWS::EC2::InternetGateway", "AWS::EC2::NatGateway", "AWS::EC2::Route", "AWS::EC2::RouteTable", "AWS::EC2::SecurityGroup", "AWS::EC2::SecurityGroupEgress", "AWS::EC2::SecurityGroupIngress", "AWS::EC2::Subnet", "AWS::EC2::SubnetRouteTableAssociation", "AWS::EC2::VPC", "AWS::EC2::VPCGatewayAttachment", "AWS::ECS::Cluster", "AWS::ECS::Service", "AWS::ECS::TaskDefinition", "AWS::ElasticLoadBalancingV2::Listener", "AWS::ElasticLoadBalancingV2::LoadBalancer", "AWS::ElasticLoadBalancingV2::TargetGroup", "AWS::IAM::Policy", "AWS::IAM::Role", "AWS::Lambda::Function", "AWS::Logs::LogGroup" ] } ] } $
次にスキャン完了したスキャンIDから「list-resource-scan-resources」コマンドを使い、スキャンされたリソースをリストで表示します。
$ aws cloudformation list-resource-scan-resources --region ap-northeast-1 --resource-scan-id arn:aws:cloudformation:ap-northeast-1:968841012693:resourceScan/5e5d51a2-15c3-4b1f-ad2c-8eaa12f77b23 { "Resources": [ { "ResourceType": "AWS::EC2::EIP", "ResourceIdentifier": { "AllocationId": "eipalloc-01ccf105077be6f0c", "PublicIp": "XXXXXXXXX" }, "ManagedByStack": true }, { "ResourceType": "AWS::EC2::EIP", "ResourceIdentifier": { 以下省略
表示された結果をresources.jsonというファイルで保存します。
JSONの構造、"ManagedByStack"、APIサイズ超過への対処
list-resource-scan-resources の出力結果をそのまま create-generated-template に渡そうとすると、いくつかエラーが発生します。
JSON構造のエラー:
create-generated-template は、"Resources" キーの下にリソースの配列が直接記述されている形式を期待します。 list-resource-scan-resources の表示結果では"Resources" から表示してしまうため、エラーとなってしまいます。
ManagedByStack パラメータのエラー:
スキャン結果に含まれる ManagedByStack パラメータは、リソースがCloudFormationによって管理されているかどうかを示す情報です。テンプレート生成時には不要なため削除が必要です。
APIサイズ超過:
大量のリソースがある場合、APIリクエストのペイロードサイズ上限に引っかかり、エラーが発生することがあります。ロググループやIAMロールが多数存在すると発生しやすいです。
これらの問題を解決するため、jq コマンドを使ってJSONの構造を整形し、不要なパラメータを削除し、さらに今回デプロイしたCDKスタック名「GenStack」を含むリソースのみをフィルタリングし、ファイルサイズを削減します。
aws cloudformation list-resource-scan-resources \ --region ap-northeast-1 \ --resource-scan-id arn:aws:cloudformation:ap-northeast-1:968841012693:resourceScan/5e5d51a2-15c3-4b1f-ad2c-8eaa12f77b23 \ | jq ' .Resources | map( select((.ResourceIdentifier | tostring) | test("GenStack")) # GenStackを含むリソースのみフィルタ | del(.ManagedByStack) # ManagedByStackパラメータを削除 ) ' > resources.json
整形された resources_clean.json の中身は以下のようになります。
$ cat resources.json [ { "ResourceType": "AWS::ECS::Cluster", "ResourceIdentifier": { "ClusterName": "GenStack-EcsDefaultClusterMnL3mNNYN926A5246-rxR3YH3htvnC" } }, # ~省略~ { "ResourceType": "AWS::Logs::LogStream", "ResourceIdentifier": { "LogGroupName": "GenStack-MyFargateServiceTaskDefwebLogGroup4A6C44E8-sxXXyK16ud6F", "LogStreamName": "MyFargateService/web/b533c9b1d4984c9791e093f7f8651983" } } ]
CloudFormationテンプレート生成とダウンロード
JSONの構造、パラメータ、サイズ上限の問題が解消されたので、以下のコマンドでテンプレートを生成します。
$ aws cloudformation create-generated-template --region ap-northeast-1 --generated-template-name ecsTemplate --resources file://resources_clean.json { "GeneratedTemplateId": "arn:aws:cloudformation:ap-northeast-1:968841012693:generatedTemplate/7f63c2c2-3405-4ce0-8a5e-e93567cda061" }
生成されたテンプレートをJSON形式でダウンロードします。
$ aws cloudformation get-generated-template \ --region ap-northeast-1 \ --generated-template-name ecsTemplate \ | jq -r '.TemplateBody | fromjson' > ecsTemplate.json
ecsTemplate.json の中身を確認すると、CloudFormationテンプレートとして出力されていることがわかります。
$ cat ecsTemplate.json { "Metadata": { "AWSToolsMetrics": { "IaC_Generator": "arn:aws:cloudformation:ap-northeast-1:968841012693:generatedTemplate/7f63c2c2-3405-4ce0-8a5e-e93567cda061" } }, # ~中略~ "Resources": { "ECSClusterGenStackEcsDefaultClusterMnL3mNNYN926A5246rxR3YH3htvnC": { "UpdateReplacePolicy": "Retain", "Type": "AWS::ECS::Cluster", "DeletionPolicy": "Retain", "Properties": { "CapacityProviders": [], "ClusterName": "GenStack-EcsDefaultClusterMnL3mNNYN926A5246-rxR3YH3htvnC", "ClusterSettings": [ { "Value": "disabled", "Name": "containerInsights" } ] } } } }
これで、AIにインプットするべきCloudFormationテンプレートの準備が整いました。
AIのモデルを有効化する
モデルはどれでもよいと思いますが、直近で有効化していたのがAnthropicの「Claude Sonnet 4」だったため、今回はこちらのモデルを使用してみます。
有効化したモデルを外部からpythonなどで実行する際にはModel IDを指定する必要があります。 モデルIDははマネジメントコンソールの「Discover」 - 「Claude Sonnet 4」 - 「Model ID」で調べることができます。
モデルIDを指定してAIへの指示を記載したpythonを記載します。
※本記事ではpgen.pyというファイル名で保存しています。
import boto3 import os # プロンプトテンプレート prompt_template = """ User: CloudFormation テンプレートからパラメータシートを生成します。 パラメータシートはマークダウン形式で記載となります。 CloudFormation テンプレートは<cloudformation-template></cloudformation-template>セクションに記載されています。 全体に関する指示 - YAML構文に従い、回答に不要な説明や説明文を含めないでください。 - 回答の最初と最後にトリプルバッククォートのYAMLマーカーを含めないでください。 CloudFormationのテンプレートに関する指示 - Cloudformationの要素はパラメータに記載せず、Propertiesという要素をパラメータシートに記載してください。 - パラメータシートとして記載するリソースは、リソースタグにptagというkeyが設定されており、trueというValueが設定されているものに限ります。 - Refで何かのIDを参照しているプロパティはパラメータシートに記載しないでください 出力形式に関する指示 - 出力は Markdown 形式で記載すること。 - 最初に [toc] を記載し、目次を自動生成するようにすること。 - 各リソース種別ごとに Markdown の「# 見出し」を作成すること。 <cloudformation-template> {INPUT_CLOUDFORMATION_TEMPLATE} </cloudformation-template> """ # prompt_variables フォルダから入力用の変数を読み込む variables = {} for filename in os.listdir('prompt_variables'): if filename.endswith('.txt'): with open(os.path.join('prompt_variables', filename), 'r') as f: variable_name = os.path.splitext(filename)[0] variables[variable_name] = f.read().strip() # プロンプトテンプレートに変数を挿入 prompt = prompt_template.format( **variables ) # Amazon Bedrock Runtime client を作成 client = boto3.client("bedrock-runtime", region_name="ap-northeast-1") model_id = "apac.anthropic.claude-sonnet-4-20250514-v1:0" conversation = [ { "role": "user", "content": [{"text": prompt}], } ] # Amazon Bedrock へメッセージを送信 response = client.converse( modelId = model_id, messages = conversation, inferenceConfig={"maxTokens": 8192, "temperature": 0.2, "topP": 0.9}, ) response_text = response["output"]["message"]["content"][0]["text"] # response_textの内容をmdファイルで保存する md_filename = f"output.md" with open(md_filename, 'w') as f: f.write(response_text)
準備と実行
次にprompt_variablesというディレクトリを作成し、INPUT_CLOUDFORMATION_TEMPLATE.txtというファイルを作成します。
mkdir prompt_variables touch prompt_variables/INPUT_CLOUDFORMATION_TEMPLATE.txt # 先ほどIaCジェネレータで生成したテンプレートをINPUT_CLOUDFORMATION_TEMPLATE.txtに記載 cat ecsTemplate.json > prompt_variables/INPUT_CLOUDFORMATION_TEMPLATE.txt # ディレクトリ構造確認 $ tree . ├── ecsTemplate.json ├── pgen.py └── prompt_variables └── INPUT_CLOUDFORMATION_TEMPLATE.txt 1 directory, 3 files $
これで準備が整いました。pythonを実行してみます。
$ python3 pgen.py $ ll total 48 drwxr-xr-x 3 nob nob 4096 Jul 7 10:06 ./ drwxr-xr-x 14 nob nob 4096 Jul 7 10:02 ../ -rw-r--r-- 1 nob nob 21314 Jul 7 10:03 ecsTemplate.json -rw-r--r-- 1 nob nob 5870 Jul 7 10:06 output.md -rw-r--r-- 1 nob nob 2634 Jul 7 10:03 pgen.py drwxr-xr-x 2 nob nob 4096 Jul 7 10:03 prompt_variables/ $ $ $ head -30 output.md [toc] # AWS::ECS::Cluster | プロパティ名 | 値 | |-------------|-----| | CapacityProviders | [] | | ClusterName | GenStack-EcsDefaultClusterMnL3mNNYN926A5246-rxR3YH3htvnC | | ClusterSettings | [{"Value": "disabled", "Name": "containerInsights"}] | | DefaultCapacityProviderStrategy | [] | # AWS::IAM::Role ## IAMRoleGenStackCustomVpcRestrictDefaultSGCustomResourcePrpiIJjWhOHvpF | プロパティ名 | 値 | |-------------|-----| | Path | / | | ManagedPolicyArns | ["arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"] | | MaxSessionDuration | 3600 | | RoleName | GenStack-CustomVpcRestrictDefaultSGCustomResourcePr-piIJjWhOHvpF | | Description | | | Policies | [{"PolicyDocument": {"Version": "2012-10-17", "Statement": [{"Resource": ["arn:aws:ec2:ap-northeast-1:968841012693:security-group/sg-096344172e49d7633"], "Action": ["ec2:AuthorizeSecurityGroupIngress", "ec2:AuthorizeSecurityGroupEgress", "ec2:RevokeSecurityGroupIngress", "ec2:RevokeSecurityGroupEgress"], "Effect": "Allow"}]}, "PolicyName": "Inline"}] | | AssumeRolePolicyDocument | {"Version": "2012-10-17", "Statement": [{"Action": "sts:AssumeRole", "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}}]} | ## IAMRoleGenStackMyFargateServiceTaskDefTaskRole62C7D3974cUKYvWxUuju | プロパティ名 | 値 | |-------------|-----| | Path | / | $
無事output.mdが生成されました。中身を見てみるとちゃんとmarddownで表記されています。
まとめと次のステップ
ご覧の通り、AIを活用することで、AWSリソースのパラメータシートの「元」となる情報を効率的に生成できました。
この「元」があれば、ゼロから全てを手動で作成するよりもはるかに早く作業を進められます。
今後は、この生成されたMarkdownファイルに対して、設定した経緯や、設計思想、合意内容などの人間系の情報を手動で追記していくことで、より完全なパラメータシートが完成します。また、AIへのプロンプトをさらに調整し、表示フォーマットの調整や、特定の情報(例えば、コストタグなど)で抽出させることも可能です。
この試みが、皆様のAWS運用におけるドキュメント作成の負担軽減につながれば幸いです。