「動くけどAWS Well-Architected じゃない」環境をAIに作らせ、直すことでベストプラクティスを学んでみた

本記事は 春のスキルアップ応援フェア2026 4/21付の記事です。

こんにちは。SCSK渡辺(大)です。

「動くけどAWS Well-Architected じゃない」環境をAIに作らせ、直すことでベストプラクティスを学んでみました。
個人的には非常に勉強になりました。

AWS Well-Architected とは、AWSが公開しているクラウド設計・運用のベストプラクティス集です。
次の6つの柱で構成されています。

  • 運用上の優秀性 — 運用の自動化、監視、改善
  • セキュリティ — データ保護、アクセス管理、検出
  • 信頼性 — 障害復旧、バックアップ、冗長化
  • パフォーマンス効率 — リソースの適切な選択と最適化
  • コスト最適化 — 無駄なコストの排除、適正サイズ
  • 持続可能性 — 環境負荷の最小化

 

全体の流れ

  1. デプロイ: アンチパターン入りCFNテンプレートを特定のリージョンでデプロイ
  2. 修正: AWSマネジメントコンソールでアンチパターンを見つけて修正
  3. クリーンアップ: スタック削除(必要に応じてスタック外リソース削除)

 

問題用に作るもの

YAMLのテンプレート1つで(AWS CloudFormationスタック1つで)以下のリソースを作ります。
これらにAWS Well-Architectedのベストプラクティスに反するアンチパターンが仕込まれています。

論理ID リソース種類 関連する問題
ChallengeVPC VPC A1, B2
PrivateSubnetA / B サブネット × 2 B2
PrivateRouteTable ルートテーブル B2
EC2SecurityGroup セキュリティグループ B2
RDSSecurityGroup セキュリティグループ B2
ChallengeBucket S3バケット B1, M3, B2
AccessLogBucket S3バケット B1, B2
EC2Role IAMロール M1, B2
EC2InstanceProfile インスタンスプロファイル —
ChallengeInstance EC2インスタンス A2, B2
RDSSubnetGroup DBサブネットグループ B2
ChallengeDB RDSインスタンス B3, M2, B2
ChallengeTable DynamoDBテーブル A3, B2
ChallengeLogGroup CloudWatch Logsロググループ B2
FlowLogsLogGroup CloudWatch Logsロググループ A1, B2

 

⚠ 各リソースの備考を見る(ネタバレ注意)
論理ID 備考
ChallengeVPC フローログ無効
PrivateSubnetA / B Multi-AZ用プライベートサブネット
PrivateRouteTable —
EC2SecurityGroup EC2用、インバウンドなし
RDSSecurityGroup RDS用、VPC内部のみ
ChallengeBucket アクセスログ無効、バージョニング無効
AccessLogBucket アクセスログの保管先として使用
EC2Role Action/Resource全開
EC2InstanceProfile EC2Roleに紐づく(タグ非対応)
ChallengeInstance EBS gp2、暗号化なし
RDSSubnetGroup タグエディタからは見つけにくい
ChallengeDB Single-AZ、バックアップ無効
ChallengeTable 過剰キャパシティ、Auto Scalingなし
ChallengeLogGroup 保持期間未設定(無期限)
FlowLogsLogGroup VPCフローログの送信先として使用

 

セキュリティリスクが実際に発生するような構成(ポート全開放など)は含めていません。
EC2・RDSはプライベートサブネット配置、S3はパブリックアクセス完全ブロック、セキュリティグループはインバウンドなしです。
コストは東京リージョンで24時間以内に削除した場合、約200〜300円です。終わったら必ずスタック削除してください。

 

問題構成

9問構成(初級3問・中級3問・上級3問)です。
ヒントは3段階で用意しています。

難易度 問題の頭文字 主な柱
初級 B (Beginner) セキュリティ、運用上の優秀性、信頼性
中級 M (Middle) セキュリティ、信頼性
上級 A (Advanced) セキュリティ、コスト最適化、パフォーマンス効率、運用上の優秀性

 

デプロイ

以下のテンプレートをAWS
CloudFormationで特定のリージョンにデプロイしてください。
デプロイは10分以内で完了します(RDSの作成に時間がかかります)。

