TECHSTEP

ITインフラ関連の記事を公開してます。

Amazon Bedrockで構成図からIaCファイルを生成してみる

今回はAmazon Bedrockのプレイグラウンドから構成図を読み込ませ、IaCファイルが生成できないかを試しました。

背景

Amazon Bedrockは複数の複数の生成AIのFoundation Modelを提供するサービスです。Amazon BedrockはPlaygroundというコンソールも提供しており、ChatGPT / Claudeのように使うこともできます。

今回はAmazon BedrockのPlaygroundからAWSの構成図を渡し、CloudFormationの定義ファイルを生成できないか試してみました。

※参考:

検証

Amazon BedrockはClaude 3 Sonnet/Opusをモデルとして使用しました。

また入力する構成図はAWSの提供するこちらから2つを使いました。

aws.amazon.com

aws.amazon.com

Amazon Bedrockのモデル有効化

なおAmazon Bedrockはリージョンによって使えるモデルが限られており、今回はオレゴン (us-west-2)を使用しました。

Amazon BedrockでClaude 3モデルを利用するため、最初にモデルの有効化を行います。Amazon Bedrockのメニューに移動し、 モデルアクセスを選択します。画面遷移後、モデルアクセスを管理を選択します。

ここから利用するモデルを選択します。Claudeを使うには、最初にユースケースの詳細を送信を選択します。

色々聞かれるので入力・送信します。

しばらくするとClaudeもリクエスト可能となります。

Claudeモデルを選択し、画面下部のモデルアクセスをリクエストを選択します。

利用可能になるまでしばらく待ちます。

アクセスが付与されましたと出たら利用可能です。

構成図を渡してCloudFormationを生成する

PlaygroundからClaudeを利用するため、画面左メニューからチャット画面に移動します。画面遷移後はまず モデルを選択 を選択します。

モデルの選択画面では カテゴリモデル を選択します。

チャットなどが入力可能となります。

今回は以下のようなプロンプトを利用しました。色々と作り込むことはできそうですが、シンプルにしてます。

出力結果は以下の通りです。

Claude 3 Sonnet: Webサイト

はい、この構成図に基づいてCloudFormationテンプレートを生成することができます。ここでは、VPC、サブネット、ロードバランサー、EC2インスタンス、RDSインスタンスなどの主要リソースを定義する必要があります。

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: MyVPC

  InternetGateway:
    Type: AWS::EC2::InternetGateway

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: 10.0.1.0/24
      MapPublicIpOnLaunch: true

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      CidrBlock: 10.0.2.0/24
      MapPublicIpOnLaunch: true

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: 10.0.3.0/24

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      CidrBlock: 10.0.4.0/24

  NatGateway1:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayEIP1.AllocationId
      SubnetId: !Ref PublicSubnet1

  NatGatewayEIP1:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NatGateway2:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGatewayEIP2.AllocationId
      SubnetId: !Ref PublicSubnet2

  NatGatewayEIP2:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  PrivateRouteTable2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

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

  PrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1

  PrivateRoute2:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable2
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway2

  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1
      RouteTableId: !Ref PublicRouteTable

  PublicSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet2
      RouteTableId: !Ref PublicRouteTable

  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable1

  PrivateSubnet2RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet2
      RouteTableId: !Ref PrivateRouteTable2

  WebServerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: WebServerSG
      GroupDescription: Allow HTTP/HTTPS traffic
      VpcId: !Ref VPC
      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

  WebServerInstance1:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0cff7528ff583bf9a
      InstanceType: t2.micro
      KeyName: mykey
      NetworkInterfaces:
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          GroupSet:
            - !Ref WebServerSecurityGroup
          SubnetId: !Ref PublicSubnet1

  WebServerInstance2:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0cff7528ff583bf9a
      InstanceType: t2.micro
      KeyName: mykey
      NetworkInterfaces:
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          GroupSet:
            - !Ref WebServerSecurityGroup
          SubnetId: !Ref PublicSubnet2

  ELBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: ELBSecurityGroup
      GroupDescription: Allow HTTP/HTTPS traffic
      VpcId: !Ref VPC
      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

  WebServerClassicELB:
    Type: AWS::ElasticLoadBalancing::LoadBalancer
    Properties:
      Subnets:
        - !Ref PublicSubnet1
        - !Ref PublicSubnet2
      SecurityGroups:
        - !Ref ELBSecurityGroup
      Listeners:
        - LoadBalancerPort: 80
          InstancePort: 80
          Protocol: HTTP
      HealthCheck:
        Target: HTTP:80/
        HealthyThreshold: 3
        UnhealthyThreshold: 5
        Interval: 30
        Timeout: 5
      Instances:
        - !Ref WebServerInstance1
        - !Ref WebServerInstance2

  DBSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: DBSecurityGroup
      GroupDescription: Allow inbound from web servers
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 3306
          ToPort: 3306
          SourceSecurityGroupId: !Ref WebServerSecurityGroup

  DBInstance1:
    Type: AWS::RDS::DBInstance
    Properties:
      DBName: mydb
      Engine: mysql
      MasterUsername: root
      MasterUserPassword: rootpassword
      DBInstanceClass: db.t2.micro
      AllocatedStorage: 20
      PubliclyAccessible: false
      VPCSecurityGroups:
        - !Ref DBSecurityGroup
      DBSubnetGroupName: !Ref DBSubnetGroup

  DBInstance2:
    Type: AWS::RDS::DBInstance
    Properties:
      DBName: mydb
      Engine: mysql
      MasterUsername: root
      MasterUserPassword: rootpassword
      DBInstanceClass: db.t2.micro
      AllocatedStorage: 20
      PubliclyAccessible: false
      VPCSecurityGroups:
        - !Ref DBSecurityGroup
      DBSubnetGroupName: !Ref DBSubnetGroup

  DBSubnetGroup:
    Type: AWS::RDS::DBSubnetGroup
    Properties:
      DBSubnetGroupDescription: Subnets available for the RDS
      SubnetIds:
        - !Ref PrivateSubnet1
        - !Ref PrivateSubnet2

