AWS

AWS LambdaでECSタスク数を自動管理する方法

o_wani

背景:月末月初がアクセス数が多くサーバー負荷が高まるため、月初から5営業日はサーバーのリソースを増強しておこうという主旨から、今回の対応が必要になった。

目的:毎回ECSタスク数の変更を手動でやっていたが、Lambdaを使って、Fargateタスク数を自動的に調整し、効率的なECSタスク数の管理の実現をする。

ecs-lamdba-eventbridge

1
IAM

Lambda用のポリシーとロールの作成

AWS LambdaがFargate(ECS)のタスク数を変更できるようにするためには、Lambda関数に適切なIAMロールを割り当て、そのロールに必要な権限を付与する必要があります。

必要なポリシーは2つあります。IAMのポリシー「ポリシーの作成」から作成し、その後、IAMのロール「ロールを作成」から作成します。

ecs用のポリシー

ECSに対する権限

Lambda関数からECSサービスを更新するためには、ecs:UpdateServiceを許可する必要があります。また、特定のクラスターやサービスにアクセスするためには、ecs:DescribeServicesも必要です。(< >で囲っている箇所は各自の環境に合わせます)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ecs:UpdateService",
        "ecs:DescribeServices"
      ],
      "Resource": [
        "arn:aws:ecs:<region>:<account-id>:service/<cluster-name>/<service-name>",
        "arn:aws:ecs:<region>:<account-id>:cluster/<cluster-name>"
      ]
    }
  ]
}
CloudWatch用のポリシー

CloudWatchに対する権限

Lambda関数の実行ログをCloudWatchに出力するには、以下の権限が必要です。(< >で囲っている箇所は各自の環境に合わせます)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:<region>:<account-id>:log-group:/aws/lambda/*"
    }
  ]
}
ポリシーのアタッチ

IAMロールの作成

  1. IAMにアクセス
  2. 新しいロールを作成し、「AWSサービス」→「ユースケース:Lambda」を選択。
  3. 必要なポリシーを追加(上記の作成済ポリシーを選択)。
  4. ロールをLambda関数にアタッチ。

この設定により、Lambda関数がECSのサービスのタスク数を管理できるようになります。

2
Lambda

Lambda関数の実装

下記条件で、Pythonのコードを記載し、関数を作成します。

  • 月初から営業日5日間は、ECSのタスク数を5個起動し、それ以外は2個起動する
  • 営業日は、祝日や土日を除く
  • 祝日は手動で管理(直接コードに記載)

デフォルトの実行ロールの変更」では、[既存のロールを使用する]を選択し、作成済みロールを選択します。

import boto3
import datetime

# AWSのクライアントを初期化
ecs = boto3.client('ecs')

# 日本の祝日リスト(例として2024年の一部記載)
HOLIDAYS_2024 = [
    datetime.date(2024, 1, 1),   # 元日
    datetime.date(2024, 1, 8),   # 成人の日
    datetime.date(2024, 2, 11),  # 建国記念の日
    datetime.date(2024, 2, 12),  # 建国記念の日 振替休日
    datetime.date(2024, 3, 20),  # 春分の日
    datetime.date(2024, 4, 29),  # 昭和の日
]

def is_business_day(date):
    # 土日または祝日でない場合は営業日
    return date.weekday() < 5 and date not in HOLIDAYS_2024

def get_business_days_from_month_start(current_date):
    first_of_month = current_date.replace(day=1)
    business_days = 0
    current_day = first_of_month
    
    while current_day <= current_date:
        if is_business_day(current_day):
            business_days += 1
        current_day += datetime.timedelta(days=1)
    
    return business_days

def get_desired_task_count(date):
    if not is_business_day(date):
        return 2  # 休日または土日の場合
    
    business_days = get_business_days_from_month_start(date)
    if business_days <= 5:
        return 5  # 月初から5営業日まで
    else:
        return 2  # それ以外の営業日

def lambda_handler(event, context):
    cluster_name = 'your-cluster-name'
    service_name = 'your-service-name'
    
    today = datetime.date.today()
    desired_count = get_desired_task_count(today)
    
    try:
        # 現在のサービスの状態を取得
        response = ecs.describe_services(cluster=cluster_name, services=[service_name])
        current_count = response['services'][0]['desiredCount']
        running_count = response['services'][0]['runningCount']
        
        print(f"Current date: {today}")
        print(f"Desired task count: {desired_count}")
        print(f"Current desired count: {current_count}")
        print(f"Current running count: {running_count}")
        
        if current_count == desired_count and running_count == desired_count:
            print("Task count is already correct. No action needed.")
            return {
                'statusCode': 200,
                'body': 'Task count is already correct. No action needed.'
            }
        else:
            # タスク数を更新
            update_response = ecs.update_service(
                cluster=cluster_name,
                service=service_name,
                desiredCount=desired_count
            )
            print(f"Updated task count from {current_count} to {desired_count}")
            return {
                'statusCode': 200,
                'body': f'Updated task count from {current_count} to {desired_count}'
            }
    except Exception as e:
        print(f"Error occurred: {str(e)}")
        return {
            'statusCode': 500,
            'body': f'Error occurred: {str(e)}'
        }

is_business_day 関数:

  • 土日と祝日以外を営業日と判定します。

get_business_days_from_month_start 関数:

  • 月初から指定日までの営業日数を計算します。

get_desired_task_count 関数:

  • 日付に基づいて適切なタスク数を決定します:
    • 休日または土日の場合は2
    • 月初から5営業日までは5
    • それ以外の営業日は2

lambda_handler:

  • 現在の日付に基づいてタスク数を決定し、ECSサービスを更新します。
  • エラーハンドリングとログ記録をします。

3
EventBridge

Amazon EventBridgeでスケジューリング

AWS EventBridgeを使用してLambda関数を定期自動実行するようスケジュールすることができます。

ターゲットの設定を行います。ここでは、作成したLambdaの関数を選択します。

以上で設定完了です。

4
Cloudwatch

動作確認

CloudWatchログは、Lambda関数の実行ログを確認するのに最適です。ECSでも該当のサービスの状態に変更があった場合にはイベント情報で確認できます。

  • Cloudwatch
    • ロググループ「/aws/lambda/ecs-auto-task-scheduler(Lambdaの関数名)」
  • ECS
    • ECSのサービスを選択し、表示される「イベント」タブにタスク更新情報が表示される

実際にタスク数が変動するかを確認はできるが、上記に処理実行時のログ情報が表示されるので、より確実に状態を把握し、動作を確認することができます。

STAFF
o_wani
o_wani
スタッフ
大学卒業後、15年間WEB業界で働く。現在はマネジメントに従事していますが、ChatGPTの登場に触発され、このブログを再開。AIをパートナーに、自分で手を動かして実装する楽しさと喜びを再発見中。時代が変わりつつある中でも、陳腐化しない情報発信も目指しています。
記事URLをコピーしました