CloudFormationを使用してElastic Beanstalk入門

CloudFormationを使用して、Elastic Beanstalk入門

CloudFormationを使用してElastic Beanstalk入門

Elastic Beanstalkを取り上げます。

Elastic Beanstalk を使用すると、アプリケーションを実行しているインフラストラクチャについて知識を得なくても、AWS クラウドでアプリケーションのデプロイと管理を簡単に行うことができます。Elastic Beanstalk は、選択肢を狭めたり制御を制限したりすることなく、管理の複雑さを軽減します。アプリケーションをアップロードするだけで、Elastic Beanstalk は容量のプロビジョニング、ロードバランシング、スケーリング、およびアプリケーション状態モニタリングといった詳細を自動的に処理します。

AWS Elastic Beanstalk とは?

本ページでは、Elastic Beanstalkに入門します。
具体的には、CloudFormationを使用して、最小構成のElastic Beanstalk環境を構築します。

構築する環境

Diagram of introduction to Elastic Beanstalk with CloudFormation.

VPCを作成し、その内部にElastic Beanstalk環境を構築します。
具体的には、Webサーバー環境を構築します。

Elastic Beanstalkを通じて、以下のリソースを作成します。

  • EC2インスタンス
  • セキュリティグループ
  • Elastic IPアドレス

Elastic Beanstalkの環境はWebサーバーを選択します。
プラットフォームはPython3.8を選択します。

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/141

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

S3バケット

Resources:
  Bucket:
    Type: AWS::S3::Bucket
    Properties:
      AccessControl: Private
      BucketName: !Ref Prefix
      NotificationConfiguration:
        EventBridgeConfiguration:
          EventBridgeEnabled: true
Code language: YAML (yaml)

S3バケットを作成します。
このバケットは、後述するElastic Beanstalk環境上で実行するスクリプト類を配置するために使用します。

NotificationConfigurationプロパティはイベント通知機能に関するパラメータです。
詳細は後述しますが、これはCloudFormationがElastic Beanstalkを構築するタイミングを制御するための設定です。
ですから直接Elastic Beanstalk構築には関係ないものです。
通知先にEventBridgeを指定します。

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

                  def application(environ, start_response):
                      response = str(datetime.datetime.now())
                      start_response("200 OK", [
                          ("Content-Type", "text/html")
                      ])
                      return [bytes(response, 'utf-8')]
                  EOF
            build:
              commands:
                - zip $SOURCE_BUNDLE_NAME -r * .[^.]*
            post_build:
              commands:
                - aws s3 cp $SOURCE_BUNDLE_NAME s3://$BUCKET_NAME/
      Visibility: PRIVATE
Code language: YAML (yaml)

CodeBuildを使用して、Elastic Beanstalk用のソースバンドルを作成します。

BuildSpecプロパティでbuildspec.yamlの中身を記載します。

pre_buildフェーズでElastic Beanstalk上で実行するソースコードを作成します。
内容は現在日時を返すというシンプルなものです。

buildフェーズでデプロイパッケージを作成します。
具体的には、前フェーズで作成したファイルをZIP化します。

post_buildフェーズで、ソースバンドルを先述のS3バケットにアップロードします。

以下がCodeBuild用のIAMロールです。

Resources:
  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)

S3バケットにオブジェクトを配置するための権限を付与します。

Elastic Beanstalk

CloudFormationを使用してElastic Beanstalkを構築する場合は、以下の4つのリソースを作成する必要があります。

  • Application
  • ApplicationVersion
  • Environment
  • Configuration Template

Application

Resources:
  Application:
    Type: AWS::ElasticBeanstalk::Application
    Properties:
      ApplicationName: !Sub "${Prefix}-application"
Code language: YAML (yaml)

AWS公式によるApplicationの説明を引用します。

Elastic Beanstalk アプリケーションは、Elastic Beanstalk コンポーネントの論理コレクションで、環境、バージョン、環境設定などがあります。Elastic Beanstalk では、アプリケーションは概念的にはフォルダと似ています。

アプリケーション

ですから設定項目は名前(ApplicationNameプロパティ)だけです。