スタック作成の最後のステップで「AWS CloudFormation によって IAM
リソースが作成される場合があることを承認します。」のチェックボックスにチェックを入れてください。
テンプレートにIAMロールが含まれているため必要です。
▶ wa-challenge.yaml(クリックで展開)
AWSTemplateFormatVersion: '2010-09-09'
Description: >
  Well-Architected Anti-Pattern Challenge.
  This template contains anti-patterns that violate Well-Architected best practices.
  Find and fix them using the AWS Management Console.

Parameters:
  LatestAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64

Resources:
  ChallengeVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true

  PrivateSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref ChallengeVPC
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: !Select [0, !GetAZs '']

  PrivateSubnetB:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref ChallengeVPC
      CidrBlock: 10.0.2.0/24
      AvailabilityZone: !Select [1, !GetAZs '']

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref ChallengeVPC

  PrivateSubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnetA
      RouteTableId: !Ref PrivateRouteTable

  PrivateSubnetBRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnetB
      RouteTableId: !Ref PrivateRouteTable

  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: EC2 Security Group - No inbound access
      VpcId: !Ref ChallengeVPC
      SecurityGroupIngress: []
      SecurityGroupEgress:
        - IpProtocol: '-1'
          CidrIp: 10.0.0.0/16

  RDSSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: RDS Security Group - VPC internal only
      VpcId: !Ref ChallengeVPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref EC2SecurityGroup
      SecurityGroupEgress:
        - IpProtocol: '-1'
          CidrIp: 10.0.0.0/16

  ChallengeBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete
    Properties:
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  AccessLogBucket:
    Type: AWS::S3::Bucket
    DeletionPolicy: Delete
    Properties:
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  EC2Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: OverlyPermissivePolicy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: '*'
                Resource: '*'

  EC2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref EC2Role

  ChallengeInstance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t3.micro
      ImageId: !Ref LatestAmiId
      SubnetId: !Ref PrivateSubnetA
      SecurityGroupIds:
        - !Ref EC2SecurityGroup
      IamInstanceProfile: !Ref EC2InstanceProfile
      PropagateTagsToVolumeOnCreation: true
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeSize: 20
            VolumeType: gp2
            Encrypted: false
            DeleteOnTermination: true

  RDSSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: Challenge RDS Subnet Group
      SubnetIds:
        - !Ref PrivateSubnetA
        - !Ref PrivateSubnetB

  ChallengeDB:
    Type: AWS::RDS::DBInstance
    DeletionPolicy: Delete
    Properties:
      DBInstanceClass: db.t3.micro
      Engine: mysql
      EngineVersion: '8.0'
      MasterUsername: admin
      MasterUserPassword: ChallengePw123!
      AllocatedStorage: '20'
      DBSubnetGroupName: !Ref RDSSubnetGroup
      VPCSecurityGroups:
        - !Ref RDSSecurityGroup
      PubliclyAccessible: false
      MultiAZ: false
      BackupRetentionPeriod: 0
      StorageEncrypted: false

  ChallengeTable:
    Type: AWS::DynamoDB::Table
    DeletionPolicy: Delete
    Properties:
      TableName: ChallengeTable
      AttributeDefinitions:
        - AttributeName: PK
          AttributeType: S
      KeySchema:
        - AttributeName: PK
          KeyType: HASH
      BillingMode: PROVISIONED
      ProvisionedThroughput:
        ReadCapacityUnits: 100
        WriteCapacityUnits: 100

  ChallengeLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Delete
    Properties:
      LogGroupName: /challenge/application

  FlowLogsLogGroup:
    Type: AWS::Logs::LogGroup
    DeletionPolicy: Delete
    Properties:
      LogGroupName: /vpc/flow-logs

Outputs:
  VPCId:
    Value: !Ref ChallengeVPC
  FlowLogsLogGroupName:
    Value: !Ref FlowLogsLogGroup
  S3BucketName:
    Value: !Ref ChallengeBucket
  AccessLogBucketName:
    Value: !Ref AccessLogBucket
  EC2InstanceId:
    Value: !Ref ChallengeInstance
  RDSEndpoint:
    Value: !GetAtt ChallengeDB.Endpoint.Address
  DynamoDBTableName:
    Value: !Ref ChallengeTable

 

問題に取り組む

