はじめに
30代未経験からエンジニアを目指して勉強中のYNと申します。
自分の学習記録としてまとめます。
CDKを使いたい人はこちら↓
全体像
下図のような開発環境をバシっとCloudFormationでつくります。
Githubのmainブランチに変更をpushするたびに自動でデプロイされます。
※Docker Hubアカウントにログインせずにビルドを行っているため、Docker Hubの制限に引っかかる場合があります。詳細はこちら
↑ Docker Hubアカウントにログインしなくても、NatGWに関連付けされたプライベートサブネット内でビルドを行えば問題ありません。
※※頻繁にStackの削除を行うと、ACM証明書の上限に引っかかります。詳細はこちら
前提条件
- AWS-CLIを設定済み
- ドメインを取得後、ネームサーバーの設定済(参照)
1) ECRのレポジトリを作成
これ以降、Regionはus-east-2
になってます。レポジトリ名はecr-test
にしておきます。
AWSTemplateFormatVersion: '2010-09-09'
Description: make ECR repo.
Parameters:
EnvironmentName:
Type: String
Default: ecs-course
Description: 'A name that will be used for namespacing our cluster resources.'
ECRName:
Type: String
Default: ecr-test
Description: 'A name that will be used for this ecr'
Resources:
Repository:
Type: AWS::ECR::Repository
Properties:
RepositoryName: !Ref ECRName
Outputs:
Repository:
Description: Test Repository
Value: !Ref Repository
Export:
Name: !Sub ${EnvironmentName}:ECRName
aws cloudformation create-stack --stack-name ecr --capabilities CAPABILITY_IAM --template-body file://./ecr.yml
2) Node.js APIを作成
今回デプロイする簡単なAPIです。ポートが3000ということだけチェックしておいて下さい。
const http = require('http');
var app = function (req, res) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('HELLO WORLD\n');
};
http.createServer(app).listen(3000);
ビルドの設定です。
FROM node:12
WORKDIR /usr/src/app
COPY app.js .
EXPOSE 3000
CMD [ "node", "app.js" ]
3) APIをビルドしてECRにpush
ビルドします。
docker build -t node-api ./Docker
ECRの認証をします。
aws ecr get-login-password --region us-east-2 | docker login --username AWS --password-stdin <your-aws-account-number>.dkr.ecr.us-east-2.amazonaws.com
ビルドしたイメージにタグをつけます。このタグがイメージの名前となります。
取り合えず最初のpushなのでinit
としておきます。
docker tag node-api <your-aws-account-number>.dkr.ecr.us-east-2.amazonaws.com/ecr-test:init
タグで指定してECRにpushします。
docker push <your-aws-account-number>.dkr.ecr.us-east-2.amazonaws.com/ecr-test
4) Node.jsのAPIのECS (Fargate) サービスをHTTPSで公開
4-1) IAMロールの作成
AWSTemplateFormatVersion: '2010-09-09'
Description: create IAM roles for ECS usage upfront
Parameters:
EnvironmentName:
Type: String
Default: ecs-course
Description: 'A name that will be used for namespacing all cluster resources.'
Resources:
ECSTaskExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [ecs-tasks.amazonaws.com]
Action: ['sts:AssumeRole']
Path: /
# 下記ManagedPolicyArnsとPoliciesは同義。ただし下のように実際に書き出した方がわかりやすい
# ManagedPolicyArns:
# - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
Policies:
- PolicyName: AmazonECSTaskExecutionRolePolicy
PolicyDocument:
Statement:
- Effect: Allow
Action:
# Allow the ECS Tasks to download images from ECR
- 'ecr:GetAuthorizationToken'
- 'ecr:BatchCheckLayerAvailability'
- 'ecr:GetDownloadUrlForLayer'
- 'ecr:BatchGetImage'
# Allow the ECS tasks to upload logs to CloudWatch
- 'logs:CreateLogStream'
- 'logs:PutLogEvents'
Resource: '*'
AutoscalingRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: [application-autoscaling.amazonaws.com]
Action: ['sts:AssumeRole']
Path: /
Policies:
- PolicyName: service-autoscaling
PolicyDocument:
Statement:
- Effect: Allow
Action:
- 'application-autoscaling:*'
- 'cloudwatch:DescribeAlarms'
- 'cloudwatch:PutMetricAlarm'
- 'ecs:DescribeServices'
- 'ecs:UpdateService'
Resource: '*'
Outputs:
AutoscalingRole:
Description: The ARN of the role used for autoscaling
Value: !GetAtt 'AutoscalingRole.Arn'
Export:
Name: !Sub ${EnvironmentName}:AutoscalingRole
ECSTaskExecutionRole:
Description: The ARN of the ECS role
Value: !GetAtt 'ECSTaskExecutionRole.Arn'
Export:
Name: !Sub ${EnvironmentName}:ECSTaskExecutionRole
aws cloudformation create-stack --stack-name ecs-iam-role-setting --capabilities CAPABILITY_IAM --template-body file://./create_IAM_roles.yml
4-2) VPCの設定
AWSTemplateFormatVersion: '2010-09-09'
Description: VPC and subnets as base for an ECS cluster
Parameters:
EnvironmentName:
Type: String
Default: ecs-course
Mappings:
SubnetConfig:
VPC:
CIDR: '172.16.0.0/16'
PublicOne:
CIDR: '172.16.0.0/24'
PublicTwo:
CIDR: '172.16.1.0/24'
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
EnableDnsSupport: true
EnableDnsHostnames: true
CidrBlock: !FindInMap ['SubnetConfig', 'VPC', 'CIDR']
PublicSubnetOne:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 0
- Fn::GetAZs: { Ref: 'AWS::Region' }
VpcId: !Ref 'VPC'
CidrBlock: !FindInMap ['SubnetConfig', 'PublicOne', 'CIDR']
MapPublicIpOnLaunch: true
PublicSubnetTwo:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone:
Fn::Select:
- 1
- Fn::GetAZs: { Ref: 'AWS::Region' }
VpcId: !Ref 'VPC'
CidrBlock: !FindInMap ['SubnetConfig', 'PublicTwo', 'CIDR']
MapPublicIpOnLaunch: true
InternetGateway:
Type: AWS::EC2::InternetGateway
GatewayAttachement:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref 'VPC'
InternetGatewayId: !Ref 'InternetGateway'
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref 'VPC'
PublicRoute:
Type: AWS::EC2::Route
DependsOn: GatewayAttachement
Properties:
RouteTableId: !Ref 'PublicRouteTable'
DestinationCidrBlock: '0.0.0.0/0'
GatewayId: !Ref 'InternetGateway'
PublicSubnetOneRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetOne
RouteTableId: !Ref PublicRouteTable
PublicSubnetTwoRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnetTwo
RouteTableId: !Ref PublicRouteTable
Outputs:
DefaultRegion:
Description: The default region setting on aws-cli
Value: { Ref: 'AWS::Region' }
Export:
Name: !Sub ${EnvironmentName}:Region
VpcId:
Description: The ID of the VPC that this stack is deployed in
Value: !Ref 'VPC'
Export:
Name: !Sub ${EnvironmentName}:VpcId
PublicSubnetOne:
Description: Public subnet one
Value: !Ref 'PublicSubnetOne'
Export:
Name: !Sub ${EnvironmentName}:PublicSubnetOne
PublicSubnetTwo:
Description: Public subnet two
Value: !Ref 'PublicSubnetTwo'
Export:
Name: !Sub ${EnvironmentName}:PublicSubnetTwo
aws cloudformation create-stack --stack-name ecs-core-infrastructure --capabilities CAPABILITY_IAM --template-body file://./vpc-setup.yml
4-3) ECSクラスターの設定
AWSTemplateFormatVersion: '2010-09-09'
Description: ECS cluster launchtype Fargate.
Parameters:
EnvironmentName:
Type: String
Default: ecs-course
Description: 'A name that will be used for namespacing our cluster resources.'
ClusterName:
Type: String
Default: ecs-course-fargate
Description: 'A name that will be used for the ecs cluster.'
ContainerPort:
Type: Number
Default: 3000 #nodeの解放ポートに合わせます
Description: What port number the application inside the docker container is binding to
Resources:
ECSCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: !Ref 'ClusterName'
ContainerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the ECS hosts that run containers
VpcId:
Fn::ImportValue: !Sub ${EnvironmentName}:VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: !Ref 'ContainerPort'
CidrIp: 172.16.0.0/16 #VPC内からの通信のみ許可
Outputs:
ECSCluster:
Description: The name of the ECS cluster
Value: !Ref 'ECSCluster'
Export:
Name: !Sub ${EnvironmentName}:ECSCluster
ClusterName:
Description: The name of the ECS cluster
Value: !Ref 'ClusterName'
Export:
Name: !Sub ${EnvironmentName}:ClusterName
ContainerSecurityGroup:
Description: The container sg of the ECS cluster
Value: !Ref 'ContainerSecurityGroup'
Export:
Name: !Sub ${EnvironmentName}:ContainerSecurityGroup
aws cloudformation create-stack --stack-name ecs-cluster --capabilities CAPABILITY_IAM --template-body file://./ecs-cluster.yml
4-4) Route53 + ACM + ALB + ECSの設定
AWSTemplateFormatVersion: '2010-09-09'
Description: External, public facing load balancer, for forwarding public traffic to containers
Parameters:
DomainName:
Description: FQDN of the HostZone
Type: String
Default: 'example.com' #用意したドメイン
SubDomain:
Description: FQDN of the certificate
Type: String
Default: 'test.example.com' #任意のサブドメイン
HostZoneId:
Description: FQDN of the hosted zone
Type: String
Default: 'Z05XXXXXXXXXXXXXX' #Route53のホストゾーン
EnvironmentName:
Type: String
Default: ecs-course
ServiceName:
Type: String
Default: node-service
Description: A name for the service
ImageUrl:
Type: String
Default: <your-aws-account-number>.dkr.ecr.us-east-2.amazonaws.com/ecr-test:init #最初のイメージ
Description:
The url of a docker image that contains the application process that
will handle the traffic for this service
ContainerPort:
Type: Number
Default: 3000
Description: What port number the application inside the docker container is binding to
ContainerCpu:
Type: Number
Default: 256
Description: How much CPU to give the container. 1024 is 1 CPU
ContainerMemory:
Type: Number
Default: 512
Description: How much memory in megabytes to give the container
Priority:
Type: Number
Default: 1
Description: The priority for the routing rule added to the load balancer.
This only applies if your have multiple services which have been
assigned to different paths on the load balancer.
DesiredCount:
Type: Number
Default: 1
Description: How many copies of the service task to run
Role:
Type: String
Default: ''
Description:
(Optional) An IAM role to give the service's containers if the code within needs to
access other AWS resources like S3 buckets, DynamoDB tables, etc
Conditions:
HasCustomRole: !Not [!Equals [!Ref 'Role', '']]
Resources:
# A log group for storing the stdout logs from this service's containers
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub ${EnvironmentName}-service-${ServiceName}
# The task definition. This is a simple metadata description of what
# container to run, and what resource requirements it has.
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Ref 'ServiceName'
Cpu: !Ref 'ContainerCpu'
Memory: !Ref 'ContainerMemory'
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ExecutionRoleArn:
Fn::ImportValue: !Sub ${EnvironmentName}:ECSTaskExecutionRole
TaskRoleArn:
Fn::If: #三項演算子。1行目がtrueなら2行目が参照され、falseなら3行目が参照される
- 'HasCustomRole'
- !Ref 'Role'
- !Ref 'AWS::NoValue'
ContainerDefinitions:
- Name: !Ref 'ServiceName'
Cpu: !Ref 'ContainerCpu'
Memory: !Ref 'ContainerMemory'
Essential: true
Image: !Ref 'ImageUrl'
PortMappings:
- ContainerPort: !Ref 'ContainerPort'
LogConfiguration:
LogDriver: 'awslogs'
Options:
awslogs-group: !Sub ${EnvironmentName}-service-${ServiceName}
awslogs-region: !Ref 'AWS::Region'
awslogs-stream-prefix: !Ref 'ServiceName'
# The service. The service is a resource which allows you to run multiple
# copies of a type of task, and gather up their logs and metrics, as well
# as monitor the number of running tasks and replace any that have crashed
Service:
Type: AWS::ECS::Service
DependsOn: HTTPSLoadBalancerListener
Properties:
ServiceName: !Ref 'ServiceName'
Cluster:
Fn::ImportValue: !Sub ${EnvironmentName}:ECSCluster
LaunchType: FARGATE
DeploymentConfiguration:
MaximumPercent: 200
MinimumHealthyPercent: 75
DesiredCount: !Ref 'DesiredCount'
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
SecurityGroups:
- Fn::ImportValue: !Sub ${EnvironmentName}:ContainerSecurityGroup
Subnets:
- Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetOne
- Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetTwo
TaskDefinition: !Ref 'TaskDefinition'
LoadBalancers:
- ContainerName: !Ref 'ServiceName'
ContainerPort: !Ref 'ContainerPort'
TargetGroupArn: !Ref 'TargetGroup'
ApiDomain:
Type: AWS::Route53::RecordSet
DependsOn: PublicLoadBalancer
Properties:
HostedZoneId: !Sub '${HostZoneId}'
Name: !Sub '${SubDomain}'
Type: CNAME
TTL: 60
ResourceRecords:
- !GetAtt PublicLoadBalancer.DNSName
ACMCertificate:
Type: AWS::CertificateManager::Certificate
Properties:
DomainName: !Sub '${SubDomain}'
DomainValidationOptions:
- DomainName: !Sub '${SubDomain}'
HostedZoneId: !Sub '${HostZoneId}'
ValidationMethod: DNS
PublicLoadBalancerSG:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Access to the public facing load balancer
VpcId:
Fn::ImportValue: !Sub ${EnvironmentName}:VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
PublicLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
LoadBalancerAttributes:
- Key: idle_timeout.timeout_seconds
Value: '30'
Subnets:
- Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetOne
- Fn::ImportValue: !Sub ${EnvironmentName}:PublicSubnetTwo
SecurityGroups: [!Ref 'PublicLoadBalancerSG']
HTTPLoadBalancerListener: # 80=>443に転送する
Type: AWS::ElasticLoadBalancingV2::Listener
DependsOn:
- PublicLoadBalancer
Properties:
DefaultActions:
- Type: 'redirect'
RedirectConfig:
Protocol: 'HTTPS'
Port: 443
Host: '#{host}'
Path: '/#{path}'
Query: '#{query}'
StatusCode: 'HTTP_301'
LoadBalancerArn: !Ref 'PublicLoadBalancer'
Port: 80
Protocol: HTTP
HTTPSLoadBalancerListener: # SSL設定してTargetGroupに転送
Type: AWS::ElasticLoadBalancingV2::Listener
DependsOn: PublicLoadBalancer
Properties:
LoadBalancerArn: !Ref 'PublicLoadBalancer'
Port: 443
Protocol: HTTPS
DefaultActions:
- Type: 'forward'
TargetGroupArn: !Ref 'TargetGroup'
SslPolicy: 'ELBSecurityPolicy-2016-08'
Certificates:
- CertificateArn: !Ref 'ACMCertificate'
TargetGroup: # Serviceを参照させる
Type: AWS::ElasticLoadBalancingV2::TargetGroup
DependsOn: PublicLoadBalancer
Properties:
HealthCheckIntervalSeconds: 6
HealthCheckPath: /
HealthCheckProtocol: HTTP
HealthCheckTimeoutSeconds: 5
HealthyThresholdCount: 2
TargetType: ip #fargateなのでVPC内のipをtargetにする。EC2であればinstanceがtargetになる
Name: !Ref 'ServiceName'
Port: !Ref 'ContainerPort'
Protocol: HTTP
UnhealthyThresholdCount: 2
VpcId:
Fn::ImportValue: !Sub ${EnvironmentName}:VpcId
Outputs:
ExternalUrl:
Description: The url of the external load balancer
Value: !Sub https://${PublicLoadBalancer.DNSName}
Export:
Name: !Sub ${EnvironmentName}:ExternalUrl
ServiceName:
Description: The name of ecs service
Value: !Ref 'ServiceName'
Export:
Name: !Sub ${EnvironmentName}:ServiceName
aws cloudformation create-stack --stack-name https-node-service --capabilities CAPABILITY_IAM --template-body file://./https-node-service.yml
4-5) オートスケーリングの設定
AWSTemplateFormatVersion: '2010-09-09'
Description: AutoScaling setup for fargate service
Parameters:
EnvironmentName:
Type: String
Default: ecs-course
Description: 'A name that will be used for namespacing our cluster resources.'
TaskMinContainerCount:
Type: Number
Description: Minimum number of containers to run for the service
Default: 1
MinValue: 1
ConstraintDescription: Value must be at least one
TaskMaxContainerCount:
Type: Number
Description: Maximum number of containers to run for the service when auto scaling out
Default: 2
MinValue: 1
ConstraintDescription: Value must be at least one
ServiceScaleEvaluationPeriods:
Description: The number of periods over which data is compared to the specified threshold
Type: Number
Default: 2
MinValue: 2
ServiceCpuScaleOutThreshold:
Type: Number
Description: Average CPU value to trigger auto scaling out
Default: 50
MinValue: 0
MaxValue: 100
ConstraintDescription: Value must be between 0 and 100
ServiceCpuScaleInThreshold:
Type: Number
Description: Average CPU value to trigger auto scaling in
Default: 25
MinValue: 0
MaxValue: 100
ConstraintDescription: Value must be between 0 and 100
Resources:
ServiceScalingTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
MinCapacity: !Ref TaskMinContainerCount
MaxCapacity: !Ref TaskMaxContainerCount
ResourceId:
!Sub #この書き方により、コンテキスト内でのみ有効な変数を定義できる
- service/${ECSClusterName}/${ECSServiceName} #auto scalingを適用するECSservieをこのフォーマットで指定。
- ECSClusterName:
Fn::ImportValue: !Sub ${EnvironmentName}:ECSCluster
ECSServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
RoleARN:
Fn::ImportValue: !Sub ${EnvironmentName}:AutoscalingRole
ScalableDimension: ecs:service:DesiredCount
ServiceNamespace: ecs
ServiceScaleOutPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: !Sub
- ${EnvironmentName}-${ECSServiceName}-ScaleOutPolicy
- EnvironmentName: !Ref EnvironmentName
ECSServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
PolicyType: StepScaling
ScalingTargetId: !Ref ServiceScalingTarget
StepScalingPolicyConfiguration:
AdjustmentType: ChangeInCapacity
Cooldown: 60
MetricAggregationType: Average
StepAdjustments:
- ScalingAdjustment: 1
MetricIntervalLowerBound: 0
DependsOn: ServiceScalingTarget
ServiceScaleInPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyName: !Sub
- ${EnvironmentName}-${ECSServiceName}-ScaleInPolicy
- EnvironmentName: !Ref EnvironmentName
ECSServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
PolicyType: StepScaling
ScalingTargetId: !Ref ServiceScalingTarget
StepScalingPolicyConfiguration:
AdjustmentType: ChangeInCapacity
Cooldown: 60
MetricAggregationType: Average
StepAdjustments:
- ScalingAdjustment: -1
MetricIntervalUpperBound: 0
DependsOn: ServiceScalingTarget
ServiceScaleOutAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub
- ${EnvironmentName}-${ECSServiceName}-ScaleOutAlarm
- EnvironmentName: !Ref EnvironmentName
ECSServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
EvaluationPeriods: !Ref ServiceScaleEvaluationPeriods
Statistic: Average
TreatMissingData: notBreaching
Threshold: !Ref ServiceCpuScaleOutThreshold
AlarmDescription: Alarm to add capacity if CPU is high
Period: 60
AlarmActions:
- !Ref ServiceScaleOutPolicy
Namespace: AWS/ECS
Dimensions:
- Name: ClusterName
Value:
Fn::ImportValue: !Sub ${EnvironmentName}:ECSCluster
- Name: ServiceName
Value: !Sub
- ${EnvironmentName}-${ECSServiceName}
- EnvironmentName: !Ref EnvironmentName
ECSServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
ComparisonOperator: GreaterThanThreshold
MetricName: CPUUtilization
DependsOn:
- ServiceScaleOutPolicy
ServiceScaleInAlarm:
Type: AWS::CloudWatch::Alarm
Properties:
AlarmName: !Sub
- ${EnvironmentName}-${ECSServiceName}-ScaleInAlarm
- EnvironmentName: !Ref EnvironmentName
ECSServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
EvaluationPeriods: !Ref ServiceScaleEvaluationPeriods
Statistic: Average
TreatMissingData: notBreaching
Threshold: !Ref ServiceCpuScaleInThreshold
AlarmDescription: Alarm to reduce capacity if container CPU is low
Period: 300
AlarmActions:
- !Ref ServiceScaleInPolicy
Namespace: AWS/ECS
Dimensions:
- Name: ClusterName
Value:
Fn::ImportValue: !Sub ${EnvironmentName}:ECSCluster
- Name: ServiceName
Value: !Sub
- ${EnvironmentName}-${ECSServiceName}
- EnvironmentName: !Ref EnvironmentName
ECSServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
ComparisonOperator: LessThanThreshold
MetricName: CPUUtilization
DependsOn:
- ServiceScaleInPolicy
aws cloudformation create-stack --stack-name auto-scaling --capabilities CAPABILITY_IAM --template-body file://./auto_scaling_setup.yml
5) CodePipelineの設定
↓本記事の内容は古いのでGithub連携については下記参照
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
EnvironmentName:
Type: String
Default: ecs-course
Description: 'A name that will be used for namespacing our cluster resources.'
GitHubRepositoryName:
Type: String
Default: <your-github-repo-name> #設定要
GitHubAccountName:
Type: String
Default: <your-github-account-name> #設定要
GitHubPipelineWebhookName:
Type: String
Default: <inut-your-unique-webhook-name> # レポジトリ内の他のwebhookと被らないようにする
Branch:
Type: String
Default: <input-your-Github-branch-for-CodePipeline> #設定要
GitHubOAuthToken: #別途パラメータ入力要
Type: String
NoEcho: true #本来secretをパラメータで渡すのは良くないが、この方法ならばパラメータ画面に表示されない
Resources:
CodeBuildServiceRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service: codebuild.amazonaws.com
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser
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
- s3:GetBucketAcl
- s3:GetBucketLocation
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:
Fn::ImportValue: !Sub ${EnvironmentName}:ECRName
- Name: CONTAINER_NAME
Value:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
Artifacts:
Type: CODEPIPELINE
Source:
Type: CODEPIPELINE
BuildSpec: buildspec.yml
# 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: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Resource:
- !Sub arn:aws:s3:::${ArtifactBucket}/*
Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketVersioning
- Resource: '*'
Effect: Allow
Action:
- ecs:DescribeServices
- ecs:DescribeTaskDefinition
- ecs:DescribeTasks
- ecs:ListTasks
- ecs:RegisterTaskDefinition
- ecs:UpdateService
- codebuild:StartBuild
- codebuild:BatchGetBuilds
- iam:PassRole
# S3Bucket
ArtifactBucket:
Type: AWS::S3::Bucket
# 外部イベント発生のwebhook
PipelineWebhook:
Type: 'AWS::CodePipeline::Webhook'
Properties:
Authentication: GITHUB_HMAC
AuthenticationConfiguration:
SecretToken: !Ref GitHubOAuthToken
Filters:
- JsonPath: '$.ref'
MatchEquals: !Sub
- refs/heads/${Branch}
- Branch: !Ref Branch
- JsonPath: '$.created'
MatchEquals: false
- JsonPath: '$.deleted'
MatchEquals: false
# 特定のBranchの変更(push)を検知してwebhookを実行。このときBranchの作成と消去は無視する。
# 詳しくは、Githubの Settings => Webhooks => Recent Deliveries => Payload を見ればわかる。
TargetPipeline: !Ref Pipeline
TargetAction: SourceAction
Name: !Ref GitHubPipelineWebhookName
TargetPipelineVersion: !GetAtt Pipeline.Version
RegisterWithThirdParty: 'true'
# 継続的デプロイに必要な権限
# CodePipeLine
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
RoleArn: !GetAtt CodePipelineServiceRole.Arn
Name: test-pipeline-for-node-pjt
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 #ポーリング処理はせず、webhookで動かす。
Branch: !Ref Branch
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:
- Name: Deploy
ActionTypeId:
Category: Deploy
Owner: AWS
Version: 1
Provider: ECS
Configuration:
ClusterName:
Fn::ImportValue: !Sub ${EnvironmentName}:ClusterName
ServiceName:
Fn::ImportValue: !Sub ${EnvironmentName}:ServiceName
FileName: image.json
InputArtifacts:
- Name: BuildOutput
RunOrder: 1
version: 0.2
phases:
pre_build:
commands:
- IMAGE_URI="${REPOSITORY_URI}:$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} | head -c 7)"
- $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
build:
commands:
- echo Build started on $(date)
- DEFAULT=`pwd`
- cd ./Docker
- docker build --tag ${IMAGE_URI} .
- docker push ${IMAGE_URI}
- cd ${DEFAULT}
post_build:
commands:
- echo Build completed on $(date)
- printf '[{"name":"%s","imageUri":"%s"}]' "${CONTAINER_NAME}" "$IMAGE_URI" > image.json
# jsonのキーのnameとimageUriは必須。必要であればparametersを追加。
artifacts:
files: image.json
aws cloudformation create-stack --stack-name code-pipeline --capabilities CAPABILITY_IAM --template-body file://./code-pipeline.yml --parameters ParameterKey=GitHubOAuthToken,ParameterValue=ghp_XXXXXXXXXX