CloudFormationを使用して、Elastic BeanstalkのALB環境を構築

CloudFormationを使用して、Elastic BeanstalkのALB環境を構築

以下のページで、Elastic Beanstalkの最小構成をご紹介しました。

あわせて読みたい
CloudFormationを使用してElastic Beanstalk入門 【CloudFormationを使用してElastic Beanstalk入門】 Elastic Beanstalkを取り上げます。 Elastic Beanstalk を使用すると、アプリケーションを実行しているインフラス...

今回は上記を発展させて、Elastic BeanstalkでALB構成を作成します。

構築する環境

Diagram of Elastic Beanstalk ALB environment using CloudFormation.

VPC内に、Elastic Beanstalk環境を構築します。
具体的には、ALBおよびAuto Scalingグループで構成されたWebサーバー環境を作成します。
プラットフォームはPython3.8を選択します。

VPCには2つのAZに2つずつサブネットを作成します。
それぞれパブリックサブネットとプライベートサブネットとします。

EC2インスタンス上で動作するPythonスクリプトを、自動的にビルドします。
具体的には、CodeBuildでスクリプトを作成後、S3バケットに配置します。
このCodeBuildを実行するトリガーは、CloudFormationカスタムリソースを使用します。

またElastic Beanstalkを構築を開始するタイミングを、CloudFormationのWaitConditionで制御します。
具体的には、CodeBuildによってS3バケットにソースバンドルの配置が完了後に、Elastic Beanstalkの構築が開始されるように工夫します。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

https://github.com/awstut-an-r/awstut-fa/tree/main/143

テンプレートファイルのポイント解説

(参考)VPC

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCCidrBlock

  IGW:
    Type: AWS::EC2::InternetGateway

  IGWAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref IGW

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

  NATGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt EIP.AllocationId
      SubnetId: !Ref PublicSubnet1

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref CidrIp1
      VpcId: !Ref VPC
      AvailabilityZone: !Sub "${AWS::Region}${AvailabilityZone1}"

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref CidrIp2
      VpcId: !Ref VPC
      AvailabilityZone: !Sub "${AWS::Region}${AvailabilityZone2}"

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref CidrIp3
      VpcId: !Ref VPC
      AvailabilityZone: !Sub "${AWS::Region}${AvailabilityZone1}"

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: !Ref CidrIp4
      VpcId: !Ref VPC
      AvailabilityZone: !Sub "${AWS::Region}${AvailabilityZone2}"

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

  RouteToInternet:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref IGW

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

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

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

  RouteToNATGateway:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway

  PrivateSubnetRouteTableAssociation1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1
      RouteTableId: !Ref PrivateRouteTable

  PrivateSubnetRouteTableAssociation2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet2
      RouteTableId: !Ref PrivateRouteTable
Code language: YAML (yaml)

ALBおよびAuto Scalingグループの土台となるVPCやサブネット等を定義します。

特にポイントとなるリソースはNATゲートウェイです。

今回の構成では、Auto Scalingグループはプライベートサブネットに作成します。
つまりElastic Beanstalkで管理されるEC2インスタンスがプライベートサブネットに作成されることになります。
Elastic Beanstalkにおいて、プライベートサブネットにEC2インスタンスする場合、注意するべき点があります。
それはインターネットへの到達性です。

プライベートサブネット – インスタンスは NAT デバイスを使用してインターネットにアクセスします。

Amazon VPC で Elastic Beanstalk を使用する

上記に従い、パブリックサブネットにNATゲートウェイを配置します。
そしてプライベートサブネット用のルートテーブルに、同ゲートウェイ向けのルートを定義します。

Elastic Beanstalk

