自分のメモとして残します。
手動でECSを立ち上げる
サンプルアプリ
今回利用するアプリをローカルで実行してみます。
const http = require('http')
var app = function(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello\n');
};
http.createServer(app).listen(3000);
FROM node:12
WORKDIR /usr/src/app
COPY app.js .
EXPOSE 3000
CMD [ "node", "app.js" ]
試しに実行してみます。
$ docker build -t ecs_node:latest .
$ docker run -p 3000:3000 -d ecs_node:latest
$ curl localhost:3000
Hello
CloudFormationテンプレート
各種テンプレートです。
AWSTemplateFormatVersion: '2010-09-09'
Resources:
Repository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: deploy-test-repository
Outputs:
Repository:
Description: Test Repository
Value: !Ref Repository
Export:
Name: DeployTestRepository
AWSTemplateFormatVersion: '2010-09-09'
Resources:
Cluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: deploy-test-cluster
Outputs:
Cluster:
Description: ECS Cluster
Value: !Ref Cluster
Export:
Name: DeployTestCluster
ECSサービスのテンプレートファイルです。
DockerImageは更新されることが想定されるため、パラメータとして渡せるようにしています。
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
SecurityGroup:
Type: String
Subnet:
Type: String
DockerImage:
Type: String
Resources:
# Fargate の起動に必要な権限
ExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service: ecs-tasks.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
Path: /
# Fargate(アプリケーション)に付与する権限
TaskRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service: ecs-tasks.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
Path: /
# Task定義
ApTaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
# Family: !Ref AWS::StackName
ExecutionRoleArn: !GetAtt ExecutionRole.Arn
RequiresCompatibilities:
- FARGATE
ContainerDefinitions:
- Name: app
Image: !Ref DockerImage
Essential: true
PortMappings:
- ContainerPort: 3000
Cpu: 256
Memory: 512
NetworkMode: awsvpc
TaskRoleArn: !GetAtt TaskRole.Arn
# Service定義
FargateService:
Type: AWS::ECS::Service
Properties:
Cluster: !ImportValue DeployTestCluster
LaunchType: FARGATE
DesiredCount: 1
TaskDefinition: !Ref ApTaskDefinition
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- !Ref SecurityGroup
Subnets:
- !Ref Subnet
スタック作成
まずリポジトリとECSクラスターを作成します。
$ aws cloudformation deploy --template ecr.yaml --stack-name create-ecr
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - create-ecr
$ aws cloudformation deploy --template ecs-cluster.yaml --stack-name create-ecs-cluster
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - create-ecs-cluster
続いて作成したECRにサンプルアプリのDockerイメージをpushします。
詳細はこちらをどうぞ Dockerイメージをレジストリに登録する(ECR含む)
$ $(aws ecr get-login --no-include-email)
$ aws ecr describe-repositories
{
"repositories": [
{
"repositoryArn": "arn:aws:ecr:ap-northeast-1:XXXXXXXX:repository/deploy-test-repository",
"registryId": "XXXXXXXX",
"repositoryName": "deploy-test-repository",
"repositoryUri": "XXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/deploy-test-repository",
"createdAt": 1592618305.0
}
]
}
$ docker image tag ecs_node:latest XXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/deploy-test-repository:init
$ docker push XXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/deploy-test-repository:init
デプロイ
$ aws cloudformation deploy --template ecs-service.yaml --stack-name create-ecs-service --capabilities CAPABILITY_IAM --parameter-overrides DockerImage=XXXXXXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/deploy-test-repository:init
デプロイ結果の確認
IPアドレスはECSをコンソールで開いて確認しました。
なぜかaws ecs describe-tasks
だと表示されませんでした・・・
$ curl http://XXX.XXX.XXX.XXX:3000/
Hello
CodePiepelineの作成
作業の前にGithubでアクセストークンを設定してください。
私は以下参考にして設定しました。
CFnでGitHub + Fargate + CodePipelineを構築してみる
テンプレートファイルの作成
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
SecurityGroup:
Type: String
Subnet:
Type: String
GitHubRepositoryName:
Type: String
GitHubAccountName:
Type: String
GitHubOAuthToken:
Type: String
Resources:
CodeBuildServiceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service: codebuild.amazonaws.com
Policies:
- PolicyName: code-build-service
PolicyDocument:
Statement:
- Effect: Allow
Action:
- cloudformation:ValidateTemplate
Resource: "*"
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- ecr:GetAuthorizationToken
- s3:GetObject
- s3:PutObject
- s3:GetObjectVersion
- ecr:GetDownloadUrlForLayer
- ecr:BatchGetImage
- ecr:BatchCheckLayerAvailability
- ecr:PutImage
- ecr:InitiateLayerUpload
- ecr:UploadLayerPart
- ecr:CompleteLayerUpload
Resource: "*"
Path: /
# アプリケーションのテストやビルドの定義(開発環境)
CodeBuild:
Type: AWS::CodeBuild::Project
Properties:
Name: deploy-test-build
ServiceRole: !Ref CodeBuildServiceRole
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Type: LINUX_CONTAINER
Image: aws/codebuild/docker:18.09.0-1.7.0
PrivilegedMode: true
EnvironmentVariables:
- Name: REPOSITORY_URI
Value: !Sub
- ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Repository}
- Repository: !ImportValue DeployTestRepository
- Name: Subnet
Value: !Ref Subnet
- Name: SecurityGroup
Value: !Ref SecurityGroup
Artifacts:
Type: CODEPIPELINE
Source:
Type: CODEPIPELINE
BuildSpec: ecs_deploy/application/deploy/buildspec.yaml
# CodePipelineに適用するIAMRole
CodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
Path: /
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service: codepipeline.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: code-pipeline-service
PolicyDocument:
Version: 2012-10-17
Statement:
- Resource: "*"
Effect: Allow
Action:
- codecommit:GetRepository
- codecommit:ListBranches
- codecommit:GetUploadArchiveStatus
- codecommit:UploadArchive
- codecommit:CancelUploadArchive
- cloudformation:CreateChangeSet
- cloudformation:CreateStack
- cloudformation:CreateUploadBucket
- cloudformation:DeleteStack
- cloudformation:Describe*
- cloudformation:List*
- cloudformation:UpdateStack
- cloudformation:ValidateTemplate
- cloudformation:ExecuteChangeSet
- codebuild:StartBuild
- codebuild:StopBuild
- codebuild:BatchGet*
- codebuild:Get*
- codebuild:List*
- codecommit:GetBranch
- codecommit:GetCommit
- s3:*
- iam:PassRole
# S3Bucket
ArtifactBucket:
Type: AWS::S3::Bucket
# 外部イベント発生のwebhook
PipelineWebhook:
Type: "AWS::CodePipeline::Webhook"
Properties:
Authentication: GITHUB_HMAC
AuthenticationConfiguration:
SecretToken: "secret"
Filters:
- JsonPath: "$.ref"
MatchEquals: "refs/heads/master"
TargetPipeline: !Ref Pipeline
TargetAction: SourceAction
Name: GitHubPipelineWebhook
TargetPipelineVersion: !GetAtt Pipeline.Version
RegisterWithThirdParty: "true"
# 継続的デプロイに必要な権限
CloudFormationRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service: cloudformation.amazonaws.com
Policies:
- PolicyName: code-pipeline-service
PolicyDocument:
Statement:
- Effect: Allow
Action:
- iam:*
- ecs:RegisterTaskDefinition
- ecs:DeregisterTaskDefinition
- ecs:CreateService
- ecs:Delete*
- logs:Delete*
- cloudwatch:Delete*
Resource: "*"
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
- arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole
- arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole
Path: /
# CodePipeLine
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
RoleArn: !GetAtt CodePipelineServiceRole.Arn
Name: pipeline-test-build
ArtifactStore:
Type: S3
Location: !Ref ArtifactBucket
Stages:
- Name: Source
Actions:
- Name: SourceAction
ActionTypeId:
Category: Source
Owner: ThirdParty
Version: 1
Provider: GitHub
Configuration:
Owner: !Ref GitHubAccountName
Repo: !Ref GitHubRepositoryName
PollForSourceChanges: false
Branch: master
OAuthToken: !Ref GitHubOAuthToken
RunOrder: 1
OutputArtifacts:
- Name: SourceCode
- Name: Build
Actions:
- Name: Build
ActionTypeId:
Category: Build
Owner: AWS
Version: 1
Provider: CodeBuild
Configuration:
ProjectName: !Ref CodeBuild
RunOrder: 1
InputArtifacts:
- Name: SourceCode
OutputArtifacts:
- Name: BuildOutput
- Name: Deploy
Actions:
Actions:
- Name: CreateEdgeChangeSet
ActionTypeId:
Category: Deploy
Provider: CloudFormation
Owner: AWS
Version: 1
Configuration:
StackName: create-ecs-service
ActionMode: CHANGE_SET_REPLACE
ChangeSetName: ChangeSet
RoleArn: !GetAtt CloudFormationRole.Arn
Capabilities: CAPABILITY_NAMED_IAM
TemplatePath: SourceCode::ecs_deploy/application/deploy/ecs-service.yaml
TemplateConfiguration: BuildOutput::config.json
InputArtifacts:
- Name: SourceCode
- Name: BuildOutput
OutputArtifacts:
- Name: CreatedEdgeChangeSet
RunOrder: 1
- Name: ExecuteEdgeChangeSet
ActionTypeId:
Category: Deploy
Provider: CloudFormation
Owner: AWS
Version: 1
Configuration:
StackName: create-ecs-service
ActionMode: CHANGE_SET_EXECUTE
ChangeSetName: ChangeSet
InputArtifacts:
- Name: CreatedEdgeChangeSet
OutputArtifacts:
- Name: EdgeDeployed
RunOrder: 2
スタック作成します。
各種パラメータもここで設定して実行します。
$ aws cloudformation deploy --template code-pipeline.yaml --stack-name create-code-pipeline --parameter-overrides SecurityGroup=sg-XXXXXX Subnet=subnet-XXXXXXXX GitHubRepositoryName=XXXX GitHubAccountName=XXXXX GitHubOAuthToken=XXXXXXXXXXXXXXXXXXXXXXX --capabilities CAPABILITY_IAM
buildSpecの作成
buildSpecを作成します。
途中でDEFAULT変数にパスを格納して戻っていますが、最後にconfig.jsonを出力する際に、初期の場所で吐き出さないとエラーになってしまうからです。
ここで出力したファイルは、CloudFormationを更新するときのパラメータとして利用されます。
version: 0.2
phases:
pre_build:
commands:
- IMAGE_NAME="${REPOSITORY_URI}:$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} | head -c 7)"
- $(aws ecr get-login --no-include-email)
build:
commands:
- echo Build started on $(date)
- DEFAULT=`pwd`
- cd ./ecs_deploy/application
- docker build --tag ${IMAGE_NAME} .
- docker push ${IMAGE_NAME}
- cd ${DEFAULT}
post_build:
commands:
- echo Build completed on $(date)
- printf '{"Parameters":{"Subnet":"%s","SecurityGroup":"%s","DockerImage":"%s"}}' ${Subnet} ${SecurityGroup} ${IMAGE_NAME} > config.json
artifacts:
files: config.json
結果確認
対象リポジトリを修正してpushすれば修正した内容がデプロイされるようになります!!
const http = require('http')
var app = function(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Goodmorning\n');
};
http.createServer(app).listen(3000);
$ curl http://YYY.YYY.YYY.YYY:3000/
Goodmorning
今回は省きましたが、ECS Service Discoveryを利用することで新しく作成されたIPをRoute53に登録可能です。
参考情報
Jawsug Fargate アプリケーションの継続的デリバリー
Classmethod CloudFormationのスタック間でリソースを参照する
AWSユーザーガイド ECS リソースタイプのリファレンス
今回のコードは以下に配置しております。
https://github.com/uc4w6c/aws-test/tree/master/ecs_deploy