CloudFormationを使用してElastic Beanstalk入門
Elastic Beanstalkを取り上げます。
Elastic Beanstalk を使用すると、アプリケーションを実行しているインフラストラクチャについて知識を得なくても、AWS クラウドでアプリケーションのデプロイと管理を簡単に行うことができます。Elastic Beanstalk は、選択肢を狭めたり制御を制限したりすることなく、管理の複雑さを軽減します。アプリケーションをアップロードするだけで、Elastic Beanstalk は容量のプロビジョニング、ロードバランシング、スケーリング、およびアプリケーション状態モニタリングといった詳細を自動的に処理します。
AWS Elastic Beanstalk とは?
本ページでは、Elastic Beanstalkに入門します。
具体的には、CloudFormationを使用して、最小構成のElastic Beanstalk環境を構築します。
構築する環境
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関数を自動的に実行します。
カスタムリソースに関する詳細につきましては、以下のページご確認ください。
実行する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バケットにソースバンドルが配置された後に制御できます。
詳細につきましては、以下のページをご確認ください。
上記のページでは、Lambda関数を作成する際に、関数本体と、デプロイパッケージを作成する順序を制御する方法を取り上げています。
Lambda関数用のデプロイパッケージとElastic Beanstalk用のソースバンドルとの違いはありますが、考え方は全く同じです。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- CodeBuildプロジェクト:fa-141
- S3バケット:fa-141
- Elastic Beanstalkアプリケーション:fa-141-application
- Elastic Beanstalk環境:fa-141-env
AWS Management Consoleから各リソースの作成状況を確認します。
CodeBuildプロジェクトの動作状況を確認します。
CodeBuildが自動実行されています。
これはCloudFormationカスタムリソースに紐づくLambda関数によって、自動的にプロジェクトが開始されたということです。
これを受けてElastic Beanstalk上で実行するソースバンドルがビルドされました。
S3バケットを確認します。
オブジェクトが配置されています。
CodeBuildプロジェクトによってビルドされたソースバンドルです。
このファイルがElastic Beanstalk上で実行されます。
動作確認
準備が整いましたので、Elastic Beanstalkを確認します。
まずElastic Beanstalkアプリケーションを確認します。
確かにアプリケーションが作成されています。
アプリケーションという枠組みの中に、Elastic Beanstalk環境が作成されています。
Elastic Beanstalk環境を確認します。
環境が正常に作成されています。
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.973500
Code language: Bash (bash)
確かにCodeBuildでビルドしたPythonスクリプトの実行結果が返されています。
Elastic Beanstalk入門ということで、CloudFormationを使用して、最小構成を構築しました。
(参考) Elastic BeanstalkとCloudFormationの関係
Elastic Beanstalkの内部では、CloudFormationが動作しています。
以下の画像は、上記の構成を構築時に自動的に作成されたCloudFormationスタックです。
以下に自動的に生成された主なリソースを挙げます。
- Auto Scalingグループ
- セキュリティグループ
- EIP
Auto Scalingグループを確認します。
Auto Scalingグループのサイズを見ると、希望値/最小値/最大値が全て「1」です。
これはElastic Beanstalk環境タイプにおいて、シングルインスタンスタイプを選択したためです。
同グループ内を見ると、確かにEC2インスタンスが1台起動しています。
インスタンスタイプを見ると、「t3.nano」とあります。
これはElastic BeanstalkのConfigurationTemplateで指定した通りのタイプです。
セキュリティグループを確認します。
インバウンドルールを見ると、全IPアドレスからのHTTP通信を許可する内容でした。
まとめ
CloudFormationを使用して、最小構成のElastic Beanstalkを構築しました。