ApplicationVersion

Resources:
  ApplicationVersion:
    Type: AWS::ElasticBeanstalk::ApplicationVersion
    Properties:
      ApplicationName: !Ref Application
      SourceBundle:
        S3Bucket: !Ref BucketName
        S3Key: !Ref SourceBundleName
Code language: YAML (yaml)

AWS公式によるApplicationVersionの説明を引用します。

Elastic Beanstalk では、アプリケーションバージョンとは、ウェブアプリケーションのデプロイ可能コードの特定のラベル付きイテレーションのことです。アプリケーションバージョンは、Java の WAR ファイルなどのデプロイ可能コードが含まれている Amazon Simple Storage Service(Amazon S3)オブジェクトを指します。

アプリケーションバージョン

つまりApplicationVertionとは、Elastic Beanstalk上で実行するプログラム類のことです。
SourceBundleプロパティで、プログラム類を配置するS3バケットと、オブジェクト名を指定します。

今回の構成において、本プロパティで指定するオブジェクトとは、先述のCodeBuildでビルドしたものです。
ですからビルドしたZIPファイルと、アップロード先のS3バケット名を指定します。

Environment

Resources:
  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
Code language: YAML (yaml)

AWS公式によるEnvironmentの説明を引用します。

環境は、アプリケーションバージョンを実行している AWS リソースのコレクションです。各環境が実行するのは一度に 1 つのアプリケーションバージョンだけですが、同じアプリケーションバージョンや複数の異なるアプリケーションバージョンを多数の環境で同時に実行できます。

環境

ポイントはTierプロパティです。
本プロパティは環境枠を指定するものです。

環境枠は環境で実行するアプリケーションのタイプを指定し、それをサポートするために Elastic Beanstalk でプロビジョニングするリソースを決定します。HTTP リクエストを処理するアプリケーションは、ウェブサーバー環境枠で実行されます。Amazon Simple Queue Service (Amazon SQS) キューからタスクを取り出すバックエンド環境は、ワーカー環境枠で実行されます。

環境枠

今回はWebサーバー環境を構築します。
ですからNameプロパティに「WebServer」を指定します。
またTypeプロパティは「Standard」を指定します。
こちらについてはWebサーバー環境において、固定の値となります。

ConfigurationTemplate

Resources:
  ConfigurationTemplate:
    Type: AWS::ElasticBeanstalk::ConfigurationTemplate
    Properties:
      ApplicationName: !Ref Application
      OptionSettings:
        - Namespace: aws:autoscaling:launchconfiguration
          OptionName: IamInstanceProfile
          Value: !Ref InstanceProfile
        - Namespace: aws:autoscaling:launchconfiguration
          OptionName: InstanceType
          Value: !Ref InstanceType
        - 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:ec2:vpc
          OptionName: VPCId
          Value: !Ref VPC
        - Namespace: aws:ec2:vpc
          OptionName: Subnets
          Value: !Ref PublicSubnet1
      SolutionStackName: !Ref SolutionStackName
Code language: YAML (yaml)

ConfigurationTemplateとは、コンソールや他ドキュメントにおける保存された設定に相当します。
AWS公式による保存された設定の説明を引用します。

保存された設定 は、一意の環境設定を作成するための開始点として使用できるテンプレートです。

保存された設定

つまりElastic Beanstalk環境を構築する上での各種パラメータを設定するものです。
このパラメータは無数に存在します。

https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/command-options-general.html

今回指定したパラメータの概要を簡単にご説明します。

名前空間aws:autoscaling:launchconfigurationでは、IamInstanceProfileおよびInstanceTypeの2つのオプションを設定します。
どちらもElastic Beanstalk環境上で起動するインスタンスに関するものです。
前者はインスタンスに割り当てるインスタンスプロファイル、後者はインスタンスタイプです。
今回はインスタンスタイプにt3.nanoを指定します。