デプロイが完了したら、AWSマネジメントコンソールで東京リージョンを選択し、以下の問題を見ながらアンチパターンを見つけて修正してください。
スタックの「リソース」タブから各種リソースを参照しにいくとスムーズに進められます。

各問題に取り組む前に、必ず上記の「問題用に作るもの」セクションでデプロイされるリソースの一覧を確認してください。どのサービスのどのリソースが作られているかを把握しておくことで、問題の対象を特定しやすくなります。

 

初級問題


B1: S3バケットのアクセスログ

柱: セキュリティ

問題: デプロイされたS3バケットにはWell-Architectedセキュリティ柱の検出に関するベストプラクティスに反する設定があります。見つけて修正してください。

関連するWell-Architectedガイド: SEC04-BP02 Capture logs, findings, and metrics in standardized locations

💡 ヒント1(方向性)

誰がいつバケットにアクセスしたかを追跡するための設定を確認してください。S3バケットのプロパティを見てみましょう。

💡 ヒント2(具体的なガイドリンク)

S3のサーバーアクセスログが無効になっています。バケットへのアクセスを監査できません。

💡 ヒント3(ほぼ答え)

S3コンソール → バケットを選択 → 「プロパティ」→ 「サーバーアクセスのログ記録」を編集 →  「有効にする」を選択し、ログの送信先にスタックで作成済みのアクセスログ用バケット(AccessLogBucket)を指定してください。


B2: リソースのタグ付け

柱: 運用上の優秀性 + コスト最適化

問題: デプロイされたリソース群にはWell-Architected運用上の優秀性の柱に反する共通の問題があります。見つけて修正してください。

注意:
APIレート制限にご注意ください。

関連するWell-Architectedガイド: COST04-BP01 Track resources over their lifetime

💡 ヒント1(方向性)

リソースの管理・追跡・コスト配分に必要な基本的な設定が欠けています。複数のリソースに共通する問題です。

💡 ヒント2(具体的なガイドリンク)

すべてのリソースにタグが一切付いていません。

💡 ヒント3(ほぼ答え)

全リソースに Environment, Project, Owner 等のタグを追加してください。各リソースのコンソール画面 → 「タグ」タブから追加できます。

注意:タグエディタで一括タグ付けする場合、リソースタイプでフィルタしてチャレンジのリソースだけを選択してください。アカウント内の全リソースを対象にするとAPIレート制限に抵触したり、マネージドルールなどタグ変更が禁止されているリソースでエラーになります。


B3: RDSの自動バックアップ

柱: 信頼性

問題: デプロイされたRDSインスタンスにはWell-Architected信頼性の柱のバックアップベストプラクティスに反する設定があります。見つけて修正してください。

関連するWell-Architectedガイド: REL09-BP03 Perform data backup automatically

💡 ヒント1(方向性)

障害時のデータ復旧に関するベストプラクティスを確認してください。RDSの設定を見てみましょう。

💡 ヒント2(具体的なガイドリンク)

RDSの自動バックアップが無効になっています(バックアップ保持期間が0日)。

💡 ヒント3(ほぼ答え)

RDSコンソール → インスタンスを選択 → 「変更」→ 「バックアップ保持期間」を1日以上(推奨: 7日)に設定してください。

 

中級問題


M1: IAMの最小権限

柱: セキュリティ

問題: デプロイされたIAMロールにはWell-Architectedセキュリティ柱のアクセス管理ベストプラクティスに反する設定があります。見つけて修正してください。

関連するWell-Architectedガイド: SEC03-BP02 Grant least privilege access

💡 ヒント1(方向性)

IAMのアクセス権限に関するベストプラクティスを確認してください。EC2に割り当てられたロールのポリシーを見てみましょう。

💡 ヒント2(具体的なガイドリンク)

EC2ロールのポリシーが Action: "*", Resource: "*" で全権限を許可しています。

💡 ヒント3(ほぼ答え)

IAMコンソール → ロール → EC2ロールを選択 → OverlyPermissivePolicy を編集し、必要なアクション/リソースのみに絞ってください。当チャレンジではEC2に特定の用途がないため、インラインポリシー OverlyPermissivePolicy を削除するか、AWS管理ポリシーAWSDenyAll をアタッチして全アクションを拒否する方法でも正解です。


M2: RDSの可用性

柱: 信頼性

