0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Step FunctionsとLambdaでジョブ管理を行う

0
Last updated at Posted at 2025-12-28

環境イメージ

Lambdaを2つ用意します。
1つはEC2インスタンスが停止しているかを確認するLambda。もう1つはインスタンスタイプを変更するLambdaになります。この2つのLambdaをStep Functionsで制御していきます。
停止が確認取れた場合にのみ、次のジョブを実行する形にします。
複雑な制御も出来ると思いますが、今回は出来るだけシンプルな構成で作っていきます。

JP1やHinemos的なノリ。
aws04-ページ6.drawio.png

環境構築・ハンズオン

Lambda

関数を適当に作ります。
Python3.13を使用していきます。
screencapture-ap-northeast-1-console-aws-amazon-lambda-home-2025-12-28-03_20_33 (1).png

ランタイムをデフォルトの3秒から30秒に修正しておきます。
image (1).png

また、それぞれのLambdaに紐づいているIAMロールにEC2FullAccessというPolicyを紐づけます。
これを付けていないとLambdaからEC2へ操作が一切効かず、Lambda実行時にエラーを吐かれます。
image (2).png

sf-ec2-instance-stop-check

import boto3
from botocore.exceptions import ClientError
import json

# Python 3.13 ランタイム
# 必要なIAM権限: ec2:DescribeInstances

def lambda_handler(event, context):
    ec2 = boto3.client('ec2')
    
    # Step Functionsから {"instance-id": "i-xxxxxxxx"} という形式で渡される想定
    target_id = event.get('instance-id')
    
    if not target_id:
        raise ValueError("Error: 'instance_id' is required in the input event.")

    try:
        # 特定のインスタンスIDを指定して情報を取得
        response = ec2.describe_instances(InstanceIds=[target_id])
        
        # 配列構造からステータスを取り出す
        instance_data = response['Reservations'][0]['Instances'][0]
        state_name = instance_data['State']['Name']
        
        return {
            "instance_id": target_id,
            "status": state_name,  # running, stopped, pending, stopping, etc.
            "exists": True
        }

    except ClientError as e:
        # エラーハンドリング(例: インスタンスが存在しない場合など)
        print(f"AWS Error: {e}")
        raise e  # Step Functionsにエラーを通知するために再送出する、または適切なJSONを返す
        
    except Exception as e:
        print(f"Unexpected Error: {e}")
        raise e

sf-ec2-instance-type-change

対象のEC2のインスタンスタイプがLambda実行時t3.microならt3.smallに変更し、t3.smallであればt3.microに変更するものになります。

import json
import boto3
from botocore.exceptions import ClientError

def lambda_handler(event, context):
    # クライアント作成
    client = boto3.client('ec2')

    # Step Functionsから {"instance-id": "i-xxxxxxxx"} という形式で渡される想定
    target_id = event.get('instance-id')

    print(f"対象インスタンス: {target_id}")

    try:
        # 1. 現在のインスタンスタイプを取得 
        response = client.describe_instances(InstanceIds=[target_id])
        
        # インスタンスが存在しない場合のエラーハンドリング
        if not response['Reservations']:
            return _response(404, f'{target_id}が見つかりません。')
            
        current_type = response['Reservations'][0]['Instances'][0]['InstanceType']
        print(f"現在のタイプ: {current_type}")

        # 2. タイプの切り替え判定 (トグル動作)
        if current_type == 't3.micro':
            new_type = 't3.small'
        elif current_type == 't3.small':
            new_type = 't3.micro'
        else:
            msg = f'変更スキップ: 現在のタイプは {current_type} です。対象外のため変更しません。'
            print(msg)
            return _response(200, msg)

        # 3. インスタンスタイプの変更実行
        # (停止チェックはしていませんが、起動中の場合はここでAWSからエラーが返り例外処理へ飛びます)
        client.modify_instance_attribute(
            InstanceId=target_id,
            InstanceType={
                'Value': new_type
            }
        )
        
        success_msg = f'成功: {current_type} から {new_type} に変更しました。'
        print(success_msg)
        
        return _response(200, success_msg)

    except ClientError as e:
        # AWS側のエラー(例: インスタンスが起動中で変更できない場合など)をキャッチ
        error_code = e.response['Error']['Code']
        error_msg = e.response['Error']['Message']
        print(f"AWS Error: {error_code} - {error_msg}")
        return _response(500, f"AWS処理エラー: {error_msg}")
        
    except Exception as e:
        # その他の予期せぬエラー
        print(f"Error: {e}")
        return _response(500, f"予期せぬエラー: {e}")

