S3にアップロードしたAWS構成図をCloudFormationに変換するシステムを作ってみた

テンプレートの正確性が不十分なため本番利用は難しそうです。ぜひ検証用途で遊んでみてください。
2024.03.12

こんにちは、つくぼし(tsukuboshi0755)です!

以前以下のブログで、マルチモーダルAIであるChatGPT V4を利用して、AWSの構成図からIaCコードを生成する方法を紹介しました。

そして最近、BedrockにもついにマルチモーダルAIを備えたClaude 3がリリースされました!

これにより、AWSのみでマルチモーダルAIを利用したシステムを完結させる事が可能になっています。

そこで今回は、S3にアップロードしたAWS構成図をCloudFormationテンプレートに変換し、別のS3にアップロードするシステムを作ってみたので紹介してみたいと思います。

なお変換したテンプレートは、CloudFormationスタック作成時にオブジェクトURLで指定する事でそのまま使用可能です!

前提条件

今回は下記ソフトウェアの使用を前提としています。

項目 バージョン
AWS CLI 2.4
AWS SAM CLI 1.111
Python 3.11

構成図

今回の構成図は以下になります。

今回構築するシステムが実施する処理は以下の通りです。

1. インプット用S3バケットにAWS構成図をアップロード
2. LambdaがS3にアップロードされた構成図を取得
3. Lambdaが構成図をloudFormationテンプレートに変換するようBedrock Claude 3にリクエスト
4. Lambdaが変換したテンプレートに対してValidateTemplateを用いて検証し、問題がある場合はテンプレート名にerrorを付与
5. テンプレート名に実行時刻をつけた上でアウトプット用S3バケットにテンプレートをアップロード

コード

今回使用するコードは以下のリポジトリにアップロードしています。

構築の流れ

事前準備

2023年3月現在では、Bedrock Claude 3 (Sonnet)はバージニア及びオレゴンでのみ利用可能です。

今回はオレゴンリージョンを利用するため、以下のリンクを参考にオレゴンでモデルアクセスを有効化します。

以下の通り、対象リージョンでBedrock Claude 3 (Sonnet)が有効化されていればOKです。

デプロイ

リポジトリをクローン後、SAMアプリをビルド及びデプロイします。

ここで構成図には記載されていないがテンプレート生成時に考慮して欲しい情報がある場合、補足プロンプトであるprompt.txtに補足情報を記載します。

例えば今回以下のWeb3層構成図sample.pngをアップロードするとします。

その場合、構成図に記載されていない補足情報を以下のように記載します。

prompt.txt

- VPC及びサブネット含めて新規で作成してください
- EC2のAMI IDには`ami-039e8f15ccb15368a`を使用してください
- EC2のキーペアは使用せず、SSM Session Managerでアクセスできるようにしてください
- ALBのセキュリティグループは、HTTP及びHTTPSのみ受け付けるようにしてください
- EC2のセキュリティグループは、ALBのセキュリティグループからのHTTP及びHTTPSのみ許可するようにしてください
- RDSのセキュリティグループは、EC2のセキュリティグループからのMySQLのみ許可するようにしてください

補足情報の記載が完了したら、以下コマンドでSAMアプリをビルドします。

sam build

ビルド完了後に以下のコマンドを実行すると、S3バケット×2、Lambda関数、及び関連するリソースがデプロイされます。

sam deploy

なおBedrockのリージョン、モデルID、及び補足プロンプトファイルのパスは変更できるようにパラメータ化しています。

パラメータを上書きしたい場合は、以下のようにデプロイ時に--parameter-overridesオプションを付与し、対象のパラメータに値を指定してください。

sam deploy --parameter-overrides \
  BedrockRegion=us-west-2 \
  BedrockModelId=anthropic.claude-3-sonnet-20240229-v1:0 \
  PromptPath=prompt.txt

使い方

以下の通り、convert-diagram-to-cfn-input-diagram-bucket-<アカウントID>バケットに対して構成図をアップロードしてください。

なお構成図の形式は現状pngしか対応していないため、jpeg等の形式の場合は変換してからアップロードをお願いします。

アップロードするとS3イベント通知によりLambda関数が発火され、構成図及び元にClaudeにテンプレートを生成するようリクエストを投げます。

Claudeからレスポンスが返ってくると、Lambda関数がテンプレートの検証を実施し、問題がなければ以下のようにconvert-diagram-to-cfn-output-template-bucket-<アカウントID>バケットに実行時刻のファイル名を持つテンプレートをアップロードします。

今回アップロードした構成図sample.pngから生成されたテンプレートの中身は以下のようになっていました。

テンプレート

20240312105138.yaml

# VPC設定
Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: ap-northeast-1

  PublicSubnetA1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: ap-northeast-1a
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: Public subnet A1

  PublicSubnetC1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: ap-northeast-1c
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: Public subnet C1

  PrivateSubnetA1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: ap-northeast-1a
      CidrBlock: 10.0.3.0/24
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: Private subnet A1

  PrivateSubnetC1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: ap-northeast-1c
      CidrBlock: 10.0.4.0/24
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: Private subnet C1

  InternetGateway:
    Type: AWS::EC2::InternetGateway

  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: Public Route Table

  PublicRoute:
    Type: AWS::EC2::Route
    DependsOn: VPCGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

  PublicSubnetA1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetA1
      RouteTableId: !Ref PublicRouteTable

  PublicSubnetC1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetC1
      RouteTableId: !Ref PublicRouteTable

