目次
1. Networkの構築
ここでは、VPC, Subnet, RouteTable, InternetGateway, VPC Endpoint, Security Groupを作成する
詳細は以下を参考
2. ECS on Fargateの構築
クラスター作成
ECSClusterBackendApplication:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Sub "${ProjectName}-backend-application"
コンテナ内では基本的に永続データを保持しないような設計にするために、ログデータを外に吐き出す必要がある
Fluentd
などがあるがここでは、CloudWatch Logs
を使用する
Log groupの作成をしておき、ここで作成したロググループをTask定義で使用することとする
CloudWatchLogGroupBackendApplication:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "/ecs/logs/${ProjectName}-backend-application"
RetentionInDays: 7
タスク実行ロールの作成
タスク実行ロールとは、ECSコンテナエージェントから他のAWSサービスへアクセスするためのロールを定義するもの
似ているロールとしてタスクロールが存在するが、これはコンテナ内から他のAWSサービスへアクセスするためのロールを定義するものとなる
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "${ProjectName}-ECSTaskExecutionRolePolicy"
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
タスク定義の作成
タスク定義とはコンテナ群やタスクのためのCPU、Memory、ネットワークの設定などを記述する
CPUやMemoryを記入する部分が二箇所あるがこれはタスク自体のためのCPUやMemoryとコンテナのためのものとの2つがある
CPUによって利用可能なMemoryの値の範囲が決まる
逆にMemoryによってCPUの利用可能な値の範囲が決まる
Fargateを使用している場合タスクレベルのCPU、Memoryの指定は必須である
Fargateを使用している場合、コンテナ定義する際のCPUやメモリーの指定はオプションであるが、タスクレベルのCPUやメモリーで指定されている値を超えてはいけない
Essentialをtrueにしておくことで、そのコンテナが何かしらの理由でダウンした場合そのタスク内の他のコンテナも停止させる
MemoryReservation
コンテナ用に予約されるメモリ量であり、システムメモリの競合が激しい場合このメモリ値までは使用するよう試みる。しかし、コンテナがよりメモリを消費したい場合は、Memory
parameterで指定された値かコンテナインスタンスの残りのリソース量まで使用することができる
ECSTaskDefinitionBackendApplication:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Sub "${ProjectName}-backend-application-def"
Cpu: !Ref ECSBackendApplicationTaskCPUUnit
Memory: !Ref ECSBackendApplicationTaskMemory
ExecutionRoleArn: !Ref ECSTaskExecutionRole
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ContainerDefinitions:
- Name: !Sub "${ProjectName}-backend-application"
Image: !Ref ECSBakendApplicationImageName
Essential: true
Cpu: 256
MemoryReservation: 128
PortMappings:
- ContainerPort: 80
HostPort: 80
Protocol: tcp
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref CloudWatchLogGroupBackendApplication
awslogs-region: !Ref AWS::Region
awslogs-stream-prefix: ecs
ロードバランサー作成
まずはロードバランサーのみの作成をしていく
Scheme: internet-facing
を指定することで、インターネットの世界とのやりとりができるようになる
internal
を指定するとプライベートIP addressを扱いVPC内でのやり取りしかできない
LoadBalancerAttributes
についてはこちら
InternetALB:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub "${ProjectName}-ingress-alb"
Scheme: internet-facing
LoadBalancerAttributes:
- Key: deletion_protection.enabled
Value: false
- Key: idle_timeout.timeout_seconds
Value: 120
- Key: access_logs.s3.enabled
Value: true
- Key: access_logs.s3.bucket
Value: !Sub "${ProjectName}-alb-log-${AWS::AccountId}"
SecurityGroups:
- !Ref ALBSecurityGroupId
Subnets:
- !Ref ALBSubnetId1
- !Ref ALBSubnetId2
Type: application
Tags:
- Key: Name
Value: !Sub "${ProjectName}-internet-application-loadbalancer"
次にロードバランサーがルーティングする対象となるターゲットグループを作成する
AWS::ECS::TaskDefinitons
パラメータの中のContainerDefinitions
でヘルスチェックを指定していなかったのでここで設定している
今回は、Blue/Green Deploymentを想定しているので、ここでは記載しないがGreen用のターゲットグループも作成しておくこと
TargetGroupBackendApplicationBlue:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
VpcId: !Ref VpcId
Name: !Sub "${ProjectName}-tg-backend-application-blue"
Protocol: HTTP
Port: 80
TargetType: ip
HealthCheckIntervalSeconds: 15
HealthCheckPath: /healthcheck
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 3
UnhealthyThresholdCount: 3
Matcher:
HttpCode: 200
最後に、ロードバランサーとターゲットグループの紐付けを行う
ALBIngressListner1:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
DefaultActions:
- Type: forward
TargetGroupArn: !Ref TargetGroupBackendApplicationBlue
LoadBalancerArn: !Ref InternetALB
Protocol: HTTP
Port: 80
VPC Endpoint作成
ECSによりあるサブネットにFargate(Container)を起動させるためには、ECRからimageをpullしたりしなければならない
これらのAWSサービスはVPC内に存在しているわけではないので本来ならばNAT Gatewayなどを通ってインターネットの世界に出た後に望むAWSサービスへとアクセスする必要がある
しかし、VPC Endpointを使用することでNAT Gatewayを用意することなく他サービスと連携することができる
ECRと接続するためには、ecr.qpi
ecr.dkr
の両方が必要になってくる
コンテナ内からログを出力するためとしてlogs
これらはVPC Endpointのinterface
を使用している
S3は今後CodePipelineなどでartifactを出力するために作成してある
また、S3のみGateway
となっている
gateway
型は、Internet gateway
などと同じようにこのgatewayにルーティングされるよう対象のサブネットに関連づけられているルートテーブルに追加する必要がある
この設定は以下のようにRouteTableIds
で設定する
EcrApiEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ecr.api"
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroupId
SubnetIds:
- !Ref VPCEndpointSubnetId1
- !Ref VPCEndpointSubnetId2
VpcEndpointType: Interface
VpcId: !Ref VpcId
EcrDkrEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.ecr.dkr"
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroupId
SubnetIds:
- !Ref VPCEndpointSubnetId1
- !Ref VPCEndpointSubnetId2
VpcEndpointType: Interface
VpcId: !Ref VpcId
S3Endpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
RouteTableIds:
- !Ref VPCGatewayRouteTableId
VpcEndpointType: Gateway
VpcId: !Ref VpcId
LogsEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub "com.amazonaws.${AWS::Region}.logs"
PrivateDnsEnabled: true
SecurityGroupIds:
- !Ref EndpointSecurityGroupId
SubnetIds:
- !Ref VPCEndpointSubnetId1
- !Ref VPCEndpointSubnetId2
VpcEndpointType: Interface
VpcId: !Ref VpcId
3. CodePipelineの構築
IAM Roleの作成
CodePipline
を使用してデプロイする場合、CodeBuild
でECR
からdocker imageをpullするための権限などを作成していく
CodeWatchEvent
CloudWatchEvent
からCodePipeline
に対してパイプラインを開始させるためのポリシーを設定している
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${ProjectName}-CloudWatchEventRole
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- events.amazonaws.com
Action: sts:AssumeRole
Path: /
Policies:
- PolicyName: CloudWatchEventsPipelineExecution
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: codepipeline:StartPipelineExecution
Resource: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}
CodeBuild
- ログの作成変更
- ArtifactのためのS3操作
-
codebuild
関連は未調査 -
ECR
からimageをpullするあたりの操作
など
CodeBuildServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${ProjectName}-CodeBuildRole
Path: /
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: !Sub ${ProjectName}CodeBuildAccess
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Resource: '*'
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- Effect: Allow
Resource: !Sub arn:aws:s3:::${ArtifactBucket}/*
Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketAcl
- s3:GetBucketLocation
- Effect: Allow
Action:
- codebuild:CreateReportGroup
- codebuild:CreateReport
- codebuild:UpdateReport
- codebuild:BatchPutTestCases
- codebuild:BatchPutCodeCoverages
Resource: '*'
- Effect: Allow
Action:
- ecr:GetAuthorizationToken
- ecr:BatchCheckLayerAvailability
- ecr:GetDownloadUrlForLayer
- ecr:GetRepositoryPolicy
- ecr:DescribeRepositories
- ecr:ListImages
- ecr:DescribeImages
- ecr:BatchGetImage
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
- ecr:PutImage
Resource: '*'
CodePipeline
ecs-tasks
に対してロールを渡す権限を付与している
これは、Blue/Green デプロイタイプを使用する Amazon ECS サービス内のタスクでタスク実行ロールまたはタスクロール上書きを使用する必要がある場合は、各タスクのタスク実行ロールまたはタスクロール上書きに対する iam:PassRole アクセス許可を、CodeDeploy IAM ロールにインラインポリシーとして追加する必要があるから
具体例は未調査である
iam:PassRoleの付与する理由(最後らへんに記述されてる)
iam:PassRole
についてはこちら
AWSのコンソール側でCodePipelineを作成すると「新しいサービスロールを作成」を選択でき、これにより自動で作成されたロールがCodePipelineにアタッチされる
このロールが持つポリシーを参考にするのが良いと思う
CodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub ${ProjectName}-CodePipelineRole
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: !Sub ${ProjectName}PipelinePolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Action:
- iam:PassRole
Resource: '*'
Effect: Allow
Condition:
StringEqualsIfExists:
iam:PassedToService:
- ecs-tasks.amazonaws.com
- Resource:
- !Sub arn:aws:s3:::${ArtifactBucket}/*
Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketVersioning
- Action:
- codecommit:CancelUploadArchive
- codecommit:GetBranch
- codecommit:GetCommit
- codecommit:GetRepository
- codecommit:GetUploadArchiveStatus
- codecommit:UploadArchive
Resource: '*'
Effect: Allow
- Action:
- codedeploy:CreateDeployment
- codedeploy:GetApplication
- codedeploy:GetApplicationRevision
- codedeploy:GetDeployment
- codedeploy:GetDeploymentConfig
- codedeploy:RegisterApplicationRevision
- codedeploy:*
Resource: '*'
Effect: Allow
- Action:
- elasticbeanstalk:*
- ec2:*
- elasticloadbalancing:*
- autoscaling:*
- cloudwatch:*
- sns:*
- cloudformation:*
- rds:*
- sqs:*
- ecs:*
Resource: '*'
Effect: Allow
- Action:
- codebuild:BatchGetBuilds
- codebuild:StartBuild
- codebuild:BatchGetBuildBatches
- codebuild:StartBuildBatch
Resource: '*'
Effect: Allow
事前準備(リソース)
ここでは、CodePipelineを構築する前に必要となるリソースを手動やCFnで作成する
ECR、ECS
それぞれ、手動でコンソールから作成してください!
S3
CodeBuildで作成されたArtifacts(成果物)などを保存しておくためのS3を作成
ArtifactBucket:
Type: AWS::S3::Bucket
Properties:
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
CloudWatch Event
CodeCommitのリポジトリでmain
ブランチの状態変更を検出するCloudWatch Eventを作成
referenceCreated
: 対象のブランチが作成されたとき
referenceUpdated
: 対象のブランチがマージなどによって更新されたとき
CodeCommit イベントのモニタリング
CloudWatch Events Event Examples From Supported Services
CloudWatch Events のイベントパターン
上記のイベント発生時にcodepipeline
を起動させる
AmazonCloudWatchEventRule:
Type: AWS::Events::Rule
Properties:
EventPattern:
source:
- aws.codecommit
detail-type:
- CodeCommit Repository State Change
resources:
- Fn::Join:
- ''
- - 'arn:aws:codecommit:'
- !Ref 'AWS::Region'
- ':'
- !Ref 'AWS::AccountId'
- ':'
- !Ref CodeCommitRepositoryName
detail:
event:
- referenceCreated
- referenceUpdated
referenceType:
- branch
referenceName:
- main
Targets:
- Arn: !Sub arn:aws:codepipeline:${AWS::Region}:${AWS::AccountId}:${Pipeline}
RoleArn: !GetAtt CloudwatchEventRole.Arn
Id: codepipeline-AppPipeline
Deployment Groupの作成
CodePipelineのパラメータに指定する必要があるDeploy用のApplicationsとDeploymentGroupの作成を手動で行う
なぜCFnで作成しないかというと現状、CodeDeployによる ECS blue/green deploymentsのDeployment Group作成にAWSが対応していない
Amazon ECS blue/green deployments through CodeDeploy do not use the AWS::CodeDeploy::DeploymentGroup resource. To perform Amazon ECS blue/green deployments, use the AWS::CodeDeploy::BlueGreen hook.
CFnで完結させる方法もあるが、いくつか制約があるので今回は手動作成で行う
デプロイ用のアプリケーションはApplication NameとCompute typeを指定するのみ
Deployment Groupに関しては、
Deployment group name
, Service role
, ECS cluster name
, Load balancers
, Target group
などの指定が必要になる
クラスターやロードバランサーなどは
2. ECS on Fargateの構築
で作成したCFnによって構築されるものを指定する必要がある
CodeBuildの作成
CodeBuildではDocker imageの作成やテストなどを行うことができる
今回はDocker imageを作成しECRにpushする処理を行う
Artifacts
CodeBuild
によって作成されるartifactsの出力設定を指定する
-
Type
artifactの種類CODEPIPELINE | NO_ARTIFACTS | S3
-
Location
CodeBuildが生成するartifactの出力場所
Type
によって有効な値が異なる
CODEPIPELINE
=> CodePipelineが出力場所を管理するのでこの値は無視される
NO_ARTIFACTS
=> artifactがないので無視される
S3
=> bucket name
Source
CodeBuildで処理する対象となるソースコードの設定を指定する
-
Type
ソースコードが存在するリポジトリタイプ
CODECOMMIT
CODEPIPELINE
Github
S3
などなど
CODEPIPELINE
が指定された場合ソースコードに関する設定はCodePipelineが管理する -
Location
Type
で指定したリポジトリタイプに応じて実際のソースコードが存在するURLなどを指定する -
BuildSpec
CodeBuildで行う実際の処理を記述したもの
この値が設定されなかった場合はプロジェクトのルートパス配下に.buildspec.yml
ファイルを置く必要がある -
Environment
CodeBuildを実行する環境の設定
CodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: !Ref AWS::StackName
ServiceRole: !Ref CodeBuildServiceRole
Artifacts:
Type: CODEPIPELINE
Source:
Type: CODEPIPELINE
BuildSpec: |
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- $(aws ecr get-login --no-include-email)
- IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -t $REPOSITORY_URI:$IMAGE_TAG .
- docker tag $REPOSITORY_URI:$IMAGE_TAG $REPOSITORY_URI:$IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker images...
- docker push $REPOSITORY_URI:$IMAGE_TAG
- echo Writing imageDetail json...
- echo "{\"name\":\"${ContainerName}\",\"ImageURI\":\"${REPOSITORY_URI}:${IMAGE_TAG}\"}" > imageDetail.json
artifacts:
files: imageDetail.json
Environment:
PrivilegedMode: true
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/standard:4.0
Type: LINUX_CONTAINER
EnvironmentVariables:
- Name: AWS_DEFAULT_REGION
Value: !Ref AWS::Region
- Name: REPOSITORY_URI
Value: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${ECRName}
- Name: ContainerName
Value: !Ref ContainerName
- Name: DOCKER_BUILDKIT
Value: '1'
CodePipelineの作成
Source, Build, Deployなど一連のCI/CDの流れを管理するPipelineを指定する
ArtifactStore
では、CodeBuildで指定しなかったArtifactの保存先の設定を行う
ドキュメント見た感じS3のみ指定可能で、S3 bucketの名前を指定する
InputArtifacts
、OutputArtifacts
を指定することによって各ステージでのArtifactに関する入出力の対象となるものを指定する
各ステージで利用可能なConfiguration
などのプロパティの値は以下の記事を参考
Action structure requirements in CodePipeline
CodeCommit
CodeBuild
CodeDeployToECS
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
RoleArn: !GetAtt CodePipelineServiceRole.Arn
Name: !Sub ${ProjectName}-pipeline
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucket
Stages:
- Name: Source
Actions:
- Name: SourceAction
ActionTypeId:
Category: Source
Owner: AWS
Version: '1'
Provider: CodeCommit
Configuration:
RepositoryName: !Ref CodeCommitRepositoryName
PollForSourceChanges: false
BranchName: main
RunOrder: 1
OutputArtifacts:
- Name: SourceArtifact
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Version: '1'
Provider: CodeBuild
Configuration:
ProjectName: !Ref CodeBuildProject
RunOrder: 1
InputArtifacts:
- Name: SourceArtifact
OutputArtifacts:
- Name: BuildOutput
# - Name: Approval
# Actions:
# - Name: Manual_Approval
# ActionTypeId:
# Category: Approval
# Owner: AWS
# Version: '1'
# Provider: Manual
# Configuration:
# CustomData: !Sub '${ServiceName} will be updated. Do you want to deploy it?'
# NotificationArn: arn:aws:sns:ap-northeast-1:xxxxxxxx:hogehoge
- Name: Deploy
Actions:
- Name: Deploy
ActionTypeId:
Category: Deploy
Owner: AWS
Version: '1'
Provider: CodeDeployToECS
Configuration:
AppSpecTemplateArtifact: App
AppSpecTemplatePath: appspec.yaml
TaskDefinitionTemplateArtifact: App
TaskDefinitionTemplatePath: taskdef.json
ApplicationName: !Ref CodeDeployAppName
DeploymentGroupName: !Ref CodeDeployDGName
Image1ArtifactName: BuildOutput
Image1ContainerName: IMAGE1_NAME
RunOrder: 1
InputArtifacts:
- Name: App
- Name: BuildOutput
Region: !Ref AWS::Region
taskdef.json
ECSのタスク定義を記述したファイル
<IMAGE1_NAME>
はプレースホルダーとして機能する
以下のようなファイルは 2. で行ったECS構築の際に作成したタスク定義からJSON formatをコピーしてimage
propertyの部分を変更するなどして作成すると良い
{
"executionRoleArn": "arn:aws:iam::<AccountID>:role/<ProjectName>-ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "<ProjctName>-backend-application",
"image": "<IMAGE1_NAME>",
"essential": true,
"portMappings": [
{
"hostPort": 80,
"protocol": "tcp",
"containerPort": 80
}
]
}
],
"requiresCompatibilities": ["FARGATE"],
"networkMode": "awsvpc",
"cpu": "256",
"memory": "512",
"family": "ecs-demo"
}
appspec.yml
デプロイ手順をまとめたもの
<TASK_DEFINITION>
はプレースホルダーでありデプロイステージで自動的に対象のタスク定義のバージョンに置き換わる
ContainerName
: これは、Amazon ECS アプリケーションを含む Amazon ECS コンテナの名前。Amazon ECS タスク定義で指定されたコンテナにする必要があります。
version: 1
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: <TASK_DEFINITION>
LoadBalancerInfo:
ContainerName: "<ProjectName>-backend-application"
ContainerPort: 80
taskdef.json
appspec.yaml
ともにプレースホルダーを使用しているがここにはどこから何が入るのだろうが
ここらは予想になってしまう
まず、CodeBuildでartifactとして
echo "{\"name\":\"${ContainerName}\",\"ImageURI\":\"${REPOSITORY_URI}:${IMAGE_TAG}\"}" > imageDetail.json
上記のファイルを出力している
このファイルにimageURI
が含まれている。これが、<IMAGE1_NAME>
のプレースホルダーに代入されるのではないか
次にappspec.yaml
にてTASK_DEFINITION
が使用されているがここにはtaskdef.json
によって作成されたタスク定義が代入されるのではないかと予想している
次にやりたいこと
- Sorce ProviderをCode CommitからGithubに変更
- Code BuildをCircle CIに移行
- CodePiplienを廃止し、CodeDeployのみAWSで実行
- Deploy完了を通知
- Fargateを使用しない間停止させる
- Terraformでで構築