- 1. 解説動画
- 2. 概要
- Fargate
- ECS Cluster (クラスター)
- ECS Instance (インスタンス)
- ECS Task (タスク)
- ECS Task Definition (タスク定義)
- ECS Service (サービス)
- イメージ図(FargateとECSタスク)
- ECR (Elastic Container Registry)
- 構成図
- リソース一覧
- 命名規則
- 公式URL
- 3. 解説手順
- 4. CFnテンプレート
- ECS Cluster
- ECS Service
- 5. 予習
- 6. FAQ(随時追加)
Fargate起動タイプのECS(ECS Cluster, Task Definition, Service)を動かし、EC2起動タイプとの違いについて学んでいきましょう!
1. 解説動画
※動画の画質が悪い場合は、歯車から720p以上の画質を選択ください
※以下の動画でスタック名やファイル名を[SystemName]-prod-viacdn-fargate-
と説明しておりましたが、名前の長さで不具合がありましたので[SystemName]-prod-fargate-
に変更させて頂きました。viacdn
なしでよろしくお願いいたします。
2. 概要
Fargate
前回との違いは、ECSの起動タイプが「EC2」か「FARGATE」かの違いです。後者を特にAWS Fargateと呼んでいますが、実態はどちらもECSです。
EC2のメンテナンスが不要になりますが、リージョンによってはFargateに対応していなかったり、2020年4月まではEFSがマウントできなかったり、2021年3月まではdocker container exec
でコンテナの中にログインできなかったり、EC2起動タイプに比べて劣る点があるので使う際は注意してください。Amazon ECS Execというサービスが登場してコンテナの中にログインできるようになるなど、次第に改善されています。あとはEC2起動タイプより高いという印象があります。
ECSは5つの言葉が重要でしたが、Fargateの場合は2.ECS Instanceが不要になりますので、残る4つを使います。
- ECS Cluster (クラスター)
- ECS Instance (インスタンス) ※不要
- ECS Task (タスク)
- ECS Task Definition (タスク定義)
- ECS Service (サービス)
ECS Cluster (クラスター)
Dockerコンテナを動かす基盤。①複数のEC2インスタンスを使う ②Fargateを使う(EC2インスタンスを使わない)の2パターンあり、今回は後者で構築します。それぞれメリット・デメリットがあり、
- ①EC2のメリット:EC2を通してDockerコンテナの中に入れる(デバッグしやすい)、Fargateより安い、Fargateより起動が若干早い
- ②Fargateのメリット:EC2の管理が不要になる(バージョンアップやインスタンスのローテーションなど)
ECS Instance (インスタンス)
ECSを動かす基盤のEC2インスタンスのことで、Fargateでは不要です。
ECS Task (タスク)
Dockerコンテナを起動させる単位・ひとまとまり。前回EC2起動タイプとの差異は次の通りです。
- タスクごとにIPアドレスが振られる(前回はEC2のIPアドレスを使っていた)
上記はネットワークモードがawsvpc
になった(前回はbridge
だった)ための差異です。
ECS Task Definition (タスク定義)
その名の通りECSタスクの定義。前回EC2起動タイプとの差異は次の通りです。
FARGATE
用の定義(前回はEC2
)- ネットワークモードが
awsvpc
(前回はbridge
) - CPU,Memoryを指定(前回は指定せず)※Fargateでは必須
- Nginx(web)コンテナのポートマッピングで動的ホストポートを削除(前回は動的ホストポート)
- Nginx(web)コンテナの
NGINX_BACKEND
環境変数の値が127.0.0.1
(前回はapp
) - Nginx(web)コンテナのリンクを削除(前回は
app
)
上記4,5,6はネットワークモードがawsvpc
になった(前回はbridge
だった)ための差異です。
ECS Service (サービス)
ECSタスク定義を元にタスクを常時起動させる仕組み。前回EC2起動タイプとの差異は次の通りです。
- 起動タイプが
FARGATE
(前回はEC2
) - プラットフォームバージョンが
LATEST
(前回は設定そのものがない) - プレースメント戦略を削除(前回はAvailabilityZone間、インスタンス間でばらけさせた)
- ネットワーク設定(サブネット、セキュリティグループ、パブリックIP自動割り当て)を追加(前回は設定そのものがない)
上記4はネットワークモードがawsvpc
になった(前回はbridge
だった)ための差異です。
イメージ図(FargateとECSタスク)
絵にするとこんな感じです。今回は右側のようにFargateの上でECSタスクを起動させます。左側はEC2起動タイプの場合です。
https://aws.amazon.com/jp/blogs/compute/aws-fargate-a-product-overview/ より抜粋
ECR (Elastic Container Registry)
docker image build
したDockerイメージを docker image push
して格納しておける場所。今回は前回と同じDockerイメージを使いますので、特に手は加えません。Nginxのイメージを工夫してどちらでも使えるように作ったためです。
構成図
- ECS Clusterを作ります
- Fargate用のECS Task DefinitionとECS Serviceによって、Fargate(画面上では見えません)の上でECS Taskが起動します
- ECS TaskはALBに新たに作成したTarget Groupに追加され、それはListener Ruleに追加されます
今回作成するECS Clusterの構成図はこちらです。ECS Task Definition, ECS Service, ALB Listener Rule, Target Groupは省略しています。 ※小さくて見づらいので、右クリックして新しいタブで開いてください。
リソース一覧
Name | 目的 | 備考 |
---|---|---|
ECS: Cluster | ECSタスクを動かすための基盤である「クラスター」 | ECSクラスターはただ単に名前しか設定しない。ECSサービス側でECSクラスターを名指しして使う |
EC2: SecurityGroup, SecurityGroupIngress | ECSタスクに対するInbound(Ingress)=内向きOutbound(Outgress)=外向きの通信を許可する「セキュリティグループ」 | 内向きは許可するものだけルールを追加していく。今回は①ELBからECSタスクへHTTPポート(TCP 80番)をオープンにする(前回はECSタスクが |
IAM: Role | ECSタスクへ権限付与するための「ロール」 | Inline Policyで(サンプルとして)静的コンテンツ用S3バケットの |
ElasticLoadBalancingV2: TargetGroup | ALBの振り分け先(=ターゲット)をまとめるための「ターゲットグループ」 | 今回のターゲットは、HTTP(TCP 80番ポート)が開いているECSタスクのNginx(web)コンテナ。ターゲットグループの設定の中でもヘルスチェック、スティッキーセッションの設定が特に重要。今までは全て |
ElasticLoadBalancingV2: ListenerRule | リスナーの挙動(条件とアクションのセット)を定義できる「リスナールール」 | CloudFront経由で |
Logs: LogGroup | CloudWatchにログを貯めるための「ロググループ」 | ECS側にロググループ作成の権限を付与しないため、事前にCFnで作成しておく。ログ自体はECSタスクが出力していく。いわゆる |
SSM: Parameter | パラメータの値を安全に保管できる「パラメータストア」 | 本来はAPIキーやSlack WebHook URLなどの秘密情報を保管すると良い。秘密でなくても使っても良く、今回はちょうど良い例がなかったのでRailsのEnvをサンプルに作った |
ECS: TaskDefinition | ECSタスクの設計「タスク定義」 | docker-compose.ymlのFargate版。このタスク定義を元にタスクが作られる。前回ECS(EC2)との違いは次の通り。FARGATE用の定義。ネットワークモードが |
ECS: Service | ECSタスクを常時起動させるための「サービス」 | ECSクラスターとタスク定義を指定して、希望タスク数を起動する。Nginx(web)コンテナをALBターゲットグループに追加する。前回ECS(EC2)との違いは次の通り。キャパシティプロバイダー戦略を使って安い |
命名規則
Classmethodさんの命名規則 https://dev.classmethod.jp/articles/aws-name-rule/ に準拠します。
公式URL
Fargate
- Docs: AWS Fargate とは - Amazon ECS
- CFn: ECS リソースタイプのリファレンス - AWS CloudFormation
- CLI: ecs — AWS CLI v2 Command Reference
3. 解説手順
※今回はRDSを使いますので事前に起動してください
- CFnでECS Cluster, ECS Serviceを作成する
- コマンドで動作確認を実施する
※Windowsの方はCloudShellか、EC2にSessionManagerで繋いで実行してください
curl -v https://cdn.[ご自身のドメイン名]/users
※特定パスにリクエスト ※応答内容はどこかにコピペして保存しておいてください 👉 宿題に出ます - ブラウザで動作確認を実施する
http://cdn.[ご自身のドメイン名]/
にアクセスするとどうなるか? - ECS Exec(正式名 Execute Command)でコンテナの中に入る 👉 うまくいかない場合はFAQを参照ください ※以下、Mac・Linuxの場合
- Fargateの学習が終わったら、ECS ServiceのDesiredCountを
0
にして、RDSを停止しておく。動作確認する場合は再度起動が必要になるのでご注意ください!
SystemName=awsmaster ### [Change System Name]
Environment=prod
ResourceName=fargate
TaskID=120d5b99c06d4aa7a64838027d86acde ### [Change Task ID]
ContainerName=app ### [Change Container Name] app or web
aws ecs execute-command \
--cluster ${SystemName}-${Environment}-${ResourceName}-ecs-cluster \
--task ${TaskID} \
--container ${ContainerName} \
--interactive \
--command "bash"
curl -IXGET localhost:3000
※以下、Windows10の場合はWindows PowerShellで実行してください。
↓ "
は消さないでください!!
$SystemName="awsmaster" ### [Change System Name]
$Environment="prod"
$ResourceName="fargate"
$TaskID="120d5b99c06d4aa7a64838027d86acde" ### [Change Task ID]
$ContainerName="app" ### [Change Container Name] app or web
aws ecs execute-command `
--cluster ${SystemName}-${Environment}-${ResourceName}-ecs-cluster `
--task ${TaskID} `
--container ${ContainerName} `
--interactive `
--command "bash"
curl -IXGET localhost:3000
4. CFnテンプレート
ECS Cluster
- [SystemName]=
awsmaster
をお好きなシステム名に置換してください。変えなくてもOKです。
※1.解説動画でスタック名やファイル名を[SystemName]-prod-viacdn-fargate-
と説明しておりましたが、名前の長さで不具合がありましたので、[SystemName]-prod-fargate-
に変更させて頂きました。viacdn
なしでよろしくお願いいたします。
Stack Name: [SystemName]-prod-fargate-ecs-cluster File Name: [SystemName]/fargate/ecs-cluster.yml
---
### [Change System Name] awsmaster
AWSTemplateFormatVersion: "2010-09-09"
Description: Create ECS Cluster
Parameters:
SystemName:
Description: System Name
Type: String
Default: awsmaster ### [Change System Name]
Environment:
Description: Environment
Type: String
Default: prod
AllowedValues:
- prod
- stg
- dev
ResourceName:
Description: Resource Name
Type: String
Default: fargate
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Environment Configuration"
Parameters:
- SystemName
- Environment
- ResourceName
Resources:
## ECS: Cluster
Cluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-cluster
CapacityProviders:
- FARGATE
- FARGATE_SPOT
DefaultCapacityProviderStrategy:
- CapacityProvider: FARGATE
Base: 0
Weight: 0
- CapacityProvider: FARGATE_SPOT
Base: 0
Weight: 1
Outputs:
## ECS: Cluster
Cluster:
Value: !Ref Cluster
Export:
Name: !Sub ${AWS::StackName}-Cluster
ClusterArn:
Value: !GetAtt Cluster.Arn
Export:
Name: !Sub ${AWS::StackName}-ClusterArn
【変更履歴】
- 2021-08-28:CapacityProvidersの追加(動作環境をFargateからFargate Spotに変更)
- 2023-04-14:コメント文を全般的に修正
ECS Service
- [SystemName]=
awsmaster
をお好きなシステム名に置換してください。変えなくてもOKです。 - [UseSubnetProtected]=
true
ですが、もし④VPCでNAT Gatewayを削除していたら、必ずfalse
に変更してください。
Stack Name: [SystemName]-prod-fargate-ecs-service File Name: [SystemName]/fargate/ecs-service.yml
---
### [Change System Name] awsmaster
## The following CFn stack must be created first in order to be referenced by the ImportValue function.
## 1. iam-role
## 2. ${SystemName}-${Environment}-route53
## 3. ${SystemName}-${Environment}-vpc
## 4. ${SystemName}-${Environment}-rds
## 5. ${SystemName}-${Environment}-elb
## 6. ${SystemName}-${Environment}-cloudfront
## 7. ${SystemName}-${Environment}-ecr
## 8. ${SystemName}-${Environment}-${ResourceName}-ecs-cluster
AWSTemplateFormatVersion: "2010-09-09"
Description: Create EC2 Security Group, IAM Policy, IAM Role, SSM Parameter, ELB Target Group, ELB Listener Rule, ECS Task Definition and ECS Service (Fargate) etc.
Mappings:
EnvironmentMap:
prod:
ServiceDesiredCount: 2
stg:
ServiceDesiredCount: 2
dev:
ServiceDesiredCount: 1
Parameters:
SystemName:
Description: System Name
Type: String
Default: awsmaster ### [Change System Name]
Environment:
Description: Environment
Type: String
Default: prod
AllowedValues:
- prod
- stg
- dev
ResourceName:
Description: Resource Name
Type: String
Default: fargate
ELBListenerRulePriority:
Description: ELB Listner Rule Priority
Type: Number
Default: 111
MinValue: 1
MaxValue: 50000
UseSubnetProtected:
Description: Use Protected Subnet
Type: String
Default: true
AllowedValues:
- true
- false
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Environment Configuration
Parameters:
- SystemName
- Environment
- ResourceName
- ELBListenerRulePriority
- UseSubnetProtected
Conditions:
# isProd: !Equals [ !Ref Environment, prod ]
ShouldUseSubnetProtected: !Equals [ !Ref UseSubnetProtected, true ]
Resources:
## EC2(VPC): Security Group
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-task-sg
GroupDescription: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-task-sg
VpcId:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-vpc-VPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-task-sg
## EC2(VPC): Security Group Inbound Rule From ELB (HTTP)
EC2SecurityGroupIngressHttpFromLoadBalancer:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref EC2SecurityGroup
IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-elb-EC2SecurityGroup
Description: !Sub ${SystemName}-${Environment}-alb-sg
## EC2(VPC): Security Group Inbound Rule To RDS (MySQL)
EC2SecurityGroupIngressMySQLToRDS:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-rds-EC2SecurityGroup
IpProtocol: tcp
FromPort:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-rds-DBClusterEndpointPort
ToPort:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-rds-DBClusterEndpointPort
SourceSecurityGroupId: !Ref EC2SecurityGroup
Description: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-task-sg
## IAM: Role
IAMRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-task-role
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
## To use ECS Exec
- !ImportValue iam-role-AmazonECSExecuteCommandPolicy
Policies:
- PolicyName: AmazonS3StaticContentsManipulateObjectPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action: s3:ListBucket
Resource:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-cloudfront-S3BucketStaticContentsArn
- Effect: Allow
Action: "s3:*Object"
Resource: !Sub
- ${S3BucketArn}/*
- S3BucketArn:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-cloudfront-S3BucketStaticContentsArn
## ELB: Target Group
ELBTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${SystemName}-${Environment}-${ResourceName}-tg
TargetType: ip
Protocol: HTTP
Port: 80
VpcId:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-vpc-VPC
HealthCheckEnabled: true
HealthCheckProtocol: HTTP
HealthCheckPath: /users/
HealthCheckPort: traffic-port
HealthyThresholdCount: 5
UnhealthyThresholdCount: 2
HealthCheckTimeoutSeconds: 5
HealthCheckIntervalSeconds: 30
Matcher:
HttpCode: "200,301"
# TargetGroupAttributes:
# - Key: stickiness.enabled
# Value: true
# - Key: stickiness.lb_cookie.duration_seconds
# Value: 3600
Tags:
- Key: Name
Value: !Sub ${SystemName}-${Environment}-${ResourceName}-tg
## ELB: Listener Rule
ELBListenerRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
ListenerArn:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-elb-ListenerHttps
Priority: !Ref ELBListenerRulePriority
Conditions:
- Field: http-header
HttpHeaderConfig:
HttpHeaderName: x-via-cloudfront
Values:
- !Sub
- "{{resolve:secretsmanager:${SecretForCloudFront}:SecretString:x-via-cloudfront}}"
- SecretForCloudFront:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-cloudfront-SecretForCloudFront
- Field: path-pattern
PathPatternConfig:
Values:
- /*
Actions:
- Type: forward
ForwardConfig:
TargetGroups:
- TargetGroupArn: !Ref ELBTargetGroup
## Logs: Log Group
LogsLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /ecs/${SystemName}-${Environment}-${ResourceName}-ecs-task
## SSM: Parameter
SSMParameterAppRailsEnv:
Type: AWS::SSM::Parameter
Properties:
## For some reason, I couldn't put awsmaster at the beginning. It seems to be reserved.
Name: !Sub /${Environment}/${SystemName}/${ResourceName}/ecs/environment/app/rails-env
Type: String
Value: development
## ECS: Task Definition
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-task
RequiresCompatibilities:
- FARGATE
RuntimePlatform:
OperatingSystemFamily: LINUX
CpuArchitecture: X86_64
NetworkMode: awsvpc
Cpu: 256
Memory: 512
TaskRoleArn: !GetAtt IAMRole.Arn
ExecutionRoleArn: !ImportValue iam-role-AmazonECSTaskExecutionRoleArn
ContainerDefinitions:
- Name: app
Image: !Sub
- ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepository}:latest
- ECRRepository:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-ecr-ECRRepositoryApp
Essential: true
Cpu: 0
MemoryReservation: 80
Secrets:
- Name: MYSQL_HOST
ValueFrom: !Sub
- "${SecretForRDSAwsmaster}:host::"
- SecretForRDSAwsmaster:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-rds-SecretForRDSAwsmaster
- Name: MYSQL_DATABASE
ValueFrom: !Sub
- "${SecretForRDSAwsmaster}:database::"
- SecretForRDSAwsmaster:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-rds-SecretForRDSAwsmaster
- Name: MYSQL_USER
ValueFrom: !Sub
- "${SecretForRDSAwsmaster}:username::"
- SecretForRDSAwsmaster:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-rds-SecretForRDSAwsmaster
- Name: MYSQL_PASSWORD
ValueFrom: !Sub
- "${SecretForRDSAwsmaster}:password::"
- SecretForRDSAwsmaster:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-rds-SecretForRDSAwsmaster
- Name: RAILS_ENV
ValueFrom: !Ref SSMParameterAppRailsEnv
Environment:
- Name: RAILS_CONFIG_HOSTS
Value: !Sub
- ".${DomainName}" ## Include subdomains.
- DomainName:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-route53-HostedZoneDomainName
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref LogsLogGroup
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: ecs
mode: non-blocking
max-buffer-size: 25m
HealthCheck:
Command:
- CMD-SHELL
- "curl -f http://127.0.0.1:3000/users/ || exit 1"
StartPeriod: 180
LinuxParameters:
InitProcessEnabled: true
- Name: web
Image: !Sub
- ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRRepository}:latest
- ECRRepository:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-ecr-ECRRepositoryWeb
Essential: true
PortMappings:
- ContainerPort: 80
Protocol: tcp
Cpu: 0
MemoryReservation: 16
Environment:
- Name: NGINX_BACKEND
Value: 127.0.0.1
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref LogsLogGroup
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: ecs
mode: non-blocking
max-buffer-size: 25m
DependsOn:
- ContainerName: app
Condition: HEALTHY
Command:
- /bin/bash
- -c
- "envsubst '$$NGINX_BACKEND' < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf && exec nginx -g 'daemon off;'"
LinuxParameters:
InitProcessEnabled: true
## ECS: Service
Service:
Type: AWS::ECS::Service
## The target group that has not been associated with the load balancer cannot be used with the ECS Service.
## Therefore, you need to create a listener rule first to associate the load balancer with the target group.
DependsOn: ELBListenerRule
Properties:
ServiceName: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-service
Cluster:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-cluster-Cluster
CapacityProviderStrategy:
- CapacityProvider: FARGATE
Base: 0
Weight: 0
- CapacityProvider: FARGATE_SPOT
Base: 0
Weight: 1
PlatformVersion: LATEST
TaskDefinition: !Ref TaskDefinition
SchedulingStrategy: REPLICA
DesiredCount: !FindInMap [ EnvironmentMap, !Ref Environment, ServiceDesiredCount ]
AvailabilityZoneRebalancing: ENABLED
DeploymentConfiguration:
MinimumHealthyPercent: 100
MaximumPercent: 200
DeploymentCircuitBreaker:
Enable: true
Rollback: true
NetworkConfiguration:
AwsvpcConfiguration:
Subnets:
Fn::If:
- ShouldUseSubnetProtected
- - Fn::ImportValue: !Sub ${SystemName}-${Environment}-vpc-SubnetProtectedA
- Fn::ImportValue: !Sub ${SystemName}-${Environment}-vpc-SubnetProtectedC
- Fn::ImportValue: !Sub ${SystemName}-${Environment}-vpc-SubnetProtectedD
- - Fn::ImportValue: !Sub ${SystemName}-${Environment}-vpc-SubnetPublicA
- Fn::ImportValue: !Sub ${SystemName}-${Environment}-vpc-SubnetPublicC
- Fn::ImportValue: !Sub ${SystemName}-${Environment}-vpc-SubnetPublicD
SecurityGroups:
- !Ref EC2SecurityGroup
AssignPublicIp: !If [ ShouldUseSubnetProtected, DISABLED, ENABLED ]
LoadBalancers:
- ContainerName: web
ContainerPort: 80
TargetGroupArn: !Ref ELBTargetGroup
HealthCheckGracePeriodSeconds: 300
EnableECSManagedTags: true
EnableExecuteCommand: true
## Comment out because it cannot be edited on the management console.
# ## Application Auto Scaling: Scalable Target
# ApplicationAutoScalingScalableTarget:
# Type: AWS::ApplicationAutoScaling::ScalableTarget
# Properties:
# ServiceNamespace: ecs
# ScalableDimension: ecs:service:DesiredCount
# ResourceId: !Sub
# - service/${ClusterName}/${Service.Name}
# - ClusterName:
# Fn::ImportValue:
# !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-cluster-Cluster
# MinCapacity: !FindInMap [ EnvironmentMap, !Ref Environment, ServiceDesiredCount ]
# MaxCapacity: !FindInMap [ EnvironmentMap, !Ref Environment, ServiceDesiredCount ]
# RoleARN: !Sub arn:aws:iam::${AWS::AccountId}:role/aws-service-role/ecs.application-autoscaling.amazonaws.com/AWSServiceRoleForApplicationAutoScaling_ECSService ## IAM role created automatically.
# ScheduledActions:
# Fn::If:
# - isProd
# - - ScheduledActionName: !Sub ${SystemName}-${Environment}-${ResourceName}-scheduled-action-scale-out
# ScalableTargetAction:
# MinCapacity: !FindInMap [ EnvironmentMap, !Ref Environment, ServiceDesiredCount ]
# MaxCapacity: !FindInMap [ EnvironmentMap, !Ref Environment, ServiceDesiredCount ]
# Schedule: cron(0 22 ? * SUN-THU) ## Monday-Friday 07:00(JST) @ Prod
# - ScheduledActionName: !Sub ${SystemName}-${Environment}-${ResourceName}-scheduled-action-scale-in
# ScalableTargetAction:
# MinCapacity: 1
# MaxCapacity: 1
# Schedule: cron(0 14 ? * MON-FRI) ## Monday-Friday 23:00(JST) @ Prod
# - - ScheduledActionName: !Sub ${SystemName}-${Environment}-${ResourceName}-scheduled-action-start
# ScalableTargetAction:
# MinCapacity: !FindInMap [ EnvironmentMap, !Ref Environment, ServiceDesiredCount ]
# MaxCapacity: !FindInMap [ EnvironmentMap, !Ref Environment, ServiceDesiredCount ]
# Schedule: cron(50 22 ? * SUN-THU) ## Monday-Friday 07:50(JST) @ Not Prod
# - ScheduledActionName: !Sub ${SystemName}-${Environment}-${ResourceName}-scheduled-action-stop
# ScalableTargetAction:
# MinCapacity: 0
# MaxCapacity: 0
# Schedule: cron(0 13 ? * MON-FRI) ## Monday-Friday 22:00(JST) @ Not Prod
Outputs:
## EC2(VPC): Security Group
EC2SecurityGroup:
Value: !Ref EC2SecurityGroup
Export:
Name: !Sub ${AWS::StackName}-EC2SecurityGroup
EC2SecurityGroupVpcId:
Value: !GetAtt EC2SecurityGroup.VpcId
Export:
Name: !Sub ${AWS::StackName}-EC2SecurityGroupVpcId
## IAM: Role
IAMRole:
Value: !Ref IAMRole
Export:
Name: !Sub ${AWS::StackName}-IAMRole
IAMRoleArn:
Value: !GetAtt IAMRole.Arn
Export:
Name: !Sub ${AWS::StackName}-IAMRoleArn
## ELB: Target Group
ELBTargetGroup:
Value: !Ref ELBTargetGroup
Export:
Name: !Sub ${AWS::StackName}-ELBTargetGroup
ELBTargetGroupLoadBalancerArns1:
Value: !Select [ 0, !GetAtt ELBTargetGroup.LoadBalancerArns ]
Export:
Name: !Sub ${AWS::StackName}-ELBTargetGroupLoadBalancerArns1
ELBTargetGroupFullName:
Value: !GetAtt ELBTargetGroup.TargetGroupFullName
Export:
Name: !Sub ${AWS::StackName}-ELBTargetGroupFullName
ELBTargetGroupName:
Value: !GetAtt ELBTargetGroup.TargetGroupName
Export:
Name: !Sub ${AWS::StackName}-ELBTargetGroupName
## ELB: Listener Rule
ELBListenerRule:
Value: !Ref ELBListenerRule
Export:
Name: !Sub ${AWS::StackName}-ELBListenerRule
## Logs: Log Group
LogsLogGroup:
Value: !Ref LogsLogGroup
Export:
Name: !Sub ${AWS::StackName}-LogsLogGroup
LogsLogGroupArn:
Value: !GetAtt LogsLogGroup.Arn
Export:
Name: !Sub ${AWS::StackName}-LogsLogGroupArn
## SSM: Parameter
SSMParameterAppRailsEnv:
Value: !Ref SSMParameterAppRailsEnv
Export:
Name: !Sub ${AWS::StackName}-SSMParameterAppRailsEnv
## ECS: Task Definition
TaskDefinition:
Value: !Ref TaskDefinition
Export:
Name: !Sub ${AWS::StackName}-TaskDefinition
## ECS: Service
Service:
Value: !Ref Service
Export:
Name: !Sub ${AWS::StackName}-Service
ServiceName:
Value: !GetAtt Service.Name
Export:
Name: !Sub ${AWS::StackName}-ServiceName
## Comment out because it cannot be edited on the management console.
# ## Application Auto Scaling: Scalable Target
# ApplicationAutoScalingScalableTarget:
# Value:
# Ref: ApplicationAutoScalingScalableTarget
# Export:
# Name: !Sub ${AWS::StackName}-ApplicationAutoScalingScalableTarget
【変更履歴】
- 2021-08-28:CapacityProvidersの追加(動作環境をFargateからFargate Spotに変更)
- 2022-02-15:
AmazonECSExecuteCommandPolicy
インラインポリシーを管理ポリシーに変更 - 2022-07-20:ApplicationAutoScaling::ScalableTargetの追加(ただし画面上で変更できないためコメントアウト)
- 2022-12-01:ヘルスチェックのパスを変更
- 2023-04-06:冒頭でImportValueについてのコメント補足追加
- 2023-04-14:コメント文を全般的に修正
- 2024-01-26:ECS Serviceの
DependsOn
にコメントを追加 - 2024-09-19:
- ECS Task Definitionに
RuntimePlatform
を追加 - ECS Task Definition(appとwebコンテナ)の
LogConfiguration
にmode
とmax-buffer-size
を追加 - ECS Serviceに
DeploymentCircuitBreaker
を追加 - 2024-12-18:ECS Serviceに
AvailabilityZoneRebalancing
を追加
5. 予習
- 解説動画の通り、設定・作成してください ※CFnでエラーになった場合は、EventsタブのスクリーンショットをSlackでください (AWSの画面が変わっている場合は同じ設定項目を探してください)
- CFnで作成したFargateを手でも作ってください。作成中に分からなかった設定項目については調べたり、質問のためにメモしておいてください (手順が分かりづらいので、分からない場合はSlackかZoomでお答えします)
- 【宿題】下記の項目について調べて、次回のZoomで説明してください (公式の情報が優先で、分からない場合はブログ等を参照ください)
- Fargateの設定と挙動を理解するために、解説手順の動作確認でアクセスした1回のリクエストについて、応答結果を上記の表にまとめてください。①HTTPリクエストはAWSの中でどう通って、②最終的に誰(=Server)が、③どんなHTTP応答(=HTTPステータスコード)を返したか? ④それらの設定箇所はAWSコンソール画面でどこにあるか? を調べてください
- (⑪ECSと比べて)Target Groupのヘルスチェックのパスを変更することで、RDSまでチェックするようにしました。ECSタスクを起動したままRDSを停止することで、ヘルスチェックがどのような働きをするのか、確認してください。具体的には『ECSタスク(appコンテナとwebコンテナ)がどうなるのか? ブラウザで1.と同じURLにアクセスしたら、どのような表示になるのか? HTTPレスポンスコードはどうなるのか? 時間が経てば、ブラウザの表示やHTTPレスポンスコードは変わるのか?』についてお答えください。 ※余裕がある方は、Target Groupのヘルスチェックのパスを⑪ECSの時の設定に戻して『』内の動作確認をしてみてください
- EC2起動タイプとFargate起動タイプの違いは何か? EC2起動タイプと比較して、Fargate起動タイプのメリットとデメリットは何か?
- FargateとFargate Spotの料金体系。Fargate Spot以外にFargateを安くする方法はあるか? どれくらい安くなるか?
- 例えば0.25vCPU・0.5GBのECSタスクを2個起動した時、次の場合ではそれぞれ1ヶ月あたりいくらか?(1ヶ月=30日×24時間の連続稼働とする)
- EC2 オンデマンドインスタンス t2.micro 1台 ※厳密には0.5GBのECSタスクは2個起動できない点は一旦無視してください
- Fargate
- EC2 スポットインスタンス t2.micro 1台
- Fargate Spot
- 前回ECSと今回Fargateの違いとして、ECSタスクで使えるCPUとメモリの違いを理解しましょう。※明記していない前提条件は、⑪ECSと⑫FargateのCFnテンプレの設定値を参照ください
- まず前回ECSから。ECSで使うEC2を1台だけ起動させ、ECSタスク希望数を0個としてください。EC2インスタンス上でECSタスクに使えるCPUとメモリ(Availableの値)を確認して表を埋めてください。確認手順は⑪ECSの宿題に出ました(EC2はt2.micro・t3a.microどちらでもかまいません)
- 次にECSタスク希望数を1個として、同様にAvailableの値を確認して表を埋めてください
- 最後にECSタスク希望数を2個として、同様にAvailableの値を確認して表を埋めてください ※確認が終わりましたらEC2希望数、ECSタスク希望数を両方0にして停止してOKです
- では本題に移ります。上記でECSタスクが1個だけ起動していた場合、そのECSタスクが使えるCPUとメモリの上限はいくつでしょうか?
- 次に上記でECSタスクが2個起動していた場合、そのうち1個のECSタスクが使えるCPUとメモリの上限はいくつでしょうか?
- 最後に今回Fargateについて。FargateでECSタスクを1個だけ起動していた場合、そのECSタスクが使えるCPUとメモリの上限はいくつでしょうか?
- 次にFargateでECSタスクを2個起動していた場合、そのうち1個のECSタスクが使えるCPUとメモリの上限はいくつでしょうか?
- ECSタスク希望数を2個にしていると、タスクのデプロイ(切り替え)時には新旧合わせて2倍の4個のECSタスクが起動することになります。その際、⑪ECSと⑫Fargateの料金はそれぞれ何倍に増えるでしょうか? ※明記していない前提条件は、⑪ECSと⑫FargateのCFnテンプレの設定値を参照ください
- 前回ECSと今回Fargateの違い、正確に言えばネットワークモード
bridge
とawsvpc
の違いを表にまとめてください - 【応用課題】余裕のある方は実際に挑戦して頂くと勉強になると思います! ※時間の都合でZoomで解説はしませんので、答え合わせをされたい方はSlackでDMください
- オートスケーリングの動作を確認してみてください。有効にするためには、CFnテンプレートの ①
Conditions
にあるisProd
②Resources
にあるApplicationAutoScalingScalableTarget
③Outputs
にあるApplicationAutoScalingScalableTarget
のコメントを外した上で、CloudFormationのスタックを更新してください - 上の宿題8-2.に関連して、Nginx(web)コンテナ自身のIPアドレスは
bridge
とawsvpc
の場合、それぞれどうなっているか? ※コンテナにECS Execでログインした後、hostname -I
コマンドを実行するとIPアドレスを確認できます - 上の宿題8-3.に関連して、Nginx(web)コンテナ自身のポート番号は
bridge
とawsvpc
の場合、それぞれどうなっているか?
6. FAQ(随時追加)
- 【Session Manager Pluginがインストールされていない】
- 【AWS CLI (Command Line Interface) のバージョンが古い】
- 【zshを使っていて
# コメント文
が使えない】 - 【TaskIDのコピペミス】
- 【IAMユーザーのアクセスキーとシークレットアクセスキーが無効】
- 【内部エラーの発生】
- それでも解決しない場合はエラーメッセージをSlackでお送りください!
SessionManagerPlugin is not found. Please refer to SessionManager Documentation here:
http://docs.aws.amazon.com/console/systems-manager/session-manager-plugin-not-found
と表示された場合は (オプション) AWS CLI 用の Session Manager plugin をインストールする - AWS Systems Manager を参照して、プラグインをインストールしてください
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
aws: error: argument operation: Invalid choice, valid choices are:
と表示された場合はAWS CLIをバージョンアップしてください
※Macの場合: brew upgrade awscli
の後にターミナルを再起動してください
※Windowsの場合: https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv2-windows.html より最新バージョンをインストールください
zsh: bad pattern: [Change
zsh: bad pattern: [Change
zsh: bad pattern: [Change
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
aws: error: argument --cluster: expected one argument
と表示された場合は次のコマンドを打ってコメント文を使えるようにしてください
echo 'setopt interactivecomments' >> ~/.zshrc && source ~/.zshrc
(余談)私はMacでもAmazon Linux 2と合わせてbashを使っていますが、zshの方は zshとか便利コマンドあれこれ の情報が参考になると思います。
An error occurred (InvalidParameterException) when calling the ExecuteCommand operation:
task length should be one of [32,36]
TaskID=120d5b99c06d4aa7a64838027d86acdeRun ### [Change Task ID]
のように最後にRun
が入っていたり、TaskIDのコピペミスをご確認ください
An error occurred (UnrecognizedClientException) when calling the ExecuteCommand operation:
The security token included in the request is invalid.
アクセスキーを発行したIAMユーザーを削除したり、アクセスキーを無効化や削除していないでしょうか。MacやLinuxの場合は~/.aws/credentials
、Windowsの場合はC:\Users\[username]\.aws\credentials
に書かれているアクセスキーとシークレットアクセスキーが有効なものか、改めてIAMユーザーの画面と照らし合わせてみてください。aws configure
コマンドで再設定が可能です
An error occurred (TargetNotConnectedException) when calling the ExecuteCommand
operation:
The execute command failed due to an internal error. Try again later.
原因不明ですが、上記のエラーが表示された場合はECSタスクをデプロイし直す(=起動し直す)ことを試してみてください
以上、お疲れさまでした!