Skip to content
Permalink
Browse files

Scheduled task support (#73)

* Lock versions of examples to < 0.12

* Fixed bug when load balancing type & route53_record_type are "none"

* Added support for ECS scheduled tasks.

* Added documentation

* Documented EFS mount points.
* Documented scheduled ECS tasks.
  • Loading branch information...
mhvelplund authored and maartenvanderhoef committed Aug 30, 2019
1 parent f2a4f1c commit f335adc9600eba047560da8a203037d8af3b7d50
@@ -58,4 +58,46 @@ module "demo_web" {

## Volume Mounting

TODO
When running on an EC2 backed ECS cluster, it is possible to mount folders from the host machines inside task containers.

```json
module "mycluster" {
source = "blinkist/airship-ecs-cluster/aws"
version = "0.5.1"
name = "mycluster"
...
cluster_properties = {
...
efs_enabled = true # Mount a volume to "/efs" on every EC2 host instance in the cluster
efs_id = "${var.efs_id}" # EFS volume id
}
}
module "myservice" {
source = "blinkist/airship-ecs-service/aws"
version = "0.9.7"
name = "myservice"
bootstrap_container_image = "nginx:stable"
container_cpu = 256
container_memory = 512
ecs_cluster_id = "${module.mycluster.id}"
region = "eu-west-1"
# Maps host folders as volumes
host_path_volumes = [{
name = "efs"
host_path = "/mnt/efs/myservice"
}]
# Mounts volumes inside containers
mountpoints = [{
sourceVolume = "efs"
containerPath = "/usr/share/nginx/html" # This dir is what the nginx server serves at http://localhost:80/
readOnly = "false" # Must be quoted string!
}]
}
```
@@ -2,10 +2,18 @@
sidebarDepth: 2
---

# ECS Cron tasks
# Scheduled tasks

## Introduction
In many environments it's common to have cronjob like tasks. This module provide an easy way of configuring crontjobs for a running docker. The cronjobs will not be executed inside a running docker but a new task will be executed and the configured command will be ran.
Airship supports two types of scheduled tasks.

"Cron tasks" are commands that are run inside running task containers at intervals.

"ECS Scheduled Tasks" are containers that are started at intervals by CloudWatch rules, and run until they terminate naturally.


## Cron tasks

In many environments it's common to have cronjob like tasks. This module provide an easy way of configuring cron jobs for a running docker. The cronjobs will not be executed inside a running docker but a new task will be executed and the configured command will be ran.

<mermaid/>
<div class="mermaid">
@@ -18,7 +26,7 @@ sequenceDiagram

## Implementation

The `ecs_cron_tasks` param holds a list of maps with information regarding the 'cron' jobs.
The `ecs_cron_tasks` parameter holds a list of maps with information regarding the 'cron' jobs.

```json
# ecs_cron_tasks holds a list of maps defining scheduled jobs
@@ -47,3 +55,28 @@ The `ecs_cron_tasks` param holds a list of maps with information regarding the '
}
]
```

## ECS Scheduled Tasks

Scheduled tasks are a good fit for replacing containers that spend most of their time idle, and only occasionally run batch jobs or other maintenance.
They use AWS [scheduled tasks](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/scheduling_tasks.html) to start temporary containers at intervals.
The containers are expected to run and terminate when they are done.

```json
module "myservice" {
source = "blinkist/airship-ecs-service/aws"
version = "0.9.7"
name = "myservice"
bootstrap_container_image = "hello-world:latest"
container_cpu = 256
container_memory = 512
ecs_cluster_id = "${var.cluster_id}"
region = "eu-west-1"
# Run the hello world task in one container every 15 minutes
is_scheduled_task = true
scheduled_task_expression = "rate(15 minutes)" # Same as "cron(0,15,30,45 * * * *)"
scheduled_task_count = 1
}
```
@@ -1,5 +1,5 @@
terraform {
required_version = "~> 0.11"
required_version = "~> 0.11.0"
}

provider "aws" {
@@ -0,0 +1,6 @@
# with_scheduled_task

Creates a scheduled [ECS task](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/scheduling_tasks.html)
that runs a [hello-world](https://hub.docker.com/_/hello-world) once every minute.

The task uses Fargate to host the container.
@@ -0,0 +1,19 @@
# Import references to some of the existing default VPC and subnet values.
# Obviously, real world applications should never use these!

data "aws_availability_zones" "available" {}

data "aws_vpc" "selected" {
default = true
}

data "aws_subnet" "selected" {
availability_zone = "${data.aws_availability_zones.available.names[0]}"
vpc_id = "${data.aws_vpc.selected.id}"
default_for_az = true
}

data "aws_security_group" "selected" {
name = "default"
vpc_id = "${data.aws_vpc.selected.id}"
}
@@ -0,0 +1,41 @@
terraform {
required_version = "~> 0.11.0"
}

locals {
region = "eu-west-1"

tags = {
Environment = "${terraform.workspace}"
}
}

resource "aws_ecs_cluster" "this" {
name = "${terraform.workspace}-cluster"

lifecycle {
create_before_destroy = true
}
}

# Create a task defintion and associate a scheduling rule with it
module "scheduled_task" {
source = "../../"

name = "${terraform.workspace}-scheduled-task"
ecs_cluster_id = "${aws_ecs_cluster.this.id}"
region = "${local.region}"
bootstrap_container_image = "hello-world:latest"
container_cpu = 256
container_memory = 512
fargate_enabled = true
awsvpc_enabled = true
awsvpc_subnets = ["${data.aws_subnet.selected.id}"]
awsvpc_security_group_ids = ["${data.aws_security_group.selected.id}"]
tags = "${local.tags}"

# Scheduled task configuration
is_scheduled_task = true # Make this a scheduled task
scheduled_task_expression = "rate(1 minute)" # Every minute
scheduled_task_count = 1 # The number of tasks to run
}
@@ -0,0 +1,20 @@
provider "aws" {
region = "${local.region}"
skip_get_ec2_platforms = true
skip_metadata_api_check = true
skip_region_validation = true
skip_credentials_validation = true
version = "~> 2.22"
}

provider "archive" {
version = "~> 1.2"
}

provider "null" {
version = "~> 2.1"
}

provider "template" {
version = "~> 2.1"
}
@@ -0,0 +1,8 @@
@ECHO OFF

REM What is this you ask? Someone put a "sleep 30" in the ECS cluster module, which doesn't work on Windows.
REM Having this in the same folder makes it Terraform run it transparently on Windows "fixing" the issue.
REM Full credit goes to Fæster@JPPOL for the hack to fix the hack.

REM Waits %1 -1 seconds ...
ping 127.0.0.1 -n %1
@@ -0,0 +1,9 @@
# with_secrets

This is an example of creating a service that places SSM secrets in the task environment variables.

Variables declared in `container_secrets` can come in two formats:

* `DB_USER = "arn:aws:ssm:${local.region}:${local.remote_account_id}:parameter/myapp/dev/db.user"`:
This is useful for cross account access, where the SSM registry is in another account.
* `DB_PASSWORD = "/myapp/dev/db.password"`: This is useful for referring to SSM variables in the same account
@@ -1,5 +1,5 @@
terraform {
required_version = "~> 0.11"
required_version = "~> 0.11.0"
}

locals {
@@ -38,7 +38,7 @@ resource "aws_ssm_parameter" "password" {
}

module "ecs-base" {
source = "../with_nlb"
source = "../with_nlb" # Reuse the infrastructure defined in the "with_nlb" example :)
region = "${local.region}"
}

47 main.tf
@@ -52,6 +52,9 @@ module "iam" {

# The container uses secrets and needs a task execution role to get access to them
container_secrets_enabled = "${var.container_secrets_enabled}"

# If this is a scheduled task, cloudwatch needs permission to start the task
is_scheduled_task = "${var.is_scheduled_task}"
}

#
@@ -99,7 +102,7 @@ module "alb_handling" {

# route53_record_type sets the record type of the route53 record, can be ALIAS, CNAME or NONE, defaults to ALIAS
# In case of NONE no record will be made
route53_record_type = "${var.load_balancing_properties_route53_record_type}"
route53_record_type = "${var.is_scheduled_task ? "NONE" : var.load_balancing_properties_route53_record_type}"

# Sets the zone in which the sub-domain will be added for this service
route53_zone_id = "${var.load_balancing_properties_route53_zone_id}"
@@ -293,7 +296,7 @@ module "ecs_service" {
name = "${var.name}"

# create defines if resources are being created inside this module
create = "${var.create}"
create = "${var.create && !var.is_scheduled_task}"

cluster_id = "${var.ecs_cluster_id}"

@@ -376,7 +379,7 @@ module "ecs_autoscaling" {
source = "./modules/ecs_autoscaling/"

# create defines if resources inside this module are being created.
create = "${var.create && length(var.scaling_properties) > 0 ? true : false }"
create = "${var.create && !var.is_scheduled_task && length(var.scaling_properties) > 0 ? true : false }"

cluster_name = "${local.ecs_cluster_name}"

@@ -393,14 +396,13 @@ module "ecs_autoscaling" {
scaling_properties = "${var.scaling_properties}"
}

#
# This modules creates scheduled tasks for the ecs service
#
# This modules creates scheduled tasks for the ecs service. This is not the same as ECS scheduled tasks!
# Instead it runs a command inside an existing task, similar to a cron job on that task.
module "lambda_ecs_task_scheduler" {
source = "./modules/lambda_ecs_task_scheduler/"

# create defines if resources inside this module are being created.
create = "${var.create && length(var.ecs_cron_tasks) > 0 ? true : false }"
create = "${var.create && !var.is_scheduled_task && length(var.ecs_cron_tasks) > 0 ? true : false }"

ecs_cluster_id = "${var.ecs_cluster_id}"

@@ -415,3 +417,34 @@ module "lambda_ecs_task_scheduler" {
# lambda_ecs_task_scheduler_role_arn sets the role arn of the task scheduling lambda
lambda_ecs_task_scheduler_role_arn = "${module.iam.lambda_ecs_task_scheduler_role_arn}"
}

# ECS scheduled task configuration. This uses a CloudWatch rulle to start an ECS task at given intervals.
data "aws_ecs_cluster" "this" {
cluster_name = "${var.ecs_cluster_id}"
}

resource "aws_cloudwatch_event_rule" "scheduled_task" {
name = "${var.name}_scheduled_task"
description = "Run ${var.name} task at a scheduled time (${var.scheduled_task_expression})"
schedule_expression = "${var.scheduled_task_expression}"
}

data "aws_caller_identity" "current" {}

resource "aws_cloudwatch_event_target" "scheduled_task" {
rule = "${aws_cloudwatch_event_rule.scheduled_task.name}"
arn = "${data.aws_ecs_cluster.this.arn}"
role_arn = "${module.iam.scheduled_task_cloudwatch_role_arn}"

ecs_target {
group = "${var.scheduled_task_group}"
launch_type = "${local.launch_type}"
task_count = "${var.scheduled_task_count}"
task_definition_arn = "arn:aws:ecs:${var.region}:${data.aws_caller_identity.current.account_id}:task-definition/${module.ecs_task_definition_selector.selected_task_definition_for_deployment}"

network_configuration = {
subnets = ["${var.awsvpc_subnets}"]
security_groups = ["${var.awsvpc_security_group_ids}"]
}
}
}
@@ -1,5 +1,5 @@
data "aws_lb" "main" {
count = "${var.create ? 1 : 0}"
count = "${var.create && var.route53_record_type != "NONE" ? 1 : 0}"
arn = "${var.lb_arn}"
}

@@ -45,15 +45,15 @@ locals {
softLimit = "${var.ulimit_soft_limit}"
hardLimit = "${var.ulimit_hard_limit}"
name = "${var.ulimit_name}"
}
},
]

without_ulimits = []
}

repository_credentials = {
with_credentials = {
credentialsParameter = "${var.repository_credentials_secret_arn}"
credentialsParameter = "${var.repository_credentials_secret_arn}"
}

without_credentials = {}

0 comments on commit f335adc

Please sign in to comment.
You can’t perform that action at this time.