Resources:
  Application:
    Type: AWS::ElasticBeanstalk::Application
    Properties:
      ApplicationName: !Sub "${Prefix}-application"

  ApplicationVersion:
    Type: AWS::ElasticBeanstalk::ApplicationVersion

    Properties:
      ApplicationName: !Ref Application
      SourceBundle:
        S3Bucket: !Ref BucketName
        S3Key: !Ref SourceBundleName

  Environment:
    Type: AWS::ElasticBeanstalk::Environment
    Properties:
      ApplicationName: !Ref Application
      CNAMEPrefix: !Ref Prefix
      EnvironmentName: !Sub "${Prefix}-env"
      TemplateName: !Ref ConfigurationTemplate
      Tier:
        Name: WebServer
        Type: Standard
      VersionLabel: !Ref ApplicationVersion

  ConfigurationTemplate:
    Type: AWS::ElasticBeanstalk::ConfigurationTemplate
    Properties:
      ApplicationName: !Ref Application
      OptionSettings:
        - Namespace: aws:autoscaling:asg
          OptionName: MaxSize
          Value: !Ref MaxSize
        - Namespace: aws:autoscaling:asg
          OptionName: MinSize
          Value: !Ref MinSize
        - Namespace: aws:autoscaling:launchconfiguration
          OptionName: IamInstanceProfile
          Value: !Ref InstanceProfile
        - Namespace: aws:ec2:instances
          OptionName: InstanceTypes
          Value: !Ref InstanceType
        - Namespace: aws:ec2:instances
          OptionName: SupportedArchitectures
          Value: !Ref InstanceArchitecture
        - Namespace: aws:ec2:vpc
          OptionName: VPCId
          Value: !Ref VPC
        - Namespace: aws:ec2:vpc
          OptionName: Subnets
          Value: !Join
            - ","
            - - !Ref PrivateSubnet1
              - !Ref PrivateSubnet2
        - Namespace: aws:ec2:vpc
          OptionName: ELBSubnets
          Value: !Join
            - ","
            - - !Ref PublicSubnet1
              - !Ref PublicSubnet2
        - Namespace: aws:elasticbeanstalk:environment
          OptionName: EnvironmentType
          Value: !Ref EnvironmentType
        - Namespace: aws:elasticbeanstalk:environment
          OptionName: ServiceRole
          Value: !Sub "arn:aws:iam::${AWS::AccountId}:role/service-role/aws-elasticbeanstalk-service-role"
        - Namespace: aws:elasticbeanstalk:environment
          OptionName: LoadBalancerType
          Value: !Ref LoadBalancerType
      SolutionStackName: !Ref SolutionStackName

  InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: /
      Roles:
        - !Ref InstanceRole

  InstanceRole:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - ec2.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier
Code language: YAML (yaml)

本ページでは、Elastic Beanstalkを使用してALB環境を作成する方法を中心に取り上げます。
Elastic Beanstalkに関する基本的な事項については、以下のページをご確認ください。

あわせて読みたい
CloudFormationを使用してElastic Beanstalk入門 【CloudFormationを使用してElastic Beanstalk入門】 Elastic Beanstalkを取り上げます。 Elastic Beanstalk を使用すると、アプリケーションを実行しているインフラス...

Elastic Beanstalkを使用してALBを作成する上で、ConfigurationTemplateは重要なリソースです。
OptionSettingsプロパティでALB用の設定を行います。
以下に重要なパラメータを挙げます。

名前空間aws:autoscaling:asgでは、MaxSizeおよびMinSizeの2つを設定します。
これはAuto Scalingグループ内に作成するEC2インスタンスの最大値と最小値を指定するものです。
今回は両パラメータに「2」を指定します。

名前空間aws:ec2:vpcでは、SubnetsおよびELBSubnetsの2つを設定します。
前者はAuto Scalingグループを作成するサブネット、後者はELBに関連づけるサブネットを指定します。
今回の構成では、プライベートサブネットにAuto Scalingグループを作成しますので、前者に2つのプライベートサブネットを指定します。
そしてELBにはパブリックサブネットを関連づける必要がありますので、後者に2つのパブリックサブネットを指定します。

名前空間aws:elasticbeanstalk:environmentでは、EnvironmentTypeおよびLoadBalancerTypeの2つを設定します。
前者には「LoadBalanced」を、後者には「application」を指定することで、ALBを作成できます。

(参考)CodeBuildプロジェクト

