JTP Technology Port

    技術や情報、そして人々が集まる"港"

AWS DevOps Agent を使ってSlack連携してみた!

ついに一般提供が始まった AWS DevOps Agent を実際に触ってみました。アラートを検知しても、そこから原因を調べて対応するのは意外と大変ですよね。本記事では、障害検知から原因分析、Slack 通知までを一気通貫で自動化する流れを構築・体験した様子をご紹介します。運用をちょっとラクにしたい方、ぜひご覧ください!

 

はじめに

みなさん、こんにちは。今回は、AWS DevOps Agentが一般提供されたということで、実際にどのようなことができるのかを触って試してみました!

システム運用では、アラートを検知する仕組みはすでに多くの現場で導入されていますが、なぜこのアラートがあがっているのか、といったの特定・分析作業は人手でやっているケースが多いのではないでしょうか。

今回取り上げるAWS DevOps Agentは、そうした課題に対して、アラート検知から、原因の特定、原因の分析、その後のアクション案の提案をしてくれるサービスとなってます。

本記事では、AWS DevOps Agentを使って、アラート検知からSlack通知までの一連の流れを実際に構築・体験してみた内容をご紹介します。これから触ってみようという方や、運用の自動化に興味がある方の参考になれば嬉しいです🤖

 

AWS DevOps Agentとは?

AWS DevOps Agentとは、システムで発生したイベントやアラートをきっかけに、原因の特定・分析・対応案を提示してくれるAWSサービスです。

従来の運用では、CloudWatchなどで異常を検知したあと、「人が通知を確認して対応する」という流れが一般的でした。

しかしこの方法では、次のような課題がありました。

  • アラートに気が付かない
  • 原因の特定に時間がかかる
  • 何をすれば改善するのかを検討するのにも時間がかかる

そこで登場するのがAWS DevOpsエージェントです。DevOpsエージェントを利用すると、「アラームやイベントをトリガーに、発生したアラームの通知、特定、分析を実行し、Slackなどのツールへ通知する」といった一連の流れを構築することができます。

つまり、「検知して終わり」ではなく、「検知したら原因の特定、原因の分析、原因からの改善案」といった運用を自動化できるのが大きな特徴です。

今回はその中でも、Slack連携部分を実際に構築したのでご紹介します。

今回の構成

今回作成する構成図を紹介します。
本記事では、構成図の赤枠が対象となります。①障害発生 で利用するコンポーネントについては、以前にご紹介したChaosKitty(ミニ)の仕組みを使いたいと思います。

今回の構成で使用する主なリソースは以下の通りです。

  • AWS DevOps Agent
  • Amazon S3
  • AWS CloudWatch
  • EventBridge
  • Lambda
  • Slack(AWS外リソース)

 

実現したい動作

今回のゴールはこのような動作を想定しています。

障害発生 → アラート検知 → AWS DevOps Agent呼び出し → Slack通知

一般的なアラート通知の仕組みに見えますが、ポイントは間にAWS DevOps Agent を挟むことです。

AWS DevOps Agentを利用することで、単純な通知だけでなく、「次に何をすべきか」まで踏み込んだ処理を行うことができます。

<AWS DevOps Agentでできること>

  • アラートの内容をもとにした状況の整理
  • 原因の推定や影響範囲の分析
  • 必要に応じた対応アクションの提案

 

作成過程

それでは実際に構築してみましょう!

構築のステップは、大きく以下のステップで進めていきます。

ステップ① Slack作成

ステップ② AWS DevOps Agent設定 + Slack設定

ステップ③ CloudWatch設定

ステップ④ EventBridge + Lambda 設定

ステップ① Slack作成

まずは通知先となるSlackの作成です。ワークスペースと通知を行いたいチャンネルの準備をします。今回は通知用に別途チャンネルを作ってみました。

チャンネルを作成したら、チャンネル詳細から確認できる「チャンネルID」を控えておいてください。後ほど利用します。

ステップ② AWS DevOps Agent設定

次のステップ2では、AWS DevOps Agentの設定です。

AWSコンソールからAWS DevOps Agent に行きエージェントスペースを作成します。

※基本的にはデフォルトの設定でOKです。

エージェントスペースを作成したら、スペース内にある「コミュニケーション」項目の「統合を追加」→「Slack登録」のような操作でエージェントスペースに連携するSlackを追加していきます。

基本的な設定動作はコンソール上に表示されています。

