ForgeVision Engineer Blog

フォージビジョン エンジニア ブログ

AWSをMackerelで監視(EventBridge編)

こんにちは。クラウドインテグレーション事業部の松尾です。

前回のブログから1年半ほど経過しての投稿です。 かなり時間が経ってしまったので社内でも色んな変革があったり無かったり・・・。
※他のブログも見ておられたらお気づきかもしれませんが、事業部名がソリューション技術部からクラウドインテグレーション事業部に変更されました。

私のブログの投稿がストップしていた期間中もAWSでは様々なサービスがリリースされ、さらにそれがアップデートされるなど、日々の移り変わりは本当に早いものです。

サバの漁獲量も同様で、ブログを頻繁に投稿していた2018年は2001年以降の最大の漁獲量だったのに対し、2019年はその二分の一となり、2020年はさらに落ち込むと予測されています・・・。

サバの漁獲量は不景気ではある状況ではございますが、じゃんじゃん新機能が発表されて景気がよさそうな監視ツールは・・・・そうです!Mackerelです!!

そこにあやかった形ではありますが、最近お客様のAWS環境で起きた悩ましい障害をMackerelの新機能を利用することで回避することができました!
そこで、同様の事象に悩んでいるユーザが他にもいるのではと思い、久しぶりの投稿を決意した次第です。

今回のブログはこれまで投稿してきた汎用的なものと違い、特定の条件下で有効なMackerelの利用手法について紹介していきたいと思います!!
※本ブログの内容以外にも障害を回避する手段はいくつかありますが、弊社は監視でMackerelを利用しているため、それを有効に活用することを前提に本対応を行っております。

なお、これまでのMackerelに関する投稿は以下の通りです。
気になる記事がございましたら、是非ご一読下さい!!

techblog.forgevision.com
techblog.forgevision.com
techblog.forgevision.com
techblog.forgevision.com

機能実装までの経緯

お客様が所有するAWS環境上に構築されているWEBサーバにて「Out of memory」が頻発し、WEBアクセスの遅延が発生するだけでなく最悪の場合は接続不可となってしまうする事象が発生しておりました。

AWSの構成は以下の通りです。

f:id:matsuo1017:20200505130939p:plain
AWS構成

本サイトは複数のコンテンツを提供する大元のサーバであるため、影響範囲が大きく、問題の解決が急務となっておりました。

根本的な問題はある程度切り分けができているものの、長期間に渡って都度機能のアップデートを行ってきたため、WEBサイトの構造が複雑化しており、現状短期間での改善は困難な状況でした。

暫定的にApacheプロセスの手動再起動によりWEBアクセスが復旧できることは確認していましたが、休日・夜間中などはどうしても対応が遅れてしまうことがあり、自動化を行う必要がありました。

前提

自動化を行う上での前提として、Apacheは完全に停止しているわけではなく、子プロセスのみ停止した状態で障害(WEBアクセス不可)が発生するため、単純なプロセスの起動状況確認による再起動の自動化はできないことを考慮する必要がありました。

既に該当のシステムではMackerel導入しており、外形監視(URL監視)を行ってWEBサイトの監視を行っておりました。

この外形監視はWEBアクセスが不可となった場合に、レスポンスタイム遅延によりアラートを発報することが可能で、本事象発生時も例外無く通知がされることを確認しておりました。

そこで、この外形監視をApache再起動のトリガーとすることで現状の課題は解決できると考えました。

機能概要

Mackerelの新機能として「Amazon EventBridge」へのイベント通知が可能となったため、既に導入している外形監視とこの機能を組み合わせてのWEB復旧機能実装を進めることとしました。

以下が今回実装した機能における障害から復旧までの流れです。

f:id:matsuo1017:20200505131440p:plain
障害発生
f:id:matsuo1017:20200505131534p:plain
外形監視アラートトリガー
f:id:matsuo1017:20200505131644p:plain
EventBridge連携
f:id:matsuo1017:20200505131731p:plain
SNSトピック連携
f:id:matsuo1017:20200505131803p:plain
障害復旧

実装手順

具体的な実装方法について説明します。
※以降の手順で添付する画像はテスト環境で取得したキャプチャ画像です。

EventBridge連携設定(Mackerel側)

