生成AIにdraw.ioのAWS構成図を作図させてみた

生成AIにdraw.ioのAWS構成図を作図させてみた

Clock Icon2025.03.09

こんにちは。たかやまです。

いままでいくつものIaCツールをベースにAWS構成図作成する作図ツールがでているかと思います。

https://dev.classmethod.jp/articles/automated-diagrams-tool-cdk-dia/
https://dev.classmethod.jp/articles/terraform-visualise-pluralith/

ただ、これらは独自ツールでユーザー側で編集できないものだったり、作成された構成図のエクスポート先がPDFやPNGだったりと、後から修正や更新がしにくいという課題があります。

私は普段draw.ioやCacooを使ってAWS構成図を作成しています。

日々新しい生成AIモデルが登場していますが、draw.ioはXML形式で構成図が定義されるため、生成AIでも扱いやすいのではないかと考えました。

実際に試してみたところ、想定以上に高品質なAWS構成図を作成してくれたので今回はそちらをご紹介します。

さきにまとめ

  • CDKやCloudFormation、Terraformのコードから直接構成図を生成できる
  • モデルはClaude 3.5 Sonnet以上がおすすめ
    • Anthropicのモデル以外はAWSアイコンの認識が弱い
  • 想定通りの構成を出力するなら事前にdrawioのテンプレートを用意しておくと良い
    • 横配置や縦配置など、好みのレイアウトに合わせた構成図を作成可能

IaCからdraw.ioでAWS構成図を作成する

まずCDKでWeb3層アーキテクチャのCDKコードを用意します。このコードは、ALB、EC2、RDSを使った一般的なWeb3層アーキテクチャを定義しています。

CDKコード
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as rds from 'aws-cdk-lib/aws-rds';
import { Construct } from 'constructs';

