[CloudFormation] CFnで環境作り完全版 Part7. CICD編
目次
Introduction
今回作成するもの
AWSリソース

CFn相関図

HandsOn
ECR
アプリケーションをコンテナで動かすためにアプリケーションをイメージ化した物を保存しておく格納場所が必要です。そのためにECS専用のレジストリサービスであるECRを作成します。
.pre-commit-config.yaml
cloudformation
L iam-user.yaml
iam-role.yaml
cloudtrail.yaml
vpc.yaml
subnet.yaml
sg.yaml
route53.yaml
acm.yaml
s3-bucket.yaml
secretsmanager.yaml
prefix
L alb.yaml
cf.yaml
ecs-cluster.yaml
ecs-task-def.yaml
ecs-service.yaml
cw-logs.yaml
ecr.yaml(new)
AWSTemplateFormatVersion: "2010-09-09"
Description: "stackName: {Prefix}-{Env}-ecr. AWS::ECR::Repository"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Environment Name Configuration
Parameters:
- Prefix
- Env
- Project
Parameters:
Prefix:
Type: String
Description: AWS resource version management prefix
Default: v1
Env:
Type: String
Description: Project environment
AllowedValues: [dev, prd]
Project:
Type: String
Description: Project Name
Default: example
Mappings:
EnvMaps:
common:
ECRLifecyclePolicy: |
{
"rules": [
{
"rulePriority": 1,
"description": "Delete more than 10 images",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 10
},
"action": {
"type": "expire"
}
}
]
}
Resources:
ECR:
Type : "AWS::ECR::Repository"
Properties:
RepositoryName: !Sub "${Prefix}/${Env}/${Project}"
LifecyclePolicy:
LifecyclePolicyText: !FindInMap [EnvMaps, common, ECRLifecyclePolicy]
Outputs:
ECRUri:
Description: Output Export AWS::ECS::Cluster.RepositoryUri
Value: !GetAtt ECR.RepositoryUri
Export:
Name: !Sub ${Prefix}-${Env}-${Project}-ecr-uri
スタック名は、v1-dev-ecr
ECRライフサイクルポリシーを設定しています。10イメージだけ残して古い順に削除されるように設定しています。
CloudWatchLogs
次にCodePipeline中のCodeBuildの実行ログを保存するCloudWatchLogsを作成します
.pre-commit-config.yaml
cloudformation
L iam-user.yaml
iam-role.yaml
cloudtrail.yaml
vpc.yaml
subnet.yaml
sg.yaml
route53.yaml
acm.yaml
s3-bucket.yaml
secretsmanager.yaml
prefix
L alb.yaml
cf.yaml
ecs-cluster.yaml
ecs-task-def.yaml
ecs-service.yaml
cw-logs.yaml(modify)
ecr.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "stackName: {Prefix}-cw-logs. AWS::Logs::LogGroup"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Environment Name Configuration"
Parameters:
- Prefix
- Env
- Project
Parameters:
Prefix:
Type: String
Description: AWS resource version management prefix
Default: v1
Env:
Type: String
Description: Project environment
AllowedValues: [dev, prd]
Project:
Type: String
Description: Project Name
Default: example
Resources:
ECSCWLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub ecs/${Prefix}-${Env}-${Project}
RetentionInDays: 7
DeployCodeBuildCwLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub codebuild/${Prefix}-${Env}-${Project}
RetentionInDays: 7
Outputs:
ECSCWLogGroupName:
Description: Output Export AWS::Logs::LogGroup.Ref ECSCWLogGroup
Value: !Ref ECSCWLogGroup
Export:
Name: !Sub ${Prefix}-${Env}-${Project}-ecs-cw-loggroup-name
DeployCodeBuildCwLogGroupName:
Description: Output Export AWS::Logs::LogGroup.Ref DeployCodeBuildCwLogGroup
Value: !Ref DeployCodeBuildCwLogGroup
Export:
Name: !Sub ${Prefix}-${Env}-${Project}-deploy-codebuild-cw-loggroup-name
Artifact用のS3
CodePipelineで使用されるアーティファクトを保存するS3バケットを作成します。
.pre-commit-config.yaml
cloudformation
L iam-user.yaml
iam-role.yaml
cloudtrail.yaml
vpc.yaml
subnet.yaml
sg.yaml
route53.yaml
acm.yaml
s3-bucket.yaml(modify)
secretsmanager.yaml
prefix
L alb.yaml
cf.yaml
ecs-cluster.yaml
ecs-task-def.yaml
ecs-service.yaml
cw-logs.yaml
ecr.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "stackName: s3-bucket. AWS::S3::Bucket"
Parameters:
Project:
Type: String
Description: Project Name
Default: example
Mappings:
EnvMaps:
Common:
ApNortheast1ElbAccountId: "582318560864" # https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/enable-access-logging.html
Resources:
S3LogsS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${Project}-s3-logs
AccessControl: LogDeliveryWrite
OwnershipControls:
Rules:
- ObjectOwnership: ObjectWriter
VersioningConfiguration:
Status: Suspended
AwsResourceAccessLogsS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${Project}-aws-resource-access-logs
AccessControl: Private
OwnershipControls:
Rules:
- ObjectOwnership: ObjectWriter
VersioningConfiguration:
Status: Suspended
LifecycleConfiguration:
Rules:
- Id: ExpireAfter90Days
Status: Enabled
ExpirationInDays: 90
LoggingConfiguration:
DestinationBucketName: !Ref S3LogsS3Bucket
LogFilePrefix: !Sub ${Project}-aws-resource-access-logs/
AwsResourceAccessLogsS3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref AwsResourceAccessLogsS3Bucket
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: Policy4ALB
Effect: Allow
Principal:
AWS: !Sub
- arn:aws:iam::${ElbAccountId}:root
- ElbAccountId: !FindInMap [EnvMaps, Common, ApNortheast1ElbAccountId]
Action:
- s3:PutObject
Resource: !Sub
- ${BucketArn}/elb/*
- BucketArn: !GetAtt AwsResourceAccessLogsS3Bucket.Arn
CodePipelineArtifactsS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${Project}-codepipeline-artifacts
AccessControl: Private
VersioningConfiguration:
Status: Suspended
LifecycleConfiguration:
Rules:
- Id: ExpireAfter30Days
Status: Enabled
ExpirationInDays: 30
LoggingConfiguration:
DestinationBucketName: !Ref S3LogsS3Bucket
LogFilePrefix: !Sub ${Project}-codepipeline-artifacts/
Outputs:
AwsResourceAccessLogsS3BucketName:
Description: Output Export AWS::S3::Bucket.Ref AwsResourceAccessLogsS3Bucket
Value: !Ref AwsResourceAccessLogsS3Bucket
Export:
Name: !Sub ${Project}-aws-resource-access-logs-s3-bucket-name
CodePipelineArtifactsS3BucketName:
Description: Output Export AWS::S3::Bucket.Ref CodePipelineArtifactsS3Bucket
Value: !Ref CodePipelineArtifactsS3Bucket
Export:
Name: !Sub ${Project}-codepipeline-artifacts-s3-bucket-name
AwsResourceAccessLogsS3BucketArn:
Description: Output Export AWS::S3::Bucket.Arn AwsResourceAccessLogsS3Bucket
Value: !GetAtt AwsResourceAccessLogsS3Bucket.Arn
Export:
Name: !Sub ${Project}-aws-resource-access-logs-s3-bucket-arn
CodePipelineArtifactsS3BucketArn:
Description: Output Export AWS::S3::Bucket.Arn CodePipelineArtifactsS3Bucket
Value: !GetAtt CodePipelineArtifactsS3Bucket.Arn
Export:
Name: !Sub ${Project}-codepipeline-artifacts-s3-bucket-arn
AwsResourceAccessLogsS3BucketDomainName:
Description: Output Export AWS::S3::Bucket.DomainName AwsResourceAccessLogsS3Bucket
Value: !GetAtt AwsResourceAccessLogsS3Bucket.DomainName
Export:
Name: !Sub ${Project}-aws-resource-access-logs-s3-bucket-domain-name
Outputsも忘れないように記載してください。バケットの中のオブジェクトの保存期限は少なめに30日にしました。デプロイ時に出力されたデータはあまり長期間見ることはないと思うので。
CodePipelineのIAM Role
CodePipelineのIAMロールとCodePipeline構築の中で使用するCodeBuildのIAMロールを両方作成します。
.pre-commit-config.yaml
cloudformation
L iam-user.yaml
iam-role.yaml(modify)
cloudtrail.yaml
vpc.yaml
subnet.yaml
sg.yaml
route53.yaml
acm.yaml
s3-bucket.yaml
secretsmanager.yaml
prefix
L alb.yaml
cf.yaml
ecs-cluster.yaml
ecs-task-def.yaml
ecs-service.yaml
cw-logs.yaml
ecr.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "stackName: iam-role. AWS::IAM::Role AWS::IAM::InstanceProfile"
Parameters:
Project:
Type: String
Description: Project Name
Default: example
Resources:
CloudTrailRole:
Type: AWS::IAM::Role
Properties:
RoleName: cloudtrail-role
Path: /service-role/
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- cloudtrail.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: cloudtrail-policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: CloudWatchPutPolicy
Effect: Allow
Action:
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*
ECSInstanceProfileRole:
Type: AWS::IAM::Role
Properties:
RoleName: ecs-instance-profile-role
Path: /
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
- arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role
ECSInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
- !Ref ECSInstanceProfileRole
TaskExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: task-execution-role
Path: /service-role/
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
ECSCodeBuildServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: ecs-codebuild-service-role
Path: /service-role/
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: codebuild.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: ecs-codebuild-service-policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: S3AccessPolicy
Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:GetObjectVersion
Resource:
- !Sub
- ${ArtifactBucket}/*
- ArtifactBucket:
Fn::ImportValue: !Sub ${Project}-codepipeline-artifacts-s3-bucket-arn
- Sid: ECRAccessPolicy
Effect: Allow
Action:
- ecr:BatchCheckLayerAvailability
- ecr:BatchGetImage
- ecr:CompleteLayerUpload
- ecr:GetAuthorizationToken
- ecr:InitiateLayerUpload
- ecr:PutImage
- ecr:UploadLayerPart
- ecr:CreateRepository
- ecr:GetDownloadUrlForLayer
Resource:
- !Sub arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/*
- Sid: ECRAuthPolicy
Effect: Allow
Action:
- ecr:GetAuthorizationToken
Resource: "*"
- Sid: SSMGetParameterPolicy
Effect: Allow
Action:
- ssm:GetParameters
Resource: !Sub arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/*
- Sid: KmsDecryptPolicy
Effect: Allow
Action:
- kms:Decrypt
Resource: !Sub arn:aws:kms:${AWS::Region}:${AWS::AccountId}:key/*
- Sid: SSMCodeBuildAccessPolicy # https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/session-manager.html
Effect: Allow
Action:
- ssmmessages:CreateControlChannel
- ssmmessages:CreateDataChannel
- ssmmessages:OpenControlChannel
- ssmmessages:OpenDataChannel
Resource: "*"
- Sid: CWAccessPolicy
Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Sub arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:*
ECSCodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: ecs-codepipeline-service-role
Path: /service-role/
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- codepipeline.amazonaws.com
Action:
- sts:AssumeRole
Policies:
- PolicyName: ecs-codepipeline-service-policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: S3AccessPolicy
Effect: Allow
Action:
- s3:GetObject
- s3:GetObjectVersion
# - s3:GetBucketVersioning
- s3:PutObject
Resource:
- !Sub
- ${ArtifactBucket}/*
- ArtifactBucket:
Fn::ImportValue: !Sub ${Project}-codepipeline-artifacts-s3-bucket-arn
- Sid: ECSAccessPolicy
Effect: Allow
Action:
- ecs:DescribeServices
- ecs:DescribeTaskDefinition
- ecs:DescribeTasks
- ecs:ListTasks
- ecs:RegisterTaskDefinition
- ecs:UpdateService
Resource:
- "*"
- Sid: CodeBuildAccessPolicy
Effect: Allow
Action:
- codebuild:StartBuild
- codebuild:BatchGetBuilds
Resource: !Sub arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:project/*
- Effect: Allow
Action:
- codestar-connections:UseConnection
Resource: !Sub arn:aws:codestar-connections:${AWS::Region}:${AWS::AccountId}:connection/*
- Sid: PassRole
Effect: Allow
Action: iam:PassRole
Resource:
- "*"
Condition:
StringEqualsIfExists:
iam:PassedToService: ecs-tasks.amazonaws.com
Outputs:
CloudTrailRoleArn:
Description: Output Export AWS::IAM::Role.Arn CloudTrailRole
Value: !GetAtt CloudTrailRole.Arn
Export:
Name: cloudtrail-role-arn
ECSInstanceProfileArn:
Description: Output Export AWS::IAM::InstanceProfile.Arn ECSInstanceProfile
Value: !GetAtt ECSInstanceProfile.Arn
Export:
Name: ecs-instance-profile-arn
TaskExecutionRoleArn:
Description: Output Export AWS::IAM::Role.Arn TaskExecutionRole
Value: !GetAtt TaskExecutionRole.Arn
Export:
Name: task-execution-role-arn
ECSCodeBuildServiceRoleArn:
Description: Output Export AWS::IAM::Role.Arn ECSCodeBuildServiceRole
Value: !GetAtt ECSCodeBuildServiceRole.Arn
Export:
Name: ecs-codebuild-service-role-arn
ECSCodePipelineServiceRoleArn:
Description: Output Export AWS::IAM::Role.Arn ECSCodePipelineServiceRole
Value: !GetAtt ECSCodePipelineServiceRole.Arn
Export:
Name: ecs-codepipeline-service-role-arn
Parametersが追記されているため注意してください。
CodeStar
次にCodePipelineとGitHubを疎通するためのリソースであるCodeStarConnectionを作成します。
今まではGitHubのアクセストークンを利用してGitHubとの疎通を行なっていましたが、CodeStarConnectionができたことによりアクセストークンを利用しないためトークンのローテーションなしで安全に疎通できるようになりました。
.pre-commit-config.yaml
cloudformation
L iam-user.yaml
iam-role.yaml
cloudtrail.yaml
vpc.yaml
subnet.yaml
sg.yaml
route53.yaml
acm.yaml
s3-bucket.yaml
secretsmanager.yaml
codestar.yaml(new)
prefix
L alb.yaml
cf.yaml
ecs-cluster.yaml
ecs-task-def.yaml
ecs-service.yaml
cw-logs.yaml
ecr.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "stackName: codestar. AWS::CodeStarConnections::Connection"
Resources:
CodeStarGitHubConnection:
Type: AWS::CodeStarConnections::Connection
Properties:
ConnectionName: github-connection
ProviderType: GitHub
Outputs:
CodeStarGitHubConnectionArn:
Description: Output Export AWS::CodeStarConnections::Connection.ConnectionArn
Value: !GetAtt CodeStarGitHubConnection.ConnectionArn
Export:
Name: codestar-github-connection-arn
スタック名は、codestar
CodeStarを作成しただけでは、GitHubとの疎通はできていません。下記画像のようになっているので手動で対応が必要になってきます。

→ AWS CodePipeline のGitHub への接続(バージョン2)を作成してみた
CodePipeline
やっと本命のCodePipelineを構築しようと思います。
.pre-commit-config.yaml
cloudformation
L iam-user.yaml
iam-role.yaml
cloudtrail.yaml
vpc.yaml
subnet.yaml
sg.yaml
route53.yaml
acm.yaml
s3-bucket.yaml
secretsmanager.yaml
codestar.yaml
prefix
L alb.yaml
cf.yaml
ecs-cluster.yaml
ecs-task-def.yaml
ecs-service.yaml
cw-logs.yaml
ecr.yaml
codepipeline.yaml(new)
AWSTemplateFormatVersion: "2010-09-09"
Description: "stackName: {Prefix}-{Env}-codepipeline. AWS::CodeBuild::Project AWS::CodePipeline::Pipeline"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Environment Name Configuration
Parameters:
- Prefix
- Env
- Project
- Label:
default: GitHub Configuration
Parameters:
- GitHubDeployBranch
Parameters:
Prefix:
Type: String
Description: AWS resource version management prefix
Default: v1
Env:
Type: String
Description: Project environment
AllowedValues: [dev, prd]
Project:
Type: String
Description: Project Name
Default: example
GitHubDeployBranch:
Type: String
Description: GitHub deploy branch
Default: main
Mappings:
EnvMaps:
common:
GitHubOrganization: kita21
GitHubRepository: tech-blog-sample
Resources:
ECSDeployCodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Name: !Sub ${Prefix}-${Env}-${Project}-ecs-deploy-codebuild
Description: ${Prefix}-${Env}-${Project}-ecs-deploy-codebuild
ServiceRole: !ImportValue ecs-codebuild-service-role-arn
QueuedTimeoutInMinutes: 30
TimeoutInMinutes: 30
Artifacts:
Type: CODEPIPELINE
Cache:
Type: LOCAL
Modes:
- LOCAL_DOCKER_LAYER_CACHE
LogsConfig:
CloudWatchLogs:
GroupName:
Fn::ImportValue: !Sub ${Prefix}-${Env}-${Project}-deploy-codebuild-cw-loggroup-name
Status: ENABLED
Source:
BuildSpec: 022-cfn07-cicd/app/buildspec.yml
Type: CODEPIPELINE
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Image: aws/codebuild/amazonlinux2-x86_64-standard:4.0
Type: LINUX_CONTAINER
PrivilegedMode: true
EnvironmentVariables:
- Name: ENV
Type: PLAINTEXT
Value: !Ref Env
- Name: PREFIX
Type: PLAINTEXT
Value: !Ref Prefix
- Name: PROJECT
Type: PLAINTEXT
Value: !Ref Project
- Name: AWS_ACCOUNT_ID
Type: PLAINTEXT
Value: !Ref AWS::AccountId
- Name: AWS_REGION_NAME
Type: PLAINTEXT
Value: !Ref AWS::Region
ECSDeployCodePipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: !Sub ${Prefix}-${Env}-${Project}-ecs-codepipeline
RoleArn: !ImportValue ecs-codepipeline-service-role-arn
ArtifactStore:
Type: S3
Location:
Fn::ImportValue: !Sub ${Project}-codepipeline-artifacts-s3-bucket-name
Stages:
- Name: Source
Actions:
- Name: SourceStage
ActionTypeId:
Category: Source
Owner: AWS
Version: 1
Provider: CodeStarSourceConnection
Configuration:
FullRepositoryId: !Sub
- ${GitHubOrganization}/${GitHubRepository}
- GitHubOrganization: !FindInMap [EnvMaps, common, GitHubOrganization]
GitHubRepository: !FindInMap [EnvMaps, common, GitHubRepository]
ConnectionArn: !ImportValue codestar-github-connection-arn
BranchName: !Ref GitHubDeployBranch
DetectChanges: false
OutputArtifactFormat: CODE_ZIP
RunOrder: 1
OutputArtifacts:
- Name: SourceArtifact
- Name: Build
Actions:
- Name: BuildStage
ActionTypeId:
Category: Build
Owner: AWS
Version: 1
Provider: CodeBuild
Configuration:
ProjectName: !Ref ECSDeployCodeBuildProject
InputArtifacts:
- Name: SourceArtifact
OutputArtifacts:
- Name: BuildArtifact
RunOrder: 1
- Name: Deploy
Actions:
- Name: DeployStage
ActionTypeId:
Category: Deploy
Owner: AWS
Version: 1
Provider: ECS
Configuration:
ClusterName:
Fn::ImportValue: !Sub ${Prefix}-${Env}-${Project}-ecs-cluster-name
ServiceName:
Fn::ImportValue: !Sub ${Prefix}-${Env}-${Project}-ecs-service-name
FileName: imagedefinitions.json
InputArtifacts:
- Name: BuildArtifact
RunOrder: 1
スタック名は、v1-dev-codepipeline
以下にポイントを記載します。
- 作成したと同時にCodePipelineが実行されます。
buildspec.ymlを記載していないためビルドは失敗しますが、作成と同時に実行されないようにする方法を知っている方いたら教えてください。 - AWS::CodeBuild::Projectの
Environment.EnvironmentVariables
はCodeBuildで使用される値を入れています。 - AWS::CodeBuild::Projectの
Source.BuildSpec
は、サンプルリポジトリ用に記載しているためリポジトリに合わせて変更してください。 - AWS::CodePipeline::Pipelineの
Stages[0].Actions[0].Configuration.DetectChanges
はコミット時にCodePipelineを実行するか決めています。
私は他のサンプルコードもコミットするのでfalseにしていますが、GitHubと環境の乖離が発生しないようにtrueにすることをおすすめします。 - DeployはECSに選択しています。他にもいろいろなデプロイ方法がありますが、今回は一番簡単なECSを選択します。
ECSタスク定義の取得イメージをECRに
前回の記事でECSで動くコンテナのイメージをnginxにしました。今回GitHubから取得したコードで生成したイメージからコンテナを立てるのでイメージを格納するECRから取得するようにします。
.pre-commit-config.yaml
cloudformation
L iam-user.yaml
iam-role.yaml
cloudtrail.yaml
vpc.yaml
subnet.yaml
sg.yaml
route53.yaml
acm.yaml
s3-bucket.yaml
secretsmanager.yaml
codestar.yaml
prefix
L alb.yaml
cf.yaml
ecs-cluster.yaml
ecs-task-def.yaml(modify)
ecs-service.yaml
cw-logs.yaml
ecr.yaml
codepipeline.yaml
AWSTemplateFormatVersion: "2010-09-09"
Description: "stackName: {Prefix}-{Env}-ecs-task-def. AWS::ECS::TaskDefinition"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: Environment Name Configuration
Parameters:
- Prefix
- Env
- Project
Parameters:
Prefix:
Type: String
Description: AWS resource version management prefix
Default: v1
Env:
Type: String
Description: Project environment
AllowedValues: [dev, prd]
Project:
Type: String
Description: Project Name
Default: example
Mappings:
EnvMaps:
dev:
Cpu: 256
Memory: 516
prd:
Cpu: 256
Memory: 516
Resources:
TaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: !Sub ${Prefix}-${Env}-${Project}-task
RuntimePlatform:
CpuArchitecture: X86_64
RequiresCompatibilities:
- EC2
Cpu: !FindInMap [EnvMaps, !Ref Env, Cpu]
Memory: !FindInMap [EnvMaps, !Ref Env, Memory]
ExecutionRoleArn: !ImportValue task-execution-role-arn
ContainerDefinitions:
- Name: !Sub ${Prefix}-${Env}-${Project}-container
Cpu: !FindInMap [EnvMaps, !Ref Env, Cpu]
Memory: !FindInMap [EnvMaps, !Ref Env, Memory]
Image: !Sub
- ${URI}:latest
- URI:
Fn::ImportValue: !Sub ${Prefix}-${Env}-${Project}-ecr-uri
PortMappings:
- ContainerPort: 80
HostPort: 0
Protocol: tcp
Essential: true
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group:
Fn::ImportValue: !Sub ${Prefix}-${Env}-${Project}-ecs-cw-loggroup-name
awslogs-region: ap-northeast-1
ContainerDefinitions[0].Image
を変更しました。変更は一行なので大きな変更ではないです。
これでAWSリソースは完成です!
デプロイ用アプリケーションを作成
AWSリソースは完成したので、次はデプロイするアプリケーションを作成します。今回はただhello worldをレスポンスで返すだけのアプリケーションを作成します。筆者はTypescriptばかり書いているので今回もTypescriptで作成します。
.pre-commit-config.yaml
cloudformation
L iam-user.yaml
iam-role.yaml
cloudtrail.yaml
vpc.yaml
subnet.yaml
sg.yaml
route53.yaml
acm.yaml
s3-bucket.yaml
secretsmanager.yaml
codestar.yaml
prefix
L alb.yaml
cf.yaml
ecs-cluster.yaml
ecs-task-def.yaml
ecs-service.yaml
cw-logs.yaml
ecr.yaml
codepipeline.yaml
app(new)
アプリケーションは全て記載すると収まらないのでサンプルをご覧ください。
重要なところは、buildspec.ymlです。ここだけyamlではなくymlですが、どっちでもいいです。AWSのデフォルトがbuildspec.ymlだったので公式に合わせました。
version: 0.2
run-as: root
phases:
install:
runtime-versions:
docker: 20
pre_build:
commands:
- export AWS_REPOSITORY_URI=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION_NAME}.amazonaws.com/${PREFIX}/${ENV}/${PROJECT}
- export DATETIME=`date +"%Y%m%d%H%M%S"`
- export LATEST_IMAGE_URI=${AWS_REPOSITORY_URI}:latest
- export IMAGE_URI=${AWS_REPOSITORY_URI}:${DATETIME}
- export APP_DIR=022-cfn07-cicd/app # プロジェクトによって変更
- aws ecr get-login-password --region ${AWS_REGION_NAME} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION_NAME}.amazonaws.com
# 対象Imageがない場合でもビルド失敗にならないように
- docker pull ${LATEST_IMAGE_URI} || true
build:
commands:
- |
docker build -f ${APP_DIR}/Dockerfile \
-t ${LATEST_IMAGE_URI} \
-t ${IMAGE_URI} \
${APP_DIR}
post_build:
commands:
- docker push --all-tags ${AWS_REPOSITORY_URI}
- echo '[{"name":"'${PREFIX}-${ENV}-${PROJECT}-container'","imageUri":"'${IMAGE_URI}'"}]' > imagedefinitions.json
artifacts:
files:
- imagedefinitions.json
yamlファイルの内容としては以下になります。
- 扱いやすいように環境変数に入れていく。CodeBuildで設定した値を使用している。
- ECRにログインし、ECRの最新イメージをPullする
- Dockerfileからイメージをビルドする
この時、ECRからPullしたイメージがあればそれをキャッシュ利用する
最新のlatestと一意の時刻によるタグ付けをする - ビルドしたイメージをECRにPushする
- コンテナ名とECRのURIを記載したアーティファクト作成
アプリケーションのコードをGitHubにあげてCodePipelineを実行するとECSのコンテナに反映されると思います。
反映されたアプリケーションにアクセスしてみましょう。
curl -i https://looseller.com
HTTP/2 200
content-type: text/html; charset=utf-8
content-length: 12
date: Wed, 10 May 2023 00:11:11 GMT
x-powered-by: Express
etag: W/"c-Lve95gjOVATpfV8EL5X4nxwjKHE"
x-cache: Miss from cloudfront
via: 1.1 1b3fd5e3e9b3fd38054dc45b58346688.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT12-C3
x-amz-cf-id: O86hc2jhLai6xd3_HGq7B77035i3jMcVswMeJ7H4T4ZwRyg-C6eObQ==
Hello World!
200レスポンスでHello World!が返ってきましたね! これでやっと全て完了です!長かった…
かかった金額
今回のサンプルAWSリソースを作成してかかった金額が以下になります。
無料枠も使用しています。

こう見るとNatGatewayが高いですね。一日1.5ドルほどかかっているので日数が経つと、ちりつもで痛い…
NatGateway使用しないで構築する方法もあるにはあるのですが、NatGatewayの方が一般的で分かりやすいので
また、月末で料金がプラスでかかってくるかもしれないので、コスト確定の通知が来たら追加で貼ろうかなと思います。
終わりに
最後のCICD編が一番テンプレート多かったですね…
それにhello world!
をただ出すアプリケーションにしてはリッチすぎるかもしれません
(´・ω・`)
RDSやWafなどプロジェクトによっては他にも追加するリソースはあるかもしれませんが、ほとんどのプロジェクトはこの記事のサンプルコードをただ流せば1時間もかけずに環境が構築できてしまいますね。ドメインの取得はお忘れなく
あ、また繰り返しになりますが、このサンプルコードのテンプレートの切り分けはあくまで記事用に作成したものです。AWSのベストプレクティスからは外れているのでまんまコピペして先輩エンジニアに怒られないように気をつけてくださいね。
この記事を作成するにあたって、クラスメソッドさんのDevelopersIOに大変お世話になりました。困ったらDevelopersIOで検索すれば大体は解決しますね。
長くなりましたが、記事で利用したドメインもあと1日で切れそうですし、筆(指)を置きたいと思います。