【コスト削減】Amazon ECSタスクを自動起動、自動停止する

記事タイトルとURLをコピーする

f:id:swx-furukawa:20220201170526p:plain DS課の古川です。最近AWS Lambdaをよく使うので、今回もAWS Lambdaを使った記事を投稿します。

はじめに


昨今、コンテナの導入が非常に流行っています。AWSのコンテナサービスといえば、Amazon ECS、Amazon EKSが上げられます。

Amazon ECS on FargateはOSやミドルウェアを意識せずに運用できるメリットがありますが、他のサーバレスサービスと比較するとコストがかかるというデメリットがあります。

ECS上で動いているアプリケーションを使用しない時間帯(夜間等)は、ECSタスクを停止させておくことでコスト削減が実現できますが、デフォルト操作ではECSタスクの手動停止しか方法はないようです。

そこで、今回はAWS Lambda(以下、Lambda)を使用して、Amazon ECS on Fargate(以下、ECS)で稼働しているECSタスクの自動停止、自動起動を試してみます。

リソース

  • Amazon ECS on Fargate環境
  • AWS Lambda(Python3.9)
  • Amazon EventBridge
  • IAM Role(Lambda用)

構成図

f:id:swx-furukawa:20220131090326p:plain

構築

serverless.yml

Amazon ECS以外のリソースは、全てServerless Frameworkにて用意します。

以下に、yamlテンプレートを記載します。

service: poc
frameworkVersion: '2'

provider:
  name: aws
  runtime: python3.9
  lambdaHashingVersion: 20201221
  region: ${opt:region, "ap-northeast-1"}
  stackName: ${self:service}
  eventBridge:
    useCloudFormation: true

package:
 patterns:
    - <不要なファイルを記載>

plugins:
  - serverless-python-requirements

functions:
  ECSTaskControllerFunction:
    handler: src.task_control.handler
    name: ${self:service}-ecs-task-controller
    description: ECSタスク起動停止用
    memorySize: 256
    timeout: 30
    role: ECSTaskControllerLambdaRole
    layers:
      - arn:aws:lambda:${self:provider.region}:017000801446:layer:AWSLambdaPowertoolsPython:4
    environment:
      ECS_CLUSTER_NAME: ecs-poc-cluster
      ECS_SERVICE_NAME: ecs-poc-service
    events:
      - schedule:
          name: ${self:service}-ecs-task-stop-rule
          description: ECSタスク停止用
          rate: cron(0 11 * * ? *)
          input:
            process: stop
      - schedule:
          name: ${self:service}-ecs-task-start-rule
          description: ECSタスク起動用
          rate: cron(0 21 * * ? *)
          input:
            process: start

