Zenn

Fargateのデプロイの比較

2025/02/26に公開

概要

Fargateをデプロイしようとしたが、CodeCommitの新規受付が終了していたり、シンプルな環境を整備したかったりでいろいろと迷ってみた。

  1. Bitbucket pipelineで、AWS CLIで全部やる
  2. Bitbucket pipelineでイメージをビルド、ECRにPush→Codepipeline
  3. BitbucketにPush→CodeStarConnection→Codepipeline

結論

  1. Bitbucket pipelineでイメージをビルド、ECRにPush→Codepipeline

これがECR→ECSのpipelineだけ先に作ってしまえばいいので楽だと思いました。

コード(Fargateの作成+α)

全ての方法でほぼ共通している部分です。リソースの作成をしています。

  1. 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,
    })
  }
}

  1. 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,
    }
  }
}

  1. db-stack.ts (略)

コード(1. Bitbucket pipelineで、AWS CLIで全部やる)

cliからpipelineを作ってそれを使ってデプロイ…とするとおなじになっちゃうので、自力で頑張ります。

  1. 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,
    })
  }
}

  1. 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デプロイはやりません。

  1. 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,
        }),
      ],
    })
  }
}

  1. 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,
    })
  }
}

  1. 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を使えばいいらしい。

  1. 接続の作成
    1. 対象のBitbucketアカウントにログインしておく
    2. デベロッパー用ツール(CodeBuild, CodeDeployとか)にアクセス
    3. サイドバー → 設定 → 接続 → 接続を作成
      1. 必要事項を入力 → 次へ
    4. 新しいアプリをインストール → (Bitbucketのページ)→ アクセスを許可する
  2. 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,
        }),
      ],
    })
  }
}

所感

  1. Bitbucket pipelineで、AWS CLIで全部やる
  • メリット
    • cliなので、手軽に手元で試せる。
    • イメージのタグも自動でつけられるのでよさそう。
    • リソースの作成権限を自由に得られない場合だと、ほかのやつに比べ楽。
  • デメリット
    • BitbucketPipelineで実行するので、デバッグが面倒。
    • デプロイユーザーの権限がいろいろ必要なので設定が面倒。
    • デプロイの承認など、Approvalを挟めない。
  1. Bitbucket pipelineでイメージをビルド、ECRにPush→Codepipeline
  • メリット
    • 古いコンテナの処理とかを面倒見てくれるので楽。
    • AWSの環境さえ作ればなんとかなるので早さが必要な時にはこれ。
    • ローカルでイメージのビルド→Pushもできるのでデプロイも早い(はず)。
      • テスト・運用・多人数な時にはやってはいけない。
      • ホストPCのビルド設定をちゃんとしないとバグる。
  • デメリット
    • imagedefinitions.json ( or appspec.yml)の生成時にごちゃごちゃするので、ここらへんで詰まるとデバッグが面倒。
  1. BitbucketにPush→CodeConnection→Codepipeline
  • メリット
    • 古いコンテナの処理とかを面倒見てくれるので楽。
    • ほとんどAWS側の設定だけでいける。
    • 一番スマートに見える。
      • ベストプラクティスはこれ?
  • デメリット
    • imagedefinitions.json ( or appspec.yml)の生成時にごちゃごちゃするので、ここらへんで詰まるとデバッグが面倒。
    • AWS側の権限めんどくさい。
    • GitHub:オーナー権限、Bitbucket:チーム管理者権限が必要でめんどくさい。
      • GitbuhはCodeConnectionを使わなくてもいける?
    • 事前にコンソールから接続を作成しておく必要あり。

感想

CDKのリソース作り直しで循環参照になるのがつらかったです。
不備等あれば指摘いただけるとありがたいです。

ユカイ工学テックブログ

Discussion

ログインするとコメントできます