まずはMackerel画面にてAmazon EventBridgeとの連携設定を行います。
Mackerelの管理画面にログインし、左ペインの[Channels]を選択し、[通知グループ/通知チャンネルの追加]を押下します。

f:id:matsuo1017:20200507180629p:plain
Channels画面

通知チャンネルの作成画面で[Amazon EventBridge]を選択します。

f:id:matsuo1017:20200507181416p:plain
通知チャンネル作成画面(作成前)

Amazon EventBridge用のチャンネル設定を行います。
設定内容は以下の通りです。
※今回は検証時の情報を入力しております。

f:id:matsuo1017:20200507181554p:plain
Amazon EventBridge通知設定

  • 通知チャンネル名
    Mackerelで管理するチャンネル名を入力します。今回は検証時の名前「matsuo-EventBridge-test」を入力します。
    ※AWS側には表示されません。

  • AWSアカウントID
    連携を行いたいAWSのアカウントIDを入力します。

  • イベント名
    AWS側にイベントソースとして表示する名前を入力します。
    今回は検証時の名前「WEB-Access-Failed」を入力します。

  • 通知を受け取るAWSのリージョン
    AWS側に機能実装を行うリソースが存在するリージョンを選択します。
    今回は東京リージョンを選択します。

  • 通知するイベント
    このチャンネルで通知を行うイベントの種類を選択します。
    今回はアラートのみの通知のため、「アラート通知」のみ有効化します。

  • デフォルト通知グループに追加する
    デフォルト通知グループに通知を追加する場合にチェックを入れます。
    私は基本的にデフォルトの通知グループは利用しないため、チェックを外します。

各設定入力後[作成]を押下し、通知チャンネルが作成されたことを確認します。

f:id:matsuo1017:20200507181701p:plain
通知チャンネル画面(作成後)

次に外形監視のアラートのみを通知する通知グループの作成を行います。 通知グループの設定におきましては冒頭でも紹介した以下のブログをご参照下さい。 techblog.forgevision.com

外形監視の設定方法や仕様につきましては以下のMackerelサイトをご参照下さい。 mackerel.io

EventBridge連携設定(AWS側)

AWS側は以下のリソースの作成と設定を行います。

  • SNSトピック作成
    WEBの自動再起動の結果を送付する通知先として利用する

  • SSM実行用のIAMロールの作成及び対象のEC2インスタンスへのアタッチ
    Systems Managerの機能であるRunCommandを実行するために必要

  • Lambda関数実行用のIAMロール作成
    Lambda関数で他サービスと連携を行うために必要

  • Lambda関数の作成
    EventBridgeの通知をトリガーとしてRunCommandでWEB再起動を行うために利用する

  • EventBridgeの設定
    Mackerelの外形監視からイベントを受け取り、Lambda関数へ処理を受け渡すために利用する

SNSトピックの作成と、SSM実行用IAMロールの作成に関しましては、一般的な手順での作成・設定となりますので、以下のAWSドキュメントをご参照の上設定を行ってください。

docs.aws.amazon.com

docs.aws.amazon.com

※本ブログ投稿時点のドキュメントURLとなりますので、更新される可能性がございます。

Lambda関数実行用のIAMロール作成

SNSトピックの作成と、SSM実行用IAMロールの作成・アタッチが完了したら、Lambda実行用のIAMロールの作成を行います。

IAMロールの作成方法につきましては説明を省略させていただき、以降はアタッチするIAMポリシーについて説明します。

今回以下のような権限を持つIAMポリシーの作成を行います。

  • EC2インスタンスの読み取り権限
    EC2インスタンスが停止している場合、RunCommand実行前にエラーとなってしまうため、起動状態を確認するために必要

  • RunCommandの実行権限
    RunCommandの実行と進捗を確認するために必要

  • CloudWatchLogsへのログ出力権限
    Lambdaの実行結果をCloudWatchLogsに出力するために必要

  • SNSへの通知権限
    SNSトピックに実行結果をパブリッシュするために必要