resources:
  Resources:
    # -----
    # IAM:ECSタスク起動停止用
    # -----
    ECSTaskControllerLambdaRole:
      Type: AWS::IAM::Role
      Properties:
        RoleName: ${self:service}-lambda-role
        ManagedPolicyArns:
          - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
          - !Ref ECSTaskControllerPolicy
        AssumeRolePolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Principal:
                Service:
                  - lambda.amazonaws.com
              Action: sts:AssumeRole

    ECSTaskControllerPolicy:
      Type: AWS::IAM::ManagedPolicy
      Properties:
        ManagedPolicyName: ${self:service}-policy
        PolicyDocument:
          Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action:
                - ecs:UpdateService
                - ecs:DescribeServices
              Resource:
                - arn:aws:ecs:${aws:region}:${aws:accountId}:service/*

上から説明します。

Provider

Provider下部に以下のような記載をしています。

  eventBridge:
    useCloudFormation: true

Amazon EventBridgeをServerless Frameworkにて構築する場合、デフォルトでEventBridge構築用のAWS Lambdaも生成されます。
つまり、構築後余分なLambdaが環境に残ってしまうことになるので、上記の設定を記載することで、EventBridge構築用Lambdaの自動生成を抑止することができます。


気になる方は、以下のブログを参照してください。
Serverless Framework で自動的に作成される Amazon EventBridge 構築用の AWS Lambda を抑止する

functions

Lambda関数の設定を記載しています。

layers:
      - arn:aws:lambda:${self:provider.region}:017000801446:layer:AWSLambdaPowertoolsPython:4
  • events
    • Amazon EventBridgeをLambda実行トリガーに設定
    • rateにてEventルールを設定。停止は毎日11:00(日本時間で20:00)、起動は毎日21:00(日本時間で6:00)
    • inputにてLambaに渡す定数を設定。processという定数に、それぞれstopとstartを設定
    events:
      - schedule:
          name: ${self:service}-ecs-task-stop-rule
          description: ECSタスク停止用
          rate: cron(0 11 * * ? *)
          input:
            process: stop
      - schedule:
          name: ${self:service}-ecs-task-start-rule
          description: ECSタスク起動用
          rate: cron(0 21 * * ? *)
          input:
            process: start

resources

  • Resources
    • LambdaからECSヘアクセスできるよう、IAM Roleに以下のポリシーを設定
      • AWS管理ポリシーAWSLambdaBasicExecutionRole
      • ECSサービスの更新とECSサービスの指定ができるカスタマーポリシー

pythonファイル

Lambda用のpythonファイルを以下に記載します。ファイル名はtask_control.pyとします。
1つのファイルの中に、起動用と停止用の関数を用意します。

import os
 
import boto3
 
from aws_lambda_powertools import Logger
from aws_lambda_powertools.utilities.data_classes import EventBridgeEvent
from aws_lambda_powertools.utilities.typing import LambdaContext
 
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
ECS_CLUSTER_NAME = os.environ['ECS_CLUSTER_NAME']
ECS_SERVICE_NAME = os.environ['ECS_SERVICE_NAME']
 
 
logger = Logger(level=LOG_LEVEL, service=__name__)
ecs = boto3.client('ecs')
 
 
@logger.inject_lambda_context()
def handler(event=EventBridgeEvent, context=LambdaContext) -> None:
 
    try:
        if event['process'] == 'stop':
            stop_ecs_task()
        elif event['process'] == 'start':
            start_ecs_task()
        else:
            raise Exception('Invalid argument')
    except Exception:
        logger.error('Error occurred')
  
  
def stop_ecs_task():
    ecs.update_service(
        cluster=ECS_CLUSTER_NAME,
        service=ECS_SERVICE_NAME,
        desiredCount=0,
    )
 
 
def start_ecs_task():
    ecs.update_service(
        cluster=ECS_CLUSTER_NAME,
        service=ECS_SERVICE_NAME,
        desiredCount=1,
    )
  • serverless.ymlで設定したEventBridgeの定数で条件分岐をすることで、関数stop_ecs_taskを呼ぶか、関数start_ecs_taskを呼ぶか判断します
  • boto3とupdate_serviceメソッドを使用します
  • update_serviceメソッドの引数に、操作するECSクラスターとECSサービスを指定し、desiredCount=0にすることで起動しているECSタスクが停止、desiredCount=1にすることで停止しているECSタスクが起動します

実行

それではserverless.ymlをデプロイし、実際に時間通りECSタスクが起動停止するかを確認します。

停止

ECSタスクが日本時間の20:00に停止できているか確認します。

実行前

ECSコンソール画面を確認すると、ECSタスクが起動しています。

f:id:swx-furukawa:20220201093347p:plain

実行後

ECSコンソール画面を確認すると、ECSタスクが停止しています。

f:id:swx-furukawa:20220201093258p:plain

念のため、Lambdaのログを確認してみると、20:00にLambdaが実行されていることを確認できました。 f:id:swx-furukawa:20220201093542p:plain

起動

今度は、ECSタスクが日本時間の06:00に起動できているか確認します。

実行前

ECSコンソール画面を確認すると、ECSタスクが停止しています。

f:id:swx-furukawa:20220201093946p:plain

実行後

ECSコンソール画面を確認すると、ECSタスクが起動しています。

f:id:swx-furukawa:20220201093912p:plain

念のため、Lambdaのログを確認してみると、06:00にLambdaが実行されていることを確認できました。

f:id:swx-furukawa:20220201094012p:plain

最後に

serverless.yamlとpythonファイルを1つ用意することで、Amazon ECSのコスト削減を手軽に実現できました。 ぜひ、利用してみてください。

古川敏光 (執筆記事の一覧)

アプリケーションサービス部・ディべロップメント課

AWSによるサーバレス開発をメインに日々研鑽しております。 最近ハマっている趣味はサーフィンです。