このテンプレートでは、VPC、パブリックサブネット、プライベートサブネット、ネートゲートウェイ、ルートテーブル、セキュリティグループ、EC2インスタンス、ElasticLoadBalancing、RDSインスタンス、DBサブネットグループを定義しています。画像の構成図に合わせて、適切なリソース間の関連付けも行っています。

必要に応じて、AMIIDやインスタンスタイプ、ポート番号、CIDRブロックなどの値を調整する必要があります。また、WAFやCertificate Managerについては、この構成図には含まれていないので追加する必要があります。

Claude 3 Sonnet: イベント駆動

この構成図に基づいて、以下のようなCloudFormationテンプレートを作成できます。

Resources:
  SourceBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: source-image-bucket

  DestinationBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: processed-image-bucket

  ThumbnailFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Runtime: nodejs12.x
      Code:
        ZipFile: |
          // Node.js コードをここに記述
          // Amazon S3 からイメージを取得し、
          // サムネイルを生成し、別の S3 バケットに保存
      Role: !GetAtt LambdaRole.Arn
      Environment:
        Variables:
          SOURCE_BUCKET: !Ref SourceBucket
          DEST_BUCKET: !Ref DestinationBucket

  LambdaRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        - arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
        - arn:aws:iam::aws:policy/AmazonS3WriteOnlyAccess

  PermissionForBucket:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !GetAtt ThumbnailFunction.Arn
      Action: "lambda:InvokeFunction"
      Principal: s3.amazonaws.com
      SourceAccount: !Ref AWS::AccountId
      SourceArn: !GetAtt SourceBucket.Arn

  ThumbnailFunctionTrigger:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Ref SourceBucket
      NotificationConfiguration:
        LambdaConfigurations:
          - Event: s3:ObjectCreated:*
            Function: !GetAtt ThumbnailFunction.Arn

このテンプレートでは、以下のリソースを定義しています。

  • SourceBucket: 元の画像ファイルを格納する S3 バケット
  • DestinationBucket: サムネイル画像を格納する S3 バケット
  • ThumbnailFunction: サムネイル生成の Lambda 関数
  • LambdaRole: Lambda 関数の実行ロール
  • PermissionForBucket: S3 がLambda関数を呼び出すためのアクセス許可
  • ThumbnailFunctionTrigger: SourceBucketにファイルがアップロードされた時にLambda関数をトリガーする設定

Lambda 関数のコードは ZipFile プロパティ内に直接記述するか、別のファイルから読み込む必要があります。

