Elastic Beanstalkのデプロイポリシー:Rolling with an additional batch
Elastic Beanstalkでは、さまざまなデプロイポリシーが用意されています。
今回はRolling with an additional batch (追加バッチによるローリング)の挙動を検証します。
Rolling with additional batch (追加バッチによるローリング) – 可用性の低下を回避します。ただし、[Rolling (ローリング)] 方法よりもデプロイ時間が長くなります。デプロイ全体で同じ帯域幅を保持する必要がある場合に適しています。この方法では、Elastic Beanstalk はインスタンスの追加バッチを起動し、ローリングデプロイを実行します。追加バッチの起動に時間をかけることで、デプロイ全体で同じ帯域幅が確実に保持されます。
デプロイポリシーの選択
構築する環境
基本的な構成は以下のページと同様です。
具体的には、ALBおよびAuto Scalingグループで構成されたWebサーバー環境を作成します。
プラットフォームはPython3.8を選択します。
上記のページと異なる点は、Elastic Beanstalkにデプロイポリシー:Rolling with an additional batchを設定していることです。
この構成においては、Auto Scalingグループ内のインスタンスサイズは2です。
デプロイポリシーのバッチサイズタイプをFixed、バッチサイズを1とすることで、デプロイ中は一時的にインスタンス数が3となります。
デプロイ完了後は、再びインスタンス数が2に戻ります。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-dva/tree/main/01/003
テンプレートファイルのポイント解説
本ページは、Elastic Beanstalkのデプロイポリシー(Rolling with an additional batch)の挙動を確認することを目的としています。
Elastic BeanstalkでALB環境を構築する方法については、以下のページをご確認ください。
Elastic Beanstalk
Resources:
Application:
Type: AWS::ElasticBeanstalk::Application
Properties:
ApplicationName: !Sub "${Prefix}-application"
ApplicationVersion1:
Type: AWS::ElasticBeanstalk::ApplicationVersion
Properties:
ApplicationName: !Ref Application
SourceBundle:
S3Bucket: !Ref BucketName
S3Key: !Ref SourceBundleName1
ApplicationVersion2:
Type: AWS::ElasticBeanstalk::ApplicationVersion
Properties:
ApplicationName: !Ref Application
SourceBundle:
S3Bucket: !Ref BucketName
S3Key: !Ref SourceBundleName2
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 ApplicationVersion1
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:command
OptionName: DeploymentPolicy
Value: !Ref DeploymentPolicy
- Namespace: aws:elasticbeanstalk:command
OptionName: BatchSizeType
Value: !Ref BatchSizeType
- Namespace: aws:elasticbeanstalk:command
OptionName: BatchSize
Value: !Ref BatchSize
- 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)
ポイントは2つです。
1点目はアプリケーションバージョンを2つ作成する点です。
1つ目(ApplicationVersion1)はElastic Beanstalkの初期構築時に参照します。
2つ目(ApplicationVersion2)はアップデート時に参照することになります。
両者とも、後述のCodeBuildプロジェクトでビルドされたソースバンドルを使用して作成します。
2点目はConfigurationTemplateのデプロイポリシー用のパラメータです。
OptionSettingsプロパティにおける名前空間aws:elasticbeanstalk:commandに関するパラメータが特にポイントです。
DeploymentPolicyはデプロイポリシーを指定するものです。
今回はRolling with an additional batchを選択するために、「RollingWithAdditionalBatch」を指定します。
BatchSizeおよびBatchSizeTypeにそれぞれ「1」そして「Fixed」を指定します。
これで1台ずつデプロイするという挙動となります。
(参考) 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_NAME1
Type: PLAINTEXT
Value: !Ref SourceBundleName1
- Name: SOURCE_BUNDLE_NAME2
Type: PLAINTEXT
Value: !Ref SourceBundleName2
- 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:
- mkdir app1
- mkdir app2
- |
cat << EOF > app1/$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
- |
cat << EOF > app2/$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('updated: '+result.stdout, 'utf-8')]
EOF
build:
commands:
- zip -j $SOURCE_BUNDLE_NAME1 -r app1/*
- zip -j $SOURCE_BUNDLE_NAME2 -r app2/*
post_build:
commands:
- aws s3 cp $SOURCE_BUNDLE_NAME1 s3://$BUCKET_NAME/
- aws s3 cp $SOURCE_BUNDLE_NAME2 s3://$BUCKET_NAME/
Visibility: PRIVATE
Code language: YAML (yaml)
CodeBuildを使用して、Elastic Beanstalk上で実行させるアプリケーションをビルドし、S3バケットに配置します。
今回はデプロイポリシーの検証ということで、アプリケーション用のソースバンドルを2つ用意します。
1つ目はElastic Beanstalk初期構築時のアプリケーション用です。
2つ目はデプロイポリシーを検証するためのアップデート用です。
両アプリケーションで実行する内容ですが、アプリケーションを実行するEC2インスタンスのIDを返すものです。
アップデート用の方は、インスタンスIDの前に「updated: 」という文字列を追加します。
(参考) CodeBuildプロジェクトでソースバンドルが作成された後にElastic Beanstalkリソースを作成する
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.codebuild
detail-type:
- CodeBuild Build State Change
detail:
build-status:
- SUCCEEDED
project-name:
- !Ref CodeBuildProject
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)
Elastic Beanstalkリソースの構築が開始されるタイミングを、CloudFormationのWaitConditionで制御します。
具体的には、CodeBuildプロジェクトによってS3バケットにソースバンドルの配置が完了した後とします。
考え方は以下のページで説明しているものと同様です。
上記のページと異なる点は、EventBridgeルールです。
上記のページですと、WaitConditionに成功シグナルを通知するLambda関数を実行する条件は、S3バケットにソースバンドルが配置されることでした。
今回の構成では、CodeBuildプロジェクトが正常に終了することを条件としました。
これは今回の構成では、ソースバンドルを2つ作成するためです。
つまり両方のバンドルのビルドが完了しない内に、Elastic Beanstalkリソース作成が開始されることを防止するためです。
なおCodeBuildに関するEventBridgeルールの作成においては、以下のページを参考としました。
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/sample-build-notifications.html
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- Elastic Beanstalkアプリケーション:fa-143-application
- Elastic Beanstalk環境:fa-143-env
- Elastic Beanstalkバージョン1:dva-01-003-elasticbeanstalkstack-gskerhhpiq7r-applicationversion1-0dzuyn8onsz7
- Elastic Beanstalkバージョン2:dva-01-003-elasticbeanstalkstack-gskerhhpiq7r-applicationversion2-wgik48ddoasm
- Elastic Beanstalkドメイン:http://dva-01-003.ap-northeast-1.elasticbeanstalk.com/
AWS Management Consoleから各リソースの作成状況を確認します。
Elastic Beanstalkアプリケーションを確認します。
正常にアプリケーションが作成されています。
アプリケーションバージョンを確認します。
2つのアプリケーションバージョンが作成されています。
既存の環境には、1つ目のバージョンが適用されていることがわかります。
またSourceの列を見ると、CodeBuildプロジェクトでビルドされたソースバンドルだということがわかります。
つまり同プロジェクトでビルドされ、S3バケットに配置されたソースバンドルを使用して、両バージョンは作成されているということです。
アプリケーション環境を確認します。
こちらも正常に作成されています。
Elastic Beanstalkによっていくつかのリソースが自動的に作成されています。
今回はその内のALBに紐づくAuto Scalingグループを確認します。
最大数/最小数/希望数がいずれも2のグループ内に、2つのEC2インスタンスが動作していることがわかります。
動作確認
デプロイ前
準備ができました。
まずはデプロイ前の挙動を確認します。
curlコマンドを使用して、Elastic Beanstalkドメインにアクセスします。
$ curl http://dva-01-003.ap-northeast-1.elasticbeanstalk.com/
instance-id: i-096c93704b418aafb
$ curl http://dva-01-003.ap-northeast-1.elasticbeanstalk.com/
instance-id: i-0d572f8438188e42e
Code language: Bash (bash)
確かにALB配下の2つのEC2インスタンスにアクセスできています。
デプロイ
いよいよ新しいバージョンをデプロイします。
先ほど確認したもう1つのバージョンを環境に適用します。
すぐにデプロイが開始されます。
以下がElastic Beanstalk環境に表示されるログです。
デプロイポリシーRolling with an additional batchでデプロイが始まりました。
この時点でのAuto Scalingグループを確認します。
最大数/最小数/希望数がいずれも3に変更されています。
そして既存の2台のEC2インスタンスに加えて、新しく1台インスタンスが起動中です。
以下はこの直後にElastic Beanstalkドメインにアクセスした結果です。
$ curl http://dva-01-003.ap-northeast-1.elasticbeanstalk.com/
instance-id: i-0d572f8438188e42e
$ curl http://dva-01-003.ap-northeast-1.elasticbeanstalk.com/
instance-id: i-096c93704b418aafb
$ curl http://dva-01-003.ap-northeast-1.elasticbeanstalk.com/
updated: instance-id: i-00db22aadbc8b7897
Code language: Bash (bash)
3台のEC2インスタンスから応答がありました。
2台は古いアプリケーションバージョンの内容です。
残りの1台は新しいアプリケーションバージョンの内容です。
このようにデプロイポリシーRolling with an additional batchを選択した場合、インスタンス数は維持されますが、新旧のアプリケーションが混在する形となります。
引き続きデプロイ状況を確認します。
$ curl http://dva-01-003.ap-northeast-1.elasticbeanstalk.com/
instance-id: i-0d572f8438188e42e
$ curl http://dva-01-003.ap-northeast-1.elasticbeanstalk.com/
updated: instance-id: i-00db22aadbc8b7897
$ curl http://dva-01-003.ap-northeast-1.elasticbeanstalk.com/
updated: instance-id: i-096c93704b418aafb
Code language: Bash (bash)
3台中、2台のインスタンスに対して、新しいアプリケーションバージョンが適用されました。
デプロイ完了後
しばらく待つと、デプロイが完了します。
ログには、デプロイの完了に伴い、一時的に追加していたインスタンスが削除された旨が記載されています。
改めてAuto Scalingグループを確認します。
最大数/最小数/希望数がいずれも2に戻り、グループ内には2台のインスタンスが動作しています。
最後にElastic Beanstalkドメインにアクセスします。
$ curl http://dva-01-003.ap-northeast-1.elasticbeanstalk.com/
updated: instance-id: i-096c93704b418aafb
$ curl http://dva-01-003.ap-northeast-1.elasticbeanstalk.com/
updated: instance-id: i-00db22aadbc8b7897
Code language: Bash (bash)
確かに2台のインスタンスに新しいアプリケーションバージョンが適用されています。
まとめ
Elastic BeanstalkのRolling with an additional batch (追加バッチによるローリング)の挙動を検証しました。