表示された手順に沿って操作していくと、ワークスペースの選択画面になるので、

「①Slack作成」 にて準備したSlackのワークスペースを選択します。

該当のワークスペースを選択したら「許可する」を押してあげましょう。

許可すると、Slackチャンネルを指定する画面になるので、これまた「①Slack作成」 にて控えたチャンネルIDを指定してください。

次はSlack側の設定となります。

Slack画面に戻り、以下の手順でアプリの追加画面に行きます。

「Slackチャンネル詳細」→「インテグレーション」→「アプリを追加」

アプリの項目に AWS DevOps Agent が表示されているはずです、追加してあげましょう。

するとSlackチャンネルにDevOps Agentが入ってくれます。

Slack連携の設定はこれで終わりですが、次にLambdaで利用するwebhookの設定を行います。

DevOps エージェント画面から、「ウェブフック」→「ウェブフックを追加」で設定していきます。

「URLとシークレットキーを生成」をすると、webhook用のURLとURLに紐づくシークレットが生成されるので、忘れずに保存してwebhookを追加しましょう!

このURLとシークレットは後のLambdaで使います!

ステップ③ CloudWatch設定

今回は、S3のパブリックアクセスブロック設定の変更を検知するので、検知用のロググループの作成と、検知時に 「アラーム → DevOpsエージェント連携」 の流れを取るので、検知用のアラームも作成します。

特別な設定は無いので省略します。

 

ステップ④ EventBridge + Lambda設定

最後に、EventBridgeとLambdaの設定です。まずは、EventBridge側ですが、内容はシンプルで、「アラーム → Lambda実行」の流れになります。

以下のような設定で作成していきます。

トリガーイベント:CloudWatch Alarm State Change

↓のコードを張り付ける形で設定できます。

Lambdaコード (クリックでコードを表示)

{
  "source": ["aws.cloudwatch"],
  "detail-type": ["CloudWatch Alarm State Change"],
  "detail": {
    "state": {
      "value": ["ALARM"]
    }
  }
}

ターゲット:Lambda関数

この時点では、ターゲットとなるLambda関数が無いので併せて作成していきます。

Lambda関数の設定は以下のような内容です。

ランタイム:Python3.4 今回はPythonで実装してみます。

環境変数:

[DEVOPS_AGENT_URL:作成したwebhookのURL]

[DEVOPS_AGENT_SECRET:作成したwebhook用のシークレット]

今回利用する中身のコードがこちらです。

※今回はpythonで実装しましたが、webhook作成時にTypescript を使った実装例があるので、特にこだわりが無ければTypescriptの方が作りやすいかもしれません。

Pythonコード (クリックでコードを表示)

import json
import os
import hmac
import hashlib
import base64
import urllib.request
from datetime import datetime
import uuid

DEVOPS_AGENT_URL = os.environ.get("DEVOPS_AGENT_URL")
DEVOPS_AGENT_SECRET = os.environ.get("DEVOPS_AGENT_SECRET")


def lambda_handler(event, context):
    try:
        print("===== Received Event =====")
        print(json.dumps(event, indent=2))

        alarm_data = event["detail"]

        timestamp = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ')

        incident_id = f"cw-{alarm_data['alarmName']}-{int(datetime.utcnow().timestamp())}"

        payload = {
            "eventType": "incident",
            "incidentId": incident_id,
            "action": "created",
            "priority": "HIGH",
            "title": f"CloudWatch Alarm: {alarm_data['alarmName']}",
            "description": alarm_data["state"]["reason"],
            "timestamp": timestamp,
            "service": alarm_data["configuration"]["metrics"][0]["metricStat"]["metric"]["namespace"],
            "data": {
                "alarmName": alarm_data["alarmName"],
                "alarmArn": event["resources"][0],
                "state": alarm_data["state"],
                "previousState": alarm_data["previousState"],
                "configuration": alarm_data["configuration"]
            }
        }

        payload_str = json.dumps(payload, separators=(',', ':'), ensure_ascii=False)

        print("===== Payload (FINAL) =====")
        print(payload_str)

        message = f"{timestamp}:{payload_str}".encode("utf-8")

        signature = base64.b64encode(
            hmac.new(
                DEVOPS_AGENT_SECRET.encode("utf-8"),
                message,
                hashlib.sha256
            ).digest()
        ).decode()

        print("===== Signature =====")
        print(signature)

        headers = {
            "Content-Type": "application/json",
            "x-amzn-event-timestamp": timestamp,
            "x-amzn-event-signature": signature
        }

        req = urllib.request.Request(
            DEVOPS_AGENT_URL,
            data=payload_str.encode("utf-8"),
            headers=headers,
            method="POST"
        )

        with urllib.request.urlopen(req) as response:
            body = response.read().decode()
            print("===== Response =====")
            print(response.status, body)

        return {
            "statusCode": 200,
            "body": "Success"
        }

    except Exception as e:
        print("===== ERROR =====")
        print(str(e))
        return {
            "statusCode": 500,
            "body": str(e)
        }

