jeanphix

How to configure ECS scheduled (cron) task using cloudformation

04 Oct 2017

AWS CloudFormation now allows to define scheduled tasks to be run within ECS clusters. It’s pretty straight forward to setup and ensure the task is properly placed (probably run just once in most cases) within a cluster.

First of all, we need to define the task (AWS::ECS::TaskDefinition):

scheduled_worker_task_definition = TaskDefinition(
    "ScheduledWorkerTask",
    template=template,
    ContainerDefinitions=[
        ContainerDefinition(
            Name="ScheduledWorker",
            Cpu=200,
            Memory=300,
            Essential=True,
            Image="<image>",
            EntryPoint=['<entry_point>']
        ),
    ],
)

Then we need to create a role (AWS::IAM::Role) with proper policies to run a task within our cluster:

run_task_role = iam.Role(
    "RunTaskRole",
    template=template,
    AssumeRolePolicyDocument=dict(Statement=[dict(
        Effect="Allow",
        Principal=dict(Service=["events.amazonaws.com"]),
        Action=["sts:AssumeRole"],
    )]),
    Path="/",
    Policies=[
        iam.Policy(
            PolicyName="RunTaskPolicy",
            PolicyDocument=dict(
                Statement=[dict(
                    Effect="Allow",
                    Action=[
                        "ecs:RunTask",
                    ],
                    Resource=["*"],
                    Condition=dict(
                        ArnEquals={
                            "ecs:cluster": GetAtt(cluster, "Arn"),
                        }
                    )
                )],
            ),
        ),
    ],
)

And finally an event rule (AWS::Events::Rule) that is responsible of the scheduling:

Rule(
    "SchedulingRule",
    template=template,
    Description="My schedule task rule",
    State="ENABLED",
    ScheduleExpression="rate(30 minutes)",
    Targets=[
        Target(
            Id="ScheduledWorkerTaskDefinitionTarget",
            RoleArn=GetAtt(run_task_role, "Arn"),
            Arn=GetAtt(cluster, "Arn"),
            EcsParameters=EcsParameters(
                TaskCount=1,
                TaskDefinitionArn=Ref(scheduled_worker_task_definition),
            ),
        ),
    ]
)

That’s it, the task will be placed and ran within a container every 30 minutes.

Here is the JSON template:

        ...
        "ScheduledWorkerTask": {
            "Condition": "Deploy",
            "Properties": {
                "ContainerDefinitions": [
                    {
                        "Cpu": 200,
                        "EntryPoint": [
                            "<entry_point>"
                        ],
                        "Essential": "true",
                        "Image": {
                            "Fn::Join": [
                                "",
                                [
                                    {
                                        "Ref": "AWS::AccountId"
                                    },
                                    ".dkr.ecr.",
                                    {
                                        "Ref": "AWS::Region"
                                    },
                                    ".amazonaws.com/",
                                    {
                                        "Ref": "ApplicationRepository"
                                    },
                                    ":",
                                    "<image>"
                                ]
                            ]
                        },
                        "Memory": 300,
                        "Name": "ScheduledWorker"
                    }
                ]
            },
            "Type": "AWS::ECS::TaskDefinition"
        },

        "SchedulingRule": {
            "Properties": {
                "Description": "My schedule task rule",
                "ScheduleExpression": "rate(30 minutes)",
                "State": "ENABLED",
                "Targets": [
                    {
                        "Arn": {
                            "Fn::GetAtt": [
                                "Cluster",
                                "Arn"
                            ]
                        },
                        "EcsParameters": {
                            "TaskCount": 1,
                            "TaskDefinitionArn": {
                                "Ref": "ScheduledWorkerTask"
                            }
                        },
                        "Id": "ScheduledWorkerTaskDefinitionTarget",
                        "RoleArn": {
                            "Fn::GetAtt": [
                                "RunTaskRole",
                                "Arn"
                            ]
                        }
                    }
                ]
            },
            "Type": "AWS::Events::Rule"
        },

        "RunTaskRole": {
            "Properties": {
                "AssumeRolePolicyDocument": {
                    "Statement": [
                        {
                            "Action": [
                                "sts:AssumeRole"
                            ],
                            "Effect": "Allow",
                            "Principal": {
                                "Service": [
                                    "events.amazonaws.com"
                                ]
                            }
                        }
                    ]
                },
                "Path": "/",
                "Policies": [
                    {
                        "PolicyDocument": {
                            "Statement": [
                                {
                                    "Action": [
                                        "ecs:RunTask"
                                    ],
                                    "Condition": {
                                        "ArnEquals": {
                                            "ecs:cluster": {
                                                "Fn::GetAtt": [
                                                    "Cluster",
                                                    "Arn"
                                                ]
                                            }
                                        }
                                    },
                                    "Effect": "Allow",
                                    "Resource": [
                                        "*"
                                    ]
                                }
                            ]
                        },
                        "PolicyName": "RunTaskPolicy"
                    }
                ]
            },
            "Type": "AWS::IAM::Role"
        },
        ...