class Web3TierStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const vpc = this.createVpc();
    const { albSg, webSg, dbSg } = this.createSecurityGroups(vpc);
    const webServerRole = this.createEmptyIamRole();
    const webServers = this.createEc2Instances(vpc, webSg, webServerRole);
    const alb = this.createAlb(vpc, albSg, webServers);
    const mysql = this.createMySqlInstance(vpc, dbSg);
    this.defineOutputs(alb);
  }

  /**
   * VPCとサブネットを作成します
   */
  private createVpc(): ec2.Vpc {
    const vpc = new ec2.Vpc(this, 'WebAppVpc', {
      maxAzs: 2,
      natGateways: 2,
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: 'Public',
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: 'Application',
          subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
        },
        {
          cidrMask: 24,
          name: 'Database',
          subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
        },
      ],
    });

    const cfnVpc = vpc.node.defaultChild as ec2.CfnVPC;
    cfnVpc.tags.setTag('Name', 'sample-vpc');

    const subnets = vpc.publicSubnets.concat(vpc.privateSubnets).concat(vpc.isolatedSubnets);
    subnets.forEach((subnet, index) => {
      const cfnSubnet = subnet.node.defaultChild as ec2.CfnSubnet;
      let subnetType = 'unknown';

      if (vpc.publicSubnets.includes(subnet)) {
        subnetType = 'public';
      } else if (vpc.isolatedSubnets.includes(subnet)) {
        subnetType = 'isolated';
      } else if (vpc.privateSubnets.includes(subnet)) {
        subnetType = 'private';
      }

      cfnSubnet.tags.setTag('Name', `sample-${subnetType}-subnet-${index + 1}`);
    });

    this.createSsmEndpoints(vpc);

    return vpc;
  }

  /**
   * SSM接続用のVPCエンドポイントを作成します
   */
  private createSsmEndpoints(vpc: ec2.Vpc): void {
    new ec2.InterfaceVpcEndpoint(this, 'SsmEndpoint', {
      vpc,
      service: ec2.InterfaceVpcEndpointAwsService.SSM,
      subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
      privateDnsEnabled: true,
    });

    new ec2.InterfaceVpcEndpoint(this, 'SsmMessagesEndpoint', {
      vpc,
      service: ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES,
      subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
      privateDnsEnabled: true,
    });

    new ec2.InterfaceVpcEndpoint(this, 'Ec2MessagesEndpoint', {
      vpc,
      service: ec2.InterfaceVpcEndpointAwsService.EC2_MESSAGES,
      subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
      privateDnsEnabled: true,
    });
  }

  /**
   * セキュリティグループを作成します
   */
  private createSecurityGroups(vpc: ec2.Vpc): {
    albSg: ec2.SecurityGroup;
    webSg: ec2.SecurityGroup;
    dbSg: ec2.SecurityGroup;
  } {
    const albSg = new ec2.SecurityGroup(this, 'AlbSecurityGroup', {
      vpc,
      description: 'Security group for ALB',
      allowAllOutbound: true,
    });
    (albSg.node.defaultChild as ec2.CfnSecurityGroup).tags.setTag('Name', 'sample-alb-sg');
    albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP from anywhere');

    const webSg = new ec2.SecurityGroup(this, 'WebSecurityGroup', {
      vpc,
      description: 'Security group for Web servers',
      allowAllOutbound: true,
    });
    (webSg.node.defaultChild as ec2.CfnSecurityGroup).tags.setTag('Name', 'sample-web-sg');
    webSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP from ALB');

    const dbSg = new ec2.SecurityGroup(this, 'DbSecurityGroup', {
      vpc,
      description: 'Security group for RDS',
      allowAllOutbound: false,
    });
    (dbSg.node.defaultChild as ec2.CfnSecurityGroup).tags.setTag('Name', 'sample-db-sg');
    dbSg.addIngressRule(webSg, ec2.Port.tcp(3306), 'Allow MySQL from Web servers');

    return {
      albSg,
      webSg,
      dbSg,
    };
  }

  /**
   * EC2インスタンス用のIAMロールを作成します(SSM接続用の権限付き)
   */
  private createEmptyIamRole(): iam.Role {
    const role = new iam.Role(this, 'WebServerRole', {
      assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
      description: 'Role for Web Servers with SSM access',
      roleName: 'sample-ec2-role',
    });

    role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'));

    return role;
  }

  /**
   * EC2インスタンスを作成します
   */
  private createEc2Instances(
    vpc: ec2.Vpc,
    securityGroup: ec2.SecurityGroup,
    role: iam.Role
  ): ec2.Instance[] {
    const amznLinux2023 = ec2.MachineImage.latestAmazonLinux2023({
      cpuType: ec2.AmazonLinuxCpuType.X86_64,
    });

    const instances: ec2.Instance[] = [];
    const subnetSelection = vpc.selectSubnets({
      subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
    });

    for (let i = 0; i < Math.min(2, subnetSelection.subnets.length); i++) {
      const userData = ec2.UserData.forLinux();
      userData.addCommands(
        'yum update -y',
        'yum install -y httpd',
        'systemctl start httpd',
        'systemctl enable httpd',
        `echo "<html><body><h1>インスタンス${i + 1}</h1></body></html>" > /var/www/html/index.html`
      );

      const instance = new ec2.Instance(this, `WebServer${i + 1}`, {
        vpc,
        vpcSubnets: {
          subnets: [subnetSelection.subnets[i]],
        },
        instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
        machineImage: amznLinux2023,
        securityGroup,
        role,
        userData,
      });

      const cfnInstance = instance.node.defaultChild as ec2.CfnInstance;
      cfnInstance.tags.setTag('Name', `sample-web-server-${i + 1}`);

      instances.push(instance);
    }

    return instances;
  }

  /**
   * ALBを作成します
   */
  private createAlb(
    vpc: ec2.Vpc,
    securityGroup: ec2.SecurityGroup,
    instances: ec2.Instance[]
  ): elbv2.ApplicationLoadBalancer {
    const alb = new elbv2.ApplicationLoadBalancer(this, 'WebAppALB', {
      vpc,
      internetFacing: true,
      securityGroup,
      vpcSubnets: {
        subnetType: ec2.SubnetType.PUBLIC,
      },
      loadBalancerName: 'sample-alb',
    });

    const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', {
      vpc,
      port: 80,
      protocol: elbv2.ApplicationProtocol.HTTP,
      targetType: elbv2.TargetType.INSTANCE,
      healthCheck: {
        path: '/',
        interval: cdk.Duration.seconds(30),
        timeout: cdk.Duration.seconds(5),
      },
      targetGroupName: 'sample-tg',
    });

    instances.forEach((instance) => {
      targetGroup.addTarget(new targets.InstanceTarget(instance));
    });

    alb.addListener('HttpListener', {
      port: 80,
      open: true,
      defaultTargetGroups: [targetGroup],
    });

    instances.forEach((instance, index) => {
      new cdk.CfnOutput(this, `WebServerInstance${index + 1}Id`, {
        value: instance.instanceId,
        description: `EC2 Instance ${index + 1} ID`,
        exportName: `WebServerInstance${index + 1}Id`,
      });
    });

    new cdk.CfnOutput(this, 'TargetGroupArn', {
      value: targetGroup.targetGroupArn,
      description: 'Target Group ARN',
      exportName: 'WebServerTargetGroupArn',
    });

    return alb;
  }

  /**
   * RDS for MySQLインスタンスをMultiAZ構成で作成します
   */
  private createMySqlInstance(
    vpc: ec2.Vpc,
    securityGroup: ec2.SecurityGroup
  ): rds.DatabaseInstance {
    const instance = new rds.DatabaseInstance(this, 'WebAppDatabase', {
      engine: rds.DatabaseInstanceEngine.mysql({
        version: rds.MysqlEngineVersion.VER_8_0_33,
      }),
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.MICRO),
      multiAz: true,
      vpc,
      vpcSubnets: {
        subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
      },
      securityGroups: [securityGroup],
      databaseName: 'webappdb',
      credentials: rds.Credentials.fromGeneratedSecret('admin'),
      allocatedStorage: 20,
      storageType: rds.StorageType.GP2,
      backupRetention: cdk.Duration.days(0),
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      deletionProtection: false,
      deleteAutomatedBackups: true,
      instanceIdentifier: 'sample-mysql',
      parameterGroup: rds.ParameterGroup.fromParameterGroupName(
        this,
        'MySqlParameterGroup',
        'default.mysql8.0'
      ),
    });

    new cdk.CfnOutput(this, 'DatabaseEndpoint', {
      value: instance.dbInstanceEndpointAddress,
      description: 'The endpoint of the MySQL database',
      exportName: 'MySqlEndpoint',
    });

    return instance;
  }

  /**
   * CloudFormation出力を定義します
   */
  private defineOutputs(alb: elbv2.ApplicationLoadBalancer): void {
    new cdk.CfnOutput(this, 'AlbDnsName', {
      value: alb.loadBalancerDnsName,
      description: 'The DNS name of the application load balancer',
      exportName: 'WebAppAlbDnsName',
    });
  }
}