名前空間aws:elasticbeanstalk:environmentでは、EnvironmentTypeおよびServiceRoleの2つのオプションを設定します。
どちらもElastic Beanstalkに関するオプションです。
前者は環境タイプ(シングルインスタンス環境またはスケーブルな環境)を設定できます。
今回はシングルインスタンス環境を構築するために、「SingleInstance」を指定します。
後者はElastic Beanstalk用のサービスロールを指定します。
今回はデフォルトのサービスロールaws-elasticbeanstalk-service-roleを指定します。
こちらにつきましては、以下のページをご確認ください。

https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/iam-servicerole.html

名前空間aws:ec2:vpcでは、VPCIdおよびSubnetsの2つのオプションを設定します。
どちらもElastic Beanstalk環境内のインスタンスが動作するVPCおよびサブネットを指定します。

以下はEC2インスタンス用のインスタンスプロファイルおよびIAMロールです。

Resources:
  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)

AWS管理ポリシーAWSElasticBeanstalkWebTierをアタッチしたIAMロールを作成します。

(参考) CloudFormationカスタムリソース

Resources:
  CustomResource:
    Type: Custom::CustomResource
    Properties:
      ServiceToken: !GetAtt Function1.Arn

  Function1:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          CODEBUILD_PROJECT: !Ref CodeBuildProject
      Code:
        ZipFile: |
          import boto3
          import cfnresponse
          import os

          codebuild_project = os.environ['CODEBUILD_PROJECT']

          CREATE = 'Create'
          response_data = {}

          client = boto3.client('codebuild')

          def lambda_handler(event, context):
            try:
              if event['RequestType'] == CREATE:
                response = client.start_build(
                  projectName=codebuild_project
                )
                print(response)

              cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)

            except Exception as e:
              print(e)
              cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
      FunctionName: !Sub "${Prefix}-function-01"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole1.Arn

  FunctionRole1:
    Type: AWS::IAM::Role
    DeletionPolicy: Delete
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Action: sts:AssumeRole
            Principal:
              Service:
                - lambda.amazonaws.com
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Policies:
        - PolicyName: StartCodeBuildPolicy
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action:
                  - codebuild:StartBuild
                Resource:
                  - !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/${CodeBuildProject}"
Code language: YAML (yaml)

CloudFormationカスタムリソースを使用して、CloudFormationスタック作成時に、Lambda関数を自動的に実行します。

カスタムリソースに関する詳細につきましては、以下のページご確認ください。

あわせて読みたい
CloudFormationカスタムリソース入門 【CloudFormationカスタムリソースの挙動を確認する構成】 CloudFormationの機能の1つにカスタムリソースがあります。 カスタムリソースを使用すると、テンプレートにカ...

実行するLambda関数の働きは、先述のCodeBuildプロジェクトを実行することです。
CodeBuild用のクライアントオブジェクトのstart_buildメソッドを実行します。

本関数用のIAMロールにおいては、CodeBuildプロジェクトを実行する権限を与えます。

(参考) WaitCondition

Resources:
  WaitConditionHandle:
    Type: AWS::CloudFormation::WaitConditionHandle

  WaitCondition:
    Type: AWS::CloudFormation::WaitCondition
    Properties:
      Handle: !Ref WaitConditionHandle
      Timeout: !Ref WaitConditionTimeout

  EventsRule:
    Type: AWS::Events::Rule
    Properties:
      EventBusName: !Ref EventBusName
      EventPattern:
        source:
          - aws.s3
        detail-type:
          - Object Created
        detail:
          bucket:
            name:
              - !Ref BucketName
      Name: !Sub "${Prefix}-EventsRule"
      State: ENABLED
      Targets:
        - Arn: !GetAtt Function2.Arn
          Id: !Ref Function2

  EventsRulePermission:
    Type: AWS::Lambda::Permission
    Properties:
      Action: lambda:InvokeFunction
      FunctionName: !Ref Function2
      Principal: events.amazonaws.com
      SourceArn: !GetAtt EventsRule.Arn

  Function2:
    Type: AWS::Lambda::Function
    Properties:
      Architectures:
        - !Ref Architecture
      Environment:
        Variables:
          SIGNAL_URL: !Ref WaitConditionHandle
      Code:
        ZipFile: |
          import json
          import os
          import urllib3
          import uuid

          def lambda_handler(event, context):
            body = json.dumps({
                "Status": "SUCCESS",
                "Reason": "CodeBuild Project Finished Successfully",
                "UniqueId": str(uuid.uuid4()),
                "Data": "CodeBuild Project Finished Successfully"
            })
            http = urllib3.PoolManager()
            http.request('PUT', os.environ['SIGNAL_URL'], body=body)
      FunctionName: !Sub "${Prefix}-function-02"
      Handler: !Ref Handler
      Runtime: !Ref Runtime
      Role: !GetAtt FunctionRole2.Arn

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

