16
6

More than 1 year has passed since last update.

お題は不問!Qiita Engineer Festa 2023で記事投稿!

先輩がノリで作ったAWSをちゃんとコード化してみた2 (CodePipelineのコード化)

Last updated at Posted at 2023-06-20

コード化中

  1. AWS CDKの導入
  2. CodePipelineのコード化(この記事)
  3. EventBridge Schedulerのコード化
  4. ECS clusterとserviceのコード化
  5. ElastiCacheのコード化、本番環境構築

CodePipeline

  • 自分自身反映用
  • バッチ処理のリポジトリのコード反映用(Source, Build)
  • API用のリポジトリのコード反映用(Source, Build, Deploy)
  • 管理画面用のリポジトリのコード反映用(Source, Build, Deploy)

の4つ作成します

CDKで作成するCodePipelineは2種類あって

  • 自分自身を更新する場合は、software.amazon.awscdk.pipelines.CodePipeline
  • 全く別のCodePipelineは、software.amazon.awscdk.services.codepipeline.Pipeline

を使用します

自分自身を更新するCodePipeline

このワークショップが自分自身を更新する用のCodePipelineを作成していて、今回対応したいことと同じなので参考にしていきます

ゴールは、インフラ用のGithubリポジトリのdevelopブランチにpushされたらインフラの変更が反映されることです

ソース

ワークショップはCodeCommitのリポジトリを使用していますが、Githubのリポジトリにしたいので

val context = this.getNode().tryGetContext("development") as Map<String, Any>

val connectionArn = context["infraConnectionArn"].toString()
val owner = context["githubOwner"].toString()
val repository = context["infraRepository"].toString()
val branch = context["infraBranch"].toString()

val github: CodePipelineSource = CodePipelineSource
    .connection(
        owner + "/" + repository,
        branch,
        ConnectionSourceOptions.builder()
            .connectionArn(connectionArn)
            .build()
    )

cdk.jsonはこんな感じで環境ごとに分けるようにしています

cdk.json
{
  "app": "/bin/bash ./app.sh",
  "context": {
    "awsAccountId": "awsAccountId",
    "awsDefaultRegion": "ap-northeast-1",

    "development": {
      "env": "development",
      "githubOwner": "githubOwner",
      "infraConnectionArn": "arn:aws:codestar-connections:ap-northeast-1:awsAccountId:connection/infraConnectionArn",
      "infraRepository": "infraRepository",
      "infraBranch": "develop",
    }
    "production": {
      "env": "production",
      "githubOwner": "githubOwner",
      "infraConnectionArn": "arn:aws:codestar-connections:ap-northeast-1:awsAccountId:connection/infraConnectionArn",
      "infraRepository": "infraRepository",
      "infraBranch": "main",
    }
  }
}

CodePipelineをcreate

ディレクトリ階層を変えたのでprimaryOutputDirectoryの指定が必要でした

val pipeline: CodePipeline = CodePipeline.Builder
    .create(this, "MyappInfraCdkPipeline")
    .pipelineName("MyappInfraCdkPipeline")
    .synth(
        CodeBuildStep.Builder
            .create("Synth")
            .input(github)
            .installCommands(listOf(
                "npm install -g aws-cdk"
            ))
            .commands(listOf(
                "cd infra",
                "npx cdk synth"
            ))
            .primaryOutputDirectory("infra/cdk.out")
            .build()
    )
    .build()

stageの追加

val deployStage: Stage = DeployStage(this, "MyappInfraCdkDeployStage")
pipeline.addStage(deployStage)

DeployStageの中にリポジトリごとのstackを作成し、各stackでデプロイ用のCodePipelineやECSを作成しています

デプロイ用のCodePipeline

バッチ処理のリポジトリのコード反映用(Source, Build)のCodePipelineを作成します