const app = new cdk.App();
new Web3TierStack(app, 'SampleStack', {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.CDK_DEFAULT_REGION,
  },
  description: 'Web 3-tier architecture environment',
}); 

こちらのCDKコードに対して、Claude3.7 Sonnetに以下のようなプロンプトを投げてみました。

このCDKコードを元に、AWS構成図を.draw.ioファイルで作成してください。
アイコン図形はAWS 2025を利用してください。

すると、以下のような.draw.ioファイルが生成されます。

CDKで生成されたdraw.ioファイル
<mxfile host="app.diagrams.net" modified="2023-11-01T12:00:00.000Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" etag="abc123" version="15.5.0" type="device">
  <diagram id="web-3tier-architecture" name="Web 3-Tier Architecture">
    <mxGraphModel dx="1422" dy="798" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="827" math="0" shadow="0">
      <root>
        <mxCell id="0" />
        <mxCell id="1" parent="0" />
        <!-- AWS Cloud -->
        <mxCell id="2" value="AWS Cloud" style="points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_aws_cloud;strokeColor=#232F3E;fillColor=none;verticalAlign=top;align=left;spacingLeft=30;fontColor=#232F3E;dashed=0;" vertex="1" parent="1">
          <mxGeometry x="40" y="40" width="1080" height="760" as="geometry" />
        </mxCell>
        <!-- Region -->
        <mxCell id="3" value="Region" style="points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_region;strokeColor=#147EBA;fillColor=none;verticalAlign=top;align=left;spacingLeft=30;fontColor=#147EBA;dashed=1;" vertex="1" parent="2">
          <mxGeometry x="40" y="40" width="1000" height="680" as="geometry" />
        </mxCell>
        <!-- VPC -->
        <mxCell id="4" value="VPC (sample-vpc)" style="points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_vpc;strokeColor=#248814;fillColor=none;verticalAlign=top;align=left;spacingLeft=30;fontColor=#AAB7B8;dashed=0;" vertex="1" parent="3">
          <mxGeometry x="40" y="40" width="920" height="600" as="geometry" />
        </mxCell>
        <!-- AZ1 -->
        <mxCell id="5" value="Availability Zone 1" style="fillColor=none;strokeColor=#147EBA;dashed=1;verticalAlign=top;fontStyle=0;fontColor=#147EBA;whiteSpace=wrap;html=1;" vertex="1" parent="4">
          <mxGeometry x="40" y="40" width="400" height="520" as="geometry" />
        </mxCell>
        <!-- AZ2 -->
        <mxCell id="6" value="Availability Zone 2" style="fillColor=none;strokeColor=#147EBA;dashed=1;verticalAlign=top;fontStyle=0;fontColor=#147EBA;whiteSpace=wrap;html=1;" vertex="1" parent="4">
          <mxGeometry x="480" y="40" width="400" height="520" as="geometry" />
        </mxCell>
        <!-- Public Subnet 1 -->
        <mxCell id="7" value="Public Subnet 1" style="points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_security_group;grStroke=0;strokeColor=#248814;fillColor=#E9F3E6;verticalAlign=top;align=left;spacingLeft=30;fontColor=#248814;dashed=0;" vertex="1" parent="4">
          <mxGeometry x="60" y="80" width="360" height="120" as="geometry" />
        </mxCell>
        <!-- Public Subnet 2 -->
        <mxCell id="8" value="Public Subnet 2" style="points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_security_group;grStroke=0;strokeColor=#248814;fillColor=#E9F3E6;verticalAlign=top;align=left;spacingLeft=30;fontColor=#248814;dashed=0;" vertex="1" parent="4">
          <mxGeometry x="500" y="80" width="360" height="120" as="geometry" />
        </mxCell>
        <!-- Private Subnet 1 (Application) -->
        <mxCell id="9" value="Private Subnet 1 (Application)" style="points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_security_group;grStroke=0;strokeColor=#147EBA;fillColor=#E6F2F8;verticalAlign=top;align=left;spacingLeft=30;fontColor=#147EBA;dashed=0;" vertex="1" parent="4">
          <mxGeometry x="60" y="240" width="360" height="120" as="geometry" />
        </mxCell>
        <!-- Private Subnet 2 (Application) -->
        <mxCell id="10" value="Private Subnet 2 (Application)" style="points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_security_group;grStroke=0;strokeColor=#147EBA;fillColor=#E6F2F8;verticalAlign=top;align=left;spacingLeft=30;fontColor=#147EBA;dashed=0;" vertex="1" parent="4">
          <mxGeometry x="500" y="240" width="360" height="120" as="geometry" />
        </mxCell>
        <!-- Isolated Subnet 1 (Database) -->
        <mxCell id="11" value="Isolated Subnet 1 (Database)" style="points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_security_group;grStroke=0;strokeColor=#147EBA;fillColor=#E6F2F8;verticalAlign=top;align=left;spacingLeft=30;fontColor=#147EBA;dashed=0;" vertex="1" parent="4">
          <mxGeometry x="60" y="400" width="360" height="120" as="geometry" />
        </mxCell>
        <!-- Isolated Subnet 2 (Database) -->
        <mxCell id="12" value="Isolated Subnet 2 (Database)" style="points=[[0,0],[0.25,0],[0.5,0],[0.75,0],[1,0],[1,0.25],[1,0.5],[1,0.75],[1,1],[0.75,1],[0.5,1],[0.25,1],[0,1],[0,0.75],[0,0.5],[0,0.25]];outlineConnect=0;gradientColor=none;html=1;whiteSpace=wrap;fontSize=12;fontStyle=0;container=1;pointerEvents=0;collapsible=0;recursiveResize=0;shape=mxgraph.aws4.group;grIcon=mxgraph.aws4.group_security_group;grStroke=0;strokeColor=#147EBA;fillColor=#E6F2F8;verticalAlign=top;align=left;spacingLeft=30;fontColor=#147EBA;dashed=0;" vertex="1" parent="4">
          <mxGeometry x="500" y="400" width="360" height="120" as="geometry" />
        </mxCell>

        <!-- ALB -->
        <mxCell id="13" value="ALB&#xa;(sample-alb)" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#4D27AA;strokeColor=none;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;pointerEvents=1;shape=mxgraph.aws4.application_load_balancer;" vertex="1" parent="4">
          <mxGeometry x="440" y="120" width="40" height="40" as="geometry" />
        </mxCell>

        <!-- EC2 Instance 1 -->
        <mxCell id="14" value="EC2 Instance 1&#xa;(sample-web-server-1)" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#D45B07;strokeColor=none;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;pointerEvents=1;shape=mxgraph.aws4.instance2;" vertex="1" parent="4">
          <mxGeometry x="220" y="280" width="40" height="40" as="geometry" />
        </mxCell>

        <!-- EC2 Instance 2 -->
        <mxCell id="15" value="EC2 Instance 2&#xa;(sample-web-server-2)" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#D45B07;strokeColor=none;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;pointerEvents=1;shape=mxgraph.aws4.instance2;" vertex="1" parent="4">
          <mxGeometry x="660" y="280" width="40" height="40" as="geometry" />
        </mxCell>

        <!-- RDS MySQL -->
        <mxCell id="16" value="RDS MySQL&#xa;Multi-AZ&#xa;(sample-mysql)" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#2E27AD;strokeColor=none;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;pointerEvents=1;shape=mxgraph.aws4.rds_multi_az;" vertex="1" parent="4">
          <mxGeometry x="440" y="440" width="40" height="40" as="geometry" />
        </mxCell>

        <!-- NAT Gateway 1 -->
        <mxCell id="17" value="NAT Gateway" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#4D27AA;strokeColor=none;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;pointerEvents=1;shape=mxgraph.aws4.nat_gateway;" vertex="1" parent="4">
          <mxGeometry x="220" y="120" width="40" height="40" as="geometry" />
        </mxCell>

        <!-- NAT Gateway 2 -->
        <mxCell id="18" value="NAT Gateway" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#4D27AA;strokeColor=none;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;pointerEvents=1;shape=mxgraph.aws4.nat_gateway;" vertex="1" parent="4">
          <mxGeometry x="660" y="120" width="40" height="40" as="geometry" />
        </mxCell>

        <!-- Internet Gateway -->
        <mxCell id="19" value="Internet Gateway" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#4D27AA;strokeColor=none;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;pointerEvents=1;shape=mxgraph.aws4.internet_gateway;" vertex="1" parent="4">
          <mxGeometry x="440" y="10" width="40" height="40" as="geometry" />
        </mxCell>

        <!-- VPC Endpoints -->
        <mxCell id="20" value="VPC Endpoints&#xa;(SSM, SSM Messages, EC2 Messages)" style="sketch=0;outlineConnect=0;fontColor=#232F3E;gradientColor=none;fillColor=#4D27AA;strokeColor=none;dashed=0;verticalLabelPosition=bottom;verticalAlign=top;align=center;html=1;fontSize=12;fontStyle=0;aspect=fixed;pointerEvents=1;shape=mxgraph.aws4.endpoints;" vertex="1" parent="4">
          <mxGeometry x="440" y="280" width="40" height="40" as="geometry" />
        </mxCell>

        <!-- Security Groups -->
        <mxCell id="21" value="Security Groups:&#xa;- sample-alb-sg&#xa;- sample-web-sg&#xa;- sample-db-sg" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontColor=#333333;align=left;verticalAlign=top;spacing=5;" vertex="1" parent="4">
          <mxGeometry x="700" y="10" width="200" height="60" as="geometry" />
        </mxCell>

        <!-- Connections -->
        <!-- Internet to IGW -->
        <mxCell id="22" value="" style="endArrow=classic;startArrow=classic;html=1;rounded=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;" edge="1" parent="4" source="13" target="19">
          <mxGeometry width="50" height="50" relative="1" as="geometry">
            <mxPoint x="460" y="110" as="sourcePoint" />
            <mxPoint x="460" y="60" as="targetPoint" />
          </mxGeometry>
        </mxCell>

        <!-- ALB to EC2 Instance 1 -->
        <mxCell id="23" value="" style="endArrow=classic;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="4" source="13" target="14">
          <mxGeometry width="50" height="50" relative="1" as="geometry">
            <mxPoint x="430" y="140" as="sourcePoint" />
            <mxPoint x="220" y="270" as="targetPoint" />
            <Array as="points">
              <mxPoint x="220" y="140" />
            </Array>
          </mxGeometry>
        </mxCell>

        <!-- ALB to EC2 Instance 2 -->
        <mxCell id="24" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="4" source="13" target="15">
          <mxGeometry width="50" height="50" relative="1" as="geometry">
            <mxPoint x="490" y="140" as="sourcePoint" />
            <mxPoint x="660" y="270" as="targetPoint" />
            <Array as="points">
              <mxPoint x="660" y="140" />
            </Array>
          </mxGeometry>
        </mxCell>

        <!-- EC2 Instance 1 to RDS -->
        <mxCell id="25" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;" edge="1" parent="4" source="14" target="16">
          <mxGeometry width="50" height="50" relative="1" as="geometry">
            <mxPoint x="220" y="330" as="sourcePoint" />
            <mxPoint x="430" y="450" as="targetPoint" />
            <Array as="points">
              <mxPoint x="220" y="460" />
            </Array>
          </mxGeometry>
        </mxCell>

        <!-- EC2 Instance 2 to RDS -->
        <mxCell id="26" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;" edge="1" parent="4" source="15" target="16">
          <mxGeometry width="50" height="50" relative="1" as="geometry">
            <mxPoint x="660" y="330" as="sourcePoint" />
            <mxPoint x="490" y="450" as="targetPoint" />
            <Array as="points">
              <mxPoint x="660" y="460" />
            </Array>
          </mxGeometry>
        </mxCell>

        <!-- EC2 Instance 1 to VPC Endpoints -->
        <mxCell id="27" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="4" source="14" target="20">
          <mxGeometry width="50" height="50" relative="1" as="geometry">
            <mxPoint x="270" y="300" as="sourcePoint" />
            <mxPoint x="430" y="300" as="targetPoint" />
          </mxGeometry>
        </mxCell>

        <!-- EC2 Instance 2 to VPC Endpoints -->
        <mxCell id="28" value="" style="endArrow=classic;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="4" source="15" target="20">
          <mxGeometry width="50" height="50" relative="1" as="geometry">
            <mxPoint x="650" y="300" as="sourcePoint" />
            <mxPoint x="490" y="300" as="targetPoint" />
          </mxGeometry>
        </mxCell>

        <!-- EC2 Instance 1 to NAT Gateway 1 -->
        <mxCell id="29" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;dashed=1;" edge="1" parent="4" source="14" target="17">
          <mxGeometry width="50" height="50" relative="1" as="geometry">
            <mxPoint x="220" y="270" as="sourcePoint" />
            <mxPoint x="220" y="170" as="targetPoint" />
          </mxGeometry>
        </mxCell>

        <!-- EC2 Instance 2 to NAT Gateway 2 -->
        <mxCell id="30" value="" style="endArrow=classic;html=1;rounded=0;exitX=0.5;exitY=0;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;dashed=1;" edge="1" parent="4" source="15" target="18">
          <mxGeometry width="50" height="50" relative="1" as="geometry">
            <mxPoint x="660" y="270" as="sourcePoint" />
            <mxPoint x="660" y="170" as="targetPoint" />
          </mxGeometry>
        </mxCell>

        <!-- NAT Gateway 1 to Internet Gateway -->
        <mxCell id="31" value="" style="endArrow=classic;html=1;rounded=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;dashed=1;" edge="1" parent="4" source="17" target="19">
          <mxGeometry width="50" height="50" relative="1" as="geometry">
            <mxPoint x="270" y="140" as="sourcePoint" />
            <mxPoint x="430" y="50" as="targetPoint" />
            <Array as="points">
              <mxPoint x="350" y="140" />
              <mxPoint x="350" y="50" />
            </Array>
          </mxGeometry>
        </mxCell>

        <!-- NAT Gateway 2 to Internet Gateway -->
        <mxCell id="32" value="" style="endArrow=classic;html=1;rounded=0;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=1;entryDx=0;entryDy=0;entryPerimeter=0;dashed=1;" edge="1" parent="4" source="18" target="19">
          <mxGeometry width="50" height="50" relative="1" as="geometry">
            <mxPoint x="650" y="140" as="sourcePoint" />
            <mxPoint x="490" y="50" as="targetPoint" />
            <Array as="points">
              <mxPoint x="570" y="140" />
              <mxPoint x="570" y="50" />
            </Array>
          </mxGeometry>
        </mxCell>

        <!-- Title and Legend -->
        <mxCell id="33" value="Web 3-Tier Architecture" style="text;html=1;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;fontSize=16;fontStyle=1" vertex="1" parent="3">
          <mxGeometry x="40" y="10" width="200" height="30" as="geometry" />
        </mxCell>

        <mxCell id="34" value="Legend:&#xa;→ HTTP Traffic&#xa;⟶ Database Connection&#xa;--→ Internet Access" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontColor=#333333;align=left;verticalAlign=top;spacing=5;" vertex="1" parent="3">
          <mxGeometry x="800" y="10" width="160" height="70" as="geometry" />
        </mxCell>

      </root>
    </mxGraphModel>
  </diagram>
