102 types
This module is part of the AWS Cloud Development Kit project.
AWS Batch is a batch processing tool for efficiently running hundreds of thousands computing jobs in AWS. Batch can dynamically provision Amazon EC2 Instances to meet the resource requirements of submitted jobs and simplifies the planning, scheduling, and executions of your batch workloads. Batch achieves this through four different resources:
ComputeEnvironments can be managed or unmanaged. Batch will automatically provision EC2 Instances in a managed ComputeEnvironment and will
not provision any Instances in an unmanaged ComputeEnvironment. Managed ComputeEnvironments can use ECS, Fargate, or EKS resources to spin up
EC2 Instances in (ensure your EKS Cluster has been configured
to support a Batch ComputeEnvironment before linking it). You can use Launch Templates and Placement Groups to configure exactly how these resources
will be provisioned.
JobDefinitions can use either ECS resources or EKS resources. ECS JobDefinitions can use multiple containers to execute distributed workloads.
EKS JobDefinitions can only execute a single container. Submitted Jobs use JobDefinitions as templates.
JobQueues must link at least one ComputeEnvironment. Jobs exit the Queue in FIFO order unless a SchedulingPolicy is specified.
SchedulingPolicys tell the Scheduler how to choose which Jobs should be executed next by the ComputeEnvironment.
Spot instances are significantly discounted EC2 instances that can be reclaimed at any time by AWS.
Workloads that are fault-tolerant or stateless can take advantage of spot pricing.
To use spot spot instances, set spot to true on a managed Ec2 or Fargate Compute Environment:
vpc = AWSCDK::EC2::VPC.new(self, "VPC")
AWSCDK::Batch::FargateComputeEnvironment.new(self, "myFargateComputeEnv", {
vpc: vpc,
spot: true,
})
Batch allows you to specify the percentage of the on-demand instance that the current spot price
must be to provision the instance using the spot_bid_percentage.
This defaults to 100%, which is the recommended value.
This value cannot be specified for FargateComputeEnvironments
and only applies to ManagedEc2EcsComputeEnvironments.
The following code configures a Compute Environment to only use spot instances that
are at most 20% the price of the on-demand instance price:
Note: For FargateComputeEnvironment, while the FargateComputeEnvironmentProps interface includes properties like replace_compute_environment, terminate_on_update, update_timeout, and update_to_latest_image_version, these specific properties are not applicable when configuring AWS Batch Fargate compute environments. They primarily apply to EC2-based compute environments. Please refer to the official AWS Batch UpdateComputeEnvironment API documentation and User Guide for details on updating Fargate compute environments.
vpc = AWSCDK::EC2::VPC.new(self, "VPC")
AWSCDK::Batch::ManagedEC2ECSComputeEnvironment.new(self, "myEc2ComputeEnv", {
vpc: vpc,
spot: true,
spot_bid_percentage: 20,
})
For stateful or otherwise non-interruption-tolerant workflows, omit spot or set it to false to only provision on-demand instances.
There are several ways to configure instance types for your compute environment:
AWS Batch provides default instance classes that automatically select cost-effective, up-to-date instances based on your region. This is the recommended approach for new projects:
vpc = AWSCDK::EC2::VPC.new(self, "Vpc")
# Use ARM64 instances (e.g., m6g, c6g, r6g, c7g families)
AWSCDK::Batch::ManagedEC2ECSComputeEnvironment.new(self, "Arm64Ec2ComputeEnv", {
vpc: vpc,
default_instance_classes: [AWSCDK::Batch::DefaultInstanceClass::ARM64],
})
# Use x86_64 instances (e.g., m6i, c6i, r6i, c7i families)
AWSCDK::Batch::ManagedEC2ECSComputeEnvironment.new(self, "X86_64Ec2ComputeEnv", {
vpc: vpc,
default_instance_classes: [AWSCDK::Batch::DefaultInstanceClass::X86_64],
})
The default_x86_64 and default_arm64 categories are dynamically updated by AWS as new instance families become available in your region.
To use only specific instance types without any automatic defaults, set useOptimalInstanceClasses: false:
vpc = AWSCDK::EC2::VPC.new(self, "Vpc")
# Use only R4 instance class (Batch chooses the size)
AWSCDK::Batch::ManagedEC2ECSComputeEnvironment.new(self, "R4Ec2ComputeEnv", {
vpc: vpc,
use_optimal_instance_classes: false,
instance_classes: [AWSCDK::EC2::InstanceClass::R4],
})
# Use only a specific instance type
AWSCDK::Batch::ManagedEC2ECSComputeEnvironment.new(self, "M5AdLargeEc2ComputeEnv", {
vpc: vpc,
use_optimal_instance_classes: false,
instance_types: [AWSCDK::EC2::InstanceType.of(AWSCDK::EC2::InstanceClass::M5AD, AWSCDK::EC2::InstanceSize::LARGE)],
})
By default, use_optimal_instance_classes is true, which adds the optimal instance type.
Note: Since November 2025, optimal behaves the same as default_x86_64 and is dynamically updated as AWS introduces new instance families. Both options automatically select cost-effective x86_64 instance types (from the m6i, c6i, r6i, and c7i families) based on your region.
You can combine this with additional instance types:
vpc = nil # AWSCDK::EC2::IVPC
compute_env = AWSCDK::Batch::ManagedEC2ECSComputeEnvironment.new(self, "Ec2ComputeEnv", {
vpc: vpc,
instance_types: [AWSCDK::EC2::InstanceType.of(AWSCDK::EC2::InstanceClass::M5AD, AWSCDK::EC2::InstanceSize::LARGE)],
})
| Goal | Configuration |
|---|---|
| Use latest x86_64 instances | defaultInstanceClasses: [DefaultInstanceClass.X86_64] or no configuration (default) |
| Use latest ARM64 instances | defaultInstanceClasses: [DefaultInstanceClass.ARM64] |
| Use only specific instance classes | useOptimalInstanceClasses: false + instanceClasses: [...] |
| Use only specific instance types | useOptimalInstanceClasses: false + instanceTypes: [...] |
| Use optimal + additional instances | instanceClasses: [...] or instanceTypes: [...] |
Note: Batch does not allow specifying instance types or classes with different architectures.
For example, InstanceClass.A1 (ARM) cannot be specified alongside optimal (x86_64).
When using ARM-based instances (e.g., Graviton), use defaultInstanceClasses: [DefaultInstanceClass.ARM64], or set useOptimalInstanceClasses: false and explicitly specify ARM instance classes/types.
Note: use_optimal_instance_classes and default_instance_classes cannot be used together.
You can configure Amazon Machine Images (AMIs). This example configures your ComputeEnvironment to use Amazon Linux 2023.
vpc = nil # AWSCDK::EC2::IVPC
AWSCDK::Batch::ManagedEC2ECSComputeEnvironment.new(self, "myEc2ComputeEnv", {
vpc: vpc,
images: [
{
image_type: AWSCDK::Batch::ECSMachineImageType::ECS_AL2023,
},
],
})
If your image needs GPU resources, specify ECS_AL2023_NVIDIA:
vpc = nil # AWSCDK::EC2::IVPC
AWSCDK::Batch::ManagedEC2ECSComputeEnvironment.new(self, "myGpuComputeEnv", {
vpc: vpc,
images: [
{
image_type: AWSCDK::Batch::ECSMachineImageType::ECS_AL2023_NVIDIA,
},
],
})
| Allocation Strategy | Optimized for | Downsides |
|---|---|---|
| BEST_FIT | Cost | May limit throughput |
| BEST_FIT_PROGRESSIVE | Throughput | May increase cost |
| SPOT_CAPACITY_OPTIMIZED | Least interruption | Only useful on Spot instances |
| SPOT_PRICE_CAPACITY_OPTIMIZED | Least interruption + Price | Only useful on Spot instances |
Batch provides different Allocation Strategies to help it choose which instances to provision.
If your workflow tolerates interruptions, you should enable spot on your ComputeEnvironment
and use SPOT_PRICE_CAPACITY_OPTIMIZED (this is the default if spot is enabled).
This will tell Batch to choose the instance types from the ones you’ve specified that have
the most spot capacity available to minimize the chance of interruption and have the lowest price.
To get the most benefit from your spot instances,
you should allow Batch to choose from as many different instance types as possible.
If you only care about minimal interruptions and not want Batch to optimize for cost, use
SPOT_CAPACITY_OPTIMIZED. SPOT_PRICE_CAPACITY_OPTIMIZED is recommended over SPOT_CAPACITY_OPTIMIZED
for most use cases.
If your workflow does not tolerate interruptions and you want to minimize your costs at the expense
of potentially longer waiting times, use AllocationStrategy.BEST_FIT.
This will choose the lowest-cost instance type that fits all the jobs in the queue.
If instances of that type are not available,
the queue will not choose a new type; instead, it will wait for the instance to become available.
This can stall your Queue, with your compute environment only using part of its max capacity
(or none at all) until the BEST_FIT instance becomes available.
If you are running a workflow that does not tolerate interruptions and you want to maximize throughput,
you can use AllocationStrategy.BEST_FIT_PROGRESSIVE.
This is the default Allocation Strategy if spot is false or unspecified.
This strategy will examine the Jobs in the queue and choose whichever instance type meets the requirements
of the jobs in the queue and with the lowest cost per vCPU, just as BEST_FIT.
However, if not all of the capacity can be filled with this instance type,
it will choose a new next-best instance type to run any jobs that couldn’t fit into the BEST_FIT capacity.
To make the most use of this allocation strategy,
it is recommended to use as many instance classes as is feasible for your workload.
This example shows a ComputeEnvironment that uses BEST_FIT_PROGRESSIVE
with 'optimal' and InstanceClass.M5 instance types:
vpc = nil # AWSCDK::EC2::IVPC
compute_env = AWSCDK::Batch::ManagedEC2ECSComputeEnvironment.new(self, "myEc2ComputeEnv", {
vpc: vpc,
instance_classes: [AWSCDK::EC2::InstanceClass::M5],
})
This example shows a ComputeEnvironment that uses BEST_FIT with 'optimal' instances:
vpc = nil # AWSCDK::EC2::IVPC
compute_env = AWSCDK::Batch::ManagedEC2ECSComputeEnvironment.new(self, "myEc2ComputeEnv", {
vpc: vpc,
allocation_strategy: AWSCDK::Batch::AllocationStrategy::BEST_FIT,
})
Note: allocation_strategy cannot be specified on Fargate Compute Environments.
You can specify the maximum and minimum vCPUs a managed ComputeEnvironment can have at any given time.
Batch will always maintain minv_cpus worth of instances in your ComputeEnvironment, even if it is not executing any jobs,
and even if it is disabled. Batch will scale the instances up to maxv_cpus worth of instances as
jobs exit the JobQueue and enter the ComputeEnvironment. If you use AllocationStrategy.BEST_FIT_PROGRESSIVE,
AllocationStrategy.SPOT_PRICE_CAPACITY_OPTIMIZED, or AllocationStrategy.SPOT_CAPACITY_OPTIMIZED,
batch may exceed maxv_cpus; it will never exceed maxv_cpus by more than a single instance type. This example configures a
minv_cpus of 10 and a maxv_cpus of 100:
vpc = nil # AWSCDK::EC2::IVPC
AWSCDK::Batch::ManagedEC2ECSComputeEnvironment.new(self, "myEc2ComputeEnv", {
vpc: vpc,
instance_classes: [AWSCDK::EC2::InstanceClass::R4],
minv_cpus: 10,
maxv_cpus: 100,
})
You can tag any instances launched by your managed EC2 ComputeEnvironments by using the CDK Tags API:
vpc = nil # AWSCDK::EC2::IVPC
tag_ce = AWSCDK::Batch::ManagedEC2ECSComputeEnvironment.new(self, "CEThatMakesTaggedInstnaces", {
vpc: vpc,
})
AWSCDK::Tags.of(tag_ce).add("super", "salamander")
Unmanaged ComputeEnvironments do not support maxv_cpus or minv_cpus because you must provision and manage the instances yourself;
that is, Batch will not scale them up and down as needed.
Multiple JobQueues can share the same ComputeEnvironment.
If multiple Queues are attempting to submit Jobs to the same ComputeEnvironment,
Batch will pick the Job from the Queue with the highest priority.
This example creates two JobQueues that share a ComputeEnvironment:
vpc = nil # AWSCDK::EC2::IVPC
shared_compute_env = AWSCDK::Batch::FargateComputeEnvironment.new(self, "spotEnv", {
vpc: vpc,
spot: true,
})
low_priority_queue = AWSCDK::Batch::JobQueue.new(self, "JobQueue", {
priority: 1,
})
high_priority_queue = AWSCDK::Batch::JobQueue.new(self, "JobQueue", {
priority: 10,
})
low_priority_queue.add_compute_environment(shared_compute_env, 1)
high_priority_queue.add_compute_environment(shared_compute_env, 1)
You can react to jobs stuck in RUNNABLE state by setting a job_state_time_limit_actions in JobQueue.
Specifies actions that AWS Batch will take after the job has remained at the head of the queue in the
specified state for longer than the specified time.
AWSCDK::Batch::JobQueue.new(self, "JobQueue", {
job_state_time_limit_actions: [
{
action: AWSCDK::Batch::JobStateTimeLimitActionsAction::CANCEL,
max_time: AWSCDK::Duration.minutes(10),
reason: AWSCDK::Batch::JobStateTimeLimitActionsReason::INSUFFICIENT_INSTANCE_CAPACITY,
state: AWSCDK::Batch::JobStateTimeLimitActionsState::RUNNABLE,
},
],
})
Batch JobQueues execute Jobs submitted to them in FIFO order unless you specify a SchedulingPolicy.
FIFO queuing can cause short-running jobs to be starved while long-running jobs fill the compute environment.
To solve this, Jobs can be associated with a share.
Shares consist of a share_identifier and a weight_factor, which is inversely correlated with the vCPU allocated to that share identifier.
When submitting a Job, you can specify its share_identifier to associate that particular job with that share.
Let's see how the scheduler uses this information to schedule jobs.
For example, if there are two shares defined as follows:
| Share Identifier | Weight Factor |
|---|---|
| A | 1 |
| B | 1 |
The weight factors share the following relationship:
A_{vCpus} / A_{Weight} = B_{vCpus} / B_{Weight}
where BvCpus is the number of vCPUs allocated to jobs with share identifier 'B', and B_weight is the weight factor of B.
The total number of vCpus allocated to a share is equal to the amount of jobs in that share times the number of vCpus necessary for every job.
Let's say that each A job needs 32 VCpus (A_requirement = 32) and each B job needs 64 vCpus (B_requirement = 64):
A_{vCpus} = A_{Jobs} * A_{Requirement}
B_{vCpus} = B_{Jobs} * B_{Requirement}
We have:
A_{vCpus} / A_{Weight} = B_{vCpus} / B_{Weight}
A_{Jobs} * A_{Requirement} / A_{Weight} = B_{Jobs} * B_{Requirement} / B_{Weight}
A_{Jobs} * 32 / 1 = B_{Jobs} * 64 / 1
A_{Jobs} * 32 = B_{Jobs} * 64
A_{Jobs} = B_{Jobs} * 2
Thus the scheduler will schedule two 'A' jobs for each 'B' job.
You can control the weight factors to change these ratios, but note that weight factors are inversely correlated with the vCpus allocated to the corresponding share.
This example would be configured like this:
fairshare_policy = AWSCDK::Batch::FairshareSchedulingPolicy.new(self, "myFairsharePolicy")
fairshare_policy.add_share({
share_identifier: "A",
weight_factor: 1,
})
fairshare_policy.add_share({
share_identifier: "B",
weight_factor: 1,
})
AWSCDK::Batch::JobQueue.new(self, "JobQueue", {
scheduling_policy: fairshare_policy,
})
Note: The scheduler will only consider the current usage of the compute environment unless you specify share_decay.
For example, a share_decay of 5 minutes in the above example means that at any given point in time, twice as many 'A' jobs
will be scheduled for each 'B' job, but only for the past 5 minutes. If 'B' jobs run longer than 5 minutes, then
the scheduler is allowed to put more than two 'A' jobs for each 'B' job, because the usage of those long-running
'B' jobs will no longer be considered after 5 minutes. share_decay linearly decreases the usage of
long running jobs for calculation purposes. For example if share decay is 60 seconds,
then jobs that run for 30 seconds have their usage considered to be only 50% of what it actually is,
but after a whole minute the scheduler pretends they don't exist for fairness calculations.
The following code specifies a share_decay of 5 minutes:
fairshare_policy = AWSCDK::Batch::FairshareSchedulingPolicy.new(self, "myFairsharePolicy", {
share_decay: AWSCDK::Duration.minutes(5),
})
If you have high priority jobs that should always be executed as soon as they arrive,
you can define a compute_reservation to specify the percentage of the
maximum vCPU capacity that should be reserved for shares that are not in the queue.
The actual reserved percentage is defined by Batch as:
(\frac{computeReservation}{100}) ^ {ActiveFairShares}
where ActiveFairShares is the number of shares for which there exists
at least one job in the queue with a unique share identifier.
This is best illustrated with an example.
Suppose there are three shares with share identifiers A, B and C respectively
and we specify the compute_reservation to be 75%. The queue is currently empty,
and no other shares exist.
There are no active fair shares, since the queue is empty. Thus (75/100)^0 = 1 = 100% of the maximum vCpus are reserved for all shares.
A job with identifier A enters the queue.
The number of active fair shares is now 1, hence
(75/100)^1 = .75 = 75% of the maximum vCpus are reserved for all shares that do not have the identifier A;
for this example, this is B and C,
(but if jobs are submitted with a share identifier not covered by this fairshare policy, those would be considered just as B and C are).
Now a B job enters the queue. The number of active fair shares is now 2,
so (75/100)^2 = .5625 = 56.25% of the maximum vCpus are reserved for all shares that do not have the identifier A or B.
Now a second A job enters the queue. The number of active fair shares is still 2,
so the percentage reserved is still 56.25%
Now a C job enters the queue. The number of active fair shares is now 3,
so (75/100)^3 = .421875 = 42.1875% of the maximum vCpus are reserved for all shares that do not have the identifier A, B, or C.
If there are no other shares that your jobs can specify, this means that 42.1875% of your capacity will never be used!
Now, A, B, and C can only consume 100% - 42.1875% = 57.8125% of the maximum vCpus.
Note that the this percentage is not split between A, B, and C.
Instead, the scheduler will use their weight_factors to decide which jobs to schedule;
the only difference is that instead of competing for 100% of the max capacity, jobs compete for 57.8125% of the max capacity.
This example specifies a compute_reservation of 75% that will behave as explained in the example above:
AWSCDK::Batch::FairshareSchedulingPolicy.new(self, "myFairsharePolicy", {
compute_reservation: 75,
shares: [
{weight_factor: 1, share_identifier: "A"},
{weight_factor: 0.5, share_identifier: "B"},
{weight_factor: 2, share_identifier: "C"},
],
})
You can specify a priority on your JobDefinitions to tell the scheduler to prioritize certain jobs that share the same share identifier.
Certain workflows may result in Jobs failing due to intermittent issues. Jobs can specify retry policies to respond to different failures with different actions. There are three different ways information about the way a Job exited can be conveyed;
exit_code: the exit code returned from the process executed by the container. Will only match non-zero exit codes.reason: any middleware errors, like your Docker registry being down.status_reason: infrastructure errors, most commonly your spot instance being reclaimed.For most use cases, only one of these will be associated with a particular action at a time.
To specify common exit_codes, reasons, or status_reasons, use the corresponding value from
the Reason class. This example shows some common failure reasons:
job_defn = AWSCDK::Batch::ECSJobDefinition.new(self, "JobDefn", {
container: AWSCDK::Batch::ECSEC2ContainerDefinition.new(self, "containerDefn", {
image: AWSCDK::ECS::ContainerImage.from_registry("public.ecr.aws/amazonlinux/amazonlinux:latest"),
memory: AWSCDK::Size.mebibytes(2048),
cpu: 256,
}),
retry_attempts: 5,
retry_strategies: [
AWSCDK::Batch::RetryStrategy.of(AWSCDK::Batch::Action::EXIT, AWSCDK::Batch::Reason.CANNOT_PULL_CONTAINER),
],
})
job_defn.add_retry_strategy(AWSCDK::Batch::RetryStrategy.of(AWSCDK::Batch::Action::EXIT, AWSCDK::Batch::Reason.SPOT_INSTANCE_RECLAIMED))
job_defn.add_retry_strategy(AWSCDK::Batch::RetryStrategy.of(AWSCDK::Batch::Action::EXIT, AWSCDK::Batch::Reason.CANNOT_PULL_CONTAINER))
job_defn.add_retry_strategy(AWSCDK::Batch::RetryStrategy.of(AWSCDK::Batch::Action::EXIT, AWSCDK::Batch::Reason.custom({
on_exit_code: "40*",
on_reason: "some reason",
})))
When specifying a custom reason,
you can specify a glob string to match each of these and react to different failures accordingly.
Up to five different retry strategies can be configured for each Job,
and each strategy can match against some or all of exit_code, reason, and status_reason.
You can optionally configure the number of times a job will be retried,
but you cannot configure different retry counts for different strategies; they all share the same count.
If multiple conditions are specified in a given retry strategy,
they must all match for the action to be taken; the conditions are ANDed together, not ORed.
Batch can run jobs on ECS or EKS. ECS jobs can be defined as single container or multinode.
This example creates a JobDefinition that runs a single container with ECS:
my_file_system = nil # AWSCDK::EFS::IFileSystem
my_job_role = nil # AWSCDK::IAM::Role
my_file_system.grant_read(my_job_role)
job_defn = AWSCDK::Batch::ECSJobDefinition.new(self, "JobDefn", {
container: AWSCDK::Batch::ECSEC2ContainerDefinition.new(self, "containerDefn", {
image: AWSCDK::ECS::ContainerImage.from_registry("public.ecr.aws/amazonlinux/amazonlinux:latest"),
memory: AWSCDK::Size.mebibytes(2048),
cpu: 256,
volumes: [
AWSCDK::Batch::ECSVolume.efs({
name: "myVolume",
file_system: my_file_system,
container_path: "/Volumes/myVolume",
use_job_role: true,
}),
],
job_role: my_job_role,
}),
})
For workflows that need persistent storage, batch supports mounting Volumes to the container.
You can both provision the volume and mount it to the container in a single operation:
my_file_system = nil # AWSCDK::EFS::IFileSystem
job_defn = nil # AWSCDK::Batch::ECSJobDefinition
job_defn.container.add_volume(AWSCDK::Batch::ECSVolume.efs({
name: "myVolume",
file_system: my_file_system,
container_path: "/Volumes/myVolume",
}))
job_defn = AWSCDK::Batch::ECSJobDefinition.new(self, "JobDefn", {
container: AWSCDK::Batch::ECSFargateContainerDefinition.new(self, "myFargateContainer", {
image: AWSCDK::ECS::ContainerImage.from_registry("public.ecr.aws/amazonlinux/amazonlinux:latest"),
memory: AWSCDK::Size.mebibytes(2048),
cpu: 256,
ephemeral_storage_size: AWSCDK::Size.gibibytes(100),
fargate_cpu_architecture: AWSCDK::ECS::CpuArchitecture.ARM64,
fargate_operating_system_family: AWSCDK::ECS::OperatingSystemFamily.LINUX,
}),
})
You can enable ECS Exec for interactive debugging and troubleshooting by setting enable_execute_command to true.
When enabled, you'll be able to execute commands interactively in running containers.
job_defn = AWSCDK::Batch::ECSJobDefinition.new(self, "JobDefn", {
container: AWSCDK::Batch::ECSEC2ContainerDefinition.new(self, "Ec2Container", {
image: AWSCDK::ECS::ContainerImage.from_registry("public.ecr.aws/amazonlinux/amazonlinux:latest"),
memory: AWSCDK::Size.mebibytes(2048),
cpu: 256,
enable_execute_command: true,
}),
})
The same functionality is available for Fargate containers:
job_defn = AWSCDK::Batch::ECSJobDefinition.new(self, "JobDefn", {
container: AWSCDK::Batch::ECSFargateContainerDefinition.new(self, "FargateContainer", {
image: AWSCDK::ECS::ContainerImage.from_registry("public.ecr.aws/amazonlinux/amazonlinux:latest"),
memory: AWSCDK::Size.mebibytes(2048),
cpu: 256,
enable_execute_command: true,
}),
})
When enable_execute_command is set to true:
job_role is provided, a new IAM role will be automatically created with the required SSM permissionsjob_role is already provided, the necessary SSM permissions will be added to the existing roleYou can expose SecretsManager Secret ARNs or SSM Parameters to your container as environment variables.
The following example defines the MY_SECRET_ENV_VAR environment variable that contains the
ARN of the Secret defined by my_secret:
my_secret = nil # AWSCDK::SecretsManager::ISecret
job_defn = AWSCDK::Batch::ECSJobDefinition.new(self, "JobDefn", {
container: AWSCDK::Batch::ECSEC2ContainerDefinition.new(self, "containerDefn", {
image: AWSCDK::ECS::ContainerImage.from_registry("public.ecr.aws/amazonlinux/amazonlinux:latest"),
memory: AWSCDK::Size.mebibytes(2048),
cpu: 256,
secrets: {
MY_SECRET_ENV_VAR: AWSCDK::Batch::Secret.from_secrets_manager(my_secret),
},
}),
})
Batch also supports running workflows on EKS. The following example creates a JobDefinition that runs on EKS:
job_defn = AWSCDK::Batch::EKSJobDefinition.new(self, "eksf2", {
container: AWSCDK::Batch::EKSContainerDefinition.new(self, "container", {
image: AWSCDK::ECS::ContainerImage.from_registry("amazon/amazon-ecs-sample"),
volumes: [
AWSCDK::Batch::EKSVolume.empty_dir({
name: "myEmptyDirVolume",
mount_path: "/mount/path",
medium: AWSCDK::Batch::EmptyDirMediumType::MEMORY,
readonly: true,
size_limit: AWSCDK::Size.mebibytes(2048),
}),
],
}),
})
You can mount Volumes to these containers in a single operation:
job_defn = nil # AWSCDK::Batch::EKSJobDefinition
job_defn.container.add_volume(AWSCDK::Batch::EKSVolume.empty_dir({
name: "emptyDir",
mount_path: "/Volumes/emptyDir",
}))
job_defn.container.add_volume(AWSCDK::Batch::EKSVolume.host_path({
name: "hostPath",
host_path: "/sys",
mount_path: "/Volumes/hostPath",
}))
job_defn.container.add_volume(AWSCDK::Batch::EKSVolume.secret({
name: "secret",
optional: true,
mount_path: "/Volumes/secret",
secret_name: "mySecret",
}))
Some workflows benefit from parallellization and are most powerful when run in a distributed environment,
such as certain numerical calculations or simulations. Batch offers MultiNodeJobDefinitions,
which allow a single job to run on multiple instances in parallel, for this purpose.
Message Passing Interface (MPI) is often used with these workflows.
You must configure your containers to use MPI properly,
but Batch allows different nodes running different containers to communicate easily with one another.
You must configure your containers to use certain environment variables that Batch will provide them,
which lets them know which one is the main node, among other information.
For an in-depth example on using MPI to perform numerical computations on Batch,
see this blog post
In particular, the environment variable that tells the containers which one is the main node can be configured on your MultiNodeJobDefinition as follows:
multi_node_job = AWSCDK::Batch::MultiNodeJobDefinition.new(self, "JobDefinition", {
instance_type: AWSCDK::EC2::InstanceType.of(AWSCDK::EC2::InstanceClass::R4, AWSCDK::EC2::InstanceSize::LARGE),
# optional, omit to let Batch choose the type for you
containers: [
{
container: AWSCDK::Batch::ECSEC2ContainerDefinition.new(self, "mainMPIContainer", {
image: AWSCDK::ECS::ContainerImage.from_registry("yourregsitry.com/yourMPIImage:latest"),
cpu: 256,
memory: AWSCDK::Size.mebibytes(2048),
}),
start_node: 0,
end_node: 5,
},
],
})
# convenience method
multi_node_job.add_container({
start_node: 6,
end_node: 10,
container: AWSCDK::Batch::ECSEC2ContainerDefinition.new(self, "multiContainer", {
image: AWSCDK::ECS::ContainerImage.from_registry("amazon/amazon-ecs-sample"),
cpu: 256,
memory: AWSCDK::Size.mebibytes(2048),
}),
})
If you need to set the control node to an index other than 0, specify it in directly:
multi_node_job = AWSCDK::Batch::MultiNodeJobDefinition.new(self, "JobDefinition", {
main_node: 5,
instance_type: AWSCDK::EC2::InstanceType.of(AWSCDK::EC2::InstanceClass::R4, AWSCDK::EC2::InstanceSize::LARGE),
})
Batch allows you define parameters in your JobDefinition, which can be referenced in the container command. For example:
AWSCDK::Batch::ECSJobDefinition.new(self, "JobDefn", {
parameters: {echo_param: "foobar"},
container: AWSCDK::Batch::ECSEC2ContainerDefinition.new(self, "containerDefn", {
image: AWSCDK::ECS::ContainerImage.from_registry("public.ecr.aws/amazonlinux/amazonlinux:latest"),
memory: AWSCDK::Size.mebibytes(2048),
cpu: 256,
command: [
"echo",
"Ref::echoParam",
],
}),
})
By default, when you update a Job Definition, AWS Batch automatically deregisters the previous revision. This means any jobs that were submitted using the old revision may fail if they haven't started yet.
You can preserve previous revisions by setting skip_deregister_on_update to true:
job_defn = AWSCDK::Batch::ECSJobDefinition.new(self, "JobDefn", {
container: AWSCDK::Batch::ECSEC2ContainerDefinition.new(self, "containerDefn", {
image: AWSCDK::ECS::ContainerImage.from_registry("public.ecr.aws/amazonlinux/amazonlinux:latest"),
memory: AWSCDK::Size.mebibytes(2048),
cpu: 256,
}),
skip_deregister_on_update: true,
})
AWS Batch uses an allocation strategy to determine what compute resource will efficiently handle incoming job requests. By default, BEST_FIT will pick an available compute instance based on vCPU requirements. If none exist, the job will wait until resources become available. However, with this strategy, you may have jobs waiting in the queue unnecessarily despite having more powerful instances available. Below is an example of how that situation might look like:
Compute Environment:
1. m5.xlarge => 4 vCPU
2. m5.2xlarge => 8 vCPU
Job Queue:
---------
| A | B |
---------
Job Requirements:
A => 4 vCPU - ALLOCATED TO m5.xlarge
B => 2 vCPU - WAITING
In this situation, Batch will allocate Job A to compute resource #1 because it is the most cost efficient resource that matches the vCPU requirement. However, with this BEST_FIT strategy, Job B will not be allocated to our other available compute resource even though it is strong enough to handle it. Instead, it will wait until the first job is finished processing or wait a similar m5.xlarge resource to be provisioned.
The alternative would be to use the BEST_FIT_PROGRESSIVE strategy in order for the remaining job to be handled in larger containers regardless of vCPU requirement and costs.
You can grant any Principal the batch:submitJob permission on both a job definition and a job queue like this:
vpc = nil # AWSCDK::EC2::IVPC
ecs_job = AWSCDK::Batch::ECSJobDefinition.new(self, "JobDefn", {
container: AWSCDK::Batch::ECSEC2ContainerDefinition.new(self, "containerDefn", {
image: AWSCDK::ECS::ContainerImage.from_registry("public.ecr.aws/amazonlinux/amazonlinux:latest"),
memory: AWSCDK::Size.mebibytes(2048),
cpu: 256,
}),
})
queue = AWSCDK::Batch::JobQueue.new(self, "JobQueue", {
compute_environments: [
{
compute_environment: AWSCDK::Batch::ManagedEC2ECSComputeEnvironment.new(self, "managedEc2CE", {
vpc: vpc,
}),
order: 1,
},
],
priority: 10,
})
user = AWSCDK::IAM::User.new(self, "MyUser")
ecs_job.grant_submit_job(user, queue)