def _response(code, message):
    """レスポンス生成用ヘルパー関数"""
    return {
        'statusCode': code,
        'body': json.dumps(message, ensure_ascii=False)
    }

EC2

対象となるEC2を適当に作っておきます。
パブリックサブネットに一応作成。インスタンスタイプはt3.microで作成します。作成後は停止しておきます。
image (3).png

Step Functions

ステートマシンの作成は空白から作成・標準を指定します。

image (4).png

画面の左のアクションからLambda InvokeをD&Dします。
画面右の関数名にsf-ec2-instance-stop-checkを選択します。
またペイロードには以下のように入力します。

{
  "instance-id": "i-002cd95c94f1a9c73"
}

引数には自動入力でLambdaやペイロードの情報が記載されていることを確認します。

{
  "FunctionName": "arn:aws:lambda:ap-northeast-1:535002847634:function:sf-ec2-instance-stop-check:$LATEST",
  "Payload": {
    "instance-id": "i-002cd95c94f1a9c73"
  }
}

image (5).png

同じような設定をもう片方のLambdaに設定しておきます。
そしてChoiceとfailedを画面左のフロータブからD&Dして以下のように配置します。
ChoiceをアクティブにしてRule#1のConditionに以下のような設定を入れます。

{% $states.input.status = "stopped" %}

image.png

Defaultの方のStateについてはFailにします。
image.png

sf-ec2-instance-stop-checkに戻って、エラー処理のErrorsのところにStatus:ALLを指定します。
これを指定することでLambdaが実行できなかった場合もFailに推移するようにしています。
画面右上の保存を押下します。
image.png

Step FunctionsからLambdaを呼び出すのに不足しているIAMロールを自動で作成してくれます。
確認ボタンを押下します。
image (8).png

Step FunctionsをJSON形式にしたものが以下になります。

{
  "Comment": "A description of my state machine",
  "StartAt": "Invoke-ec2-stop-check-lambda",
  "States": {
    "Invoke-ec2-stop-check-lambda": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Output": "{% $states.result.Payload %}",
      "Arguments": {
        "FunctionName": "arn:aws:lambda:ap-northeast-1:535002847634:function:sf-ec2-instance-stop-check:$LATEST",
        "Payload": {
          "instance-id": "i-002cd95c94f1a9c73"
        }
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "BackoffRate": 2,
          "JitterStrategy": "FULL"
        }
      ],
      "Next": "Choice",
      "Catch": [
        {
          "ErrorEquals": [
            "States.ALL"
          ],
          "Next": "Fail"
        }
      ]
    },
    "Choice": {
      "Type": "Choice",
      "Choices": [
        {
          "Next": "Invoke-ec2-instance-type-change-lambda",
          "Condition": "{% $states.input.status = \"stopped\" %}"
        }
      ],
      "Default": "Fail"
    },
    "Invoke-ec2-instance-type-change-lambda": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Output": "{% $states.result.Payload %}",
      "Arguments": {
        "FunctionName": "arn:aws:lambda:ap-northeast-1:535002847634:function:sf-ec2-instance-type-change:$LATEST",
        "Payload": {
          "instance-id": "i-002cd95c94f1a9c73"
        }
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 1,
          "MaxAttempts": 3,
          "BackoffRate": 2,
          "JitterStrategy": "FULL"
        }
      ],
      "End": true
    },
    "Fail": {
      "Type": "Fail"
    }
  },
  "QueryLanguage": "JSONata"
}

動作確認

無事に作成されました。
実行ボタンを押下します。
image.png

このような画面が表示されます。特に設定を入れずに実行を開始を押下します。
image (10).png
実行が全て成功するとそれぞれのLambdanの背景が緑色に変わります。
image.png

EC2を見てみるとインスタンスタイプが変わっていることがわかります。
上手く動いていそうですね。
image.png

EC2を起動しておいて、意図的に失敗させようと思います。
image.png

失敗しましたね。
ここではインスタンスタイプがstopped以外であるために、Failに処理が分岐したという事になります。
image.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?