</mxfile>

生成された構成図を見ると、CDKコードで定義した以下の要素がきちんと表現されていますね

  • マルチAZのネットワーク構成
  • ALB、EC2、RDS、NAT Gateway、VPCエンドポイントなどのリソース
  • セキュリティグループの関係性

ただ、プロンプトの指示とは違いアイコンは古いもの使っていそうです。
このあたりはTipsで紹介する事前にテンプレートを用意したり、プロンプトで補正をかけると改善されるかと思います。

ai-architecutre-1.png

CDKコードで生成されたCFnで同様にプロンプトを投げても、同様に構成図が作成されます。

202503042030-cfn-ai-architecture.png

Terraformもサンプルコードを用意して構成図を作成してみましたが、こちらも問題なく構成図が作成されました。

Terraformのサンプルコード
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
  required_version = "= 1.11.1"
}

provider "aws" {
  region = "ap-northeast-1"
}

# 変数定義
variable "project" {
  type        = string
  default     = "web3tier"
  description = "プロジェクト名"
}

variable "environment" {
  type        = string
  default     = "dev"
  description = "環境名 (dev/stg/prod)"
}

variable "vpc_cidr" {
  type        = string
  default     = "10.0.0.0/16"
  description = "VPCのCIDRブロック"
}

variable "availability_zones" {
  type        = list(string)
  default     = ["ap-northeast-1a", "ap-northeast-1c"]
  description = "使用するAZ"
}

