AWS DevOps Agent (Preview)をPagerDutyからトリガーしてみた #AWSreInvent

AWS DevOps Agent (Preview)をPagerDutyからトリガーしてみた #AWSreInvent

2025.12.19

こんにちは。たかやまです。

以前、PagerDuty連携とWebhook設定を使ってDevOps Agentを起動する検証を行いました。

https://dev.classmethod.jp/articles/aws-devops-agent-preview-pagerduty-webhook-integration/

この時は、PagerDutyから直接DevOps Agentを起動する方法を試そうと思いましたがネイティブの設定がなさそうだったので断念しました。

今回はそのリベンジとして、PagerDutyのIncident Workflowsを使ってDevOps Agentをトリガーする方法を検証しました。

やってみる

今回構築する構成は以下のようになります。

CleanShot 2025-12-19 at 22.29.25@2x.png

シンプルにCloudWatch AlarmからDevOps Agentを起動する方法は以下のブログを参照してください。

https://dev.classmethod.jp/articles/aws-devops-agent-cloudwatch-automated-incident-investigation/

PagerDutyのインテグレーション設定

CloudWatch AlarmとPagerDutyを連携する方法には、「Event Orchestration」と「PagerDuty Service」を使う2つのパターンがあります。

今回はPagerDuty Serviceを作成して、CloudWatch Alarmとの連携を行います。

CleanShot 2025-12-19 at 19.45.45@2x.png
Amazon CloudWatch Integration Guide | PagerDuty

PagerDutyのServicesメニューからNew Serviceボタンをクリックしてサービスを作成します。

CleanShot 2025-12-09 at 16.08.21@2x.png

サービス作成ウィザードが表示されます。ステップ1 Name and Description 〜 ステップ3の Reduce Noise までは適宜設定していきます。

CleanShot 2025-12-10 at 14.57.59@2x.png

CleanShot 2025-12-10 at 18.37.16@2x.png

CleanShot 2025-12-10 at 18.41.37@2x.png

ステップ4のIntegrationsでは、アラートを送信する連携先を選択します。Amazon CloudWatchを検索して選択します。

NR4vmBqwhg1g.png

サービスが作成されると、Amazon CloudWatch Integrationの設定画面が表示されます。

この後のSNSトピックの設定で利用するのでIntegration URLを控えておきます。

bBcRvUaYRs3b.png

PagerDuty インテグレーション用のSNSトピックを作成

AWSマネジメントコンソールからAmazon SNSのトピックを作成します。トピックタイプはスタンダードを選択し、トピック名を入力します。

CleanShot 2025-12-17 at 14.47.06@2x.png

作成したトピックにサブスクリプションを設定していきます。

プロトコルはHTTPSを選択し、エンドポイントには先ほど控えたPagerDutyのIntegration URLを入力します。

PagerDutyとの連携において、rawメッセージ配信は無効にしておきます。

  • プロトコル : HTTPS
  • エンドポイント : PagerDutyのIntegration URL
  • rawメッセージ配信 : 無効

CleanShot 2025-12-17 at 14.35.29@2x.png

CloudWatch AlarmとSNSトピックの連携

CloudWatch Alarmの設定を行います。今回は以前作成したテストオプションBの環境(Lambda Error Testアラーム)を利用します。

https://docs.aws.amazon.com/devopsagent/latest/userguide/getting-started-with-aws-devops-agent-creating-a-test-environment.html#test-option-b-lambda-error-rate-test

CloudWatch Alarmの詳細画面で、アラームの状態を確認できます。この例ではOK状態になっています。

CleanShot 2025-12-17 at 11.21.52@2x.png

アラームの編集画面に移動し、アクションの設定を行います。PagerDutyにインシデントを通知するためには、アラーム状態OK状態の両方でSNSトピックを設定する必要があります。

  • アラーム状態トリガー : 作成したSNSトピックを選択
  • OK状態トリガー : 作成したSNSトピックを選択

CleanShot 2025-12-17 at 14.55.14@2x.png

設定のプレビュー画面で、メトリクス、条件、アクションが正しく設定されていることを確認します。

