毎日短時間だけ稼働したいEC2 Spot Instanceを定期起動してみた

定期的にEC2を使いたいけど、頑張って節約したいときに。なお、スポットインスタンスの性質上、重要度の高いサーバーにはお勧めできません

こんにちは、AWS事業本部の荒平(@0Air)です。

EC2のスポットインスタンス、使い方さえ押さえれば、価格を低く抑えたいときに大変便利ですよね。
日中しか利用しないアプリケーションをスポットインスタンスで構築し、定期起動する仕組みを作ってみました。

参考:Amazon EC2 スポットインスタンス (耐障害性を備えたワークロードを最大 90% OFF で実行)

構成図

実装方法はいくつかあると思いますが、今回はCloudFormation・Lambda・EventBridgeを利用します。

やってみた

1. Spot Instance用の起動テンプレートを作成する

スポットインスタンスとして起動させるためのEC2起動テンプレートを作成します。
EC2インスタンスを立てる際のUIとほぼ同様で作成が可能です。

起動テンプレートには、「バージョン」の概念があり、このバージョンは後のLambda内で指定が必要です。

2. S3バケットにCloudFormationテンプレートを格納する

今回は以下のように定義してみました。
このテンプレートをS3バケットに格納します。

Resources:
  MySpotFleet:
    Type: "AWS::EC2::SpotFleet"
    Properties: 
      SpotFleetRequestConfigData: 
        IamFleetRole: "arn:aws:iam::xxxxxxxxxxxx:role/aws-ec2-spot-fleet-tagging-role"
        LaunchTemplateConfigs: 
          - LaunchTemplateSpecification: 
              LaunchTemplateId: "lt-0xxxxxxxxxxxxxx"
              Version: "xx"
            Overrides: 
              - InstanceType: "c6i.large"
                WeightedCapacity: "1"
                SubnetId: "subnet-0xxxxxxxxxxxxxx"
        SpotPrice: "0.05"
        TargetCapacity: "1"
        TerminateInstancesWithExpiration: "true"
        Type: "request"

LaunchTemplateId, Version, IamFleetRole, SubnetIdは環境によって読み替えてください。

また、インスタンスタイプやSpotPriceについても同様に、必要に応じて変更が必要です。

3. CloudFormation Stackを作成するLambdaを用意

CloudFormationのスタックをデプロイするためのLambdaを用意します。
S3, EC2, CloudFormationの権限はそれぞれ忘れずに付与します。

import boto3

def lambda_handler(event, context):
    client = boto3.client('cloudformation')
    stack_name = 'spot-req'
    template_url = 'https://cf-templates-xxxxxxxx-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/xxxxxxx.yaml' # 前手順で格納したファイルを指定

    try:
        # スタックの存在を確認
        client.describe_stacks(StackName=stack_name)

        # スタックが存在する場合、削除を行う
        print(f"Deleting stack {stack_name}")
        client.delete_stack(StackName=stack_name)
        response_message = f"Deleted stack {stack_name}"

    except client.exceptions.ClientError as e:
        # スタックが存在しない場合、デプロイを行う
        print(f"Creating stack {stack_name}")
        client.create_stack(
            StackName=stack_name,
            TemplateURL=template_url,
            Parameters=[
            ],
            Capabilities=[
                "CAPABILITY_NAMED_IAM"
            ]
        )
        response_message = f"Created stack {stack_name}"

    return {
        'message': response_message
    }

4. LambdaをEventBridge (CloudWatch Events)でフックする

LambdaのトリガーとしてEventBridge (CloudWatch Events)を設定します。

例えば、10時〜22時に起動したい場合は、cron式で毎日10時・22時にフックするように設定します。

スポットリクエストの条件が満たされていれば、状態がActiveになりEC2インスタンスが立ち上がります。

定期稼働させていた例(19時〜24.5時):

おわりに

1ヶ月ほど毎晩稼働させていたのですが、Spot Instanceによって強制終了させられることは一度もありませんでした。
Spot Instanceは在庫が無くなると強制的に終了させられるイメージがありましたが、観測期間では停止されることが無かったのでちょこっと処理したいものなどには使えそうです。

このエントリが誰かの助けになれば幸いです。
それでは、AWS事業本部 コンサルティング部の荒平(@0Air)がお送りしました!