コード化中
- AWS CDKの導入
- CodePipelineのコード化(この記事)
- EventBridge Schedulerのコード化
- ECS clusterとserviceのコード化
- 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はこんな感じで環境ごとに分けるようにしています
{
"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を作成します
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が作成されます
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を渡すように変更します
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()
}
class BatchStack(
parent: Construct,
name: String,
stackProps: StackProps
): Stack(parent, name, stackProps) {
}
これでECSにデプロイまで行うCodePipelineが完成です
次回
EventBridge Schedulerでバッチの定義をする