CloudFormationを使用して、Elastic Beanstalkと、同環境で実行するアプリケーションのソースバンドルを、同時に作成する上でのポイントは、作成する順序です。
最初に先にソースバンドルを作成して、S3バケットに配置します。
その後にElastic Beanstalkを作成します。

そのためにCloudFormation WaitConditionを使用して、リソースの作成タイミングを制御します。
WaitConditionを使用して、Elastic Beanstalkの作成を待機させます。
先述の通り、CodeBuildプロジェクトはソースバンドルを作成し、S3バケットに配置します。
EventBridgeルールによって、S3バケットにオブジェクトが配置されたことによって、Lambda関数がトリガーされます。
この関数によって、WaitConditionに成功シグナルが通知されて、待機が解除されます。
このように、WaitConditionを使用することによって、Elastic Beanstalkが作成されるタイミングを、S3バケットにソースバンドルが配置された後に制御できます。

詳細につきましては、以下のページをご確認ください。

あわせて読みたい
CFNのWaitConditionを使用して、Lambdaデプロイパッケージのビルドを待つ 【CFNのWaitConditionを使用して、Lambdaデプロイパッケージのビルドを待つ】 CloudFormationを使用して、Lambda関数を作成することを考えます。 以下のページでご紹介...

上記のページでは、Lambda関数を作成する際に、関数本体と、デプロイパッケージを作成する順序を制御する方法を取り上げています。
Lambda関数用のデプロイパッケージとElastic Beanstalk用のソースバンドルとの違いはありますが、考え方は全く同じです。

環境構築

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

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

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

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

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

  • CodeBuildプロジェクト:fa-141
  • S3バケット:fa-141
  • Elastic Beanstalkアプリケーション:fa-141-application
  • Elastic Beanstalk環境:fa-141-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.

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

Domainを見ると、今回作成されたアプリケーションのURL(http://fa-141.ap-northeast-1.elasticbeanstalk.com/)が確認できます。

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

$ curl http://fa-141.ap-northeast-1.elasticbeanstalk.com/
2023-09-04 11:07:10.449608

$ curl http://fa-141.ap-northeast-1.elasticbeanstalk.com/
2023-09-04 11:07:12.973500Code language: Bash (bash)

確かにCodeBuildでビルドしたPythonスクリプトの実行結果が返されています。

Elastic Beanstalk入門ということで、CloudFormationを使用して、最小構成を構築しました。

(参考) Elastic BeanstalkとCloudFormationの関係

Elastic Beanstalkの内部では、CloudFormationが動作しています。

以下の画像は、上記の構成を構築時に自動的に作成されたCloudFormationスタックです。

Detail of CloudFormation 1.

以下に自動的に生成された主なリソースを挙げます。

  • Auto Scalingグループ
  • セキュリティグループ
  • EIP

Auto Scalingグループを確認します。

Detail of Auto Scaling 1.

Auto Scalingグループのサイズを見ると、希望値/最小値/最大値が全て「1」です。
これはElastic Beanstalk環境タイプにおいて、シングルインスタンスタイプを選択したためです。

Detail of Auto Scaling 2.

同グループ内を見ると、確かにEC2インスタンスが1台起動しています。
インスタンスタイプを見ると、「t3.nano」とあります。
これはElastic BeanstalkのConfigurationTemplateで指定した通りのタイプです。

セキュリティグループを確認します。

Detail of VPC 1.

インバウンドルールを見ると、全IPアドレスからのHTTP通信を許可する内容でした。

まとめ

CloudFormationを使用して、最小構成のElastic Beanstalkを構築しました。