OPS

AWS Step Functionsでの一次対応自動化について

AWS Step Functionsでの一次対応自動化について

2023.04.11

本記事のポイント

本記事は、AWS Step Functionsを用いて1次対応を自動化する設定などをまとめてご紹介します。運用負荷軽減に興味のある方はぜひ活用ください。



はじめに

Amazon CloudWatch (以下、CloudWatch)でアラートは設定したままで終わっていないでしょうか。設定後、運用の負荷は変わらず悩んでいる方もいらっしゃると思います。
そこで、今回はAWS Step Functions(以下、Step Functions)を用いて1次対応の自動化をご紹介します。

※事前にApacheがインストール済みで、ページ表示されるAmazon EC2(以下、EC2)インスタンスをご用意ください

Canaryの作成

まず、初めにCanaryの作成を行います。
※今回はEC2にApacheをインストールし、テストページの監視を行います。

画面上部の検索窓から、「Canary」と入力し、CloudWatch Syntheticsを選択します。
画面中央の「Canaryを作成」をクリックし、設定画面へ進みます。

AWS Step Functions Canary

以下のように選択します。

AWS Step Functions Canaryを作成

Canary ビルダーは下記パラメータで設定を行います。

名前 Monitoring-test
テストするアプリケーションまたはエンドポイントURL 監視したいURL

(例)

AWS Step Functions Canaryビルダー

これでCanaryの作成は完了です。次はアラートの作成へ進みます。

アラートの作成

次にCloudWatchでアラームを作成します。画面上部の検索窓から、「CloudWatch」と入力し、CloudWatchを選択します。左ペインよりアラーム→すべてのアラーム、画面中央のアラームの作成をクリックし、以下の順序で作成していきます。

【メトリクスの選択】し、【CloudWatchSynthetics】を選択

AWS Step Functions アラートの設定

【Canary】をクリック

AWS Step Functions アラートの設定2

メトリクス名を「Failed」にチェックをつけ、【メトリクスの選択】をクリック

AWS Step Functions アラートの設定3

以下の内容になっていることを確認

AWS Step Functions アラートの設定4

発生条件は以下内容で設定

AWS Step Functions アラートの設定5

通知条件は以下内容で設定

▼発生

アラーム状態トリガー アラーム状態
次の SNS トピックに通知を送信 既存のSNSトピックを選択
※通知したいトピックを選択してください

▼OK

アラーム状態トリガー OK
次の SNS トピックに通知を送信 次の SNS トピックに通知を送信
※通知したいトピックを選択してください

アラーム名とアラームの説明は適当に設定を行い【アラームの作成】をクリックし、アラームが作成されることを確認します。
上記と同じ手順で、「4xx」のメトリクスでのアラームも作成します。

AWS Lambdaの作成

Step Functionsで実行させるためのAWS Lambda (以下、Lambda)を作成していきます。
今回はLambdaをStep Functionsと紐づけたいのでデフォルトのままで作成します。

AWS Step Functionsの作成

画面上部の検索窓から、「Step Functions」と入力し、【Step Functions】を選択します。
画面中央の【ステートマシンの作成】をクリックし、設定画面へ進みます。
作成方法を選択にて【ワークフローを視覚的に設計】→【標準】→【次へ】へ進みます。

ステップ2では以下の設定を行います。

状態名 Trigger_Check
統合タイプ Optimized
APIパラメータ 前項で作成したlambda
次の状態 最後に移動

ステップ3ではステップ2で作成した内容に問題ないか確認し次へ進みます。
ステートマシンの名を入力し、【ステートマシンの作成】をクリックし、作成完了です。

Amazon EventBridgeの作成

前項で作成した、ステートマシン(Step Functions)に紐づけるルールを作成していきます。
画面上部の検索窓から、「Amazon EventBridge」と入力し、【Amazon EventBridge】を選択します。
左ペインの【ルール】をクリックし、【ルールを作成】進み以下の要領で設定を行います。

ステップ 1 次へ

名前 EventBridge_to_StepFunctions
イベントバス Default
ルールタイプ イベントパターンを持つルール

ステップ2

イベントソース AWS イベントまたは EventBridge パートナーイベント
作成のメソッド パターンフォームを使用する
イベントパターン イベントソース
AWS のサービス
イベントタイプ
{
  "source": ["aws.cloudwatch"],
  "detail-type": ["CloudWatch Alarm State Change"],
  "resources": [{
    "prefix": "arn:aws:cloudwatch: 〓ARN名〓:alarm:Canary monitoring is"
  }],
  "detail": {
    "state": {
      "value": ["ALARM"]
    }
  }
}

ステップ3

ターゲットタイプ AWS のサービス
ターゲットを選択 Step Functions ステートマシン
〓項番5で作成したステートマシンを指定〓
実行ロール 【この特定のリソースについて新しいロールを作成】を選択

作成後CLIで以下を実行し、Amazon EventBridge (以下、EventBridge)にStepFunctionsが連携できているか確認します。

aws cloudwatch set-alarm-state --alarm-name "Canary monitoring is failed" --state-value ALARM --state-reason "test"