問題: デプロイされたRDSインスタンスにはWell-Architected信頼性の柱の障害管理ベストプラクティスに反する設定があります。見つけて修正してください。(B3とは別の問題です)

関連するWell-Architectedガイド: Failure management – Reliability Pillar

💡 ヒント1(方向性)

単一障害点の排除に関するベストプラクティスを確認してください。

💡 ヒント2(具体的なガイドリンク)

RDSがSingle-AZ構成で、AZ障害時にフェイルオーバーできません。

💡 ヒント3(ほぼ答え)

RDSコンソール → インスタンスを選択 → 「変更」→ 「可用性と耐久性」セクションで「マルチAZデプロイメント」を「スタンバイインスタンスを作成する」に変更してください。


M3: S3のデータ保護

柱: 信頼性 + セキュリティ

問題: デプロイされたS3バケットにはWell-Architectedのデータ保護ベストプラクティスに反する設定があります。見つけて修正してください。(B1とは別の問題です)

関連するWell-Architectedガイド: REL09-BP02 Secure and encrypt backups

💡 ヒント1(方向性)

データの誤削除からの保護に関するベストプラクティスを確認してください。

💡 ヒント2(具体的なガイドリンク)

S3バケットのバージョニングが無効で、オブジェクトの誤削除から復旧できません。

💡 ヒント3(ほぼ答え)

S3コンソール → バケットを選択 → 「プロパティ」→ 「バージョニング」を「有効」に変更してください。

 

上級問題


A1: ネットワーク監査ログ

柱: セキュリティ + 運用上の優秀性

問題: デプロイされたVPCにはWell-Architectedセキュリティ柱の検出に関するベストプラクティスに反する設定があります。見つけて修正してください。

関連するWell-Architectedガイド: Security Pillar – Detection

💡 ヒント1(方向性)

ネットワークトラフィックの監査・可視化に関するベストプラクティスを確認してください。

💡 ヒント2(具体的なガイドリンク)

VPCフローログが無効で、ネットワークトラフィックの監査ができません。

💡 ヒント3(ほぼ答え)

VPCコンソール → VPCを選択 → 「フローログ」タブ → 「フローログを作成」で以下を設定してください:

  • 名前 – オプション: 任意(空欄でもOK)
  • フィルタ: すべて
  • 最大集約間隔: 10分(デフォルトのまま)
  • 送信先: CloudWatch Logsに送信
  • 送信先ロググループ: スタックで作成済みの /vpc/flow-logs を選択
  • IAMロール: 「Set Up Permissions」から新規作成、または既存のフローログ用ロールを選択
  • ログレコードの形式: AWSのデフォルト形式(デフォルトのまま)
  • 追加のメタデータ: なし(デフォルトのまま)

A2: EBSストレージの最適化

柱: コスト最適化 + セキュリティ

問題: デプロイされたEC2インスタンスのEBSボリュームにはWell-Architectedのコスト最適化とセキュリティのベストプラクティスに反する設定があります。見つけて修正してください。

関連するWell-Architectedガイド:

💡 ヒント1(方向性)

ストレージの世代選択とデータ保護の2つの観点で確認してください。

💡 ヒント2(具体的なガイドリンク)

2つの問題があります:

  1. EBSがgp2(旧世代)で、gp3の方がコスト効率が良い
  2. EBSの暗号化が無効
💡 ヒント3(ほぼ答え)

gp2→gp3の変更: EC2コンソール → ボリューム → ボリュームを選択 → 「変更」→ タイプをgp3に変更 → 「変更」をクリック。

暗号化の対応: 既存ボリュームでは暗号化を直接有効にできないため、以下の手順が必要です:

  1. EC2コンソール → インスタンス → 対象インスタンスを「停止」する
  2. ボリューム → 対象ボリュームを選択 → 「スナップショットを作成」
  3. スナップショット → 作成したスナップショットを選択 → 「スナップショットをコピー」→ 「暗号化」にチェック → コピー
  4. 暗号化済みスナップショットを選択 → 「スナップショットからボリュームを作成」→ AZを元のボリュームと同じにする
  5. 元のボリュームをインスタンスからデタッチ
  6. 暗号化済みボリュームをインスタンスにアタッチ(デバイス名: /dev/xvda)
  7. インスタンスを起動