variable "public_subnet_cidrs" {
  type        = list(string)
  default     = ["10.0.1.0/24", "10.0.2.0/24"]
  description = "パブリックサブネットのCIDRブロック"
}

variable "app_subnet_cidrs" {
  type        = list(string)
  default     = ["10.0.11.0/24", "10.0.12.0/24"]
  description = "アプリケーション層サブネットのCIDRブロック"
}

variable "db_subnet_cidrs" {
  type        = list(string)
  default     = ["10.0.21.0/24", "10.0.22.0/24"]
  description = "データベース層サブネットのCIDRブロック"
}

variable "ec2_instance_type" {
  type        = string
  default     = "t3.micro"
  description = "EC2インスタンスタイプ"
}

variable "db_instance_class" {
  type        = string
  default     = "db.t3.micro"
  description = "RDSインスタンスクラス"
}

variable "db_name" {
  type        = string
  default     = "web3tierdb"
  description = "データベース名"
}

variable "db_username" {
  type        = string
  default     = "admin"
  description = "データベースのマスターユーザー名"
}

variable "db_password" {
  type        = string
  sensitive   = true
  description = "データベースのマスターパスワード"
}

# ネットワーク関連リソース
# VPCの作成
resource "aws_vpc" "main" {
  cidr_block           = var.vpc_cidr
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name        = "${var.project}-${var.environment}-vpc"
    Environment = var.environment
  }
}