この構成では、画像ファイルが SourceBucket にアップロードされると、Lambda 関数がトリガーされ、そのファイルに対してサムネイル生成処理を行い、生成したサムネイル画像を DestinationBucket に保存します。

Claude 3 Opus: Webサイト
以下のCloudFormationテンプレートは、ご提供いただいた構成図に基づいて作成されたものです。

AWSTemplateFormatVersion: '2010-09-09'
Description: 'AWS architecture diagram'

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: '10.0.0.0/16'
      EnableDnsHostnames: true
      EnableDnsSupport: true
      Tags:
        - Key: Name
          Value: 'Main VPC'

  InternetGateway:
    Type: AWS::EC2::InternetGateway

  VPCGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway
  
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      CidrBlock: '10.0.1.0/24'
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: 'Public Subnet'

  PrivateSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC  
      AvailabilityZone: !Select [1, !GetAZs '']
      CidrBlock: '10.0.2.0/24'
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: 'Private Subnet'
          
  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
      
  PublicSubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref PublicRouteTable
      
  EC2InstancePublic:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: 'ami-0c55b159cbfafe1f0' 
      InstanceType: t2.micro
      NetworkInterfaces:
        - AssociatePublicIpAddress: "true"
          DeviceIndex: "0"
          GroupSet: 
            - !Ref WAFSecurityGroup
          SubnetId: !Ref PublicSubnet
      Tags:
        - Key: Name
          Value: Amazon EC2 Public
          
  EC2InstancePrivate:  
    Type: AWS::EC2::Instance
    Properties:
      ImageId: 'ami-0c55b159cbfafe1f0'
      InstanceType: t2.micro
      SubnetId: !Ref PrivateSubnet
      Tags:
        - Key: Name 
          Value: Amazon EC2 Private

  WAFSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Enable HTTP access via port 80
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
      VpcId: !Ref VPC
      
  ELB:
    Type: AWS::ElasticLoadBalancing::LoadBalancer
    Properties:
      Listeners:
        - LoadBalancerPort: 80
          InstancePort: 80
          Protocol: HTTP
      AvailabilityZones: 
        - !GetAtt PublicSubnet.AvailabilityZone
        - !GetAtt PrivateSubnet.AvailabilityZone  
      Instances:
        - !Ref EC2InstancePublic
        - !Ref EC2InstancePrivate
      SecurityGroups:
        - !GetAtt WAFSecurityGroup.GroupId      
        
  WAF:
    Type: AWS::WAFv2::WebACL
    Properties:
      Name: SampleWebACL
      Scope: REGIONAL
      DefaultAction: 
        Allow: {}
      VisibilityConfig:
        SampledRequestsEnabled: true
        CloudWatchMetricsEnabled: true
        MetricName: SampleWebACL
        
  ElasticLoadBalancingV2Association:
    Type: AWS::WAFv2::WebACLAssociation
    Properties:
      ResourceArn: !Ref ELB
      WebACLArn: !GetAtt 'WAF.Arn'

  RDSInstance:
    Type: AWS::RDS::DBInstance
    Properties:
      DBName: 'mydb'
      Engine: 'MySQL'
      MasterUsername: 'admin'
      MasterUserPassword: !Sub '{{resolve:secretsmanager:MyRDSSecret:SecretString:password}}'
      DBInstanceClass: 'db.t3.micro'
      AllocatedStorage: '5'
      VPCSecurityGroups: 
        - !GetAtt WAFSecurityGroup.GroupId
      DBSubnetGroupName: !Ref RDSSubnetGroup
 
  RDSSubnetGroup: 
    Type: AWS::RDS::DBSubnetGroup
    Properties: 
      DBSubnetGroupDescription: 'Subnet group for RDS'
      SubnetIds: 
        - !Ref PrivateSubnet
        - !Ref PublicSubnet

  CertificateManager:
    Type: AWS::CertificateManager::Certificate
    Properties:
      DomainName: 'example.com' 
      ValidationMethod: DNS

このテンプレートでは、以下のリソースが作成されます。

  • VPC
  • パブリックサブネットとプライベートサブネット
  • インターネットゲートウェイ
  • パブリックとプライベートのEC2インスタンス
  • Elastic Load Balancing
  • AWS WAF
  • RDS (パブリックとプライベートサブネットに配置)
  • AWS Certificate Manager

