AWS Elastic Beanstalk と Application Load Balancer を連携する CloudFormation テンプレート
今回は AWS Elastic Beanstalk(以下 EB)と Application Load Balancer(以下 ALB)を連携して、Docker コンテナを利用したアプリケーションをデプロイするための CloudFormation テンプレートを紹介します。このテンプレートは、ALB を使用してトラフィックを管理し、必要に応じて Auto Scaling グループを利用することでスケーラビリティを確保します。
CloudFormation テンプレートの概要
以下にテンプレートの内容を示します。このテンプレートは、必要なパラメータとリソースを定義し、EB と ALB を設定します。
パラメータ
AWSTemplateFormatVersion: "2010-09-09"
Parameters:
TemplateBucket:
Type: String
Description: "The S3 bucket where the templates are stored"
ENV:
Type: String
Description: "The environment for the application."
AllowedValues:
- develop
- staging
- production
## https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/platforms/platforms-supported.html##platforms-supported.docker
SolutionStackName:
Type: String
Description: "The name of the solution stack used by Elastic Beanstalk, such as '64bit Amazon Linux 2023 v4.3.5 running Docker'."
## https://aws.amazon.com/jp/ec2/instance-types/
InstanceType:
Type: String
Description: "The EC2 instance type, e.g., 't3.micro'."
EBRoleArn:
Type: String
Description: "The ARN of the Elastic Beanstalk service role. This role requires the following managed policies: AmazonSSMManagedInstanceCore, AmazonS3FullAccess, AWSElasticBeanstalkEnhancedHealth, AWSElasticBeanstalkService."
EC2RoleArn:
Type: String
Description: "The ARN of the EC2 instance profile. This profile requires the following managed policies: AmazonSSMManagedInstanceCore, AWSElasticBeanstalkMulticontainerDocker, AWSElasticBeanstalkWebTier, AWSElasticBeanstalkWorkerTier, CloudWatchLogsFullAccess, AmazonS3ReadOnlyAccess, AmazonEC2ContainerRegistryReadOnly."
VPCId:
Type: String
Description: "The ID of the VPC in which the Elastic Beanstalk environment will be launched."
Subnets:
Type: String
Description: "A comma-separated list of subnet IDs within the specified VPC."
EC2KeyName:
Type: String
Description: "The name of the EC2 key pair to enable SSH access to the instances."
ElasticacheSecurityGroupId:
Type: String
Description: "The security group ID for the ElastiCache cluster used by the application."
SharedLoadBalancerArn:
Type: String
Description: "The ARN of the shared Application Load Balancer."
ALBSecurityGroupId:
Type: String
Description: "The security group ID associated with the Application Load Balancer."
HostHeaders:
Type: String
Description: "A comma-separated list of host headers for routing traffic to the application."
Resources:
Application:
Type: "AWS::ElasticBeanstalk::Application"
Properties:
ApplicationName: !Sub "app-server-${ENV}"
ApplicationVersion:
Type: "AWS::ElasticBeanstalk::ApplicationVersion"
Properties:
ApplicationName: !Ref Application
SourceBundle:
S3Bucket: !Ref TemplateBucket
S3Key: "Dockerrun.aws.json"
SecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupName: !Sub "${Application}-sg"
GroupDescription: "Security group for the Elastic Beanstalk environment"
VpcId: !Ref VPCId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: "0.0.0.0/0"
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: "0.0.0.0/0"
IdentityServerSecurityGroupInboundRule:
Type: "AWS::EC2::SecurityGroupIngress"
Properties:
GroupId: !GetAtt SecurityGroup.GroupId
IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref ALBSecurityGroupId
Description: "security group for ALB"
ElasticacheSecurityGroupInboundRule:
Type: "AWS::EC2::SecurityGroupIngress"
Properties:
GroupId: !Ref ElasticacheSecurityGroupId
IpProtocol: tcp
FromPort: 6379
ToPort: 6379
SourceSecurityGroupId: !GetAtt SecurityGroup.GroupId
Description: "security group for app-server"
Environment:
Type: "AWS::ElasticBeanstalk::Environment"
Properties:
EnvironmentName: !Sub "${Application}"
CNAMEPrefix: !Sub "${Application}-app-com"
ApplicationName: !Ref Application
VersionLabel:
!Ref ApplicationVersion
## https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/platforms/platforms-supported.html##platforms-supported.docker
SolutionStackName: !Ref SolutionStackName
## https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/command-options.html
OptionSettings:
## リクエストを直接受けるかLBを挟むかの設定
- Namespace: "aws:elasticbeanstalk:environment"
OptionName: "EnvironmentType"
Value: "LoadBalanced"
- Namespace: "aws:elasticbeanstalk:environment"
OptionName: "ServiceRole"
Value: !Ref EBRoleArn
- Namespace: "aws:elasticbeanstalk:environment"
OptionName: "LoadBalancerType"
Value: "application"
## 共有または専有どちらのLBに接続するのか設定
## https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/command-options-general.html##command-options-general-elasticbeanstalkenvironment
- Namespace: "aws:elasticbeanstalk:environment"
OptionName: "LoadBalancerIsShared"
Value: "true"
- Namespace: "aws:autoscaling:launchconfiguration"
OptionName: "InstanceType"
Value: !Ref InstanceType
- Namespace: "aws:autoscaling:launchconfiguration"
OptionName: "EC2KeyName"
Value: !Ref EC2KeyName
- Namespace: "aws:autoscaling:launchconfiguration"
OptionName: "IamInstanceProfile"
Value: !Ref EC2RoleArn
## セキュリティグループはデフォルトでEBのものが作成されます。
## 追加でACLを操作したい場合は、別途セキュリティグループを作成してアタッチする
- Namespace: "aws:autoscaling:launchconfiguration"
OptionName: "SecurityGroups"
Value: !Ref SecurityGroup
- Namespace: "aws:ec2:vpc"
OptionName: "VPCId"
Value: !Ref VPCId
- Namespace: "aws:ec2:vpc"
OptionName: "Subnets"
Value: !Ref Subnets
## Auto Scaling グループのインスタンスにパブリック IP アドレスを割り当てるためのオプション
## https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/command-options-general.html##command-options-general-ec2vpc
- Namespace: "aws:ec2:vpc"
OptionName: "AssociatePublicIpAddress"
Value: "true"
- Namespace: "aws:elbv2:loadbalancer"
OptionName: "SharedLoadBalancer"
Value: !Ref SharedLoadBalancerArn
- Namespace: "aws:autoscaling:asg"
OptionName: "MinSize"
Value: "1"
- Namespace: "aws:autoscaling:asg"
OptionName: "MaxSize"
Value: "4"
- Namespace: "aws:autoscaling:asg"
OptionName: "Cooldown"
Value: "300" ## 5 min
- Namespace: "aws:autoscaling:trigger"
OptionName: "MeasureName"
Value: "CPUUtilization"
- Namespace: "aws:autoscaling:trigger"
OptionName: "Statistic"
Value: "Average"
- Namespace: "aws:autoscaling:trigger"
OptionName: "Unit"
Value: "Percent"
- Namespace: "aws:autoscaling:trigger"
OptionName: "Period"
Value: "3" ## 3 min
- Namespace: "aws:autoscaling:trigger"
OptionName: "EvaluationPeriods"
Value: "3"
- Namespace: "aws:autoscaling:trigger"
OptionName: "UpperThreshold"
Value: "80" ## 80%
- Namespace: "aws:autoscaling:trigger"
OptionName: "UpperBreachScaleIncrement"
Value: "1"
- Namespace: "aws:autoscaling:trigger"
OptionName: "LowerThreshold"
Value: "20" ## 20%
- Namespace: "aws:autoscaling:trigger"
OptionName: "LowerBreachScaleIncrement"
Value: "-1"
- Namespace: "aws:autoscaling:updatepolicy:rollingupdate"
OptionName: "RollingUpdateEnabled"
Value: "true"
- Namespace: "aws:autoScaling:updatepolicy:rollingupdate"
OptionName: "MaxBatchSize"
Value: "1"
- Namespace: "aws:autoScaling:updatepolicy:rollingupdate"
OptionName: "MinInstancesInService"
Value: "1"
- Namespace: "aws:autoScaling:updatepolicy:rollingupdate"
OptionName: "RollingUpdateType"
Value: "Health"
- Namespace: "aws:autoScaling:updatepolicy:rollingupdate"
OptionName: "pauseTime"
Value: "PT3M"
## アプリケーションコードのデプロイポリシーを設定
- Namespace: "aws:elasticbeanstalk:command"
OptionName: "DeploymentPolicy"
Value: "Rolling"
- Namespace: "aws:elasticbeanstalk:command"
OptionName: "Timeout"
Value: "600" ## Unit: seconds
- Namespace: "aws:elasticbeanstalk:command"
OptionName: "BatchSizeType"
Value: "Fixed"
- Namespace: "aws:elasticbeanstalk:command"
OptionName: "BatchSize"
Value: "1"
- Namespace: "aws:elasticbeanstalk:command"
OptionName: "IgnoreHealthCheck"
Value: "false"
## リクエストの流れ client -> (https) -> Application Load balancer -> (http) -> EC2(application)
- Namespace: "aws:elasticbeanstalk:environment:process:default"
OptionName: "Port"
Value: "80"
- Namespace: "aws:elasticbeanstalk:environment:process:default"
OptionName: "Protocol"
Value: "HTTP"
- Namespace: "aws:elasticbeanstalk:environment:process:default"
OptionName: "HealthCheckPath"
Value: "/auth/health"
- Namespace: "aws:elasticbeanstalk:environment:process:default"
OptionName: "HealthCheckInterval"
Value: "30" ## ヘルスチェックの実行間隔(秒)
- Namespace: "aws:elasticbeanstalk:environment:process:default"
OptionName: "HealthCheckTimeout"
Value: "5" ## ヘルスチェックのタイムアウト(秒)
- Namespace: "aws:elasticbeanstalk:environment:process:default"
OptionName: "HealthyThresholdCount"
Value: "5" ## 正常とみなすまでの連続成功回数
- Namespace: "aws:elasticbeanstalk:environment:process:default"
OptionName: "UnhealthyThresholdCount"
Value: "2" ## 異常とみなすまでの連続失敗回数
## アプリケーションのインスタンスログストリーミングを設定
- Namespace: "aws:elasticbeanstalk:cloudwatch:logs"
OptionName: "StreamLogs"
Value: "true"
- Namespace: "aws:elasticbeanstalk:cloudwatch:logs"
OptionName: "DeleteOnTerminate"
Value: "true"
- Namespace: "aws:elasticbeanstalk:cloudwatch:logs"
OptionName: "RetentionInDays"
Value: "5"
## リスナールールの設定
## 下記を参考にした
## https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/environments-cfg-alb-shared.html##environments-cfg-alb-shared-ebcli
## リスナールールはルートとPathPatternsの2つのルールが作成される
- Namespace: "aws:elbv2:listener:443"
OptionName: "Rules"
Value: "default,apprule" ## defaultは必須で設定しないとエラーになる。
- Namespace: "aws:elbv2:listenerrule:apprule"
OptionName: "PathPatterns"
Value: "/auth/*"
## https://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/environments-cfg-alb-shared.html##environments-cfg-alb-shared-intro
## > Elastic Beanstalk は、ロードバランサーを共有する環境間でルールの優先順位設定を相対的なものとして扱い、作成時に絶対的な優先順位にマッピングします。
## と書いてある通り、設定通りの優先順位になるわけではない。
- Namespace: "aws:elbv2:listenerrule:apprule"
OptionName: "Priority"
Value: "32"
- Namespace: "aws:elbv2:listenerrule:apprule"
OptionName: "HostHeaders"
Value: !Ref HostHeaders
- Namespace: "aws:elbv2:listenerrule:apprule"
OptionName: "Process"
Value: "default"
## アプリケーションの環境変数を設定
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KC_DB_URL_HOST"
Value: !Ref KcDbURLHost
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KC_DB_URL_DATABASE"
Value: "keycloak"
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KC_DB_USERNAME"
Value: !Ref KcDbUsername
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KC_DB_PASSWORD"
Value: !Ref KcDbPassword
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KC_HOSTNAME_URL"
Value: !Sub "${LoadBalancerURL}/auth"
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KC_HOSTNAME_ADMIN_URL"
Value: !Sub "${LoadBalancerURL}/auth"
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KC_HOSTNAME_STRICT"
Value: "true"
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KC_HOSTNAME_STRICT_BACKCHANNEL"
Value: "true"
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KC_PROXY"
Value: "edge"
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KC_PROXY_HEADERS"
Value: "xforwarded"
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KC_HTTP_ENABLED"
Value: "true"
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KEYCLOAK_ADMIN"
Value: !Ref KeycloakAdmin
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KEYCLOAK_ADMIN_PASSWORD"
Value: !Ref KeycloakAdminPassword
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "TZ"
Value: "Asia/Tokyo"
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "JGROUPS_DISCOVERY_PROTOCOL"
Value: "JDBC_PING"
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KC_CACHE"
Value: "ispn"
- Namespace: "aws:elasticbeanstalk:application:environment"
OptionName: "KC_LOG_LEVEL"
Value: "info"
Outputs:
ApplicationName:
Value: !Ref Application
EnvironmentName:
Value: !Ref Environment