CleanShot 2025-12-17 at 16.18.39@2x.png

Lambda関数を作成する

PagerDutyからDevOps AgentのWebhookをトリガーするLambda関数を作成します。

Webhookの作成については以下のブログを参照してください。

https://dev.classmethod.jp/articles/aws-devops-agent-preview-pagerduty-webhook-integration/#Webhook連携の設定

Lambda関数の内容は以下のとおりです。

この後設定するPagerDutyのIncident Workflowからインシデント情報を受け取り、DevOps AgentのWebhookに通知しています

// index.mjs
import { createHmac } from "node:crypto";
import https from "node:https";
import { URL } from "node:url";

const EVENT_AI_WEBHOOK_URL = process.env.EVENT_AI_WEBHOOK_URL;
const EVENT_AI_SECRET = process.env.EVENT_AI_SECRET;
const AWS_ACCOUNT_ID = process.env.AWS_ACCOUNT_ID || "unknown";
const AWS_REGION = process.env.AWS_REGION || "us-east-1";

export const handler = async (event) => {
  try {
    console.log("Raw event:", JSON.stringify(event, null, 2));
    if (event.source !== "pagerduty") {
      console.warn("Not a PagerDuty event, skipping:", event.source);
      return { statusCode: 200, body: "Not a PagerDuty event, ignored" };
    }
    const payload = processPagerDutyEvent(event);
    const response = await sendToEventAi(payload);
    console.log("event-ai response:", response.statusCode, response.body);
    return response;
  } catch (err) {
    console.error("Error in event handler:", err);
    return { statusCode: 500, body: "Internal server error" };
  }
};

function processPagerDutyEvent(event) {
  console.log("Processing PagerDuty event");
  const incidentId = event.incident_id || "";
  const incidentTitle = event.incident_title || "PagerDuty Incident";
  const incidentStatus = event.incident_status || "";
  const incidentUrl = event.incident_url || "";
  // alert_detailsからCloudWatch Alarm情報を取得
  const alertDetails = event.alert_details?.body?.details || {};
  const cefDetails = event.alert_details?.body?.cef_details || {};
  const trigger = alertDetails.Trigger || {};
  const dimensions = trigger.Dimensions || [];
  // timestampは現在時刻を使用(重複回避のため)
  const timestamp = new Date().toISOString();
  // descriptionを構築(alert_detailsから詳細情報を取得)
  const descriptionLines = [];
  descriptionLines.push("Please respond in Japanese.");
  descriptionLines.push(`Status: ${incidentStatus}`);
  if (alertDetails.NewStateReason) {
    descriptionLines.push(`Reason: ${alertDetails.NewStateReason}`);
  }
  // Lambda関数名をDimensionsから取得
  const functionDim = dimensions.find(d => d.name === "FunctionName");
  if (functionDim?.value) {
    descriptionLines.push(`Lambda Function: ${functionDim.value}`);
  }
  if (trigger.Namespace && trigger.MetricName) {
    descriptionLines.push(`Metric: ${trigger.Namespace}/${trigger.MetricName}`);
  }
  if (alertDetails.AlarmName) {
    descriptionLines.push(`Alarm Name: ${alertDetails.AlarmName}`);
  }
  if (alertDetails.Region) {
    descriptionLines.push(`Region: ${alertDetails.Region}`);
  }
  if (alertDetails.AWSAccountId) {
    descriptionLines.push(`Account ID: ${alertDetails.AWSAccountId}`);
  }
  if (alertDetails.StateChangeTime) {
    descriptionLines.push(`Incident Time: ${alertDetails.StateChangeTime}`);
  }
  if (incidentUrl) {
    descriptionLines.push(`URL: ${incidentUrl}`);
  }
  const description = descriptionLines.join("\n");
  // affectedResources: alert_detailsからARNを取得
  const affectedResources = [];
  if (alertDetails.AlarmArn) {
    affectedResources.push(alertDetails.AlarmArn);
  }
  if (cefDetails.source_origin) {
    affectedResources.push(cefDetails.source_origin);
  }
  // alert_detailsにARNがない場合はダミーARNを生成
  if (affectedResources.length === 0) {
    affectedResources.push(`arn:aws:pagerduty:${AWS_REGION}:${AWS_ACCOUNT_ID}:incident/${incidentId}`);
  }
  return {
    eventType: "incident",
    incidentId: `pagerduty:${incidentId}`,
    action: "created",
    priority: "HIGH",
    title: incidentTitle,
    description,
    timestamp,
    service: "pagerduty",
    affectedResources
  };
}

