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

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

Last updated at Posted at 2023-06-21

コード化中

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

ECS task definistion

EventBridge Schedulerの中でタスク定義を使用するので合わせてコード化します
特筆することは特に無いです
先輩が作成した環境と同じになるように実装しました

var taskExecutionRole: Role = Role.Builder
    .create(this, "developmentMyappBatchCdkTaskExecutionRole")
    .roleName("developmentMyappBatchCdkTaskExecutionRole")
    .assumedBy(ServicePrincipal.Builder
        .create("ecs-tasks.amazonaws.com")
        .build()
    )
    .managedPolicies(listOf(
        ManagedPolicy.fromAwsManagedPolicyName("service-role/AmazonECSTaskExecutionRolePolicy")
    ))
    .build()

var taskDefinition: TaskDefinition = TaskDefinition.Builder
    .create(this, "developmentMyappBatchCdkEcsTaskDefinition")
    .family("developmentMyappBatchCdkEcsTaskDefinition")
    .compatibility(Compatibility.FARGATE)
    .cpu("1024")
    .memoryMiB("2048")
    .executionRole(taskExecutionRole)
    .taskRole(taskExecutionRole)
    .runtimePlatform(RuntimePlatform.builder()
        .operatingSystemFamily(OperatingSystemFamily.LINUX)
        .cpuArchitecture(CpuArchitecture.X86_64)
        .build())
    .build()

val logGroup: LogGroup = LogGroup.Builder
    .create(this, "developmentMyappBatchCdkEcsContainerLogGroup")
    .logGroupName("/ecs/" + "developmentMyappBatchCdkEcsContainer")
    .retention(RetentionDays.INFINITE)
    .build()

val ecsEnvironment = context["ecsEnvironment"] as Map<String, String>
val dbSettings = context["dbSettings"] as Map<String, String>

taskDefinition.addContainer(
    "developmentMyappBatchCdkTaskContainerDefinition",
    ContainerDefinitionOptions.builder()
        .containerName("batch")
        .image(ContainerImage.fromEcrRepository(ecrRepository))
        .cpu(1024)
        .memoryLimitMiB(2048)
        .essential(true)
        .environment(ecsEnvironment + dbSettings)
        .logging(LogDriver.awsLogs(AwsLogDriverProps.builder()
            .logGroup(logGroup)
            .streamPrefix("ecs")
            .build()))
        .build()
)

EventBridge Scheduler

EventBridge SchedulerはL2のライブラリが無いのでL1のCfnScheduleを使用します
EventBridge Schedulerの設定については先輩の記事をどうぞ

複数のバッチを定義したいのでListにしてループさせています

val awsAccountId = this.getNode().tryGetContext("awsAccountId").toString()
val batchTaskDefinitionArn = ecsTaskDefinition.getTaskDefinitionArn()
val batchSecurityGroup = context["batchSecurityGroup"].toString()
val batchSubnet = context["batchSubnet"].toString()

// バッチを定義
// listOf("batch name", "cron式", "ENABLED or DISABLED", "command")
val scheduleArray = listOf(
    listOf(target + "MyappBatch1", "cron(0 0 * * ? *)", "ENABLED", "--batch.execute=batch1"),
    listOf(target + "MyappBatch2", "cron(0 0 * * ? *)", "ENABLED", "--batch.execute=batch2")
)

val group: CfnScheduleGroup = CfnScheduleGroup.Builder
    .create(this, target + "MyappBatchCdkScheduleGroup")
    .name(target + "MyappBatchCdkScheduleGroup")
    .build()

val runTaskPolicy: PolicyDocument = PolicyDocument.Builder
    .create()
    .statements(listOf(
        PolicyStatement.Builder
            .create()
            .actions(listOf("ecs:RunTask"))
            .resources(listOf(batchTaskDefinitionArn))
            .conditions(mapOf(
                "ArnLike" to mapOf("ecs:cluster" to ecsClusterArn)
            ))
            .build()
    ))
    .build()
val passRolePolicy: PolicyDocument = PolicyDocument.Builder
    .create()
    .statements(listOf(
        PolicyStatement.Builder
            .create()
            .actions(listOf("iam:PassRole"))
            .resources(listOf(ecsTaskExecutionRole.getRoleArn()))
            .build()
    ))
    .build()
