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?

【AWS備忘】Auto Scaling スケールイン時と定時にEC2インスタンスの任意のログをS3に保存【ライフサイクルフック】

Last updated at Posted at 2025-03-04

目的

・Auto Scalingでスケールインするサーバーについて、直前のログを保存しておきたい
・決まった時刻にサーバーのログを保存しておきたい
・StepFunctionsを使うならなるべく1つにまとめておきたい

前提

メインのサーバーが1台常時起動している
メインのサーバーのAMIから起動テンプレートを作成し、0~3台で推移するAuto Scalingグループを作成している
S3作成済み
それぞれのリソースで使うIAMは良いように設定

概要

利用するサービス

・Auto Scalingのライフサイクルフックルール
・EventBridge
・StepFunctions
・Lambda
・SSMドキュメント
・S3

手順

1.Auto Scalingグループの設定

ライフサイクルフックを設定する

【設定】
ライフサイクルフック名:任意
ライフサイクル移行:インスタンス終了
ハートビートタイムアウト:任意(短すぎるとStepFunctions実行が終わらないのである程度確保)
デフォルトの結果:CONTINUEだとタイムアウト時に残りのアクションを続行する、ABANDONだと直ちにインスタンスを終了する
image.png

※追加設定として、CLIから、ライフサイクルフック発動時に通知を飛ばす設定ができるよう。
image.png

2.SSMドキュメントの設定

S3ログ転送を行うSSMドキュメントを作成する

・ログ転送をサーバー内のシェルスクリプトで行う場合

【設定】
ターゲットタイプ:/AWS::EC2::Instance

schemaVersion: '2.2'
description: 'Execute XXXXX.sh on the specified instance'
mainSteps:
  - name: ExecuteScript
    action: 'aws:runShellScript'
    inputs:
      runCommand:
        - '【シェルスクリプトのディレクトリ】'

シェルスクリプトの中身

# S3バケット名を設定
S3_BUCKET="s3://【S3バケット名】"

# 現在の日時を取得
CURRENT_DATE=$(date +"%Y/%m/%d")
CURRENT_DATETIME=$(date +"%Y%m%d-%H%M%S")

# ランダム文字列を生成 (8文字)
RANDOM_STRING=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 8 | head -n 1)

# アップロードするファイルのパス
SOURCE_FILE="【アップロードするファイルのパス】"

# S3の目的地パス
DESTINATION_PATH="${S3_BUCKET}/${CURRENT_DATE}/${CURRENT_DATETIME}-${RANDOM_STRING}/"

# 変数の内容を確認
echo "SOURCE_FILE: ${SOURCE_FILE}"
echo "DESTINATION_PATH: ${DESTINATION_PATH}"

# ファイルの存在確認
if [ ! -f "${SOURCE_FILE}" ]; then
    echo "エラー: ソースファイル ${SOURCE_FILE} が見つかりません。"
    exit 1
fi

# ファイルをS3にアップロード
aws s3 cp "${SOURCE_FILE}" "${DESTINATION_PATH}$(basename ${SOURCE_FILE})"

# アップロードの成功を確認
if [ $? -eq 0 ]; then
    echo "ファイルが正常にアップロードされました: ${DESTINATION_PATH}$(basename ${SOURCE_FILE})"
else
    echo "ファイルのアップロードに失敗しました"
    exit 1
fi

・ログ転送をSSMドキュメント内で行う場合(参考なので↑と格納時のフォルダ命名規則が異なります)

【設定】
ターゲットタイプ:/AWS::EC2::Instance


{
  "schemaVersion": "2.2",
  "description": "log upload",
  "parameters": {},
  "mainSteps": [
    {
      "action": "aws:runShellScript",
      "name": "configureServer",
      "inputs": {
        "runCommand": [
          "instanceid=(`ec2-metadata -i | cut -d ' ' -f 2`)",
          "echo 'Starting log upload process'",
          "aws s3 cp 【転送したいログのディレクトリ】 s3://【S3バケット名】"
        ]
      }
    }
  ]
}

3.Lambdaの設定

EventBridgeからのライフサイクルフックイベントの場合はスケールインしたインスタンスに対して、
スケジュールされたイベントの場合、環境変数から取得したインスタンスに対してSSMドキュメントを実行するLambda

【設定】
ランタイム:Python 3.13
タイムアウト:15分
環境変数
DOCUMENT_NAME:【SSMドキュメント名】
EC2_INSTANCE_ID:【定期ログを取るメインインスタンスのID】

import boto3
import os

ssm = boto3.client('ssm')

def lambda_handler(event, context):
    # インスタンスIDを取得
    if 'original' in event and event['original'].get('source') == 'aws.autoscaling':
        # Auto Scalingからのイベントの場合
        if 'detail' in event['original'] and 'EC2InstanceId' in event['original']['detail']:
            EC2InstanceId = event['original']['detail']['EC2InstanceId']
        else:
            raise ValueError("Auto Scaling eventにEC2InstanceIdが見つかりません")
    elif 'original' in event and event['original'].get('source') == 'aws.events':
        # スケジュールされたイベントの場合
        EC2InstanceId = os.environ.get('EC2_INSTANCE_ID')
        if not EC2InstanceId:
            raise ValueError("環境変数にEC2InstanceIdが見つかりません")
    elif 'Payload' in event:
        # Step Functionsから直接渡された場合
        if 'EC2InstanceId' in event['Payload']:
            EC2InstanceId = event['Payload']['EC2InstanceId']
        else:
            raise ValueError("PayloadにEC2InstanceIdが見つかりません")
    else:
        raise ValueError("EC2InstanceIdが見つかりません")

    # SSMコマンド実行フラグをチェック
    try:
        if event.get('executeSSMCommand', True):  # デフォルトでTrue
            # 環境変数からDocument nameを取得
            document_name = os.environ.get('DOCUMENT_NAME')
            if not document_name:
                raise ValueError("環境変数にDOCUMENT_NAMEが見つかりません")
            
            print(f"Executing SSM command on instance: {EC2InstanceId}")
            
            response = ssm.send_command(
                InstanceIds=[EC2InstanceId],
                DocumentName=document_name
            )
            
            return {
                'CommandId': response['Command']['CommandId'],
                'EC2InstanceId': EC2InstanceId
            }
        else:
            # SSMコマンドを実行せず、インスタンスIDのみを返す
            return {
                'EC2InstanceId': EC2InstanceId
            }
    except Exception as e:
        print(f"Error occurred: {str(e)}")
        return {
            'error': 'Error',
            'message': str(e)
        }