# パブリックサブネットの作成
resource "aws_subnet" "public" {
  count             = length(var.public_subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.public_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  map_public_ip_on_launch = true

  tags = {
    Name        = "${var.project}-${var.environment}-public-subnet-${count.index + 1}"
    Environment = var.environment
  }
}

# アプリケーション層サブネットの作成
resource "aws_subnet" "app" {
  count             = length(var.app_subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.app_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name        = "${var.project}-${var.environment}-app-subnet-${count.index + 1}"
    Environment = var.environment
  }
}

# データベース層サブネットの作成
resource "aws_subnet" "db" {
  count             = length(var.db_subnet_cidrs)
  vpc_id            = aws_vpc.main.id
  cidr_block        = var.db_subnet_cidrs[count.index]
  availability_zone = var.availability_zones[count.index]

  tags = {
    Name        = "${var.project}-${var.environment}-db-subnet-${count.index + 1}"
    Environment = var.environment
  }
}

# インターネットゲートウェイの作成
resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name        = "${var.project}-${var.environment}-igw"
    Environment = var.environment
  }
}

# Elastic IPの作成(NAT Gateway用)
resource "aws_eip" "nat" {
  domain = "vpc"
  depends_on = [aws_internet_gateway.main]

  tags = {
    Name        = "${var.project}-${var.environment}-nat-eip"
    Environment = var.environment
  }
}

# NAT Gatewayの作成
resource "aws_nat_gateway" "main" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public[0].id

  tags = {
    Name        = "${var.project}-${var.environment}-nat"
    Environment = var.environment
  }
}

# パブリックルートテーブル
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name        = "${var.project}-${var.environment}-public-rt"
    Environment = var.environment
  }
}

# プライベートルートテーブル(アプリケーション層用)
resource "aws_route_table" "private_app" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.main.id
  }

  tags = {
    Name        = "${var.project}-${var.environment}-private-app-rt"
    Environment = var.environment
  }
}

# プライベートルートテーブル(データベース層用)
resource "aws_route_table" "private_db" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name        = "${var.project}-${var.environment}-private-db-rt"
    Environment = var.environment
  }
}

# ルートテーブルの関連付け(パブリック)
resource "aws_route_table_association" "public" {
  count          = length(var.public_subnet_cidrs)
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

# ルートテーブルの関連付け(アプリケーション層)
resource "aws_route_table_association" "app" {
  count          = length(var.app_subnet_cidrs)
  subnet_id      = aws_subnet.app[count.index].id
  route_table_id = aws_route_table.private_app.id
}

# ルートテーブルの関連付け(データベース層)
resource "aws_route_table_association" "db" {
  count          = length(var.db_subnet_cidrs)
  subnet_id      = aws_subnet.db[count.index].id
  route_table_id = aws_route_table.private_db.id
}

# セキュリティグループ
# ALB用セキュリティグループ
resource "aws_security_group" "alb" {
  name_prefix = "${var.project}-${var.environment}-alb-sg"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "Allow HTTP from anywhere"
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "Allow HTTPS from anywhere"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    description = "Allow all outbound traffic"
  }

  tags = {
    Name        = "${var.project}-${var.environment}-alb-sg"
    Environment = var.environment
  }

  lifecycle {
    create_before_destroy = true
  }
}

