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用)
構成図
構築
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
- Lambda実装のサポートしてくれるLambda Powertools の設定
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サービスの指定ができるカスタマーポリシー
- AWS管理ポリシー
- LambdaからECSヘアクセスできるよう、IAM Roleに以下のポリシーを設定
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タスクが起動しています。
実行後
ECSコンソール画面を確認すると、ECSタスクが停止しています。
念のため、Lambdaのログを確認してみると、20:00にLambdaが実行されていることを確認できました。
起動
今度は、ECSタスクが日本時間の06:00に起動できているか確認します。
実行前
ECSコンソール画面を確認すると、ECSタスクが停止しています。
実行後
ECSコンソール画面を確認すると、ECSタスクが起動しています。
念のため、Lambdaのログを確認してみると、06:00にLambdaが実行されていることを確認できました。
最後に
serverless.yamlとpythonファイルを1つ用意することで、Amazon ECSのコスト削減を手軽に実現できました。 ぜひ、利用してみてください。