EventBridge Scheduler- Start/Stop ECS instance
Tags: aws, serverless, ecs, eventdriven
Published: 2023-05-05 03:03:14

As I mentioned in my prior post, we used the ECS Fargate instance in my organization. During off hours, we shut down the ECS Fargate instance every day in our lower environment to cut costs.

Here is a prior [article](https://dev.to/awsmantra/eventbridge-scheduler-startstop-ec2-instance-27kb) where I have explained about Start/Stop EC2 Instnace. This is a simple pattern that starts and stops the ECS Fargate instance using [Universal Target](https://docs.aws.amazon.com/scheduler/latest/UserGuide/managing-targets-universal.html).


![ecs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s0anu6a7jfrzi0po2rq4.png)

Important:- This code will shut down 1 ECS instance as API doesn't support multiple ones. In a future article, I will show you how to shut down multiple instances using Step Functions.

API Target and Parameters.

```typescript
Target :- arn:aws:scheduler:::aws-sdk:ecs:updateService

{
  "Service": "ecs-service",
  "Cluster": "MyCluster",
  "DesiredCount": 0
}
```
This example uses AWS CLI and CDK (TypeScript). Assuming you already Bootstrapping your account.

Here is the main stack which creates the nested stacks of role, ECSStart and ECSStop.

```typescript
import {Stack, StackProps} from 'aws-cdk-lib';
import {Construct} from 'constructs';
import {SchedulesRole} from "./scheduler-role";
import {ECSStart} from "./ecs-start";
import {ECSStop} from "./ecs-stop";

export class ECSInstanceStartStopStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);


    // Create Role
    const schedulesRole = new SchedulesRole(this,"SchedulerRoleStack")

    // Create ECSStart schedules
    const ecsStart = new ECSStart(this,"EcsStart", {
      roleArn: schedulesRole.roleArn,
    })

    // Create ECSStop schedules
    const ecsStop = new ECSStop(this,"EcsStop", {
      roleArn: schedulesRole.roleArn,
    })

  }
}
```

Give minimal permission to start and stop ECS instances.

```typescript
import { StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import {Effect, PolicyStatement, Role, ServicePrincipal} from "aws-cdk-lib/aws-iam";

export class SchedulesRole extends cdk.NestedStack  {
    private readonly _role: Role;
    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props);


        // Add scheduler assumeRole
        this._role  = new Role(this,  "scheduler-ecs-start-stop", {
          assumedBy: new  ServicePrincipal('scheduler.amazonaws.com'),
          roleName: "scheduler-ecs-start-stop"
        })

        // Add policy
        this._role.addToPolicy(  new PolicyStatement( {
            sid: 'ECSStartStopPermissions',
            effect: Effect.ALLOW,
            actions: [
                "ecs:List*",
                "ecs:Describe*",
                "ecs:UpdateService"
            ],
            resources: ["*"], //Give the least privileges
        }))
    }

    get roleArn(): string {
        return this._role.roleArn;
    }
}
```

Create an ECS start schedule. ECS instance will start at 8 AM CST time.

```typescript
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import { Role } from "aws-cdk-lib/aws-iam";
import {CfnSchedule} from "aws-cdk-lib/aws-scheduler";

interface ECSStartProps extends cdk.NestedStackProps {
    roleArn: string
}

export class ECSStart extends cdk.NestedStack {
    private readonly role: Role;

    constructor(scope: Construct, id: string, props: ECSStartProps) {
        super(scope, id, props);

        // Start ECS Instance 8 am Central Time
        new CfnSchedule(this,"ecs-start-scheduler", {
            name: "ecs-start-scheduler",
            flexibleTimeWindow: {
                mode: "OFF"
            },
            scheduleExpression: "cron(0 8 ? * * *)",
            scheduleExpressionTimezone: 'America/Chicago',
            description: 'Event that start ECS instances',
            target: {
                arn: 'arn:aws:scheduler:::aws-sdk:ecs:updateService',
                roleArn: props.roleArn,
                input: JSON.stringify
                    ({ 
                       Service: "ecs-service", 
                       Cluster: "MYCluster", 
                       DesiredCount: 1 
                    }),                           
            },
        });
    }
}
```

Create an ECS stop schedule.

```typescript
import { Construct } from 'constructs';
import * as cdk from 'aws-cdk-lib';
import { Role } from "aws-cdk-lib/aws-iam";
import {CfnSchedule} from "aws-cdk-lib/aws-scheduler";

interface ECSSopProps extends cdk.NestedStackProps {
    roleArn: string
}

export class ECSStop extends cdk.NestedStack {
    private readonly role: Role;

    constructor(scope: Construct, id: string, props: ECSSopProps) {
        super(scope, id, props);

        // Start ECS Instance 8 am Central Time
        new CfnSchedule(this,"ecs-stop-scheduler", {
            name: "ecs-stop-scheduler",
            flexibleTimeWindow: {
                mode: "OFF"
            },
            scheduleExpression: "cron(0 20 ? * * *)",
            scheduleExpressionTimezone: 'America/Chicago',
            description: 'Event that start ECS instances',
            target: {
                arn: 'arn:aws:scheduler:::aws-sdk:ecs:updateService',
                roleArn: props.roleArn,
                input: JSON.stringify(
                  { 
                    Service: 'ecs-service', 
                    Cluster: 'MyCluster', 
                    DesiredCount: 0 
                  }),
            },
        });
    }
}
```

Caution:- Please make sure about your ECS Auto Scaling policy. In my case, ECS instances were kept recycling during start/stop due to the auto-scaling policy was not configured properly. This caused Docker image to keep downloading and costing more $$$.

**Deployment Instructions:-**

```typescript
1) git clone git@github.com:awsmantra/eventbridge-schedules-ecs-start-stop.git

2) cd eventbridge-schedules-ecs-start-stop

3) modified ecs-service name and cluster name ecs-start.ts and ecs-stop.ts

4) npm install

5) cdk deploy --all -a "npx ts-node bin/app.ts" --profile dev
```

**Cleanup:-**

```typescript
cdk destroy --profile dev
```

You can download the source code form [here](https://github.com/awsmantra/eventbridge-schedules-ecs-start-stop).