以上を踏まえて作成したIAMポリシーは以下の通りです。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:Describe*"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ssm:SendCommand"
            ],
            "Resource": [
                "arn:aws:ssm:*:*:document/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ssm:SendCommand"
            ],
            "Resource": [
                "arn:aws:ec2:*:*:instance/[インスタンスID]"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "ssm:UpdateInstanceInformation",
                "ssm:ListCommands",
                "ssm:ListCommandInvocations",
                "ssm:GetDocument"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": "logs:CreateLogGroup",
            "Resource": "arn:aws:logs:ap-northeast-1:[アカウントID]:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": [
                "arn:aws:logs:ap-northeast-1:[アカウントID]:log-group:/aws/lambda/[Lambda関数名]:*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "SNS:Publish",
            "Resource": "arn:aws:sns:ap-northeast-1:[アカウントID]:[SNSトピック名]"
        }
    ]
}

※[]で括ったものは実際の値と置き換えが必要となります。
※上記は東京リージョン(ap-northeast-1)を対象とした例です。

Lambda関数の作成

次にLambda関数の作成を行います。 細かい手順は省略しますが、ランタイムは「Python3.7」を利用し、前項で作成したIAMロールを指定して関数を作成します。

関数の作成が完了したら、コードを入力します。
実装の内容は機能概要に記載した通りで、投入したコードは以下の通りです。

import boto3
import time

ec2 = boto3.client('ec2')
instance_id = "[インスタンスID]"    # RunCommandを実行するインスタンスIDを指定します。

ssm = boto3.client('ssm')
command = "service httpd restart"    # 実行するOSのコマンドを指定します。

sns = boto3.client('sns')
topic_arn = "arn:aws:sns:ap-northeast-1:[アカウントID]:[SNSトピック名]"    # SNSトピックのARNを指定します。
subject = "WEB サーバの再起動に関する通知"    # SNSはE-Mailで通知を行うため、メールの件名を指定します。


def lambda_handler(event, context):
    ec2_state = ec2.describe_instances(    # 前項で説明した通りの理由から、EC2の起動状態を確認します。
        Filters=[{'Name': 'instance-id', 'Values': [instance_id]}]
    )["Reservations"][0]["Instances"][0]['State']['Name']
    if ec2_state != "running":    # EC2ステータスが「running」以外の場合は処理を終了します
        print("WEB サーバの EC2 ステータスが " + ec2_state + " のため、Apache 再起動処理を終了します。")
        return

    status = event['detail']['alert']['status']
    if status == "critical":    # EventBridge経由でMackerelからアラートが通知された場合にRunCommand処理を実行します
        runcommand = ssm.send_command(
            InstanceIds=[instance_id],
            DocumentName="AWS-RunShellScript",
            Parameters={
                "commands": [
                    command
                ]
            },
            TimeoutSeconds=600
        )
        command_id = runcommand['Command']['CommandId']     # コマンドの実行状態を確認すためにコマンドIDを抽出しておきます。
        print("WEB サーバへの WEB アクセスができない状況が発生しているため、Apache の再起動を実行します。")

        sns_restart = sns.publish(    # 以降各ポイントごとにSNSへの通知を行います。
            TopicArn=topic_arn,
            Message="WEB サーバへの WEB アクセスができない状況が発生しているため、Apache の再起動を実行します。",
            Subject=subject
        )

        rc_status = ""
        while True:
            time.sleep(1)
            rc_list = ssm.list_command_invocations(    # コマンドの実行状況を確認します。
                CommandId=command_id,
                Details=True
            )
            print(str(rc_list))
            if rc_list and "CommandInvocations" in rc_list:
                invocations = rc_list['CommandInvocations']
                if invocations and "Status" in invocations[0]:
                    rc_status = invocations[0]['Status']

            if rc_status != "Pending" and rc_status != "InProgress":    # コマンドが実行中であれば処理を繰り返します。
                break

        if rc_status == "Failed":    # RunCommandの実行で「Failed」となった場合にその旨をSNSに通知します。
            print("RunCommand での Apache 再起動処理で実行エラーとなりました。サーバにログインし状態を確認してください。")

            sns_failed = sns.publish(
                TopicArn=topic_arn,
                Message="RunCommand での Apache 再起動処理で実行エラーとなりました。サーバにログインし状態を確認してください。",
                Subject=subject
            )
 
        elif rc_status != "Failed" and rc_status != "Success":    # RunCommandの実行で「Failed」、「Success」以外のステータスで終了する可能性があるため、その旨をSNSに通知します。
            print("RunCommand が Failed 以外の理由で正常に終了しておらず、Apache の再起動処理が完了しておりません。詳細は CloudWatchLogs を確認してください。")
            
            sns_other = sns.publish(
                TopicArn=topic_arn,
                Message="RunCommand が Failed 以外の理由で正常に終了しておらず、Apache の再起動処理が完了しておりません。詳細は CloudWatchLogs を確認してください。",
                Subject=subject
            )

    elif status == "ok":    # Mackerelの外形監視で「ok」となったことをSNSに通知します。
        print("WEB サーバへの WEB アクセスが復旧致しました。")

        sns_ok = sns.publish(
            TopicArn=topic_arn,
            Message="WEB サーバへのWEBアクセスが復旧致しました。",
            Subject=subject
        )

    elif status == "warning":    # Mackerelの外形監視で「warning」が通知された場合に、その理由をSNSに通知します。
        print("WEB サーバへの WEB アクセスに遅延が発生しております。")

        sns_warning = sns.publish(
            TopicArn=topic_arn,
            Message="WEB サーバへのWEBアクセスに遅延が発生しております。",
            Subject=subject
        )

    else:        # Mackerelの外形監視で「unknown」が通知された場合にもSNSに通知します。
        print("WEB サーバへの外形監視設定において不明なイベントを検知しております。")

        sns_unknown = sns.publish(
            TopicArn=topic_arn,
            Message="WEB サーバへの外形監視設定において不明なイベントを検知しております",
            Subject=subject
        )