# アプリケーション層用セキュリティグループ
resource "aws_security_group" "app" {
  name_prefix = "${var.project}-${var.environment}-app-sg"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port       = 80
    to_port         = 80
    protocol        = "tcp"
    security_groups = [aws_security_group.alb.id]
    description     = "Allow HTTP from ALB"
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
    description = "Allow all outbound traffic"
  }

  tags = {
    Name        = "${var.project}-${var.environment}-app-sg"
    Environment = var.environment
  }

  lifecycle {
    create_before_destroy = true
  }
}

# データベース層用セキュリティグループ
resource "aws_security_group" "db" {
  name_prefix = "${var.project}-${var.environment}-db-sg"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.app.id]
    description     = "Allow MySQL from application layer"
  }

  tags = {
    Name        = "${var.project}-${var.environment}-db-sg"
    Environment = var.environment
  }

  lifecycle {
    create_before_destroy = true
  }
}

# コンピューティングリソース
# Application Load Balancer
resource "aws_lb" "main" {
  name               = "${var.project}-${var.environment}-alb"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets           = aws_subnet.public[*].id

  tags = {
    Name        = "${var.project}-${var.environment}-alb"
    Environment = var.environment
  }
}

# Target Group
resource "aws_lb_target_group" "main" {
  name     = "${var.project}-${var.environment}-tg"
  port     = 80
  protocol = "HTTP"
  vpc_id   = aws_vpc.main.id

  health_check {
    enabled             = true
    healthy_threshold   = 2
    interval            = 30
    matcher            = "200"
    path               = "/"
    port               = "traffic-port"
    protocol           = "HTTP"
    timeout            = 5
    unhealthy_threshold = 2
  }

  tags = {
    Name        = "${var.project}-${var.environment}-tg"
    Environment = var.environment
  }
}

# ALB Listener
resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.main.arn
  port              = 80
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.main.arn
  }
}

# Launch Template
resource "aws_launch_template" "main" {
  name_prefix   = "${var.project}-${var.environment}-template"
  image_id      = "ami-0d52744d6551d851e"  # Amazon Linux 2 AMI ID
  instance_type = var.ec2_instance_type

  network_interfaces {
    associate_public_ip_address = false
    security_groups            = [aws_security_group.app.id]
  }

  user_data = base64encode(<<-EOF
              #!/bin/bash
              yum update -y
              yum install -y httpd
              systemctl start httpd
              systemctl enable httpd
              echo "<h1>Hello from Web Server</h1>" > /var/www/html/index.html
              EOF
  )

  tag_specifications {
    resource_type = "instance"
    tags = {
      Name        = "${var.project}-${var.environment}-instance"
      Environment = var.environment
    }
  }

  lifecycle {
    create_before_destroy = true
  }
}

# Auto Scaling Group
resource "aws_autoscaling_group" "main" {
  name                = "${var.project}-${var.environment}-asg"
  desired_capacity    = 2
  max_size           = 4
  min_size           = 1
  target_group_arns  = [aws_lb_target_group.main.arn]
  vpc_zone_identifier = aws_subnet.app[*].id
  health_check_type  = "ELB"
  health_check_grace_period = 300

  launch_template {
    id      = aws_launch_template.main.id
    version = "$Latest"
  }

  tag {
    key                 = "Name"
    value               = "${var.project}-${var.environment}-asg"
    propagate_at_launch = true
  }

  tag {
    key                 = "Environment"
    value               = var.environment
    propagate_at_launch = true
  }

  lifecycle {
    create_before_destroy = true
  }
}

# データベースリソース
# DB Subnet Group
resource "aws_db_subnet_group" "main" {
  name       = "${var.project}-${var.environment}-db-subnet-group"
  subnet_ids = aws_subnet.db[*].id

  tags = {
    Name        = "${var.project}-${var.environment}-db-subnet-group"
    Environment = var.environment
  }
}

# RDS Aurora Cluster
resource "aws_rds_cluster" "main" {
  cluster_identifier     = "${var.project}-${var.environment}-aurora-cluster"
  engine                = "aurora-mysql"
  engine_version        = "8.0.mysql_aurora.3.05.1"
  database_name         = var.db_name
  master_username       = var.db_username
  master_password       = var.db_password
  db_subnet_group_name  = aws_db_subnet_group.main.name
  vpc_security_group_ids = [aws_security_group.db.id]
  skip_final_snapshot   = true

  availability_zones    = var.availability_zones

  backup_retention_period = 7
  preferred_backup_window = "03:00-04:00"

  enabled_cloudwatch_logs_exports = ["audit", "error", "general", "slowquery"]

  tags = {
    Name        = "${var.project}-${var.environment}-aurora-cluster"
    Environment = var.environment
  }
}

# RDS Aurora Instance
resource "aws_rds_cluster_instance" "main" {
  count               = 2
  identifier         = "${var.project}-${var.environment}-aurora-instance-${count.index + 1}"
  cluster_identifier = aws_rds_cluster.main.id
  instance_class     = var.db_instance_class
  engine             = aws_rds_cluster.main.engine
  engine_version     = aws_rds_cluster.main.engine_version

  tags = {
    Name        = "${var.project}-${var.environment}-aurora-instance-${count.index + 1}"
    Environment = var.environment
  }
}

# 出力値
output "vpc_id" {
  description = "VPC ID"
  value       = aws_vpc.main.id
}