Lambdaを作成したらEventBridgeに紐づけて設定完了です!

動かしてみよう!

それでは、お待ちかねの「動かしてみよう!」のお時間です。今回、検知させる仕組みはChaosKitty(ミニ版)を使うので、ChaosKittyを実行します。

それでは ポチッ

すると...Slackに通知が来ました!

ちゃんとAWS DevOps Agent から通知が来てますね!

そして少し待っていると他にも通知が...

アラームがトリガーされた通知のようです。

次は...

Lambda関数によって操作されたことを特定して通知されたみたいです!それにLambda関数の特徴からなのか、ChaosKittyのカオスエンジニアリングツールによる障害注入とその復旧で起こったことを推測しているみたいです。

これはなかなかすごいですね!!!

また次には...

なぜ検知されたのかの分析をしている感じですかね、今回はアラーム検知で発火するようにしたので合ってますね。

またまた次には...

検知した内容から、改善箇所を指摘してるようです。

今回だと、アラームが出たものの、ChaosKittyの仕組みで障害を元に戻すようにしているので、その戻す際にもアラーム検知がされているとのことです。

意図した動作ではないものの推測まで通知してくれるのはありがたいですね!

そして最後に、

まとめのような通知が来て終わりました!

最後にまとめてくれるのは分かりやすくて良いですね!

ちなみに手動でS3のパブリックアクセスブロック設定を変更してみると...

ちゃんと手動だと判別されてます!

それだけでなく、ルートユーザやソースIP、MFA認証されているのか、何を実行したのかも 全て分析されてますね。

なかなかすごいサービスがリリースされましたね!

一連の流れを見てみると、このような感じで動いているように見えます。

① Symptom(何が起きたか)

② Observation(観測事実)

③ Finding(原因候補)

④ Root cause(根本原因)

⑤ Investigation gaps(調査できなかったこと)

⑥ Conclusion(まとめ)

今回調査できなかった箇所としては、CloudTrailの停止期間になります。

ChaosKittyの障害挿入内容にCloudTrailを一時的に無効化するような処理も入れていました。そのため、停止されている期間ではS3の設定変更が見えなかったよ とのことですね。

これを一瞬で特定できるのは、運用担当からしたらびっくりなのではないでしょうか。私もびっくりです。しかも早い...

それとSlackの通知ですが、アラーム検知後に調査・分析を行い通知してくれますが、最初の通知に返信する形で通知されるので、いくつもスレッドが建てられてSlackが通知で埋まらないのもいいですね!

 

使ってみた感想

AWS DevOps Agentを使ってみての感想ですが、

「めちゃめちゃ便利ですね!」

原因の特定だけでなく、改善まで通知してくれるのは AIエージェントって感じがすごいしました!

今回はAWSのリソースを主に使いましたが、AzureなどのAWS以外のサービスについても分析してくれる機能があるようなので、AWSだけでなく関連コンポーネントも分析出来るような仕組みを作って機能を使ってみたいですね。

 

最後に

ここまでお読みいただきありがとうございました。AWS DevOps Agent どうでしたでしょうか。

実際の運用に近い形で「検知 → 分析 → 通知」の流れを体験できたのは非常に良い経験でした!

特に障害発生から特定・分析までの工程は、私が行うよりも早くて正確だったので、少し悔しくも思いました...ただこれから、AWS DevOps Agent 導入されていくと運用負荷の軽減にも大きく貢献してくれるのではないかなー と感じてます!

ぜひ皆さんもこのブログを機にAWS DevOps Agentを試してみてください!これまでとは違った運用の形が見えてくるかもしれないですよ🤖

 

本記事の内容は、公開時点での内容のものです。
実際に導入を検討する際は、各製品・サービスの情報は、公式サイトのドキュメント等をご参照ください。

JTP Technology Port 新着記事