今回はCloudWatchLogsとSNSトピックの両方に結果の出力を行っておりますが、必須ではありません。 利用中の環境に合った方法でのアラート通知の実装をご検討下さい。

EventBridgeの設定

前項までの設定が全て完了したらEventBridgeの設定を行います。

AWSコンソールにログインし、Amazon EventBridgeの「パートナーイベントソース」画面を開きます。

f:id:matsuo1017:20200506133113p:plain
Amazon EventBridge画面

Mackerel側での通知チャンネル設定が問題なく完了していれば、パートナーイベントソースに表示がされます。
ステータスは「保留中」であるため、イベントソース選択後[イベントバスと関連付ける]を押下します。

f:id:matsuo1017:20200507181955p:plain
パートナーイベントソース画面

イベントバスとの関連付けを行う画面に推移します。
他アカウントとの連携は無いため、何も選択せず[関連付ける]を押下します。

f:id:matsuo1017:20200506134141p:plain
イベントバス関連付け画面

次にルールの設定を行います。
Amazon EventBridgeの左ペインにて[ルール]を選択します。
関連付けを行ったイベントバスを選択し、[ルールを作成]を押下します。

f:id:matsuo1017:20200506141315p:plain
ルール一覧画面

ルールの作成画面に推移します。
ルールの名前と、任意で説明を入力します。
パターンを定義」で以下の通りの選択を行います。

  • イベントパターン
    • イベント一致パターン
      ⇒サービスごとの事前定義パターン
    • サービスプロバイダー
      ⇒サービスパートナー
    • サービス名
      ⇒Mackerel

f:id:matsuo1017:20200506143035p:plain
ルール作成画面①

イベントバスを選択」はイベントバスを選択した上でルールの作成を開始した場合には、既に項目が選択されているため設定不要です。

ターゲットを選択」では作成したLambda関数を指定します。
[ターゲット]を「Lambda関数」にし、[機能]では作成したLambda関数を選択します。

任意でタグを指定し、[作成]を押下します。

f:id:matsuo1017:20200507181834p:plain
ルール作成画面②

以上でAWS側の設定も完了し、機能概要に記載させていただいた全ての機能の実装が完了です。

長文ではございましたが、最後までお読みいただきありがとうございます!!

次の配信について

今回の投稿では、実際にお客様環境で発生した問題に対し、Mackerelを用いて復旧の自動化を行った事例を紹介させていただきました!

次回はどういった記事を書くか未定ですが、Mackerelではどんどん新機能が追加されているため、近いうちにいくつかピックアップして、また紹介させていただきたいと思います!!

それでは次回もお楽しみに!!