「停止」と「終了」を間違えないでください。「終了」(Terminate)するとインスタンスが削除されてしまい、ボリュームのアタッチ先がなくなります。必ず「停止」(Stop)を選んでください。

暗号化の手順は複雑なため、gp2→gp3の変更だけでも正解とします。


A3: DynamoDBのキャパシティ管理

柱: コスト最適化 + パフォーマンス効率

問題: デプロイされたDynamoDBテーブルにはWell-Architectedのコスト最適化とパフォーマンス効率のベストプラクティスに反する設定があります。見つけて修正してください。

関連するWell-Architectedガイド:

💡 ヒント1(方向性)

データベースのキャパシティ管理とコスト効率に関するベストプラクティスを確認してください。

💡 ヒント2(具体的なガイドリンク)

DynamoDBがプロビジョンドモードで過剰なRCU/WCU(各100)が設定されており、Auto Scalingも無効です。

💡 ヒント3(ほぼ答え)

DynamoDBコンソール → テーブルを選択 → 「追加の設定」→ 「読み込み/書き込みキャパシティ」セクションの「編集」をクリック。
以下のいずれかを実施してください:

方法1: オンデマンドモードに変更(推奨)

  1. キャパシティモードを「オンデマンド」に変更
  2. 「変更を保存」をクリック

方法2: Auto Scalingを有効化

  1. キャパシティモードは「プロビジョンド」のまま
  2. 読み込みキャパシティ: 「Auto Scaling」をオン → 最小キャパシティを1〜5程度に設定
  3. 書き込みキャパシティ: 同様に「Auto Scaling」をオン → 最小キャパシティを1〜5程度に設定
  4. 「変更を保存」をクリック

 

クリーンアップ

CloudFormationコンソールからスタックを削除してください。

S3バケットにオブジェクトが残っているとスタック削除が失敗します。B1でアクセスログを有効にした場合、AccessLogBucket と ChallengeBucket
の中身を先に空にしてからスタックを削除してください。S3コンソール → バケットを選択 → 「空にする」で削除できます。

 

スタック外リソースの手動削除

問題を解く過程で、スタック管理外のリソースが作成されている場合があります。これらはスタック削除では消えないため、手動で削除してください。

⚠ スタック外リソースの一覧と削除手順(ネタバレ注意)
関連問題 リソース 削除手順
A1 VPCフローログ VPCコンソール → VPCを選択 → 「フローログ」タブ → フローログを選択 → 「アクション」→「フローログの削除」
A1 VPCフローログ用IAMロール IAMコンソール → ロール → VPCFlowLogs-Cloudwatch-* のようなロールを検索 → 削除
A2 EBSスナップショット(暗号化対応した場合) EC2コンソール → スナップショット → 作成したスナップショットを選択 → 「アクション」→「スナップショットの削除」
A2 デタッチ済みEBSボリューム(暗号化対応した場合) EC2コンソール → ボリューム → 状態が「available」のボリュームを選択 → 「アクション」→「ボリュームの削除」
A2のEBS暗号化対応(スナップショット→暗号化コピー→新ボリューム作成)を行わなかった場合、スナップショットとデタッチ済みボリュームの削除は不要です。

 

まとめ

テンプレートや問題文はAIに作ってもらいましたが、実際に自分で修正を試してみると、S3のデフォルト暗号化が既に有効になっていて問題として成立しなかったり(この問題は削除してもらいました)、ヒントの手順が現在のコンソールUIと合っていなかったりと、そのままでは使えない箇所がいくつもありました。
AIが生成したものを鵜呑みにせず、自分で手を動かして検証することの大切さを改めて感じました。

Well-Architected Framework は読むだけでは実感が湧きにくいですが、「動くけどベストプラクティスじゃない環境」を自分の手で直すことで、各ベストプラクティスの意味が体感できました。

「M2: RDSの可用性」ではMulti-AZ化の過程で、定期メンテナンスウィンドウの設定や延期方法についても学ぶことができました。
また「B1: S3バケットのアクセスログ」では、ログの蓄積をきっかけにS3ライフサイクルポリシーやストレージクラス移行時の料金体系についても理解が深まりました。

このように、1つのアンチパターンを修正する過程で関連するベストプラクティスにも自然と触れることができ、Well-Architectedの学習が点ではなく面で広がっていく実感がありました。

×
タイトルとURLをコピーしました