# セキュリティグループ設定  
  ALBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow HTTP/HTTPS
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp  
          FromPort: 443
          ToPort: 443
          CidrIp: 0.0.0.0/0
      VpcId: !Ref VPC

  EC2SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow HTTP/HTTPS from ALB
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          SourceSecurityGroupId: !Ref ALBSecurityGroup
        - IpProtocol: tcp
          FromPort: 443  
          ToPort: 443
          SourceSecurityGroupId: !Ref ALBSecurityGroup
      VpcId: !Ref VPC

  RDSSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow MySQL from EC2
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref EC2SecurityGroup
      VpcId: !Ref VPC

# ALB設定
  ALB:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Scheme: internet-facing
      SecurityGroups:
        - !Ref ALBSecurityGroup
      Subnets:
        - !Ref PublicSubnetA1
        - !Ref PublicSubnetC1

  ALBListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref ALBTargetGroup
      LoadBalancerArn: !Ref ALB
      Port: 80
      Protocol: HTTP

  ALBTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      HealthCheckPath: /
      Name: ALB-TargetGroup
      Port: 80
      Protocol: HTTP
      TargetType: instance
      VpcId: !Ref VPC

# EC2設定  
  EC2Instance1:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-039e8f15ccb15368a
      InstanceType: t2.micro
      SubnetId: !Ref PublicSubnetA1
      SecurityGroupIds:
        - !Ref EC2SecurityGroup
      IamInstanceProfile: !Ref EC2InstanceProfile
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash
            yum install -y httpd
            systemctl start httpd
            systemctl enable httpd

  EC2Instance2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-039e8f15ccb15368a  
      InstanceType: t2.micro
      SubnetId: !Ref PublicSubnetC1
      SecurityGroupIds:
        - !Ref EC2SecurityGroup
      IamInstanceProfile: !Ref EC2InstanceProfile
      UserData:
        Fn::Base64:
          !Sub |
            #!/bin/bash
            yum install -y httpd
            systemctl start httpd
            systemctl enable httpd

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

  EC2Role:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - ec2.amazonaws.com
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

# RDS設定
  RDS:
    Type: AWS::RDS::DBInstance
    Properties:
      DBName: mydb
      Engine: MySQL
      MasterUsername: rootuser
      MasterUserPassword: rootpassword
      DBInstanceClass: db.t2.micro
      AllocatedStorage: 20
      PubliclyAccessible: false
      VPCSecurityGroups:
        - !Ref RDSSecurityGroup
      DBSubnetGroupName: !Ref RDSSubnetGroup

  RDSSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: RDS Subnet Group
      SubnetIds:
        - !Ref PrivateSubnetA1
        - !Ref PrivateSubnetC1

# その他設定
Outputs:
  VPCID:
    Description: VPC ID
    Value: !Ref VPC
    Export:
      Name: VPCID

  PublicSubnetA1ID:
    Description: Public Subnet A1 ID
    Value: !Ref PublicSubnetA1
    Export:
      Name: PublicSubnetA1ID

  PublicSubnetC1ID:
    Description: Public Subnet C1 ID  
    Value: !Ref PublicSubnetC1
    Export:
      Name: PublicSubnetC1ID

  PrivateSubnetA1ID:
    Description: Private Subnet A1 ID
    Value: !Ref PrivateSubnetA1
    Export:
      Name: PrivateSubnetA1ID
      
  PrivateSubnetC1ID:
    Description: Private Subnet C1 ID
    Value: !Ref PrivateSubnetC1
    Export:
      Name: PrivateSubnetC1ID

  ALBEndpoint:
    Description: ALB Endpoint
    Value: !GetAtt ALB.DNSName
    Export:
      Name: ALBEndpoint

なおテンプレートの検証でエラーが発生した場合は、以下の通りアップロードされるテンプレート名の末尾にerrorが付与されます。

この場合は、Lambda関数のCloudWatch Logsのロググループである/aws/lambda/convert-diagram-to-cfn-functionにて、検証エラー内容を確認します。

その上でテンプレートを修正するか、補足プロンプトを修正し再度デプロイした上で構成図をアップロードし直してください。

さらに生成されたテンプレートのオブジェクトURIを以下の通りCloudFormationのスタック作成時のAmazon S3 URLに指定する事で、そのままスタックをデプロイする事が可能です。

今回出力されたテンプレートでは、以下のようなリソースが正常に作成されました!

なお、テンプレートの検証が正常に通った場合でも、デプロイに失敗する場合があります。

その場合もデプロイエラー内容を確認し、テンプレートを修正するか、補足プロンプトを修正し再度デプロイした上で構成図をアップロードし直してください。

最後に

今回は、S3にアップロードしたAWS構成図をCloudFormationテンプレートに変換し、別のS3にアップロードするシステムを作ってみました。

BedrockでClaude 3 Sonnetを利用できるようになった事で、外部のAPIを利用する事なく今回のようなAWSのみでマルチモーダルAIを利用したシステムを構築する事が可能になりました。

ただ生成されたテンプレートは正確性を欠いたり、必要なリソースが不足している場面が度々見られたため、もし本番利用したい場合はその辺りの欠点を補う手法を別途検討するべきかと思います。

とはいえ検証用途としては十分に利用できますので、気になる方は是非試してみてください!

以上、つくぼし(tsukuboshi0755)でした!