実行後、以下画像のように実行されていれば紐づけができています。

AWS Step Functions アラートの設定6

AWS Step Functionsの修正

次にアラートの内容によって切り分けできるようにChoiceをステートマシンに追加していきます。
左ペインの検索窓からChoiceを選択して、右側の画面にドラッグアンドドロップし、以下のルール2つを設定します。

AWS Step Functionsの修正

Rule1:件名がCanary monitoring is failed場合

SendCommand

Rule2:上記以外
こちらはデフォルトルールとなり、「Default state」はテスト用で作成したLambdaを紐づけます。
Rule1の場合は、Apacheを再起動したいので左ペインの検索窓から【SendCommand】を選択して、Choiceの下にドラッグアンドドロップします。

AWS Step Functionsの修正2

状態名を設定し、APIパラメータに以下を入力し保存します。

{
  "InstanceIds": [
    "対象のEC2インスタンスのインスタンスID"
  ],
  "DocumentName": "AWS-RunShellScript",
  "Parameters": {
    "commands": [
      "systemctl restart  httpd"
    ]
  }
}

最終的に以下の図のようになっていれば設定完了です。

AWS Step Functionsの修正3

それでは分岐されるかテストを実施します。
以下コマンドを実行して、Apacheが再起動されれば成功です。

# aws cloudwatch set-alarm-state --alarm-name "Canary monitoring is failed" --state-value ALARM --state-reason "test"
AWS Step Functionsの修正4

Canary monitoring is failed以外の場合のテストも行います。
以下コマンドを実行して、Apacheが再起動されなければ分岐が成功しています。

# aws cloudwatch set-alarm-state --alarm-name "Canary monitoring is 4xx failed" --state-value ALARM --state-reason "test"
AWS Step Functionsの修正5

一次対応失敗したときのハンドリング について

現在の設定ですとrestartの結果が確認できないので処理を追加していきます。
以下コードスニペットを貼り付け保存すると下記のワークフローが作成されます。
各ステップの機能について説明していきます。

ステップ名 説明
Alert Count Check Invoke こちらのステップでは以下のLambdaを用いて、直近5分間でステートマシン何回失敗しているか確認します。

次ステップの「Alert Count Check Condition」で失敗した数に応じて分岐される仕様になります。

import json
import boto3
from datetime import datetime