val role: Role = Role.Builder
    .create(this, target + "MyappBatchCdkSchedulerRole")
    .roleName(target + "MyappBatchCdkSchedulerRole")
    .assumedBy(ServicePrincipal.Builder
        .create("scheduler.amazonaws.com")
        .conditions(mapOf(
            "StringEquals" to mapOf("aws:SourceAccount" to awsAccountId)
        ))
        .build()
    )
    .managedPolicies(listOf(
        ManagedPolicy.fromAwsManagedPolicyName("service-role/AmazonECSTaskExecutionRolePolicy")
    ))
    .inlinePolicies(mapOf(
        "RunTaskPolicy" to runTaskPolicy,
        "PassRolePolicy" to passRolePolicy
    ))
    .build()

scheduleArray.forEach {
    val targetProperty: TargetProperty = TargetProperty.builder()
        .arn(ecsClusterArn)
        .ecsParameters(EcsParametersProperty.builder()
            .taskDefinitionArn(batchTaskDefinitionArn)
            .taskCount(1)
            .launchType("FARGATE")
            .networkConfiguration(NetworkConfigurationProperty.builder()
                .awsvpcConfiguration(AwsVpcConfigurationProperty.builder()
                    .subnets(listOf(batchSubnet))
                    .securityGroups(listOf(batchSecurityGroup))
                    .assignPublicIp("ENABLED")
                    .build())
                .build())
            .platformVersion("LATEST")
            .enableEcsManagedTags(true)
            .enableExecuteCommand(true)
            .build())
        .input("""{
"containerOverrides": [
{
    "name": "batch",
    "command": ["${it[3]}"]
}
]
}""")
        .roleArn(role.getRoleArn())
        .retryPolicy(RetryPolicyProperty.builder()
            .maximumEventAgeInSeconds(3600) // 1h = 60 * 60
            .maximumRetryAttempts(2)
            .build())
        .build()

    CfnSchedule.Builder
        .create(this, it[0])
        .flexibleTimeWindow(FlexibleTimeWindowProperty.builder()
            .mode("OFF")
            .build())
        .name(it[0])
        .groupName(group.getName())
        .scheduleExpression(it[1])
        .scheduleExpressionTimezone("Asia/Tokyo")
        .state(it[2])
        .target(targetProperty)
        .build()
}

ファイル分割

これを全部 BatchStack.kt にまとめてしまうのは読みにくいのでファイル分割を行いました

BatchStack.kt
class BatchStack(
    parent: Construct,
    name: String,
    stackProps: StackProps,
    ecsClusterArn: String
): Stack(parent, name, stackProps) {
    init {
        val context = this.getNode().tryGetContext("development") as Map<String, Any>

        val pipeline = Pipeline(this, context)
        pipeline.createPipeline()

        val ecs = Ecs(this, context)
        ecs.createTaskDefinition(pipeline.ecrRepository)

        val eventBridge = EventBridge(this, context)
        eventBridge.createScheduler(
            ecs.taskDefinition,
            ecs.taskExecutionRole,
            ecsClusterArn)
    }
}
recources/batch/Pipeline.kt
class Pipeline(val scope: Construct, val context: Map<String, Any>) {
    lateinit var ecrRepository: Repository

    fun createPipeline() {
        ...
    }
}
resources/batch/Ecs.kt
class Ecs(val scope: Construct, val context: Map<String, Any>) {
    lateinit var taskDefinition: TaskDefinition
    lateinit var taskExecutionRole: Role

    fun createTaskDefinition(ecrRepository: Repository) {
        ...
    }
}
resources/batch/EventBridge.kt
class EventBridge(val scope: Construct, val context: Map<String, Any>) {
    fun createScheduler(
        ecsTaskDefinition: TaskDefinition,
        ecsTaskExecutionRole: Role,
        ecsClusterArn: String
    ) {
        ...
    }
}

次回

ここまででバッチのリポジトリに関連するコード化が終了したので、次はDeployStage内のstackを増やしてAPIと管理画面のリポジトリに関するコード化を行います

14
5
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
14
5