一部のパラメータ(例: AMI IDやドメイン名)は、実際の環境に合わせて変更する必要があります。 またRDSのパスワードはAWS Secrets Managerに事前に登録しておく必要があります。

Claude 3 Opus: イベント駆動

以下は、提供された構成図に基づいてCloudFormationテンプレートを生成したものです。

AWSTemplateFormatVersion: '2010-09-09'
Description: 'AWS architecture diagram with S3 and Lambda'

Resources:
  OriginalS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: 'original-image-bucket'

  ThumbnailS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: 'thumbnail-image-bucket'

  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
              - lambda.amazonaws.com
          Action:
            - sts:AssumeRole
      Path: "/"
      Policies:
      - PolicyName: root
        PolicyDocument:
          Version: '2012-10-17'
          Statement:
          - Effect: Allow
            Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Resource: arn:aws:logs:*:*:*
          - Effect: Allow
            Action:
              - s3:GetObject
            Resource: !Join ['', ['arn:aws:s3:::', !Ref OriginalS3Bucket, '/*']]
          - Effect: Allow
            Action:
              - s3:PutObject
            Resource: !Join ['', ['arn:aws:s3:::', !Ref ThumbnailS3Bucket, '/*']]

  CreateThumbnailLambda:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Code:
        ZipFile: |
          const AWS = require('aws-sdk');
          const s3 = new AWS.S3();
          const sharp = require('sharp');

          exports.handler = async (event, context) => {
            const bucket = event.Records[0].s3.bucket.name;
            const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
            const params = {
              Bucket: bucket,
              Key: key
            };
            
            try {
              const image = await s3.getObject(params).promise();
              const resizedImage = await sharp(image.Body).resize(200).toBuffer();
              
              const newKey = `thumbnail-${key}`;
              await s3.putObject({
                Bucket: '${self:custom.thumbnailBucket}',  
                Key: newKey,
                Body: resizedImage
              }).promise();

              return {
                statusCode: 200,
                body: 'Thumbnail created successfully'
              };
            } catch (error) {
              console.log(error);
              return {
                statusCode: 500,
                body: error
              };
            }
          };
      Runtime: nodejs12.x
      Timeout: 10

  S3LambdaTrigger:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      EventSourceArn: !GetAtt OriginalS3Bucket.Arn
      FunctionName: !GetAtt CreateThumbnailLambda.Arn
      Events:
        - s3:ObjectCreated:*

このテンプレートでは、以下のリソースが作成されます。

  1. オリジナル画像用のS3バケット
  2. サムネイル画像用のS3バケット
  3. Lambda関数の実行ロール(S3読み書きとCloudWatch Logsへのアクセス権限を付与)
  4. サムネイル画像を生成するLambda関数
  5. オリジナル画像のS3バケットへのPUT操作をトリガーとするLambda関数の設定

Lambda関数のコードは、S3バケットにアップロードされたオリジナル画像を取得し、sharpライブラリを使用して画像のサイズを変更し、サムネイル画像をS3バケットに保存します。

注: このテンプレートを使用する前に、S3バケット名を一意の名前に変更し、Lambda関数に必要なライブラリ(sharpなど)をパッケージ化してデプロイパッケージを作成する必要があります。

また、今回は生成したファイルのうち、Claude 3 Opusで生成したイベント駆動のものをデプロイしてみました。

デプロイ前に構文チェックをしてみると、特にエラーは検出されません。

[cloudshell-user@ip-10-132-33-140 ~]$ aws cloudformation validate-template --template-body file://test06.yaml
{
    "Parameters": [],
    "Description": "AWS architecture diagram with S3 and Lambda",
    "Capabilities": [
        "CAPABILITY_IAM"
    ],
    "CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::Role]"
}
[cloudshell-user@ip-10-132-33-140 ~]$ 

ただしこのままCloudFormationをデプロイしようとすると、いくつかのエラーが発生しました。

  • S3バケット名が重複する
  • Lambda関数で nodejs 12.x をサポートしていない
  • AWS::Lambda::EventSourceMappingEventsというPropertyはサポートしていない

完璧に生成できるとは限りませんが、ここで生成したファイルをベースに修正をすれば、CloudFormationの定義ファイルをイチから用意するよりは早く作れそうです。