- 1. 解説動画
- 2. 概要
- ECS (Elastic Container Service)
- ECS Cluster (クラスター)
- ECS Instance (インスタンス)
- ECS Task (タスク)
- ECS Task Definition (タスク定義)
- ECS Service (サービス)
- イメージ図(ECSインスタンスとECSタスク)
- ECR (Elastic Container Registry)
- 構成図
- リソース一覧
- 命名規則
- 公式URL
- 3. 解説手順
- 4. CFnテンプレート
- ECR
- ECS Cluster
- ECS Service
- 5. 予習
- 6. FAQ(随時追加)
ECS(Elastic Container Service。ECS Cluster, Task Definition, Service)、ECR(Elastic Container Registry)について学んでいきましょう!
1. 解説動画
※動画の画質が悪い場合は、歯車から720p以上の画質を選択ください
2. 概要
ECS (Elastic Container Service)
ECSはAWS版 Dockerコンテナのオーケストレーションサービスです。コンテナ・オーケストレーションは、コンテナのデプロイメント(展開)、管理、スケーリング(規模の増減)、ネットワーキングなどを自動化できます。他に有名なオーケストレーションツールはdocker-compose、Docker Swarm、Kubernetes(クーバネティス)です。
ECSは設定値が多岐に渡り、言葉も最初慣れないため、理解に時間がかかります。スムーズに理解するために下記5つの言葉を確実に覚えましょう!
- ECS Cluster (クラスター)
- ECS Instance (インスタンス)
- ECS Task (タスク)
- ECS Task Definition (タスク定義)
- ECS Service (サービス)
ECS Cluster (クラスター)
Dockerコンテナを動かす基盤。①複数のEC2インスタンスを使う ②Fargateを使う(EC2インスタンスを使わない)の2パターンあり、今回は前者で構築します。それぞれメリット・デメリットがあり、
- ①EC2のメリット:EC2を通してDockerコンテナの中に入れる(デバッグしやすい)、Fargateより安い
- ②Fargateのメリット:EC2の管理が不要になる(バージョンアップやインスタンスのローテーションなど)
ECS Instance (インスタンス)
ECSクラスターとして使われるEC2インスタンス。Launch Template (起動テンプレート)とEC2 Auto Scaling (オートスケーリング)を使ってECS用のAMIから複数台起動させます。内部ではDocker AgentとECS Agentが実行されており、単に docker container run
(Dockerコンテナが起動)されているだけです。
ECS Task (タスク)
Dockerコンテナを起動させる単位・ひとまとまり。例えば今回はNginxのwebコンテナとRailsのappコンテナをまとめて起動させるので、1タスク=2コンテナとなります。1タスクで最低でも1つのコンテナを起動させます。
ECS Task Definition (タスク定義)
その名の通りECSタスクの定義。AMIを作る時に登場したdocker-compose.ymlの代わりと言っても良い存在です。タスク内で何種類のコンテナを起動させるか? 起動するDockerイメージは? 起動時の引数(環境変数)は? マウントするボリュームは? などを定義します。
Auto Scaling Groupと同じように「リビジョン」という名前でバージョン管理しています。使わなくなった古いリビジョンは「登録解除」をすることによって見えないようにすることができます。
ECS Service (サービス)
ECSタスク定義を元にタスクを常時起動させる仕組み。どのクラスターで、いくつのタスクを実行するか? タスクを起動後にどのALBターゲットグループに追加するか? オートスケールさせるか? などを設定します。
ちなみにECSサービスを使わなくても単独でECSタスクを起動させることも可能です(テストで起動させたり、バッチとして処理が終わるまで起動させたりする時に使います)
イメージ図(ECSインスタンスとECSタスク)
絵にするとこんな感じです。今回は左側のようにECSインスタンスの上でECSタスクを起動させます。右側はFargateの場合です。
https://aws.amazon.com/jp/blogs/compute/aws-fargate-a-product-overview/ より抜粋
ECR (Elastic Container Registry)
docker image build
したDockerイメージを docker image push
して格納しておける場所。今回はAMIの時に作成したDockerイメージを格納して、ECSで起動させます。
構成図
- ECRにはwebとappのDockerイメージを格納します
- Launch TemplateとAuto Scaling Groupによって、EC2 Instance[ecs-instance]を起動させます
- それらEC2 InstanceはECS Instanceとも呼ばれ、ECS Clusterの基盤となります
- ECS Instanceの上では、ECS Task DefinitionとECS ServiceによってECS Taskが起動します
- ECS TaskはALBに新たに作成したTarget Groupに追加され、それはListener Ruleに追加されます
今回作成するECR, Auto Scaling Group, EC2 Instance, ECS Clusterの構成図はこちらです。Launch Template, ECS Task Definition, ECS Service, ALB Listener Rule, Target Groupは省略しています。 ※小さくて見づらいので、右クリックして新しいタブで開いてください。
リソース一覧
Name | 目的 | 備考 |
---|---|---|
ECR: Repository | Dockerイメージをプッシュできる「リポジトリ」 | Ruby(app)用のリポジトリとNginx(web)用のリポジトリを作成する |
ECS: Cluster | ECSタスクを動かすための基盤である「クラスター」 | ECSクラスターはただ単に名前しか設定しない。ECSインスタンスやECSサービス側でECSクラスターを名指しして使う |
EC2: SecurityGroup, SecurityGroupIngress | EC2に対するInbound(Ingress)=内向きOutbound(Outgress)=外向きの通信を許可する「セキュリティグループ」 | 内向きは許可するものだけルールを追加していく。今回は①ELBからEC2へ動的ポート(TCP 32768〜65535番ポート)をオープンにする(=ECSタスクがランダムにポートを使うため)②RDSのセキュリティグループでEC2からRDSへMySQL(TCP 3306番ポート)ポートをオープンにする↲ 外向きは絞っても良いが、面倒なので通常はデフォルトのフルオープンのままで使うことが多い |
EC2: LaunchTemplate | EC2インスタンスの起動を楽にする「起動テンプレート」 | 今回はECSインスタンス用に最適化されたAMIを指定して起動テンプレートを作る。EBS最適化のフラグを「true」にしてしまうとT2系インスタンスタイプの起動時にエラーになるので注意(EBS最適化が使えるのはT3系以降のため) テンプレートを手で作る場合と異なり、設定変更によって最新バージョンができても「デフォルトバージョン」は変わらない。コンソール画面で確認する際には注意してください |
AutoScaling: AutoScalingGroup | EC2インスタンスの自動スケーリングを制御する「オートスケーリンググループ」 | 起動テンプレートの最新バージョンを使って、指定したサブネットに、指定したインスタンスタイプで、スポットインスタンスを起動させる。今回はALBターゲットグループへの追加はしない |
AutoScaling: ScheduledAction | 自動スケーリングを曜日・時間指定で制御できる「スケジュールされたアクション」 | 本番環境は平日朝07:00にスケールアウト(1→2台)同23:00にスケールイン(2→1台)させ、それ以外の検証環境などは平日朝07:50に起動(0→2台)、同22:00に停止(2→0台)する設定を行う |
ECS: CapacityProvider | ECSインスタンスの管理を強化するための「キャパシティプロバイダー」 | 今回はManaged Draining機能を使いたくて導入する |
ECS: ClusterCapacityProviderAssociations | ECSクラスターとキャパシティプロバイダーを紐付ける | ECSクラスターとキャパシティプロバイダーを紐付ける |
IAM: Role | ECSタスクへ権限付与するための「ロール」 | Inline Policyで(サンプルとして)静的コンテンツ用S3バケットの |
ElasticLoadBalancingV2: TargetGroup | ALBの振り分け先(=ターゲット)をまとめるための「ターゲットグループ」 | 今回のターゲットは、HTTP(TCP 80番ポート)が開いているECSタスクのNginx(web)コンテナ。ターゲットグループの設定の中でもヘルスチェック、スティッキーセッションの設定が特に重要。実際は80番ではなく32768〜65535番ポートだが、うまいこと対応してくれる |
ElasticLoadBalancingV2: ListenerRule | リスナーの挙動(条件とアクションのセット)を定義できる「リスナールール」 | CloudFront経由で |
Logs: LogGroup | CloudWatchにログを貯めるための「ロググループ」 | ECS側にロググループ作成の権限を付与しないため、事前にCFnで作成しておく。ログ自体はECSタスクが出力していく。いわゆる |
SSM: Parameter | パラメータの値を安全に保管できる「パラメータストア」 | 本来はAPIキーやSlack WebHook URLなどの秘密情報を保管すると良い。秘密でなくても使っても良く、今回はちょうど良い例がなかったのでRailsのEnvをサンプルに作った |
ECS: TaskDefinition | ECSタスクの設計「タスク定義」 | docker-compose.ymlのECS版。このタスク定義を元にタスクが作られる。メモリ予約値(Soft Limit)を指定。ヘルスチェックを指定。上で作ったECRリポジトリのDockerイメージを指定。SecretsManagerとParameterStoreと直書きで環境変数を指定。出力先のロググループを指定 etc. |
ECS: Service | ECSタスクを常時起動させるための「サービス」 | ECSクラスターとタスク定義を指定して、希望タスク数を起動する。Nginx(web)コンテナをALBターゲットグループに追加する |
命名規則
Classmethodさんの命名規則 https://dev.classmethod.jp/articles/aws-name-rule/ に準拠します。
公式URL
ECS
- Docs: Amazon Elastic Container Service とは - Amazon Elastic Container Service
- CFn: ECS リソースタイプのリファレンス - AWS CloudFormation
- CLI: ecs — AWS CLI v2 Command Reference
3. 解説手順
※今回はRDS, EC2 [viaelb]を使いますので事前に起動してください
- CFnでECRを作成する
- EC2 [viaelb]にログインしてDockerイメージをECRにプッシュする ※下記のコマンドはECRの画面でリポジトリを選択して「View push commands」ボタンをクリックすると同様に表示される
- CFnでECS Cluster, ECS Serviceを作成する 👉 ECS Serviceで
CREATE_FAILED
が表示されたりCREATE_IN_PROGRESS
のまま動かない場合はFAQを参照ください - (2025-01-09 訂正:対応不要です →)
Availability Zone間のリバランスによる不安定を防ぐために、コマンドでリバランス抑制設定をする(※下記はCloudFormation非対応の設定値なので、AWS CLIを使って設定する)aws autoscaling suspend-processes --scaling-processes AZRebalance --auto-scaling-group-name <value>
※<value>
にはオートスケーリンググループ名([SystemName]-prod-viacdn-ecs-instance-asg)を指定してください - コマンドで動作確認を実施する
※Windowsの方はCloudShellか、EC2にSessionManagerで繋いで実行してください
curl -v https://cdn.[ご自身のドメイン名]/users
※特定パスにリクエスト ※応答内容はどこかにコピペして保存しておいてください 👉 宿題に出ます - ブラウザで動作確認を実施する
http://cdn.[ご自身のドメイン名]/
にアクセスするとどうなるか? - ECSの学習が終わったら、EC2 [viaelb], RDSを停止しておく。さらにEC2 AutoScaling Group [viacdn-ecs-instance-asg]のDesired capacityを
0
に、ECS ServiceのDesired tasksも0
にしておく。動作確認する場合は再度起動が必要になるのでご注意ください!
bash
cd ~/awsmaster
## もしここで「No such file or directory」と表示された場合、⑧AMIの教材で作業したEC2と異なっているからです。
## 例えばCFnスタックを更新してAMI IDが変更された場合、EC2が再作成されます。元のEC2を消したのかもしれません。
## もし⑧AMIの教材で作業したEC2がなければ、⑧AMIの4.のコンテナ起動手順を再度実施してください。
AccountId=************ ### [Change AWS Account ID]
SystemName=awsmaster ### [Change System Name]
Environment=prod
## 1. ECR Login
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${AccountId}.dkr.ecr.ap-northeast-1.amazonaws.com
## 2. Docker Image Build
## ⑧AMIで既に`docker-compose build`で[awsmaster_app]イメージと
## [awsmaster_web]イメージをビルド済みという前提で割愛
## もしイメージ名が異なる場合は下記3.の`docker image tag`に続く箇所を修正ください
# cd ~/awsmaster
# docker-compose build
## 3. Docker Image Tag
docker image ls | grep awsmaster
## ①もし awsmaster_app と awsmaster_web が表示されたら
docker image tag awsmaster_app:latest ${AccountId}.dkr.ecr.ap-northeast-1.amazonaws.com/${SystemName}-${Environment}-app:latest
docker image tag awsmaster_web:latest ${AccountId}.dkr.ecr.ap-northeast-1.amazonaws.com/${SystemName}-${Environment}-web:latest
## ②もし awsmaster-app と awsmaster-web が表示されたら
docker image tag awsmaster-app:latest ${AccountId}.dkr.ecr.ap-northeast-1.amazonaws.com/${SystemName}-${Environment}-app:latest
docker image tag awsmaster-web:latest ${AccountId}.dkr.ecr.ap-northeast-1.amazonaws.com/${SystemName}-${Environment}-web:latest
## 4. Docker Image Push to ECR
docker image push ${AccountId}.dkr.ecr.ap-northeast-1.amazonaws.com/${SystemName}-${Environment}-app:latest
docker image push ${AccountId}.dkr.ecr.ap-northeast-1.amazonaws.com/${SystemName}-${Environment}-web:latest
4. CFnテンプレート
ECR
- [SystemName]=
awsmaster
をお好きなシステム名に置換してください。変えなくてもOKです。
Stack Name: [SystemName]-prod-ecr File Name: [SystemName]/ecr.yml
---
### [Change System Name] awsmaster
AWSTemplateFormatVersion: "2010-09-09"
Description: Create ECR Repository
Parameters:
SystemName:
Description: System Name
Type: String
Default: awsmaster ### [Change System Name]
Environment:
Description: Environment
Type: String
Default: prod
AllowedValues:
- prod
- stg
- dev
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Environment Configuration"
Parameters:
- SystemName
- Environment
Resources:
## ECR: Repository (App)
ECRRepositoryApp:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Sub ${SystemName}-${Environment}-app
LifecyclePolicy:
LifecyclePolicyText: |
{
"rules": [
{
"rulePriority": 1,
"description": "Expire images count more than 3",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 3
},
"action": {
"type": "expire"
}
}
]
}
## ECR: Repository (Web)
ECRRepositoryWeb:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Sub ${SystemName}-${Environment}-web
LifecyclePolicy:
LifecyclePolicyText: |
{
"rules": [
{
"rulePriority": 1,
"description": "Expire images count more than 3",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 3
},
"action": {
"type": "expire"
}
}
]
}
Outputs:
## ECR: Repository (App)
ECRRepositoryApp:
Value: !Ref ECRRepositoryApp
Export:
Name: !Sub ${AWS::StackName}-ECRRepositoryApp
ECRRepositoryAppArn:
Value: !GetAtt ECRRepositoryApp.Arn
Export:
Name: !Sub ${AWS::StackName}-ECRRepositoryAppArn
## ECR: Repository (Web)
ECRRepositoryWeb:
Value: !Ref ECRRepositoryWeb
Export:
Name: !Sub ${AWS::StackName}-ECRRepositoryWeb
ECRRepositoryWebArn:
Value: !GetAtt ECRRepositoryWeb.Arn
Export:
Name: !Sub ${AWS::StackName}-ECRRepositoryWebArn
【変更履歴】
- 2021-09-04:LifecyclePolicyの追加
- 2023-04-05:Metadataから
ResourceName
を削除(削除漏れ) - 2023-04-14:コメント文を全般的に修正
ECS Cluster
- [SystemName]=
awsmaster
をお好きなシステム名に置換してください。変えなくてもOKです。 - [UseSubnetProtected]=
true
ですが、もし④VPCでNAT Gatewayを削除していたら、必ずfalse
に変更してください。
Stack Name: [SystemName]-prod-viacdn-ecs-cluster File Name: [SystemName]/viacdn/ecs-cluster.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}-vpc
## 3. ${SystemName}-${Environment}-rds
## 4. ${SystemName}-${Environment}-elb
##
## Scheduled Action(Scale Out @ Prod): Monday-Friday 07:00(JST)
## Scheduled Action(Scale In @ Prod): Monday-Friday 23:00(JST)
## Scheduled Action(Start @ Not Prod): Monday-Friday 07:50(JST)
## Scheduled Action(Start @ Not Prod): Monday-Friday 22:00(JST)
##
## To avoid instability caused by AZ rebalancing, the following command should be executed. The command is not yet supported in CloudFormation.
## $ aws autoscaling suspend-processes --scaling-processes AZRebalance --auto-scaling-group-name <value>
AWSTemplateFormatVersion: "2010-09-09"
Description: Create ECS Cluster (EC2 Launch Template and EC2 Auto Scaling Group)
Mappings:
EnvironmentMap:
prod:
AutoScalingDesiredCapacity: 2
AutoScalingMinSize: 1
AutoScalingMaxSize: 3
stg:
AutoScalingDesiredCapacity: 2
AutoScalingMinSize: 1
AutoScalingMaxSize: 3
dev:
AutoScalingDesiredCapacity: 1
AutoScalingMinSize: 1
AutoScalingMaxSize: 3
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: viacdn
EC2ImageId:
Description: >
Specifies the AMI ID for your container instances.
https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-optimized_AMI.html
https://ap-northeast-1.console.aws.amazon.com/systems-manager/parameters/aws/service/ecs/optimized-ami/amazon-linux-2023/recommended/description
e.g. al2023-ami-ecs-hvm-2023.0.20240109-kernel-6.1-x86_64
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ecs/optimized-ami/amazon-linux-2023/recommended/image_id
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
- EC2ImageId
- UseSubnetProtected
Conditions:
isProd: !Equals [ !Ref Environment, prod ]
isNotProd: !Not [ !Equals [ !Ref Environment, prod ] ]
ShouldUseSubnetProtected: !Equals [ !Ref UseSubnetProtected, true ]
Resources:
## ECS: Cluster
Cluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-cluster
## EC2(VPC): Security Group
EC2SecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-instance-ec2-sg
GroupDescription: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-instance-ec2-sg
VpcId:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-vpc-VPC
Tags:
- Key: Name
Value: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-instance-ec2-sg
## EC2(VPC): Security Group Inbound Rule From ELB (HTTP)
EC2SecurityGroupIngressHttpFromLoadBalancer:
Type: AWS::EC2::SecurityGroupIngress
Properties:
GroupId: !Ref EC2SecurityGroup
IpProtocol: tcp
FromPort: 32768 ## Ref. https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task_definition_parameters.html
ToPort: 65535
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-instance-ec2-sg
## EC2: Launch Template
EC2LaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateName: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-instance-lt
LaunchTemplateData:
ImageId: !Ref EC2ImageId
NetworkInterfaces:
- DeviceIndex: 0
Description: Primary network interface
AssociatePublicIpAddress: !If [ ShouldUseSubnetProtected, false, true ]
Groups:
- !Ref EC2SecurityGroup
## Required in Amazon Linux 2, but no longer required in Amazon Linux 2023
# BlockDeviceMappings:
# - DeviceName: /dev/xvda
# Ebs:
# VolumeSize: 30
# VolumeType: gp3
# # Encrypted: true
TagSpecifications:
- ResourceType: instance
Tags:
- Key: Name
Value: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-instance
- ResourceType: volume
Tags:
- Key: Name
Value: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-instance
IamInstanceProfile:
Name: !ImportValue iam-role-AmazonSSMManagedECSInstanceProfile
InstanceInitiatedShutdownBehavior: terminate
Monitoring:
Enabled: false
# EbsOptimized: true ## EbsOptimized is not available for t2 instance type.
MetadataOptions:
HttpTokens: required ## V2 only (token required)
HttpPutResponseHopLimit: 2 ## 2 if you need to access the metadata from a Docker container on EC2, 1 if you don't.
InstanceMetadataTags: disabled
UserData:
Fn::Base64: !Sub |
#!/bin/bash
## Ref. https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs-agent-config.html
echo ECS_CLUSTER=${SystemName}-${Environment}-${ResourceName}-ecs-cluster >> /etc/ecs/ecs.config;
echo ECS_ENABLE_SPOT_INSTANCE_DRAINING=true >> /etc/ecs/ecs.config;
echo ECS_LOG_MAX_ROLL_COUNT=168 >> /etc/ecs/ecs.config;
## Auto Scaling: Auto Scaling Group
EC2AutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
## By defining DependsOn, EC2AutoScalingGroup is created after Cluster.
## However, the order in which they are created is not a problem;
## it is a problem if the Cluster is deleted before the EC2AutoScalingGroup,
## so DependsOn is defined.
DependsOn: Cluster
## If you don't want CloudFormation to change any of the group size property values
## when you have a scheduled action in effect, use the AutoScalingScheduledAction update policy
## and set IgnoreUnmodifiedGroupSizeProperties to true to prevent CloudFormation
## from changing the MinSize, MaxSize, or DesiredCapacity properties
## unless you have modified these values in your template.
## For Japanese translation, please refer to the following.
## Ref. https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-attribute-updatepolicy.html
## >> IgnoreUnmodifiedGroupSizeProperties
UpdatePolicy:
AutoScalingScheduledAction:
IgnoreUnmodifiedGroupSizeProperties: true
Properties:
AutoScalingGroupName: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-instance-asg
MixedInstancesPolicy:
LaunchTemplate:
LaunchTemplateSpecification:
LaunchTemplateId: !Ref EC2LaunchTemplate
Version: !GetAtt EC2LaunchTemplate.LatestVersionNumber
Overrides:
## Since the Memory is the same below, WeightedCapacity is not specified.
- InstanceType: t2.micro
- InstanceType: t3a.micro
InstancesDistribution:
OnDemandBaseCapacity: 0
OnDemandPercentageAboveBaseCapacity: 0
SpotAllocationStrategy: price-capacity-optimized
# SpotInstancePools: 4 ## Valid only when the Spot allocation strategy is lowest-price.
CapacityRebalance: true
VPCZoneIdentifier:
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
HealthCheckType: EBS
HealthCheckGracePeriod: 300
DesiredCapacity: !FindInMap [ EnvironmentMap, !Ref Environment, AutoScalingDesiredCapacity ]
MinSize: !FindInMap [ EnvironmentMap, !Ref Environment, AutoScalingMinSize ]
MaxSize: !FindInMap [ EnvironmentMap, !Ref Environment, AutoScalingMaxSize ]
## Auto Scaling: Scheduled Action (Scale Out)
ScheduledActionScaleOut:
Condition: isProd
Type: AWS::AutoScaling::ScheduledAction
Properties:
AutoScalingGroupName: !Ref EC2AutoScalingGroup
DesiredCapacity: !FindInMap [ EnvironmentMap, !Ref Environment, AutoScalingDesiredCapacity ]
MinSize: !FindInMap [ EnvironmentMap, !Ref Environment, AutoScalingMinSize ]
MaxSize: !FindInMap [ EnvironmentMap, !Ref Environment, AutoScalingMaxSize ]
Recurrence: "0 22 * * SUN-THU" ## [Cron] Monday-Friday 07:00(JST) @ Prod
## Auto Scaling: Scheduled Action (Scale In)
ScheduledActionScaleIn:
Condition: isProd
Type: AWS::AutoScaling::ScheduledAction
Properties:
AutoScalingGroupName: !Ref EC2AutoScalingGroup
DesiredCapacity: !FindInMap [ EnvironmentMap, !Ref Environment, AutoScalingMinSize ]
MinSize: !FindInMap [ EnvironmentMap, !Ref Environment, AutoScalingMinSize ]
MaxSize: !FindInMap [ EnvironmentMap, !Ref Environment, AutoScalingMaxSize ]
Recurrence: "0 14 * * MON-FRI" ## [Cron] Monday-Friday 23:00(JST) @ Prod
## Auto Scaling: Scheduled Action (Start)
ScheduledActionStart:
Condition: isNotProd
Type: AWS::AutoScaling::ScheduledAction
Properties:
AutoScalingGroupName: !Ref EC2AutoScalingGroup
DesiredCapacity: !FindInMap [ EnvironmentMap, !Ref Environment, AutoScalingDesiredCapacity ]
MinSize: !FindInMap [ EnvironmentMap, !Ref Environment, AutoScalingMinSize ]
MaxSize: !FindInMap [ EnvironmentMap, !Ref Environment, AutoScalingMaxSize ]
Recurrence: "50 22 * * SUN-THU" ## [Cron] Monday-Friday 07:50(JST) @ Not Prod
## Auto Scaling: Scheduled Action (Stop)
ScheduledActionStop:
Condition: isNotProd
Type: AWS::AutoScaling::ScheduledAction
Properties:
AutoScalingGroupName: !Ref EC2AutoScalingGroup
DesiredCapacity: 0
MinSize: 0
MaxSize: !FindInMap [ EnvironmentMap, !Ref Environment, AutoScalingMaxSize ]
Recurrence: "0 13 * * MON-FRI" ## [Cron] Monday-Friday 22:00(JST) @ Not Prod
## ECS: CapacityProvider
CapacityProvider:
Type: AWS::ECS::CapacityProvider
Properties:
## If a name is specified, it cannot start with aws, ecs, or fargate.
Name: !Sub cp-${EC2AutoScalingGroup}
AutoScalingGroupProvider:
AutoScalingGroupArn: !Ref EC2AutoScalingGroup
ManagedDraining: ENABLED
# ManagedScaling:
# Status: ENABLED
# TargetCapacity: 100
# ManagedTerminationProtection: DISABLED
## ECS: ClusterCapacityProviderAssociations
ClusterCapacityProviderAssociations:
Type: AWS::ECS::ClusterCapacityProviderAssociations
Properties:
Cluster: !Ref Cluster
CapacityProviders:
- !Ref CapacityProvider
DefaultCapacityProviderStrategy:
- CapacityProvider: !Ref CapacityProvider
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
## 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
## EC2: Launch Template
EC2LaunchTemplate:
Value: !Ref EC2LaunchTemplate
Export:
Name: !Sub ${AWS::StackName}-EC2LaunchTemplate
EC2LaunchTemplateDefaultVersionNumber:
Value: !GetAtt EC2LaunchTemplate.DefaultVersionNumber
Export:
Name: !Sub ${AWS::StackName}-EC2LaunchTemplateDefaultVersionNumber
EC2LaunchTemplateLatestVersionNumber:
Value: !GetAtt EC2LaunchTemplate.LatestVersionNumber
Export:
Name: !Sub ${AWS::StackName}-EC2LaunchTemplateLatestVersionNumber
## Auto Scaling: Auto Scaling Group
EC2AutoScalingGroup:
Value: !Ref EC2AutoScalingGroup
Export:
Name: !Sub ${AWS::StackName}-EC2AutoScalingGroup
## Auto Scaling: Scheduled Action (Scale Out)
ScheduledActionScaleOut:
Condition: isProd
Value: !Ref ScheduledActionScaleOut
Export:
Name: !Sub ${AWS::StackName}-ScheduledActionScaleOut
## Auto Scaling: Scheduled Action (Scale In)
ScheduledActionScaleIn:
Condition: isProd
Value: !Ref ScheduledActionScaleIn
Export:
Name: !Sub ${AWS::StackName}-ScheduledActionScaleIn
## Auto Scaling: Scheduled Action (Start)
ScheduledActionStart:
Condition: isNotProd
Value: !Ref ScheduledActionStart
Export:
Name: !Sub ${AWS::StackName}-ScheduledActionStart
## Auto Scaling: Scheduled Action (Stop)
ScheduledActionStop:
Condition: isNotProd
Value: !Ref ScheduledActionStop
Export:
Name: !Sub ${AWS::StackName}-ScheduledActionStop
## ECS: CapacityProvider
CapacityProvider:
Value: !Ref CapacityProvider
Export:
Name: !Sub ${AWS::StackName}-CapacityProvider
## ECS: ClusterCapacityProviderAssociations
ClusterCapacityProviderAssociations:
Value: !Ref ClusterCapacityProviderAssociations
Export:
Name: !Sub ${AWS::StackName}-ClusterCapacityProviderAssociations
【変更履歴】
- 2022-04-04:EBS VolumeTypeを
gp2
からgp3
に変更 - 2022-05-23:EBS Volumeから
DeleteOnTermination: true
とEncrypted: false
を削除 - 2022-07-20:ScheduledActionStopの
Recurrence
が間違っていたので訂正 - 2022-09-10:
EC2AutoScalingGroup
にコメント追加 - 2023-01-13:SpotAllocationStrategyを
price-capacity-optimized
に変更 ※参考:EC2 スポットインスタンスの price-capacity-optimized 戦略のご紹介 | Amazon Web Services ブログ - 2023-04-06:冒頭でImportValueについてのコメント補足追加
- 2023-04-14:コメント文を全般的に修正
- 2024-01-07:画面に合わせて
InstancesDistribution
とLaunchTemplate
の順番を逆にし、CapacityRebalance
を追加 - 2024-01-26:
- EC2インスタンスのOSをAmazon Linux 2からAmazon Linux 2023に変更
- Launch TemplateとAuto Scaling Groupの名前を変更(末尾に-ltと-asgを付ける)
- EC2インスタンス関連の名前を変更(分かりやすくするため、スタック名のecs-clusterではなく、リソース名のecs-instanceに変更)
- 2024-02-06:
- マネジメントコンソールの順番に合わせて
IamInstanceProfile
からEbsOptimized
の場所を移動 - セキュリティの観点からIMDSv2を必須とするため
MetadataOptions
の設定を追加 - 2024-09-18:Auto Scaling Groupに
HealthCheckType: EBS
を追加 - 2024-09-19:ECS::CapacityProviderとECS::ClusterCapacityProviderAssociationsを追加
- 2025-01-09:Auto Scaling Groupの「中断されたプロセス (Suspended processes)」で
AZ Rebalance
の有効化は不要
ECS Service
- [SystemName]=
awsmaster
をお好きなシステム名に置換してください。変えなくてもOKです。
Stack Name: [SystemName]-prod-viacdn-ecs-service File Name: [SystemName]/viacdn/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 IAM Policy, IAM Role, SSM Parameter, ELB Target Group, ELB Listener Rule, ECS Task Definition and ECS Service (EC2) 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: viacdn
ELBListenerRulePriority:
Description: ELB Listner Rule Priority
Type: Number
Default: 121
MinValue: 1
MaxValue: 50000
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Environment Configuration
Parameters:
- SystemName
- Environment
- ResourceName
- ELBListenerRulePriority
Resources:
## 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: instance
Protocol: HTTP
Port: 80
VpcId:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-vpc-VPC
HealthCheckEnabled: true
HealthCheckProtocol: HTTP
HealthCheckPath: /
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:
- EC2
RuntimePlatform:
OperatingSystemFamily: LINUX
CpuArchitecture: X86_64
NetworkMode: bridge
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/ || 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:
- HostPort: 0
ContainerPort: 80
Protocol: tcp
Cpu: 0
MemoryReservation: 16
Environment:
- Name: NGINX_BACKEND
Value: app
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
Links: ## Available only when "NetworkMode: bridge"
- app
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:
Fn::ImportValue: !Sub ${SystemName}-${Environment}-${ResourceName}-ecs-cluster-CapacityProvider
Base: 0
Weight: 1
TaskDefinition: !Ref TaskDefinition
SchedulingStrategy: REPLICA
DesiredCount: !FindInMap [ EnvironmentMap, !Ref Environment, ServiceDesiredCount ]
AvailabilityZoneRebalancing: ENABLED
DeploymentConfiguration:
MinimumHealthyPercent: 100
MaximumPercent: 200
DeploymentCircuitBreaker:
Enable: true
Rollback: true
LoadBalancers:
- ContainerName: web
ContainerPort: 80
TargetGroupArn: !Ref ELBTargetGroup
HealthCheckGracePeriodSeconds: 300
PlacementStrategies:
- Field: attribute:ecs.availability-zone
Type: spread
- Field: instanceId
Type: spread
EnableECSManagedTags: true
EnableExecuteCommand: true
Outputs:
## 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
【変更履歴】
- 2022-01-05:
AmazonECSExecuteCommandPolicy
インラインポリシーを管理ポリシーに変更 - 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に
CapacityProviderStrategy
とDeploymentCircuitBreaker
を追加 - 2024-09-24:ECS Serviceの
LaunchType
を削除 - 2024-12-18:ECS Serviceに
AvailabilityZoneRebalancing
を追加
5. 予習
- 解説動画の通り、設定・作成してください ※CFnでエラーになった場合は、EventsタブのスクリーンショットをSlackでください (AWSの画面が変わっている場合は同じ設定項目を探してください)
- CFnで作成したECSを手でも作ってください。作成中に分からなかった設定項目については調べたり、質問のためにメモしておいてください (手順が分かりづらいので、分からない場合はSlackかZoomでお答えします)
- 【宿題】下記の項目について調べて、次回のZoomで説明してください (公式の情報が優先で、分からない場合はブログ等を参照ください)
- ECSの設定と挙動を理解するために、解説手順の動作確認でアクセスした1回のリクエストについて、応答結果を上記の表にまとめてください。①HTTPリクエストはAWSの中でどう通って、②最終的に誰(=Server)が、③どんなHTTP応答(=HTTPステータスコード)を返したか? ④それらの設定箇所はAWSコンソール画面でどこにあるか? を調べてください
- ECSインスタンスで、何個のタスクが起動しているか確認してください。同じECSインスタンスにおいて、ECSで使用可能なメモリ量、現時点での空きメモリ量も確認してください
- ECSサービスの履歴を確認できますか?(ECSタスクを起動した、webコンテナをALBターゲットグループに追加した etc.)
- ECRでDockerイメージの「タグ」にバージョン番号(例えば
1.0.0
)を使っている場合の④ECSデプロイ手順は次の2つです。(1)ECSタスク定義の更新(=ECRのイメージタグを変更するため、新たなリビジョンを追加)→ (2)新しいECSタスク定義でECSタスクを起動させ、既存のECSタスクを停止する。ここで(2)はどういう手順で実施するでしょうか? - ECRでDockerイメージの「タグ」が固定(例えば
latest
)の場合の④ECSデプロイ手順は次の1つです。ECRのイメージタグが変わらないためECSタスク定義の更新が不要で、(1)現在のECSタスク定義で再度ECSタスクを起動させ、既存のECSタスクを停止する。ここで(1)はどういう手順で実施するでしょうか? - appコンテナとwebコンテナ、それぞれのヘルスチェックがどのように設定されているか?
- 直近で起動したECSタスクの画面で、ログを確認できますか?
- ECSインスタンスにSession Managerで接続して
sudo docker container ls
コマンドを実行してください。さらにsudo docker container exec -it [CONTAINER ID] bash
でappかwebのコンテナの中に入れることも確認してください ※ecs-agent
という名前のコンテナには入れませんのでご注意ください - ECRの料金体系。EC2がECRからDockerイメージをプルする場合の転送料金もあわせて
- CloudWatch Logsの料金体系
- Parameter Storeの料金体系
- ECSでEC2起動タイプ(ECS on EC2)の料金体系
- 【応用課題】余裕のある方は実際に挑戦して頂くと勉強になると思います! ※時間の都合でZoomで解説はしませんので、答え合わせをされたい方はSlackでDMください
- ECS関連のインフラを構築後しばらく経ってから、ECSインスタンスの画面を見ると「ECS Agent Versionが古い」と注意喚起される場合があります。
- 宿題7で確認したヘルスチェックが実際に行われているか、appコンテナのログとwebコンテナのログで確認してください。
【以下4.と5.共通】ECSでWebサービスを提供している場合、一般的に次の手順でリリースします。①プログラムの変更 → ②Dockerビルド(=Dockerイメージの再作成)→ ③ECRプッシュ(=ECRへDockerイメージをアップロード)→ ④ECSデプロイ(=ECSタスクを新しく作り替える)。ここでは最後の④ECSデプロイ手順について4.と5.で尋ねます。
この注意喚起を解消する、つまりECS Agentを最新にするにはどうすれば良いですか? 実際に試してみてください。 ※ヒント:ECS Agentは「何」にインストールされているか?=EC2の元になったのは「何」か? その「何」を更新するためにはどうしたら良いか?
6. FAQ(随時追加)
CREATE_FAILED
がすぐに表示されて失敗する
・Deployment circuit breakerが無効な場合:CREATE_IN_PROGRESS
のまま3時間変わらない
時は、どうすれば良いですか?ECSタスク(その中のDockerコンテナ)が正常に起動しない場合、上記のような状況になります。その後ロールバックされ、ECSサービス関連のリソースが消されてしまいます。
とりあえずECSタスクが正常に起動しなくてもECS ServiceのCFnスタックを作成完了させ、後から問題解決したい場合は、ECSサービスの画面をリロードしながら、ECSサービスができた瞬間にUpdateボタンからDesired tasks(希望タスク数)を手動で 0
に変更してください。そうすると希望タスク数と実際のタスク数が一致するため、CFnは正常に作成完了したと認識してくれます。
念のため具体的な手順を書いておきます ↓
- まずECSサービスのCFnスタックを作成開始してください
- マネジメントコンソールでECSサービスが新規作成されることを確認します(何回もページをリロードする)
- 確認できたらすぐECSサービスの更新 (Update service) 画面を開き、希望タスク数 (Desired Tasks) を
0
にして更新 (Update) してください - そうするとECSサービスのCFnスタックが作成完了します
- その上で希望タスク数 (Desired Tasks) を
1
に変更して、なぜECSタスクが正常に起動しないのか原因を探り、対処していきます
なぜECSタスク(その中のDockerコンテナ)が正常に起動しないかはECSタスクのログを確認ください。
参考: AWS CloudFormation で Amazon ECS サービスが安定化に失敗しないようにする
Parameterを作成する際にSecureStringを選択すると暗号化して保存されます。Secretとほぼ同じ機能を持っているため、現場でも使い分けはかなり悩みます。
結論から言えば、DBのパスワードを保存したり、CFnで乱数生成したい場合はSecretが向いていますが、それ以外はParameter (SecureString)を手で作成する方が気軽にできると思います。
全てを網羅した記事がネット上になかったので、下記にまとめておきます。とにかくややこしいです。変化も激しいので、下記の内容(2022年4月時点)が変わっている可能性も大いにあります。
名前 | Secret | Parameter(SecureString) |
---|---|---|
料金 | 有料(ごく少額ですが) | 無料 |
CFnで作成 | 可能。さらに乱数生成も可能 | 不可能 |
JSONで保存(=DBの情報など、複数のパラメータをまとめる) | 可能。さらに個別にKeyを指定して特定のValueのみを参照することも可能。そのためユーザー名・パスワードなど、DBの情報を1つのSecretで保存できる | 可能。ただし個別にKeyを指定して特定のValueのみを参照することは不可能。そのためユーザー名・パスワードなど、DBの情報を保存する際は個別に保存した方が良い |
パスワードローテーション機能 | あり(画面上にローテーション機能の表示あり。内部的にはLambda Functionで動くようになっている。昔は手でLambdaを作っていたが、今はその必要がなくなった) | なし(もちろんLambda Functionと組み合わせて自分で作り込むことはできるが、画面上にローテーションの機能の表示はない) |
パラメータのバージョン管理 ※正直かなり複雑です... | ステージングラベルを使えばバージョン管理できるが、自動的には管理されない。AWS CLIやCFnテンプレートでは複数バージョンを参照できるが、画面上では参照できない。 ※パスワードローテーションをするためには変更前・変更後で2つのパスワードを保存する必要があるため、実装されたのだろう | 過去分も自動でバージョン管理されている。画面上やCFnテンプレートでは過去分を参照できるが、AWS CLIでは参照できない。 |
CFnテンプレートでの参照 | 可能。構文: | 可能。 構文: |
ECSタスク定義での参照 | 前述の通り1つのSecretでもJSONの中のKeyを指定することで個別にパラメータ参照できる。ステージングラベルを指定して複数バージョンの参照もできる(あまりしないが) | 単に最新のパラメータを参照するだけ(JSONの中のKeyを指定して参照することや、過去バージョンを指定して参照することはできない) |
対応するサービス | 次第に増えているが、Parameter(SecureString)よりは少ない | 多い。例えばFargateでも最初の頃はParameter(SecretString)のみ対応していた |
もう片方を参照できるか? ※ややこしすぎます... | (恐らくSecrets ManagerのAPIからParameterは参照できない。後発サービスなので実装する必要性が感じられない) | CLIなどで |
参考(2018年8月の古い記事。Secrets Managerが登場した頃):AWSのParameter StoreとSecrets Manager、結局どちらを使えばいいのか?比較 - Qiita
参考(Secretのパスワードローテーション機能):AWS Secrets Managerで RDSのパスワードローテーションしてみる in 2022 | DevelopersIO
参考(Secretのステージングラベルの使い方):新サービス AWS Secrets Manager のシークレットを CLI から更新し、その動作を理解する | DevelopersIO
参考(CFnテンプレートでの動的な参照):動的な参照を使用してテンプレート値を指定する - AWS CloudFormation
参考(Parameter Storeの GetParameter
APIを使ってSecretを参照):AWS Secrets Manager パラメータからの Parameter Store シークレットの参照 - AWS Systems Manager
→ このドキュメントを読んで初めて知りましたが「AWS Systems Manager の一機能である Parameter Store は Secrets Manager と統合された」んですね...。ややこしすぎます。
以上、お疲れさまでした!