output "public_subnet_ids" {
  description = "パブリックサブネットのID"
  value       = aws_subnet.public[*].id
}

output "app_subnet_ids" {
  description = "アプリケーション層サブネットのID"
  value       = aws_subnet.app[*].id
}

output "db_subnet_ids" {
  description = "データベース層サブネットのID"
  value       = aws_subnet.db[*].id
}

output "alb_dns_name" {
  description = "Application Load BalancerのDNS名"
  value       = aws_lb.main.dns_name
}

output "rds_cluster_endpoint" {
  description = "Aurora ClusterのWriter Endpoint"
  value       = aws_rds_cluster.main.endpoint
}

output "rds_cluster_reader_endpoint" {
  description = "Aurora ClusterのReader Endpoint"
  value       = aws_rds_cluster.main.reader_endpoint
}

output "rds_cluster_database_name" {
  description = "データベース名"
  value       = aws_rds_cluster.main.database_name
}

output "rds_cluster_port" {
  description = "データベースポート"
  value       = aws_rds_cluster.main.port
} 

web3tier-architecture.png

その他の生成AIのモデル結果

今回はClaude3.7 Sonnetを利用しましたが、その他の生成AIモデルを使った結果はこちらです。

Cloude 3.5 Sonnet v2

ほぼ、Claude 3.7 Sonnetと同等の構成を出力できています。
値段も変わらないので基本的に3.7 Sonnet利用するで良さそうですが、Claude 3.7 Sonnetが利用できない環境では3.5 Sonentでも良さそうです

202503081545-cdk-claude3.5-v2.png

OpenAI o1

o1は全体的なアーキテクチャの理解は良好ですが、AWSアイコンの認識が弱く、汎用的な図形を使用しています。構成の関係性は理解できていますが、AWS特有の表現が不足しています。

202503081541-cdk-azure-o1-architecture.png

GPT-4.5 Preview

o1より新しいモデルということで、AWSアイコンの認識などが改善されるかなと思いましたが、一部リソースが表現できていないなどo1よりも構成図の観点では表現が弱いように感じました。

デザイン的な部分はAWSアイコンは認識できていないものの、カラーリングが行われてAWSっぽい構成図をされるようになっています。

202503091511-cdk-gpt4.5-preview.png

DeepSeek-R1

それっぽいアーキテクチャはできましたが、他のモデルに比べると全体的にアーキテクチャの認識ができていないようですね

202503060000-cdk-deepseek-r1-architecture.png

Gemini 2.0 Pro

めっちゃでかいVPCのアイコンが呼び出されるているのでAWSアイコンは認識できていそうですが、構成図という観点では他のモデルよりも理解できていなさそうですね

202503091518-cdk-gemini-pro.png

よりそれっぽい構成図を作成するためのTips

テンプレート構成図を用意する

さきほどのClaude 3.7 Sonnetの例では基本的に縦に配置された構成図を作成していました。ニーズとしては横に配置された構成図を作成したいケースもあると思います。

その場合は、事前にアイコンを配置するイメージのテンプレートを用意しておくと、それをベースに生成AIが作図してくれるようになります。

例として、以下のようなサブネットをすでに定義した構成図を用意しておきます。

base2.png

こちらの用意したテンプレートを元に以下のプロンプトを投げて作成したものが以下のようになります。

以下のCDKコードを元に、提供したdraw.ioのAWS構成図テンプレートを更新してください

202503051000-base2-cdk-claude3.7-architecture.png

テンプレートの構成図通りにアーキテクチャを配置してくれていますね。
ほかのサブネットの情報をベースにしたのかAWSアイコンも最新のものを利用してきています。
(何回かやった際に古いアイコンを利用するケースもあったので、このあたりはプロンプトの指示が必要そうです。)

セキュリティグループの通信経路の図など不要なものはこちらで削除することで、すぐ利用できるAWS構成図になりそうです。

このように、テンプレートを用意することで以下のメリットがあります:

  • 好みのレイアウト(横配置/縦配置)で構成図を作成できる
  • 生成AIが配置に迷うことなく、適切な位置にリソースを配置できる

また、テンプレートとなる構成図を用意するほか、プロンプトの指示次第にアーキテクチャの詳細情報を含めることで、より精度の高い構成図を作成できるようになると思います。

ブロジェクトを理解した構成図を作成させる

今回の紹介の中では1ファイルで完結するようにしましたが、ブロジェクトを理解した構成図を作成させたいケースが多いかと思います。

Cursorであれば @codebase 、GitHub Copilotであれば @workspace (あとは拡張機能でClineなど)などでプロジェクトを認識させるツールがあれば、それらを使うことでブロジェクトを理解した構成図を作成させることができるようになります。

ただ、こういったツールが利用できないケースもあるかと思いますので、そういった場合には以下の記事で紹介されているようなアプローチを取ることで1ファイルにまとめて生成AIに構成図を作成させることができるようになるのでご活用ください。

https://zenn.dev/olemi/articles/7b7992c055c64a

最後に

今回は生成AIを使ってdraw.ioでAWS構成図を作成する方法を紹介しました。特にClaude 3.7 SonnetやClaude 3.5 Sonnetなどのモデルを使うことで、高品質なAWS構成図を簡単に作成できることがわかりました。

今後、生成AIの進化によってさらに高品質な構成図が作成できるようになると思うので期待ですね!

この記事が誰かのお役に立てれば幸いです。

以上、たかやま(@nyan_kotaroo)でした。

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.