async function sendToEventAi(payload) {
  const payloadString = JSON.stringify(payload);
  const tsHeader = new Date().toISOString();
  const hmac = createHmac("sha256", EVENT_AI_SECRET);
  hmac.update(`${tsHeader}:${payloadString}`, "utf8");
  const signature = hmac.digest("base64");
  const url = new URL(EVENT_AI_WEBHOOK_URL);
  const options = {
    hostname: url.hostname,
    path: url.pathname + url.search,
    method: "POST",
    port: url.port || 443,
    headers: {
      "Content-Type": "application/json",
      "x-amzn-event-timestamp": tsHeader,
      "x-amzn-event-signature": signature,
      "Content-Length": Buffer.byteLength(payloadString)
    }
  };
  console.log("Sending payload to event-ai:", JSON.stringify(payload, null, 2));
  console.log("Request headers - timestamp:", tsHeader, "signature:", signature);
  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => {
      let data = "";
      res.on("data", (chunk) => {
        data += chunk;
      });
      res.on("end", () => {
        resolve({
          statusCode: res.statusCode || 200,
          body: data
        });
      });
    });
    req.on("error", (err) => reject(err));
    req.write(payloadString);
    req.end();
  });
}

主にやっていることは以下のとおりです。

ペイロード作成部分では同僚のから日本語指示を入れるとDevOps Agentが日本語で回答してくれると聞いたので日本語指示も追加しています。

  1. PagerDutyイベントの受信と検証
    • PagerDuty Workflowから送信されたイベントからインシデントIDやタイトル、ステータスなどの基本情報を取得
  2. CloudWatch Alarm情報の抽出
    • alert_detailsからCloudWatch Alarmの詳細情報(アラーム名、メトリクス、リージョン、Lambda関数名など)を取得
  3. DevOps Agent用ペイロードの構築
    • インシデント情報とCloudWatch Alarmの詳細を組み合わせて、DevOps Agentが理解できる形式に変換
    • (小ネタ)日本語で対応するための指示を追加
  4. event-ai Webhookへの送信
    • HMAC-SHA256署名を使用した認証
    • タイムスタンプと署名をヘッダーに含めてHTTPS POSTリクエストを送信

以下の環境変数にはWebhook作成で取得した値を設定します。

  • EVENT_AI_WEBHOOK_URL: DevOps AgentのWebhook URL
  • EVENT_AI_SECRET: Webhook認証用のシークレット

CleanShot 2025-12-19 at 19.40.40@2x.png

PagerDutyでIncident Workflowsを作成する

PagerDutyのAutomation > Incident Workflowsを選択し、New Workflowボタンをクリックします。

CleanShot 2025-12-17 at 17.38.28@2x.png

ワークフロー作成ダイアログが表示されるので、ワークフロー名にInvoke DevOps Agentを入力し、Createを選択します。

CleanShot 2025-12-17 at 17.37.27@2x.png

ワークフローが作成されると、ビルダー画面が表示されます。

この画面でトリガーとアクションを設定していきます。

CleanShot 2025-12-17 at 17.38.03@2x.png

ワークフローのトリガー条件では、Conditional Triggerを設定します。この設定でワークフローを起動する条件を定義します。

今回は以下の内容を指定します。

  • When should this workflow start?: "When an incident is created"(インシデントが作成されたとき)
  • This trigger applies to: "Specific services"(特定のサービスに対して適用)
  • Services that this rule applies to: 最初に作成した PagerDuty Service (CloudWatch Alarmと連携したサービス) を選択

