ECS Fargateを自動停止・自動起動する方法



# これは何?

ECSの自動停止・自動起動の設定を入れたので記載します。
※自分用のメモです
コンソールでポチポチするんも面倒なので、terraformで作成できるようにしました。

## 用途

コスト削減等で夜間のECSタスク数を0にして停止、早朝にタスク起動する場合に使う。

## 構成イメージ

terraform
    ├── ecs-start
    │   └──  lambda_function.py     ←　ECS起動用の関数コードを記載
    └── ecs-start
    │   └── lambda_function.py      ←　ECS停止用の関数コードを記載
    └── lambda.tf                   ←　lambda,IAM,CWlog,EventBridge（cloudwatch_event）設定を記載

## 各ファイルの設定内容

### ECS起動用の関数コード（lambda_function.py）

```lambda_function.py
import boto3
from botocore.exceptions import ClientError
def lambda_handler(event, context):
    try:
        client = boto3.client('ecs')
        for cluster_name, service_name in [('#対象のECSクラスター1を記載#', '#対象のECSサービス1を記載#'),
                                           ('#対象のECSクラスター2を記載#', '#対象のECSサービス2を記載#'),]:
            service_update_result = client.update_service(
                cluster = cluster_name,
                service = service_name,
                desiredCount = 1 # ここの数を増減させる
            )
            print(service_update_result)
    except ClientError as e:
        print("exceptin: %s" % e)


```

ECS停止用の関数コード（lambda_function.py）

```lambda_function.py
import boto3
from botocore.exceptions import ClientError
def lambda_handler(event, context):
    try:
        client = boto3.client('ecs')
        for cluster_name, service_name in [('#対象のECSクラスター1を記載#', '#対象のECSサービス1を記載#'),
                                           ('#対象のECSクラスター2を記載#', '#対象のECSサービス2を記載#'),]:
            service_update_result = client.update_service(
                cluster = cluster_name,
                service = service_name,
                desiredCount = 0 # 停止用なので0を記載
            )
            print(service_update_result)
    except ClientError as e:
        print("exceptin: %s" % e)

```

### lambda,IAM,CWlog,EventBridge設定を記載（Lambda.tf）

```lambda.tf
## lambda_function
### ECS起動用のLambda設定
resource "aws_lambda_function" "ecs-start" {
  function_name    = "${var.project-name}-ecs-start"　# 任意の名称
  handler          = "lambda_function.lambda_handler"
  role             = aws_iam_role.ecs_stop_start_lambda_role.arn
  runtime          = "python3.8"
  filename         = data.archive_file.function_source_ecs-start.output_path
  source_code_hash = data.archive_file.function_source_ecs-start.output_base64sha256

  depends_on = [aws_iam_role_policy_attachment.ecs_stop_start_lambda_policy, aws_cloudwatch_log_group.ecs_start_lambda]
}

data "archive_file" "function_source_ecs-start" {
  type        = "zip"
  source_dir  = "app-ecs-start"
  output_path = "archive/ecs-start_lambda_function.zip"
}

### ECS停止用のLambda設定
resource "aws_lambda_function" "ecs-stop" {
  function_name    = "${var.project-name}-ecs-stop"　# 任意の名称
  handler          = "lambda_function.lambda_handler"
  role             = aws_iam_role.ecs_stop_start_lambda_role.arn
  runtime          = "python3.8"
  filename         = data.archive_file.function_source_ecs-stop.output_path
  source_code_hash = data.archive_file.function_source_ecs-stop.output_base64sha256

  depends_on = [aws_iam_role_policy_attachment.ecs_stop_start_lambda_policy, aws_cloudwatch_log_group.ecs_stop_lambda]
}

data "archive_file" "function_source_ecs-stop" {
  type        = "zip"
  source_dir  = "app-ecs-stop"
  output_path = "archive/ecs-stop_lambda_function.zip"
}


## IAM
### Lambda用のIAMロール設定（ECSを操作する権限を付与）
locals {
  lambda_role_default_assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

resource "aws_iam_role" "ecs_stop_start_lambda_role" {
  name               = "${var.project-name}-ecs-stop-start-Lambda-Role" # 任意の名称
  assume_role_policy = local.lambda_role_default_assume_role_policy
}

resource "aws_iam_role_policy_attachment" "ecs_stop_start_lambda_role_aws_lambda_basic_execution_role" {
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
  role       = aws_iam_role.ecs_stop_start_lambda_role.name
}

data "aws_iam_policy_document" "ecs_stop_start_lambda_policy" {
  statement {
    effect = "Allow"

    actions = [
      "ecs:DescribeServices",
      "ecs:UpdateService"
    ]

    resources = ["*"]
  }
}

resource "aws_iam_policy" "ecs_stop_start_lambda_policy" {
  name   = "${var.project-name}-ecs-stop-start-Lambda-Policy"
  policy = data.aws_iam_policy_document.ecs_stop_start_lambda_policy.json
}

resource "aws_iam_role_policy_attachment" "ecs_stop_start_lambda_policy" {
  role       = aws_iam_role.ecs_stop_start_lambda_role.name
  policy_arn = aws_iam_policy.ecs_stop_start_lambda_policy.arn
}

## cloudwatch_log
### Lambda用のcloudwatch_log設定
resource "aws_cloudwatch_log_group" "ecs_start_lambda" {
  name = "/aws/lambda/${var.project-name}-ecs-start" # 任意の名称
}

resource "aws_cloudwatch_log_group" "ecs_stop_lambda" {
  name = "/aws/lambda/${var.project-name}-ecs-stop" # 任意の名称
}

## cloudwatch_event
### Lambda用のEventBridge設定（
#### START　月〜金 8：00
resource "aws_cloudwatch_event_rule" "ecs-start" {
  name                = "${var.project-name}-ecs-start" # 任意の名称
  description         = "${var.project-name}-ecs-start" # 任意の内容
  is_enabled          = "true"
  schedule_expression = "cron(00 23 ? * SUN-THU *)"
}

resource "aws_cloudwatch_event_target" "ecs-start" {
  arn       = aws_lambda_function.ecs-start.arn
  rule      = aws_cloudwatch_event_rule.ecs-start.name
  target_id = "${var.project-name}-ecs-start"
}

#### STOP　月〜金 21：00
resource "aws_cloudwatch_event_rule" "ecs-stop" {
  name                = "${var.project-name}-ecs-stop" # 任意の名称
  description         = "${var.project-name}-ecs-stop" # 任意の内容
  is_enabled          = "true"
  schedule_expression = "cron(00 12 ? * MON-FRI *)"
}

resource "aws_cloudwatch_event_target" "ecs-stop" {
  arn       = aws_lambda_function.ecs-stop.arn
  rule      = aws_cloudwatch_event_rule.ecs-stop.name
  target_id = "${var.project-name}-ecs-stop"
}

```

以上です。