I was wondering if I could schedule simple bash scripts using AWS Fargate for some trivial batches operations.
To be completely honest, It is also an excuse to learn more about AWS Fargate, and to convert a legacy bash script based on EC2 Spot instance to a container world.
In this post, we will see how to schedule a bash script job once a day. To do so, we will deploy the corresponding AWS infrastructure (even if it’s serverless, yes :wink:) using Terraform.
The code repository is available here: serverless-jobs-using-fargate
TL;DR#
git clone https://github.com/z0ph/serverless-jobs-using-fargate.git
- Prepare your Docker image (using
Dockerfile
)
- Adapt the
variables.tf
, and variables in Makefile
to your needs
- Run
make plan
- Run
make deploy
- Run
make build-docker
- Take a nap, enjoy :cocktail:
As a pre-requirement, you will need to edit variables.tf
file and set your own values corresponding to your needs, with special focus on XXX
:dizzy_face:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
variable "aws_region" {
default = "eu-west-1"
description = "AWS Region"
}
variable "env" {
default = "dev"
description = "Environment"
}
variable "project" {
default = "no-project-name"
description = "Project Name"
}
variable "description" {
default = "empty-project-description"
description = "Project Description"
}
variable "artifacts_bucket" {
default = "no-artifact-bucket-defined"
description = "Artifacts Bucket Name"
}
variable "ecs_event_role" {
default = "XXX"
description = "IAM Role used for CloudWatch"
}
variable "ecs_taskexec_role" {
default = "XXX"
description = "IAM Role used for Task Execution"
}
variable "subnets" {
type = list(string)
default = ["XXX"]
description = "Subnets used for Fargate Containers"
}
variable "security_groups" {
type = list(string)
default = ["XXX"]
description = "Security Groups used for Fargate"
}
variable "schedule" {
default = "rate(XXX hours)"
description = "Schedule for your job"
}
variable "assign_public_ip" {
default = "false"
description = "Set public IP on Fargate Container"
}
variable "ecs_cpu_units" {
default = "1024"
description = "Container: Number of CPU Units"
}
variable "ecs_memory" {
default = "2048"
description = "Container: Memory in MB"
}
variable "docker_image_arn" {
default = "XXXXXXXXXXXX.dkr.ecr.xxxxx.amazonaws.com/xxxx:lastone"
description = "Arn of the Docker Image"
}
|
First, we will create an Elastic Container Registry (ECR) to host our newly built Docker image.
1
2
3
4
5
6
7
|
resource "aws_ecr_repository" "ecr" {
name = "${var.project}-ecr-${var.env}"
tags = {
Project = "${var.project}"
}
}
|
Second, you will find in ecs.tf
the corresponding AWS ECS cluster, task definition json
template file.
In this file, you’ll find the target CPU, memory, docker image, etc… of your container(s). All are using vars
mapping, so you just need to update variables.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
resource "aws_ecs_cluster" "ecs_cluster" {
name = "${var.project}_ecs_cluster_${var.env}"
tags = {
Project = "${var.project}"
}
}
data "template_file" "task" {
template = "${file("./automation/tf-fargate/tasks/task_definition.json")}"
vars = {
name = "${var.project}"
aws_region = "${var.aws_region}"
docker_image_arn = "${var.docker_image}"
}
}
resource "aws_ecs_task_definition" "task_definition" {
family = "${var.project}_task_definition_${var.env}"
container_definitions = "${data.template_file.task.rendered}"
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = "${var.ecs_cpu_units}"
memory = "${var.ecs_memory}"
execution_role_arn = "${var.ecs_taskexec_role}"
task_role_arn = "${aws_iam_role.ecs_role.arn}"
tags = {
Project = "${var.project}"
}
}
|
Third, the schedule logic will land in the cloudwatch.tf
file because we are using CloudWatch Events. You can use either rate(X minutes|hours|days)
OR cron(****)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
resource "aws_cloudwatch_event_rule" "cw_run_task" {
name = "${var.project}_run_task_${var.env}"
description = "Run ${var.project} on ${var.schedule}"
schedule_expression = "${var.schedule}"
}
resource "aws_cloudwatch_event_target" "cw_event_target" {
target_id = "${var.project}_event_target_${var.env}"
arn = "${aws_ecs_cluster.ecs_cluster.arn}"
rule = "${aws_cloudwatch_event_rule.cw_run_task.name}"
role_arn = "${var.ecs_event_role}"
ecs_target {
launch_type = "FARGATE"
platform_version = "LATEST"
task_definition_arn = "${aws_ecs_task_definition.task_definition.arn}"
network_configuration {
subnets = "${var.subnets}"
security_groups = "${var.security_groups}"
assign_public_ip = "${var.assign_public_ip}"
}
}
}
|
Push your container image to ECR#
I’m using custom alpine image with awscli
pre-installed.
Using make build-docker
it will run the following commands under the hood:
1
2
3
4
|
$ aws ecr get-login --region $(AWS_REGION)
$ docker build -t fargate-image .
$ docker tag fargate-image:latest $(ECR)
$ docker push $(ECR)
|
FinOps :money_with_wings:#
For Amazon ECS, AWS Fargate pricing is calculated based on the vCPU and memory resources used from the time you start to download your container image (docker pull) until the Amazon ECS Task* terminates, rounded up to the nearest second. A minimum charge of 1 minute applies.
Pricing is per second with a 1-minute minimum. Duration is calculated from the time you start to download your container image (docker pull) until the Task terminates, rounded up to the nearest second.
Resource type |
Price (eu-west-1) |
per vCPU per hour |
$0.04048 |
per GB per hour |
$0.004445 |
Pricing Example#
Pricing for US East (N. Virginia) Region
For example, your service uses 10 ECS Tasks running for 1 hour (3600 seconds) every day for a month (30 days) where each ECS Task uses 0.25 vCPU and 1GB memory.
Monthly CPU charges
Total vCPU charges = # of Tasks x # vCPUs x price per CPU-second x CPU duration per day (seconds) x # of days
Total vCPU charges = 10 x 0.25 x 0.000011244 x 3600 x 30 = $3.04
Monthly memory charges
Total memory charges = # of Tasks x memory in GB x price per GB x memory duration per day (seconds) x # of days
Total memory charges = 10 x 1 x 0.000001235 x 3600 x 30 = $1.33
Monthly Fargate compute charges
Monthly Fargate compute charges = monthly CPU charges + monthly memory charges
Monthly Fargate compute charges = $3.04 + $1.33 = $4.37
Source: https://aws.amazon.com/fargate/pricing/?nc=sn&loc=2
Ref:
That’s all folks!
zoph.