BatchStack.kt
class BatchStack(parent: Construct, name: String): Stack(parent, name) {
    init {
        var ecrRepository: Repository = Repository.Builder
            .create(this, "developmentMyappBatchCdkRepository")
            .repositoryName("development-myapp-batch-cdk-repository")
            .encryption(RepositoryEncryption.KMS)
            .build()

        val awsDefaultRegion = this.getNode().tryGetContext("awsDefaultRegion").toString()
        val connectionArn = context["batchConnectionArn"].toString()
        val owner = context["githubOwner"].toString()
        val repository = context["batchRepository"].toString()
        val branch = context["batchBranch"].toString()

        val ecrRepositoryUri = ecrRepository.getRepositoryUri()

        val sourceArtufact: Artifact = Artifact.artifact("SourceArtifact")

        val github: Action = CodeStarConnectionsSourceAction.Builder
            .create()
            .actionName("Source")
            .owner(owner)
            .repo(repository)
            .branch(branch)
            .output(sourceArtufact)
            .connectionArn(connectionArn)
            .build()

        val source: StageOptions = StageOptions.builder()
            .stageName("Source")
            .actions(listOf(github))
            .build()

        val buildArtifact: Artifact = Artifact.artifact("BuildArtifact")

        val role: Role = Role.Builder
            .create(this, "developmentMyappBatchCdkAutoDeployRole")
            .roleName("developmentMyappBatchCdkAutoDeployRole")
            .assumedBy(ServicePrincipal("codebuild.amazonaws.com"))
            .managedPolicies(listOf(
                ManagedPolicy.fromAwsManagedPolicyName("AmazonEC2ContainerRegistryPowerUser")
            ))
            .build()

        val codeBuildProject: PipelineProject = PipelineProject.Builder
            .create(this, "developmentMyappBatchCdkProject")
            .projectName("developmentMyappBatchCdkProject")
            .environment(BuildEnvironment.builder()
                .buildImage(LinuxBuildImage.STANDARD_4_0)
                .privileged(true)
                .build()
            )
            .environmentVariables(mapOf(
                "AWS_DEFAULT_REGION" to BuildEnvironmentVariable.builder().value(awsDefaultRegion).build(),
                "ENV" to BuildEnvironmentVariable.builder().value("development").build(),
                "REPOSITORY_URI" to BuildEnvironmentVariable.builder().value(ecrRepositoryUri).build()
            ))
            .role(role)
            .build()

        val codeBuild: Action = CodeBuildAction.Builder
            .create()
            .actionName("Build")
            .project(codeBuildProject)
            .input(sourceArtufact)
            .outputs(listOf(buildArtifact))
            .build()

        val build: StageOptions = StageOptions.builder()
            .stageName("Build")
            .actions(listOf(codeBuild))
            .build()

        val pipeline: Pipeline = Pipeline.Builder
            .create(this, "developmentMyappBatchCdkAutoDeployPipeline")
            .pipelineName("developmentMyappBatchCdkAutoDeployPipeline")
            .build()

        pipeline.addStage(source)
        pipeline.addStage(build)
    }
}

CodeBuildで環境変数渡したいときは environmentVariables
CodeBuildでビルド時にdocker image構築するなら privileged(true)

これでビルドとECRへのプッシュ(アプリケーションのbuild.specで実行している)を行うCodePipelineが作成されます

スクリーンショット 2023-06-12 14.31.39.png

ECSのデプロイを追加

API用、管理画面用の2つはECSへのデプロイまで行いたいので、この2つはstageを追加します

val serviceArn = context["apiEcsServiceArn"].toString()
val service = BaseService.fromServiceArnWithCluster(this, "developmentMyappApiCdkEcsService", serviceArn)

val ecsDeploy: Action = EcsDeployAction.Builder
    .create()
    .actionName("Deploy")
    .variablesNamespace("DeployVariables")
    .input(buildArtifact)
    .service(service)
    .build()

val deploy: StageOptions = StageOptions.builder()
    .stageName("Deploy")
    .actions(listOf(ecsDeploy))
    .build()

pipeline.addStage(deploy)

しかし、これをデプロイすると

Exception in thread "main" java.lang.RuntimeException: Error: Pipeline stack which uses cross-environment actions must have an explicitly set region

のエラーが発生します
アカウント情報などを渡す必要があるようなので、App.ktから各リポジトリごとのstackまでstackPropsを渡すように変更します

App.kt
fun main(args: Array<String>): Unit {
    val app = App()
    val awsAccountId = app.getNode().tryGetContext("awsAccountId").toString()
    val awsDefaultRegion = app.getNode().tryGetContext("awsDefaultRegion").toString()

    val stackProps: StackProps = StackProps.Builder()
            .env(Environment.builder()
                .region(awsDefaultRegion)
                .account(awsAccountId)
                .build())
            .build()

    InfraPipelineStack(app, "developmentMyappInfraCdkPipelineStack", stackProps)
    app.synth()
}
BatchStack.kt
class BatchStack(
    parent: Construct,
    name: String,
    stackProps: StackProps
): Stack(parent, name, stackProps) {
}

これでECSにデプロイまで行うCodePipelineが完成です
スクリーンショット 2023-06-12 15.48.31.png

次回

EventBridge Schedulerでバッチの定義をする

16
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
6