これにより、CloudWatch Alarmから送信されたインシデントが作成された際に、自動的にこのワークフローが起動されます。

CleanShot 2025-12-17 at 17.56.10@2x.png

ワークフローのアクションでは、以下の2つのステップを設定します。

1つ目のアクションとしてGet Alerts for an Incidentを追加します。

このアクションはインシデントに関連するアラート情報を取得するもので、Incident IDには{{incident.id}}を指定します。

CleanShot 2025-12-19 at 18.17.20@2x.png

2つ目のアクションとしてAWS: Invoke a Lambda Functionを追加します。このアクションでDevOps Agentを起動するLambda関数を呼び出します。

AWS: Invoke a Lambda Functionの設定では以下を指定します。

  • Integration: 事前に設定したAWS接続を選択
  • Function name: cloudwatch-alarm-to-devops-agent(DevOps Agent起動用のLambda関数名)
  • Payload: JSON形式でインシデント情報を含むペイロードを設定
  • Invocation type: RequestResponse(同期呼び出し)
  • Region: us-east-1(Lambda関数がデプロイされているリージョン)

Payloadには、インシデント情報とアラート詳細を含むJSON構造を指定します。

{
  "source": "pagerduty",
  "incident_id": "{{incident.id}}",
  "incident_title": "{{incident.title}}",
  "incident_status": "{{incident.status}}",
  "incident_url": "{{incident.url}}",
  "created_at": "{{incident.created_at}}",
  "alert_details": {{steps['Get Alerts for an Incident'].fields['First Alert Details']}}
}

CleanShot 2025-12-19 at 18.19.27@2x.png

ここで重要な注意点があります。alert_detailsに渡している{{steps['Get Alerts for an Incident'].fields['First Alert Details']}}は、前段に設定したアクションGet Alerts for an Incidentのフィールドを渡すものです。

他のフィールドと異なり、この値はすでにJSON形式のオブジェクトであるため、ダブルクォートで囲まずにネストされたオブジェクトとして扱う必要があります。

ダブルクォートで囲んでしまうと、文字列として扱われてしまい、Lambda関数側でJSONとしてパースできなくなり以下のエラーが発生しました。

Amazon.Runtime.Internal.HttpErrorResponseException

以下がCloudWatchから送信される実際のイベント構造です。この構造全体がalert_detailsとして渡されます。

CleanShot 2025-12-19 at 18.53.31@2x.png

インシデントを起こしてDevOps Agentの調査を確認する

では、実際にテストオプションBのLambda関数を起動して、DevOps Agentが調査を開始することを確認します。

テストオプションBで作成された AWS-AIDevOps-test-lambdaのテストを実行してアラームを起動します。

CleanShot 2025-12-19 at 19.55.36@2x.png

CloudWatch Alarmが起動してPagerDutyにインシデントが作成されます。

CleanShot 2025-12-19 at 19.57.38@2x.png
CleanShot 2025-12-19 at 19.57.05@2x.png

PagerDutyのTimelineからIncident Workflowが起動していることを確認します。

ちゃんとWorkflowが起動していますね

CleanShot 2025-12-19 at 20.00.20@2x.png

DevOps AgentもLambda関数で作成したDescriptionに従って調査を開始しています。

CleanShot 2025-12-19 at 20.04.05@2x.png

また、小ネタで仕込んだ日本語の指示に従ってちゃんと回答してくれています。

CleanShot 2025-12-19 at 20.07.28@2x.png

最後に

PagerDutyからDevOps Agentをトリガーする方法を検証しました。

以前の検証ではPagerDutyのネイティブ連携でDevOps Agentを起動する方法は見当たりませんでしたが、Incident Workflowsを経由することで実現できました。

PagerDutyを利用することで、AIOpsの恩恵や柔軟なインシデントトリガーによるDevOps Agentの起動が可能になると思います。

今後、インシデント対応の自動化や効率化を検討される際の一助となれば幸いです。

以上、たかやま(@nyan_kotaroo)でした。

この記事をシェアする

FacebookHatena blogX

関連記事