⛳
Fargateのデプロイの比較
概要
Fargateをデプロイしようとしたが、CodeCommitの新規受付が終了していたり、シンプルな環境を整備したかったりでいろいろと迷ってみた。
- Bitbucket pipelineで、AWS CLIで全部やる
- Bitbucket pipelineでイメージをビルド、ECRにPush→Codepipeline
- BitbucketにPush→CodeStarConnection→Codepipeline
結論
- Bitbucket pipelineでイメージをビルド、ECRにPush→Codepipeline
これがECR→ECSのpipelineだけ先に作ってしまえばいいので楽だと思いました。
コード(Fargateの作成+α)
全ての方法でほぼ共通している部分です。リソースの作成をしています。
- main-stack.ts
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as route53 from 'aws-cdk-lib/aws-route53'
import { DeployEnv } from './deploy-env.const'
import { ServerStack } from './server-stack'
import { DeployStack } from './deploy-stack'
import { DbStack } from 'db-stack'
/**
* VPC, その他ネットワーク関係のリソース作成
* サーバースタックの呼び出し
* デプロイスタックの呼び出し
* DBスタックの呼び出し
*/
export interface MainStackProps extends cdk.StackProps {
deployEnv: DeployEnv // staging or production
projectName: string
allowIpList: string[] // 踏み台のIP制限用
domainName: string
certificateArn: string
}
export class MainStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: MainStackProps) {
super(scope, id, props)
// VPCの作成
const vpc = new ec2.Vpc(this, 'MyVpc', {
maxAzs: 2,
ipAddresses: ec2.IpAddresses.cidr('10.0.0.0/16'),
subnetConfiguration: [
{
cidrMask: 24,
name: `public-subnet-${props.deployEnv}`,
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: `private-subnet-${props.deployEnv}`,
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
{
cidrMask: 24,
name: `isolated-subnet-${props.deployEnv}`,
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
],
})
// Route53 リソースの作成
const publicHostedZone = route53.PublicHostedZone.fromLookup(this, 'PublicHostedZone', {
domainName: props.domainName,
})
const internalHostedZone = new route53.HostedZone(this, `DbAccessHostedZone`, {
[vpc],
zoneName: `db-access-internal.${props.deployEnv}`,
comment: 'internal dns for db',
})
// VPCエンドポイントの作成(SSM)(ECSコンテナにCLIでアクセスするため)
vpc.addInterfaceEndpoint('SSMEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.SSM,
})
vpc.addInterfaceEndpoint('SSMMessagesEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES,
})
vpc.addInterfaceEndpoint('EC2MessagesEndpoint', {
service: ec2.InterfaceVpcEndpointAwsService.EC2_MESSAGES,
})
// server リソースの作成
const server = new ServerStack(this, 'ServerStack', {
vpc,
publicHostedZone,
internalHostedZone,
...props,
})
// deploy リソースの作成
new DeployStack(this, 'DeployStack', {
...props,
target: server.target
})
// DB リソースの作成
new DbStack(this, 'DbStack', {
...props,
})
}
}
- server-stack.ts
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as iam from 'aws-cdk-lib/aws-iam'
import * as ecr from 'aws-cdk-lib/aws-ecr'
import * as ecr_assets from 'aws-cdk-lib/aws-ecr-assets'
import * as ecs from 'aws-cdk-lib/aws-ecs'
import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as route53Targets from 'aws-cdk-lib/aws-route53-targets'
import * as route53 from 'aws-cdk-lib/aws-route53'
import * as logs from 'aws-cdk-lib/aws-logs'
import * as ssm from 'aws-cdk-lib/aws-ssm'
import * as certificate_manager from 'aws-cdk-lib/aws-certificatemanager'
import * as ecr_deploy from 'cdk-ecr-deployment'
import * as path from 'path'
import { fileURLToPath } from 'url'
import { MainStackProps } from 'main-stack'
// ESModule環境での__dirnameの代替
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
export interface ServerStackProps extends MainStackProps {
vpc: ec2.Vpc
publicHostedZone: route53.IPublicHostedZone
internalHostedZone: route53.HostedZone
}
export class ServerStack extends Construct {
public readonly dbAccessSecurityGroups: ec2.ISecurityGroup[] = []
public readonly target: {
readonly repository: ecr.IRepository
readonly cluster: ecs.Cluster
readonly fargateService: ecs_patterns.ApplicationLoadBalancedFargateService
}
constructor(scope: Construct, id: string, props: ServerStackProps) {
super(scope, id)
const baseName = `server-${props.deployEnv}`
// ECR
const repository = new ecr.Repository(scope, 'MyEcrRepo', {
repositoryName: baseName,
removalPolicy: cdk.RemovalPolicy.DESTROY,
})
// cdk deploy時にイメージがリポジトリにないとFargateの生成時にエラーになるため、health checkが通るイメージをPush
const dockerImageAsset = new ecr_assets.DockerImageAsset(scope, 'DockerImageAsset', {
directory: path.join(__dirname, '..', 'init'),
platform: ecr_assets.Platform.LINUX_AMD64,
})
new ecr_deploy.ECRDeployment(scope, 'DeployDockerImage', {
src: new ecr_deploy.DockerImageName(dockerImageAsset.imageUri),
dest: new ecr_deploy.DockerImageName(
`${props.env?.account}.dkr.ecr.${props.env?.region}.amazonaws.com/${baseName}:latest`,
),
})
// ECSクラスターの作成
const cluster = new ecs.Cluster(this, 'ServerCluster', {
vpc: props.vpc,
clusterName: baseName,
})
// Fargateサービスの作成(ALBも同時に作成する)
const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'ServerFargateService', {
listenerPort: 443,
redirectHTTP: true,
certificate: certificate_manager.Certificate.fromCertificateArn(this, 'ServerCertificate', props.certificateArn),
cluster: cluster,
serviceName: baseName,
// deploymentController: {
// type: ecs.DeploymentControllerType.ECS (Default)
// type: ecs.DeploymentControllerType.CODE_DEPLOY (ECS Applicationを使用したBGデプロイの時)
// },
enableExecuteCommand: true, // コンテナに直接アクセスするために必要
taskImageOptions: {
image: ecs.ContainerImage.fromEcrRepository(repository),
environment: {
// ENV_VALUES: 'VALUE',
},
// パラメータストアから値を取得
secrets: {
KEY: ecs.Secret.fromSsmParameter(
ssm.StringParameter.fromSecureStringParameterAttributes(this, 'EcsSecret', {
parameterName: `/${props.projectName}/KEY`,
}),
),
},
// log出力設定
logDriver: new ecs.AwsLogDriver({
streamPrefix: 'Server',
logGroup: new logs.LogGroup(this, 'ServerLogGroup', {
logGroupName: `/ecs/${baseName}`,
removalPolicy: cdk.RemovalPolicy.DESTROY,
retention: logs.RetentionDays.ONE_MONTH,
}),
}),
},
publicLoadBalancer: true,
desiredCount: 1,
cpu: 256, // 0.25 vCPU、最小
memoryLimitMiB: 1024 * 0.5, // 0.5GB、最小
healthCheckGracePeriod: cdk.Duration.seconds(60 * 20), // 短すぎるとヘルスチェックが通らないことがある
})
// ECSコンテナにCLIでアクセスするため
fargateService.taskDefinition.taskRole.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'),
)
// ALBへのアクセスを追加
new route53.ARecord(this, 'ServerARecord', {
zone: props.publicHostedZone,
target: route53.RecordTarget.fromAlias(new route53Targets.LoadBalancerTarget(fargateService.loadBalancer)),
recordName: '', // ルートドメイン
})
// DBアクセス用の設定に使用するため、保持
this.dbAccessSecurityGroups.push(fargateService.service.connections.securityGroups[0])
// Deployスタック作成に使用するため保持
this.target = {
repository,
cluster,
fargateService,
}
}
}
- db-stack.ts (略)
コード(1. Bitbucket pipelineで、AWS CLIで全部やる)
cliからpipelineを作ってそれを使ってデプロイ…とするとおなじになっちゃうので、自力で頑張ります。
- iam-stack.ts
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as iam from 'aws-cdk-lib/aws-iam'
export class IamStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: cdk.StackProps) {
super(scope, id, props)
// BitbucketパイプラインでECR, ECSにアクセスするためのIAMユーザーを作成
// IAM ユーザーの作成
const bitbucketPipelineUser = new iam.User(this, 'BitbucketPipelineUser', {
userName: 'bitbucket-pipeline-user',
})
// マネージドポリシーをアタッチ (ECR への Push 権限)
bitbucketPipelineUser.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryPowerUser'),
)
// マネージドポリシーをアタッチ (ECS への Full 権限)
bitbucketPipelineUser.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonECS_FullAccess'))
// アクセスキーの作成
const accessKey = new iam.CfnAccessKey(this, 'BitbucketPipelineAccessKey', {
userName: bitbucketPipelineUser.userName,
})
// 出力 (Bitbucket の環境変数に設定するため)
new cdk.CfnOutput(this, 'AWSAccessKeyId', {
value: accessKey.ref,
})
new cdk.CfnOutput(this, 'AWSSecretAccessKey', {
value: accessKey.attrSecretAccessKey,
})
}
}
- bitbucket-pipelines.yml
コンソールからパイプラインを有効化しておく(repository settings → 設定 → enable pipelines)-
image: amazon/aws-cli:latest # AWS CLI がプリインストールされているイメージを使用
pipelines:
branches:
main:
- step:
name: Build and Push to ECR
services:
- docker # Dockerを有効化
script:
- echo "Install jq library..."
- apt install -y jq
- echo "Logging in to Amazon ECR..."
- aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
- aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
- aws configure set region $AWS_REGION
- aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
- echo "Building Docker image..."
- docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
- docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
- echo "Pushing Docker image to ECR..."
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
- echo "Fetching current ECS task definition ARN..."
- TASK_DEF_ARN=$(aws ecs describe-services --cluster $ECS_CLUSTER --services $ECS_SERVICE | jq -r '.services[0].taskDefinition')
- echo "Fetching current task definition JSON..."
- aws ecs describe-task-definition --task-definition $TASK_DEF_ARN | jq '.taskDefinition' > task_def.json
- echo "Preparing new task definition JSON (No changes except revision update)..."
- jq 'del(.taskDefinitionArn, .revision, .status, .requiresAttributes, .compatibilities, .registeredAt, .registeredBy)' task_def.json > new_task_def.json
- echo "Registering new task definition..."
- NEW_TASK_DEF_ARN=$(aws ecs register-task-definition --cli-input-json file://new_task_def.json | jq -r '.taskDefinition.taskDefinitionArn')
- echo "Updating ECS service to use new task definition..."
- aws ecs update-service --cluster $ECS_CLUSTER --service $ECS_SERVICE --task-definition $NEW_TASK_DEF_ARN
variables:
# bitbucket -> repository settings -> repository variablesに値をセット
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_REGION: $AWS_REGION
AWS_ACCOUNT_ID: $AWS_ACCOUNT_ID
IMAGE_REPO_NAME: $IMAGE_REPO_NAME
IMAGE_TAG: $IMAGE_TAG
ECS_CLUSTER: $ECS_CLUSTER
ECS_SERVICE: $ECS_SERVICE
コード(2. Bitbucket pipelineでイメージをビルド、ECRにPush→Codepipeline)
時間がないので別環境を用意してのBGデプロイはやりません。
- deploy-stack.ts
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as ecs from 'aws-cdk-lib/aws-ecs'
import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'
import * as ecr from 'aws-cdk-lib/aws-ecr'
import * as s3 from 'aws-cdk-lib/aws-s3'
import * as codebuild from 'aws-cdk-lib/aws-codebuild'
import * as codepipeline from 'aws-cdk-lib/aws-codepipeline'
import * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions'
import { MainStackProps } from 'main-stack'
export interface DeployStackProps extends MainStackProps {
vpc: ec2.Vpc
target: {
repository: ecr.IRepository
cluster: ecs.Cluster
fargateService: ecs_patterns.ApplicationLoadBalancedFargateService
}
}
export class DeployStack extends Construct {
constructor(scope: Construct, id: string, props: DeployStackProps) {
super(scope, id)
// アーティファクトバケットの作成
const artifactBucket = new s3.Bucket(this, 'MyPipelineArtifactBucket', {
bucketName: `deploy-artifact-bucket-${props.deployEnv}`,
accessControl: s3.BucketAccessControl.PRIVATE,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
})
// Bucket Lifecycle
artifactBucket.addLifecycleRule({
expiration: cdk.Duration.days(60),
abortIncompleteMultipartUploadAfter: cdk.Duration.days(7),
transitions: [
{
storageClass: s3.StorageClass.INTELLIGENT_TIERING,
transitionAfter: cdk.Duration.days(0),
},
],
})
// アーティファクトの定義
const sourceOutput = new codepipeline.Artifact()
const buildOutput = new codepipeline.Artifact()
// パイプラインの作成
const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', {
artifactBucket,
pipelineName: `server-deploy-${props.deployEnv}`,
})
// ソースステージの追加
pipeline.addStage({
stageName: 'Source',
actions: [
// EventBridgeでルールが作成され、ECRへのPushでトリガーされる
new codepipeline_actions.EcrSourceAction({
actionName: 'ECR_Source',
repository: props.target.repository,
imageTag: 'latest',
output: sourceOutput,
}),
],
})
// ビルドプロジェクトの定義
const containerName = props.target.fargateService.taskDefinition.defaultContainer?.containerName
const buildProject = new codebuild.PipelineProject(this, 'MyProject', {
buildSpec: codebuild.BuildSpec.fromObject({
version: '0.2',
phases: {
build: {
commands: [
'echo "Generating imagedefinitions.json..."',
// imagedefinitions.jsonがないとデプロイに失敗する
`echo '[{"name":"${containerName}","imageUri":"${props.target.repository.repositoryUri}:latest"}]' > imagedefinitions.json`,
],
},
},
artifacts: {
files: 'imagedefinitions.json',
},
}),
environment: {
buildImage: codebuild.LinuxBuildImage.STANDARD_5_0,
},
})
// ビルドステージの追加
pipeline.addStage({
stageName: 'Build',
actions: [
new codepipeline_actions.CodeBuildAction({
actionName: 'Generate_Image_Definitions',
project: buildProject,
input: sourceOutput,
outputs: [buildOutput],
}),
],
})
// デプロイステージの追加
pipeline.addStage({
stageName: 'Deploy',
actions: [
new codepipeline_actions.EcsDeployAction({
actionName: 'ECS_Deploy',
service: props.target.fargateService.service,
input: buildOutput,
}),
],
})
}
}
- iam-stack.ts
(差分= ✅)
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as iam from 'aws-cdk-lib/aws-iam'
export class IamStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: cdk.StackProps) {
super(scope, id, props)
// BitbucketパイプラインでECR, ECSにアクセスするためのIAMユーザーを作成
// IAM ユーザーの作成
const bitbucketPipelineUser = new iam.User(this, 'BitbucketPipelineUser', {
userName: 'bitbucket-pipeline-user',
})
// マネージドポリシーをアタッチ (ECR への Push 権限)
bitbucketPipelineUser.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryPowerUser'),
)
// ✅ECS へのアクセスはCodePipelineで行うので不要
// bitbucketPipelineUser.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonECS_FullAccess'))
// アクセスキーの作成
const accessKey = new iam.CfnAccessKey(this, 'BitbucketPipelineAccessKey', {
userName: bitbucketPipelineUser.userName,
})
// 出力 (Bitbucket の環境変数に設定するため)
new cdk.CfnOutput(this, 'AWSAccessKeyId', {
value: accessKey.ref,
})
new cdk.CfnOutput(this, 'AWSSecretAccessKey', {
value: accessKey.attrSecretAccessKey,
})
}
}
- bitbucket-pipelines.yml
コンソールからパイプラインを有効化しておく(repository settings → 設定 → enable pipelines)
image: amazon/aws-cli:latest # AWS CLI がプリインストールされているイメージを使用
pipelines:
branches:
main:
- step:
name: Build and Push to ECR
services:
- docker # Dockerを有効化
script:
- echo "Logging in to Amazon ECR..."
- aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
- aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
- aws configure set region $AWS_REGION
- aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com
- echo "Building Docker image..."
- docker build -t $IMAGE_REPO_NAME:latest .
- docker tag $IMAGE_REPO_NAME:latest $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest
- echo "Pushing Docker image to ECR..."
- docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$IMAGE_REPO_NAME:latest
variables:
# bitbucket -> repository settings -> repository variablesに値をセット
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_REGION: $AWS_REGION
AWS_ACCOUNT_ID: $AWS_ACCOUNT_ID
IMAGE_REPO_NAME: $IMAGE_REPO_NAME
# IMAGE_TAG: $IMAGE_TAG
コード(3. BitbucketにPush→CodeStarConnection→Codepipeline)
BitbucketSourceActionがなくなった 😱CodeConnectionを使えばいいらしい。
- 接続の作成
- 対象のBitbucketアカウントにログインしておく
- デベロッパー用ツール(CodeBuild, CodeDeployとか)にアクセス
- サイドバー → 設定 → 接続 → 接続を作成
- 必要事項を入力 → 次へ
- 新しいアプリをインストール → (Bitbucketのページ)→ アクセスを許可する
- CodeConnectionの作成
SourceAction等を変更する(差分= ✅)
import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as ecs from 'aws-cdk-lib/aws-ecs'
import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'
import * as ecr from 'aws-cdk-lib/aws-ecr'
import * as s3 from 'aws-cdk-lib/aws-s3'
import * as codebuild from 'aws-cdk-lib/aws-codebuild'
import * as codepipeline from 'aws-cdk-lib/aws-codepipeline'
import * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions'
import { MainStackProps } from 'main-stack'
export interface DeployStackProps extends MainStackProps {
vpc: ec2.Vpc
target: {
repository: ecr.IRepository
cluster: ecs.Cluster
fargateService: ecs_patterns.ApplicationLoadBalancedFargateService
}
}
export class DeployStack extends Construct {
constructor(scope: Construct, id: string, props: DeployStackProps) {
super(scope, id)
// アーティファクトバケットの作成
const artifactBucket = new s3.Bucket(this, 'MyPipelineArtifactBucket', {
bucketName: `deploy-artifact-bucket-${props.deployEnv}`,
accessControl: s3.BucketAccessControl.PRIVATE,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
})
// Bucket Lifecycle
artifactBucket.addLifecycleRule({
expiration: cdk.Duration.days(60),
abortIncompleteMultipartUploadAfter: cdk.Duration.days(7),
transitions: [
{
storageClass: s3.StorageClass.INTELLIGENT_TIERING,
transitionAfter: cdk.Duration.days(0),
},
],
})
// アーティファクトの定義
const sourceOutput = new codepipeline.Artifact()
const buildOutput = new codepipeline.Artifact()
// パイプラインの作成
// ✅ CodeConnectionにアクセスできるような権限を付与
const pipelineRole = new iam.Role(this, 'PipelineRole', {
assumedBy: new iam.ServicePrincipal('codepipeline.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('AWSCodePipeline_FullAccess'),
],
})
const pipeline = new codepipeline.Pipeline(this, 'MyPipeline', {
artifactBucket,
pipelineName: `server-deploy-${props.deployEnv}`,
})
// ソースステージの追加
pipeline.addStage({
stageName: 'Source',
actions: [
// ✅ Actionを変更
// new codepipeline_actions.EcrSourceAction({
// actionName: 'ECR_Source',
// repository: ecr.Repository.fromRepositoryName(this, 'depRepository', 'dep-test'),
// imageTag: 'latest',
// output: sourceOutput,
// }),
new codepipeline_actions.CodeStarConnectionsSourceAction({
// 先ほど作成した接続のARN
connectionArn:
'arn:aws:codeconnections:{region}:{account id}:connection/xxxxxxxxxxxxxxxxx',
output: sourceOutput,
owner: 'owner', // bitbucket.org/owner/repository
repo: 'test', // repository name
branch: 'main',
triggerOnPush: true,
actionName: 'CodeStarConnections_Source',
],
})
// ビルドプロジェクトの定義
const containerName = props.target.fargateService.taskDefinition.defaultContainer?.containerName
// ✅ ECRへPushする&imageDefinitions.jsonの作成
const buildProject = new codebuild.PipelineProject(this, 'depMyProject', {
buildSpec: codebuild.BuildSpec.fromObject({
version: '0.2',
phases: {
pre_build: {
commands: [
'echo Logging in to Amazon ECR...',
'aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com',
],
},
build: {
commands: [
'echo Build started on `date`',
'echo Building the Docker image...',
'docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .',
'docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG',
],
},
post_build: {
commands: [
'echo Build completed on `date`',
'echo Pushing the Docker image...',
'docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG',
'printf \'[{"name":"%s","imageUri":"%s"}]\' $CONTAINER_NAME $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imagedefinitions.json',
],
},
},
artifacts: {
files: ['imagedefinitions.json'],
},
}),
environment: {
buildImage: codebuild.LinuxBuildImage.STANDARD_5_0,
privileged: true,
},
environmentVariables: {
AWS_DEFAULT_REGION: { value: props.env.region },
AWS_ACCOUNT_ID: { value: props.env.account },
IMAGE_REPO_NAME: { value: repository.repositoryName },
IMAGE_TAG: { value: 'latest' },
CONTAINER_NAMME: { value: containerName },
},
})
repository.grantPullPush(buildProject)
// ビルドステージの追加
pipeline.addStage({
stageName: 'Build',
actions: [
new codepipeline_actions.CodeBuildAction({
actionName: 'Push_Image_And_Generate_Definitions',
project: buildProject,
input: sourceOutput,
outputs: [buildOutput],
}),
],
})
// デプロイステージの追加
pipeline.addStage({
stageName: 'Deploy',
actions: [
new codepipeline_actions.EcsDeployAction({
actionName: 'ECS_Deploy',
service: props.target.fargateService.service,
input: buildOutput,
}),
],
})
}
}
所感
- Bitbucket pipelineで、AWS CLIで全部やる
- メリット
- cliなので、手軽に手元で試せる。
- イメージのタグも自動でつけられるのでよさそう。
- リソースの作成権限を自由に得られない場合だと、ほかのやつに比べ楽。
- デメリット
- BitbucketPipelineで実行するので、デバッグが面倒。
- デプロイユーザーの権限がいろいろ必要なので設定が面倒。
- デプロイの承認など、Approvalを挟めない。
- Bitbucket pipelineでイメージをビルド、ECRにPush→Codepipeline
- メリット
- 古いコンテナの処理とかを面倒見てくれるので楽。
- AWSの環境さえ作ればなんとかなるので早さが必要な時にはこれ。
- ローカルでイメージのビルド→Pushもできるのでデプロイも早い(はず)。
- テスト・運用・多人数な時にはやってはいけない。
- ホストPCのビルド設定をちゃんとしないとバグる。
- デメリット
- imagedefinitions.json ( or appspec.yml)の生成時にごちゃごちゃするので、ここらへんで詰まるとデバッグが面倒。
- BitbucketにPush→CodeConnection→Codepipeline
- メリット
- 古いコンテナの処理とかを面倒見てくれるので楽。
- ほとんどAWS側の設定だけでいける。
- 一番スマートに見える。
- ベストプラクティスはこれ?
- デメリット
- imagedefinitions.json ( or appspec.yml)の生成時にごちゃごちゃするので、ここらへんで詰まるとデバッグが面倒。
- AWS側の権限めんどくさい。
- GitHub:オーナー権限、Bitbucket:チーム管理者権限が必要でめんどくさい。
- GitbuhはCodeConnectionを使わなくてもいける?
- 事前にコンソールから接続を作成しておく必要あり。
感想
CDKのリソース作り直しで循環参照になるのがつらかったです。
不備等あれば指摘いただけるとありがたいです。
Discussion