4.StepFunctionsの設定

EventBridgeからのライフサイクルフックイベントの場合はスケールインしたインスタンスに対して、
スケジュールされたイベントの場合、Lambdaが環境変数から取得したインスタンスに対してSSMドキュメントを実行するStepFunctions

image.png

{
  "Comment": "SSMコマンドを実行し、S3ログ転送を行うステートマシン",
  "StartAt": "DetermineInstanceId",
  "States": {
    "DetermineInstanceId": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.detail.EC2InstanceId",
          "IsPresent": true,
          "Next": "SetInstanceIdFromInput"
        }
      ],
      "Default": "SetOriginalFromScheduledEvent"
    },
    "SetInstanceIdFromInput": {
      "Type": "Pass",
      "Parameters": {
        "EC2InstanceId.$": "$.detail.EC2InstanceId",
        "original.$": "$"
      },
      "Next": "ExecuteSSMCommand"
    },
    "SetOriginalFromScheduledEvent": {
      "Type": "Pass",
      "Parameters": {
        "original.$": "$"
      },
      "Next": "GetInstanceIdFromEnvironment"
    },
    "GetInstanceIdFromEnvironment": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "【LambdaARN】",
        "Payload": {
          "executeSSMCommand": false,
          "original.$": "$.original"
        }
      },
      "ResultPath": "$.InstanceIdResult",
      "Next": "SetInstanceId"
    },
    "SetInstanceId": {
      "Type": "Pass",
      "Parameters": {
        "EC2InstanceId.$": "$.InstanceIdResult.Payload.EC2InstanceId",
        "original.$": "$.original"
      },
      "Next": "ExecuteSSMCommand"
    },
    "ExecuteSSMCommand": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "【LambdaARN】",
        "Payload": {
          "EC2InstanceId.$": "$.EC2InstanceId",
          "original.$": "$.original"
        }
      },
      "ResultPath": "$.ExecutionResult",
      "Next": "WaitForSSMExecution"
    },
    "WaitForSSMExecution": {
      "Type": "Wait",
      "Seconds": 60,
      "Next": "CheckSSMStatus"
    },
    "CheckSSMStatus": {
      "Type": "Task",
      "Resource": "arn:aws:states:::aws-sdk:ssm:getCommandInvocation",
      "Parameters": {
        "CommandId.$": "$.ExecutionResult.Payload.CommandId",
        "InstanceId.$": "$.EC2InstanceId"
      },
      "ResultPath": "$.SSMStatus",
      "Next": "EvaluateStatus"
    },
    "EvaluateStatus": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.SSMStatus.Status",
          "StringEquals": "Success",
          "Next": "SuccessState"
        },
        {
          "Variable": "$.SSMStatus.Status",
          "StringEquals": "Failed",
          "Next": "RetryExecution"
        },
        {
          "Variable": "$.SSMStatus.Status",
          "StringEquals": "Cancelled",
          "Next": "RetryExecution"
        },
        {
          "Variable": "$.SSMStatus.Status",
          "StringEquals": "TimedOut",
          "Next": "RetryExecution"
        }
      ],
      "Default": "WaitForSSMExecution"
    },
    "RetryExecution": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "Parameters": {
        "FunctionName": "【LambdaARN】",
        "Payload": {
          "EC2InstanceId.$": "$.EC2InstanceId",
          "original.$": "$.original"
        }
      },
      "ResultPath": "$.ExecutionResult",
      "Next": "WaitForSSMExecution"
    },
    "SuccessState": {
      "Type": "Succeed"
    }
  },
  "TimeoutSeconds": 1800
}

5.EventBridgeの設定

目的より、以下2つのEventBridgeルールを作成する
A.スケールイン発生時に該当のインスタンスのログをS3に転送するStepFunctionsを実行する
B.定期時刻にメインのインスタンスのログをS3に転送するStepFunctionsを実行する

【A.設定】
タイプ:標準
イベントパターン

{
  "source": ["aws.autoscaling"],
  "detail-type": ["EC2 Instance-terminate Lifecycle Action"]
}

ターゲット:StepFunctionsステートマシン
ターゲット名:【作成したStepFunctions】

【B.設定】
タイプ:スケジュール済みスタンダード
イベントスケジュールCron:0 21 * * ? *(毎朝6時に実行したいので)
ターゲット:StepFunctionsステートマシン
ターゲット名:【作成したStepFunctions】

実行

オートスケーリンググループのキャパシティを1→0にしてみる
image.png
image.png
※log中身省略

EventBridgeのcronを直近の時間に変更してみる
image.png
image.png
※log中身省略

おまけ 定期時刻にオートスケーリンググループ内のインスタンスIDをすべて取得し並列でログをS3に保存するLambdaとStepfunctions

いずれ記載

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?