statemachine_name = "〓ステートマシンのARN名"
def lambda_handler(event, context):
    # TODO implement
    sfn_client = boto3.client('stepfunctions')
    sfn_response = sfn_client.list_executions(
    stateMachineArn=statemachine_name,
    statusFilter='FAILED')
    failed_count = 0
    dt_now = datetime.now()
    print(dt_now)
    for i in sfn_response['executions']:
        rowdata = str(i['stopDate'])
        convert_date = datetime.strptime(rowdata[0:19], '%Y-%m-%d %H:%M:%S')
        results = dt_now - convert_date
        print(convert_date)
        sec = results.total_seconds()
        hour = int(sec//3600)
        min = int(sec%3600//60)
        if hour == 0:
            if min <=5:
                failed_count  += 1
    return {
        'failed_count':failed_count,
        'event':event
    }
Alert Count Check Condition 「Alert Count Check Invoke」の結果内容をChoiceで分岐させます。
6回以上の場合、自動処理に失敗している可能性が高いため「SNS Publish」に遷移し、指定された担当者へメール送信が行われます。
SNS Publish 自動処理に連続して失敗している可能性が高いため、手動での調査、復旧する旨が記載されたメールが送信されます。
Test Invoke 項番4で作成したテスト用のLambdaです。
こちらに処理を記載すれば、restart以外の対応も可能です。
Restart Http 「SendCommand」を使って、対象インスタンスでApacheの再起起動を実施します。APIパラメータは以下となります。
{
  "InstanceIds": [
    "〓対象のEC2インスタンスID〓"
  ],
  "DocumentName": "AWS-RunShellScript",
  "Parameters": {
    "commands": [
      "systemctl restart  httpd"
    ]
  }
}
Status Http 「SendCommand」を使って、対象インスタンスのApacheの起動状況を確認します。APIパラメータは以下となります。
※注意
ここではコマンドを送信するだけとなっています。
結果はここでは取得できません。
{
  "InstanceIds": [
    "〓対象のEC2インスタンスID〓"
  ],
  "DocumentName": "AWS-RunShellScript",
  "Parameters": {
    "commands": [
      "systemctl status httpd"
    ]
  }
}
Wait 前ステップの「Status Http」 の実行に時間がかかるので、Wait機能を用いて、10秒待機し、次項の「Status Run Command」に遷移します。
Status Run Command 「Status Http」の結果を取得し、次のステップ「Status Check」に取得結果を渡します。APIパラメータは以下となります。
{
  "CommandId.$": "$.Command.CommandId",
  "InstanceId": "〓対象EC2インスタンスID〓"
}
Status Check 「Status Run Command」の結果をChoiceで分岐をさせます。
$.StandardOutputContentに" Active: active*"が含まれている場合は、正常にApacheが再起動されたと判断し、「Success」ステップへ遷移します。
$.StandardOutputContentに" Active: active*"が含まれない場合、正常にApacheが再起動されていないと判断し、「Fail」ステップへ遷移します。
Success 実行結果を成功にするためのステップとなります。
Send Error 自動処理に失敗している可能性が高いため、手動での調査、復旧する旨が記載されたメールが送信されます。
Fail 実行結果を失敗にするためのステップとなります。
{
  "Comment": "A description of my state machine",
  "StartAt": "Alert Count Check Invoke",
  "States": {
    "Alert Count Check Invoke": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "OutputPath": "$.Payload",
      "Parameters": {
        "Payload.$": "$",
        "FunctionName": "arn:aws:lambda:〓ARN名〓:function:Alert_count:$LATEST"
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 2,
          "MaxAttempts": 6,
          "BackoffRate": 2
        }
      ],
      "Next": "Alert Count Check Condition"
    },
    "Alert Count Check Condition": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.failed_count",
          "NumericGreaterThan": 5,
          "Next": "SNS Publish",
          "Comment": "失敗が多い場合"
        }
      ],
      "Default": "Alert Name Check Condition"
    },
    "SNS Publish": {
      "Type": "Task",
      "Resource": "arn:aws:states:::sns:publish",
      "Parameters": {
        "TopicArn": "arn:aws:sns:〓ARN名〓:monitoring_topic",
        "Message": {
          "Message": "直近で自動リカバリが失敗しています。手動での調査・復旧をお願いします。"
        }
      },
      "End": true
    },
    "Alert Name Check Condition": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.event.detail.alarmName",
          "StringEquals": "Canary monitoring is failed",
          "Comment": "Canary monitoring is failedのときは実行される",
          "Next": "Restart Http"
        }
      ],
      "Default": "Test Invoke"
    },
    "Test Invoke": {
      "Type": "Task",
      "Resource": "arn:aws:states:::lambda:invoke",
      "OutputPath": "$.Payload",
      "Parameters": {
        "Payload.$": "$",
        "FunctionName": "arn:aws:lambda:〓ARN名〓:function:Trigger_check:$LATEST"
      },
      "Retry": [
        {
          "ErrorEquals": [
            "Lambda.ServiceException",
            "Lambda.AWSLambdaException",
            "Lambda.SdkClientException",
            "Lambda.TooManyRequestsException"
          ],
          "IntervalSeconds": 2,
          "MaxAttempts": 6,
          "BackoffRate": 2
        }
      ],
      "End": true
    },
    "Restart Http": {
      "Type": "Task",
      "Parameters": {
        "InstanceIds": [
          "i-0b8dba2a5042293f4"
        ],
        "DocumentName": "AWS-RunShellScript",
        "Parameters": {
          "commands": [
            "systemctl status  httpd"
          ]
        }
      },
      "Resource": "arn:aws:states:::aws-sdk:ssm:sendCommand",
      "Next": "Status Http"
    },
    "Status Http": {
      "Type": "Task",
      "Parameters": {
        "InstanceIds": [
          "i-0b8dba2a5042293f4"
        ],
        "DocumentName": "AWS-RunShellScript",
        "Parameters": {
          "commands": [
            "systemctl status  httpd | grep \"Active\""
          ]
        }
      },
      "Resource": "arn:aws:states:::aws-sdk:ssm:sendCommand",
      "Next": "Wait"
    },
    "Wait": {
      "Type": "Wait",
      "Seconds": 10,
      "Next": "Status Run Commnad"
    },
    "Status Run Commnad": {
      "Type": "Task",
      "Parameters": {
        "CommandId.$": "$.Command.CommandId",
        "InstanceId": "i-0b8dba2a5042293f4"
      },
      "Resource": "arn:aws:states:::aws-sdk:ssm:getCommandInvocation",
      "Next": "Status Check"
    },
    "Status Check": {
      "Type": "Choice",
      "Choices": [
        {
          "Variable": "$.StandardOutputContent",
          "StringMatches": "   Active: active*",
          "Next": "Success"
        }
      ],
      "Default": "Send Error"
    },
    "Success": {
      "Type": "Succeed"
    },
    "Send Error": {
      "Type": "Task",
      "Resource": "arn:aws:states:::sns:publish",
      "Parameters": {
        "TopicArn": "arn:aws:sns:〓ARN名〓:monitoring_topic",
        "Message": {
          "Message": "自動起動に失敗したのでサーバにログインして調査を開始してください"
        }
      },
      "Next": "Fail"
    },
    "Fail": {
      "Type": "Fail"
    }
  }
}

まとめ

今回はStep Functionsを用いた1次対応の自動化をご紹介しました。

本番運用の場合は、連続して失敗した場合はステートマシンが実行されないようにEventBridgeを停止させる処理などが必要になりそうです。

分岐のカ所を追加すればいろいろなアラートに対応したステートマシンが作成でき、アラートの全自動対応も可能になります。

まずは、簡単なアラートの自動化から着手して負荷を減らしてみてはいかがでしょうか?