Resources:
  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Artifacts:
        Type: NO_ARTIFACTS
      Cache:
        Type: NO_CACHE
      Environment:
        ComputeType: !Ref ProjectEnvironmentComputeType
        EnvironmentVariables:
          - Name: BUCKET_NAME
            Type: PLAINTEXT
            Value: !Ref BucketName
          - Name: SOURCE_BUNDLE_NAME
            Type: PLAINTEXT
            Value: !Ref SourceBundleName
          - Name: SOURCE_FILE_NAME
            Type: PLAINTEXT
            Value: !Ref SourceFileName
        Image: !Ref ProjectEnvironmentImage
        ImagePullCredentialsType: CODEBUILD
        Type: !Ref ProjectEnvironmentType
        PrivilegedMode: true
      LogsConfig:
        CloudWatchLogs:
          Status: DISABLED
        S3Logs:
          Status: DISABLED
      Name: !Ref Prefix
      ServiceRole: !GetAtt CodeBuildRole.Arn
      Source:
        Type: NO_SOURCE
        BuildSpec: !Sub |
          version: 0.2

          phases:
            pre_build:
              commands:
                - |
                  cat << EOF > $SOURCE_FILE_NAME
                  import datetime
                  import subprocess

                  def application(environ, start_response):
                      result = subprocess.run(
                        ['ec2-metadata', '-i'],
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        encoding='utf-8'
                      )
                      start_response("200 OK", [
                          ("Content-Type", "text/html")
                      ])
                      return [bytes(result.stdout, 'utf-8')]
                  EOF
            build:
              commands:
                - zip $SOURCE_BUNDLE_NAME -r * .[^.]*
            post_build:
              commands:
                - aws s3 cp $SOURCE_BUNDLE_NAME s3://$BUCKET_NAME/
      Visibility: PRIVATE

  CodeBuildRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - codebuild.amazonaws.com
            Action:
              - sts:AssumeRole
      Policies:
        - PolicyName: PutS3ObjectPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - s3:PutObject
                Resource:
                  - !Sub "arn:aws:s3:::${BucketName}/*"
Code language: YAML (yaml)

CodeBuildを使用して、Elastic Beanstalk上で実行させるアプリケーションをビルドし、S3バケットに配置します。

実行するアプリケーションの内容ですが、アプリケーションを実行するEC2インスタンスのIDを返すものです。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

あわせて読みたい
CloudFormationのネストされたスタックで環境を構築する 【CloudFormationのネストされたスタックで環境を構築する方法】 CloudFormationにおけるネストされたスタックを検証します。 CloudFormationでは、スタックをネストす...

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • CodeBuildプロジェクト:fa-141
  • S3バケット:fa-141
  • Elastic Beanstalkアプリケーション:fa-143-application
  • Elastic Beanstalk環境:fa-143-env

AWS Management Consoleから各リソースの作成状況を確認します。

CodeBuildプロジェクトの動作状況を確認します。

Detail of CodeBuild 1.

CodeBuildが自動実行されています。
これはCloudFormationカスタムリソースに紐づくLambda関数によって、自動的にプロジェクトが開始されたということです。
これを受けてElastic Beanstalk上で実行するソースバンドルがビルドされました。

S3バケットを確認します。

Detail of S3 1.

オブジェクトが配置されています。
CodeBuildプロジェクトによってビルドされたソースバンドルです。
このファイルがElastic Beanstalk上で実行されます。

動作確認

準備が整いましたので、Elastic Beanstalkを確認します。

まずElastic Beanstalkアプリケーションを確認します。

Detail of Elastic Beanstalk 1.

確かにアプリケーションが作成されています。
アプリケーションという枠組みの中に、Elastic Beanstalk環境が作成されています。

Elastic Beanstalk環境を確認します。

Detail of Elastic Beanstalk 2.

環境が正常に作成されています。

環境作成時のログを確認します。

Detail of Elastic Beanstalk 3.

確かにALB等が構築されていることがわかります。
そしてAuto Scalingグループ内に2つのEC2インスタンスが作成されていることもわかります。

このアプリケーションにアクセスします。

$ curl http://fa-143.ap-northeast-1.elasticbeanstalk.com
instance-id: i-04435211511b2387b

$ curl http://fa-143.ap-northeast-1.elasticbeanstalk.com
instance-id: i-03162d9ad59a56328Code language: Bash (bash)

確かにALB配下の2つのインスタンスにアクセスできています。

このようにElastic Beanstalkを用いて、ALB環境を構築することができます。

最後にセキュリティグループについて確認します。
Elastic Beanstalkによって、自動的に2つのセキュリティグループが作成されます。

  • ALB用
  • EC2インスタンス用

それぞれを確認します。

Detail of VPC 1.
Detail of VPC 2.

1枚目がALB用です。
全送信元からのHTTP通信(80/tcp)を許可する内容です。

2枚目がEC2インスタンス用です。
ALB用セキュリティグループからのHTTP通信(80/tcp)を許可する内容です。

まとめ

Elastic BeanstalkでALB構成を作成しました。