Zabbix × Zendesk × AWS DevOps Agentで実現するアラート対応の自動化

記事タイトルとURLをコピーする

はじめに

監視ツールからのアラート対応は、運用チームにとって大きな負担です。アラートの確認、原因調査、チケット起票、関係者への報告——これらを手作業で行うと、対応の遅延やヒューマンエラーが発生しがちです。

本記事では、Zabbix(監視)、Zendesk(チケット管理)、AWS DevOps Agent(AI根本原因分析)を連携させ、アラート発生からRCA(Root Cause Analysis)結果のチケット反映までを自動化する仕組みを構築した手順を紹介します。

全体アーキテクチャ

前提条件

  • AWS DevOps Agent のAgent Spaceが作成済みであること
  • Zabbixサーバーが構築済みであること(監視対象のホストが登録済み。監視内容はデフォルトのままで問題なし。)
  • Zendeskアカウントがセットアップ済みであり、API トークンが発行済みであること
  • AWS Organizations でSecurity Hub Findings集約済み(SecurityHub連携を動作検証する場合のみ)

注意: 本記事内のコマンドやコードに含まれる {AccountId}{AgentSpaceId}{your-subdomain} 等のプレースホルダーは、ご自身の環境の値に置き換えて実行してください。

1. Zabbixの設定

Zabbixには公式のZendesk連携テンプレート(Webhookメディアタイプ)が用意されています。このテンプレートをインポートすることで、アラート発生時にZendeskチケットを自動作成できます。

テンプレートソース: https://git.zabbix.com/projects/ZBX/repos/zabbix/browse/templates/media/zendesk?at=release/7.4

1-1. グローバルマクロの設定

  1. AdministrationGeneral → ドロップダウンから Macros を選択
  2. マクロ {$ZABBIX.URL} を追加し、ZabbixフロントエンドのURLを設定(例: http://192.168.7.123:8081
  3. Update をクリック

1-2. Zendeskメディアタイプのインポート

  1. AdministrationMedia typesImport をクリック
  2. テンプレートファイル media_zendesk.yaml を選択して Import
  3. インポートされた Zendesk メディアタイプを開き、以下のパラメータを設定:
パラメータ 説明
zendesk_url https://{your-subdomain}.zendesk.com ZendeskのURL
zendesk_token {email}/token:{APIトークン} 認証情報(email/token:トークンの形式)
zendesk_type incident チケットタイプ(question/incident/problems/task)
instance_id {INVENTORY.TAG} ホストインベントリのタグ(EC2インスタンスID等)
region {INVENTORY.LOCATION} ホストインベントリのロケーション(AWSリージョン等)

instance_idregion はZabbixのホストインベントリから自動取得されます。ホストインベントリとは、Zabbixが各監視対象ホストに対して保持できる資産管理情報(デバイスタイプ、シリアル番号、ロケーション等)です。本構成では、ホストインベントリの Tag フィールドにEC2インスタンスID(例: i-0abc123def456)、Location フィールドにAWSリージョン(例: ap-northeast-1)をあらかじめ登録しておきます。これにより、アラート発生時にどのAWSリソースを調査すべきかをDevOps Agentに自動で伝達できます。

カスタムフィールド: Zendeskにカスタムフィールドがある場合、customfield_{フィールドID} の形式でパラメータを追加できます(text/number/date型のみ対応)。

  1. スクリプトの修正: デフォルトのスクリプトを以下のように修正し、チケット作成時にインベントリ情報をタグとして付与します。

修正ポイント(スクリプト内のタグ生成部分):

// Build tags string with inventory info
var tagsStr = [
    params.event_tags,
    'zabbix',
    'instance:' + params.instance_id,
    'region:' + params.region
].join(' ');

この修正により、Zendeskチケットのタグに instance:{インスタンスID}region:{リージョン} が自動付与されます。後続の convertZendeskDevOps Lambda がこのタグを解析し、DevOps Agentに調査対象リソースとして渡します。

このテンプレートは以下のイベントに対応しています:

Zabbixイベント 動作
Problem(障害発生) Zendeskチケットを新規作成
Problem recovery(障害復旧) 既存チケットを更新
Problem update(障害更新) 既存チケットを更新
Discovery(ディスカバリ) Zendeskチケットを新規作成
Autoregistration(自動登録) Zendeskチケットを新規作成
Internal problem(内部障害) Zendeskチケットを新規作成

注意: タグ・優先度・ステータスはチケット作成時のみ設定され、更新時には上書きされません。カスタムフィールドとsubjectフィールドは更新時にも反映されます。

1-3. アラート通知用ユーザーの作成

  1. AdministrationUsersCreate user
  2. ユーザー名を「Zendesk User」等に設定
  3. Groups で、監視対象ホストへの読み取り権限を持つグループを選択
  4. Media タブ → Add:
    • Type: Zendesk
    • Send to: 任意のテキスト(使用されないが必須)
    • Enabled: チェック
  5. Add で保存

1-4. アクションの設定

AlertsActionsTrigger actionsCreate action

項目
Name Zendesk Ticket Creation
Conditions Trigger severity >= Warning

Operations:

項目
Send to users Zendesk User(上記で作成したユーザー)
Send only to Zendesk

2. Zendeskの設定

2-1. Zendesk → DevOps Agent 連携用Webhookの作成

Zendeskでチケットが作成された際に、AWS DevOps Agentに調査を依頼するためのWebhookを設定します。

Admin Center → Apps and integrations → Webhooks → Create webhook

項目
Name AWS DevOps Agent
Endpoint URL 後述 3-1. convertZendeskDevOps の Lambda Function URL
Request method POST
Request format JSON

Webhook Body(JSON テンプレート):

{
  "id": "{{ticket.id}}",
  "subject": "{{ticket.title}}",
  "description": "{{ticket.description}}",
  "priority": "{{ticket.priority}}",
  "tags": "{{ticket.tags}}"
}
フィールド Zendeskプレースホルダー 用途
id {{ticket.id}} チケットID(DevOps Agentの referenceId に使用)
subject {{ticket.title}} チケット件名(DevOps Agentの調査タイトル)
description {{ticket.description}} チケット本文(調査の詳細情報)
priority {{ticket.priority}} 優先度(DevOps Agentの優先度にマッピング)
tags {{ticket.tags}} タグ(instance: region: を含む)

2-2. トリガーの作成

Admin Center → Objects and rules → Business rules → Triggers → Create trigger

項目
Name Send to DevOps Agent
Conditions (ALL) Ticket is Created, Tag contains zabbix
Actions Notify webhook → AWS DevOps Agent

3. Lambda関数の構築

共通: boto3 Lambda Layer

AWS DevOps Agent のAPIを利用するには、最新のboto3が必要です。Lambda組み込みのboto3にはdevops-agentサービスモデルが含まれていないため、Layerとして最新版を追加します。

# レイヤー作成
mkdir -p /tmp/boto3-layer/python
pip3 install boto3 -t /tmp/boto3-layer/python --quiet
cd /tmp/boto3-layer && zip -r /tmp/boto3-layer.zip python -q

aws lambda publish-layer-version \
  --layer-name boto3-latest \
  --zip-file fileb:///tmp/boto3-layer.zip \
  --compatible-runtimes python3.14 \
  --region us-east-1

ランタイムバージョンについて: 本記事では執筆時点での最新バージョン python3.14 を指定しています。環境に合わせて python3.12 等の利用可能なバージョンに適宜変更してください。

注意: Lambda組み込みのboto3が優先読み込みされる問題を回避するため、各Lambda関数の先頭で以下のコードが必要です。以降のLambda関数のコードにはすべてこの処理を含めていますが、仕組みは共通です。

import sys
sys.path.insert(0, '/opt/python')
for mod_name in list(sys.modules):
    if mod_name == 'boto3' or mod_name.startswith('boto3.') or \
       mod_name == 'botocore' or mod_name.startswith('botocore.'):
        del sys.modules[mod_name]
import boto3  # レイヤーの最新版が読み込まれる

3-1. convertZendeskDevOps(Zendesk → DevOps Agent Webhook転送)

ZendeskからのWebhookを受け取り、DevOps AgentのGeneric Webhookフォーマットに変換して転送するLambda関数です。

なぜLambda Function URLでラッピングするのか:

ZendeskのWebhookはHTTPリクエストを送信するだけのシンプルな仕組みであり、DevOps AgentのGeneric Webhookが要求するHMAC-SHA256署名ヘッダー(x-amzn-event-signature)を付与する機能がありません。また、Zendeskのチケットデータ構造とDevOps Agentが期待するペイロード構造(eventType, incidentId, affectedResources 等)も異なります。そのため、間にLambda Function URLを挟み、以下の変換を行います:

  1. Zendeskのチケットデータ → DevOps Agent Webhookペイロードへの構造変換
  2. HMAC-SHA256署名の生成・付与
  3. チケットタグからAWSリソース情報(インスタンスID、リージョン)の抽出

Lambda設定:

項目
関数名 convertZendeskDevOps
Runtime python3.14
Timeout 3秒
Memory 128MB
トリガー Function URL(認証なし)

セキュリティに関する注意: 本記事では検証目的で認証なし(AuthType: NONE)のFunction URLを使用しています。URLが漏洩すると第三者からリクエストを送信される可能性があるため、本番環境では認証付きFunction URL(AuthType: AWS_IAM)の利用や、Lambda関数内でZendesk側のWebhook署名を検証する仕組みの導入を推奨します。

環境変数:

変数名 説明
DEVOPS_AGENT_WEBHOOK_URL 後述の手順で取得したWebhook URL
DEVOPS_AGENT_WEBHOOK_SECRET 後述の手順で取得したHMACシークレット

DevOps Agent Generic Webhookの発行手順

convertZendeskDevOps がDevOps Agentに調査を依頼するには、Generic WebhookのURLとHMACシークレットが必要です。以下の手順で発行します。

  1. AWS マネジメントコンソールで AWS DevOps Agent コンソールを開く
  2. 対象の Agent Space を選択
  3. Capabilities タブ → Webhook セクション → Configure をクリック
  4. Generate webhook をクリック
  5. 生成された Webhook URLHMAC Secret を控える

重要: HMACシークレットは生成時にしか表示されません。必ずこの時点で安全な場所に保存してください。再表示はできません。

  1. 控えた値をLambdaの環境変数に設定:
    • DEVOPS_AGENT_WEBHOOK_URL → Webhook URL(例: https://event-ai.us-east-1.api.aws/webhook/generic/{webhook_id}
    • DEVOPS_AGENT_WEBHOOK_SECRET → HMAC Secret

参考: Invoking DevOps Agent through Webhook(AWS公式ドキュメント)

コード:

import json
import hmac
import hashlib
import base64
import os
from datetime import datetime, timezone
from urllib.request import Request, urlopen

WEBHOOK_URL = os.environ['DEVOPS_AGENT_WEBHOOK_URL']
WEBHOOK_SECRET = os.environ['DEVOPS_AGENT_WEBHOOK_SECRET']

PRIORITY_MAP = {'urgent': 'CRITICAL', 'high': 'HIGH', 'normal': 'MEDIUM', 'low': 'LOW'}


def lambda_handler(event, context):
    if 'body' in event:
        ticket = json.loads(event['body'])
    else:
        ticket = event

    tags = ticket.get('tags', '').split()
    instance_id = ''
    region = 'ap-northeast-1'
    for tag in tags:
        if tag.startswith('instance:'):
            instance_id = tag.split(':', 1)[1]
        elif tag.startswith('region:'):
            region = tag.split(':', 1)[1]

    timestamp = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.000Z')
    payload = json.dumps({
        'eventType': 'incident',
        'incidentId': f"zendesk-{ticket['id']}",
        'action': 'created',
        'priority': PRIORITY_MAP.get(ticket.get('priority', ''), 'MEDIUM'),
        'title': ticket.get('subject', ''),
        'description': ticket.get('description', ''),
        'timestamp': timestamp,
        'service': 'ZabbixMonitoring',
        'data': {
            'metadata': {
                'region': region,
                'environment': 'production',
                'source': 'zendesk',
                'zendesk_ticket_id': str(ticket['id'])
            }
        },
        'affectedResources': [instance_id]
    })

    sig = base64.b64encode(
        hmac.new(
            WEBHOOK_SECRET.encode(),
            f"{timestamp}:{payload}".encode(),
            hashlib.sha256
        ).digest()
    ).decode()

    req = Request(WEBHOOK_URL, data=payload.encode(), method='POST', headers={
        'Content-Type': 'application/json',
        'x-amzn-event-timestamp': timestamp,
        'x-amzn-event-signature': sig
    })
    resp = urlopen(req)

    return {'statusCode': resp.status, 'body': resp.read().decode()}

ポイント: - ZendeskチケットのタグからEC2インスタンスIDやリージョンを抽出し、DevOps Agentに渡す - incidentIdzendesk-{ticket_id} を設定することで、後続のRCA結果をチケットに紐付け可能にする - HMAC-SHA256署名でWebhookの認証を行う

Zendesk → DevOps Agent Webhookのフィールドマッピング:

DevOps Agent Webhookフィールド 値の取得元 説明
eventType 固定値 incident イベント種別
incidentId zendesk-{ticket.id} Zendeskチケットとの紐付けID
priority ticket.priority urgent→CRITICAL, high→HIGH, normal→MEDIUM, low→LOW
title ticket.subject チケット件名
description ticket.description チケット本文
affectedResources タグ instance:{id} 調査対象のAWSリソース
data.metadata.region タグ region:{region} 調査対象のAWSリージョン

3-2. createZendeskTicket(SecurityHub/CloudWatch → Zendesk + DevOps Agent)

SecurityHub FindingsやCloudWatch Alarmから直接Zendeskチケットを作成し、同時にDevOps AgentにRCA調査を依頼するLambda関数です。

Lambda設定:

項目
関数名 createZendeskTicket
Runtime python3.14
Timeout 30秒
Memory 128MB
Layer boto3-latest:1

環境変数:

変数名 説明
ZENDESK_URL ZendeskのURL
ZENDESK_USER {email}/token
ZENDESK_TOKEN Zendesk APIトークン
AGENT_SPACE_ID DevOps Agent Space ID
ASSOCIATION_ID Event Channel Association ID

AGENT_SPACE_ID と ASSOCIATION_ID の確認方法:

AWS CLI で以下のコマンドを実行し、それぞれのIDを取得します。

# Agent Space ID の確認
aws devops-agent list-agent-spaces --region us-east-1

# Association ID の確認(Event Channel の associationId を使用)
aws devops-agent list-associations \
  --agent-space-id {AgentSpaceId} --region us-east-1

list-associations の結果から、"configuration": {"eventChannel": {}} を持つエントリの associationId を使用します。

IAMポリシー(インライン):

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["aidevops:CreateBacklogTask"],
    "Resource": "arn:aws:aidevops:us-east-1:{AccountId}:agentspace/{AgentSpaceId}"
  }]
}

上記に加え、AWS管理ポリシー AWSLambdaBasicExecutionRole(CloudWatch Logsへのログ出力権限)のアタッチも必要です。以降のLambda関数も同様です。

コード:

import sys
import os

sys.path.insert(0, '/opt/python')
for mod_name in list(sys.modules):
    if mod_name == 'boto3' or mod_name.startswith('boto3.') or \
       mod_name == 'botocore' or mod_name.startswith('botocore.'):
        del sys.modules[mod_name]

import json
import base64
import uuid
import boto3
from urllib.request import Request, urlopen

ZENDESK_URL = os.environ['ZENDESK_URL']
ZENDESK_USER = os.environ['ZENDESK_USER']
ZENDESK_TOKEN = os.environ['ZENDESK_TOKEN']
AGENT_SPACE_ID = os.environ['AGENT_SPACE_ID']
ASSOCIATION_ID = os.environ['ASSOCIATION_ID']

devops_client = boto3.client('devops-agent')

SEVERITY_MAP = {
    'CRITICAL': 'urgent', 'HIGH': 'high',
    'MEDIUM': 'normal', 'LOW': 'low', 'INFORMATIONAL': 'low'
}
PRIORITY_MAP = {
    'CRITICAL': 'CRITICAL', 'HIGH': 'HIGH',
    'MEDIUM': 'MEDIUM', 'LOW': 'LOW', 'INFORMATIONAL': 'MINIMAL'
}


def create_zendesk_ticket(subject, body, priority='normal'):
    auth = base64.b64encode(f'{ZENDESK_USER}:{ZENDESK_TOKEN}'.encode()).decode()
    payload = json.dumps({
        'ticket': {
            'subject': subject,
            'comment': {'body': body, 'public': False},
            'priority': priority
        }
    })
    req = Request(
        f'{ZENDESK_URL}/api/v2/tickets.json',
        data=payload.encode(), method='POST',
        headers={'Content-Type': 'application/json', 'Authorization': f'Basic {auth}'}
    )
    resp = urlopen(req)
    result = json.loads(resp.read().decode())
    return result['ticket']['id']


def create_devops_task(title, description, priority, ticket_id):
    resp = devops_client.create_backlog_task(
        agentSpaceId=AGENT_SPACE_ID,
        taskType='INVESTIGATION',
        title=title[:400],
        description=description[:10000],
        priority=priority,
        reference={
            'system': 'Event Channel',
            'title': f'EventChannel-zendesk-{ticket_id}',
            'referenceId': f'zendesk-{ticket_id}',
            'referenceUrl': f'{ZENDESK_URL}/agent/tickets/{ticket_id}',
            'associationId': ASSOCIATION_ID
        },
        clientToken=str(uuid.uuid4())
    )
    return resp['task']['taskId']


def lambda_handler(event, context):
    source = event.get('source', '')
    detail_type = event.get('detail-type', '')

    if source == 'aws.securityhub':
        f = event['detail']['findings'][0]
        severity = f.get('Severity', {}).get('Label', 'MEDIUM')
        subject = f'[SecurityHub][{severity}] {f.get("Title", "")}'
        body = f'Severity: {severity}\nAccount: {f.get("AwsAccountId", "")}\n' \
               f'Region: {f.get("Region", "")}\n{f.get("Description", "")}'
        ticket_id = create_zendesk_ticket(subject, body, SEVERITY_MAP.get(severity, 'normal'))
        create_devops_task(subject, body, PRIORITY_MAP.get(severity, 'MEDIUM'), ticket_id)

    elif source == 'aws.cloudwatch':
        detail = event['detail']
        alarm_name = detail.get('alarmName', '')
        reason = detail.get('state', {}).get('reason', '')
        subject = f'[CloudWatch Alarm] {alarm_name}'
        body = f'Alarm: {alarm_name}\nReason: {reason}'
        ticket_id = create_zendesk_ticket(subject, body, 'high')
        create_devops_task(subject, body, 'HIGH', ticket_id)

    return {'statusCode': 200}

3-3. sendBackToZendesk(DevOps Agent RCA結果 → Zendesk)

DevOps AgentのRCA完了後、結果をZendeskチケットにコメントとして投稿するLambda関数です。

Lambda設定:

項目
関数名 sendBackToZendesk
Runtime python3.14
Timeout 30秒
Memory 128MB
Layer boto3-latest:1

環境変数:

変数名 説明
ZENDESK_URL ZendeskのURL
ZENDESK_USER {email}/token
ZENDESK_TOKEN Zendesk APIトークン

IAMポリシー(インライン):

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["aidevops:GetBacklogTask", "aidevops:ListJournalRecords"],
    "Resource": "arn:aws:aidevops:us-east-1:{AccountId}:agentspace/{AgentSpaceId}"
  }]
}

リソースは Agent Space レベルで指定しています。DevOps Agent APIではタスクIDやレコードID単位でのリソース指定ができないため、Agent Space ARN が最小のリソーススコープとなります。

コード:

import sys
import os

sys.path.insert(0, '/opt/python')
for mod_name in list(sys.modules):
    if mod_name == 'boto3' or mod_name.startswith('boto3.') or \
       mod_name == 'botocore' or mod_name.startswith('botocore.'):
        del sys.modules[mod_name]

import json
import base64
import boto3
from urllib.request import Request, urlopen

ZENDESK_URL = os.environ['ZENDESK_URL']
ZENDESK_USER = os.environ['ZENDESK_USER']
ZENDESK_TOKEN = os.environ['ZENDESK_TOKEN']

devops_client = boto3.client('devops-agent')


def get_rca_summary(agent_space_id, execution_id):
    try:
        records = devops_client.list_journal_records(
            agentSpaceId=agent_space_id,
            executionId=execution_id,
            recordType='investigation_summary_md'
        ).get('records', [])
        if records:
            content = records[0].get('content', '')
            return content if isinstance(content, str) else json.dumps(content)
    except Exception as e:
        print(f'Failed to get journal records: {e}')
    return ''


def post_to_zendesk(ticket_id, comment):
    auth = base64.b64encode(f'{ZENDESK_USER}:{ZENDESK_TOKEN}'.encode()).decode()
    payload = json.dumps({'ticket': {'comment': {'body': comment, 'public': False}}})
    req = Request(
        f'{ZENDESK_URL}/api/v2/tickets/{ticket_id}.json',
        data=payload.encode(), method='PUT',
        headers={'Content-Type': 'application/json', 'Authorization': f'Basic {auth}'}
    )
    return urlopen(req).status


def lambda_handler(event, context):
    detail = event.get('detail', {})
    detail_type = event.get('detail-type', '')
    metadata = detail.get('metadata', {})
    data = detail.get('data', {})
    agent_space_id = metadata.get('agent_space_id')
    task_id = metadata.get('task_id')
    execution_id = metadata.get('execution_id')

    # タスク情報からZendeskチケットIDを取得
    task = devops_client.get_backlog_task(
        agentSpaceId=agent_space_id, taskId=task_id
    ).get('task', {})

    reference_id = task.get('reference', {}).get('referenceId', '')
    if not reference_id.startswith('zendesk-'):
        return {'statusCode': 200, 'body': 'Not a Zendesk task'}

    ticket_id = reference_id.replace('zendesk-', '')
    rca_execution_id = execution_id

    # Investigation Linked の場合、親タスクのRCAを取得
    if detail_type == 'Investigation Linked':
        primary_task_id = task.get('primaryTaskId')
        if primary_task_id:
            primary_task = devops_client.get_backlog_task(
                agentSpaceId=agent_space_id, taskId=primary_task_id
            ).get('task', {})
            rca_execution_id = primary_task.get('executionId')

    # RCAサマリーを取得
    rca_body = get_rca_summary(agent_space_id, rca_execution_id) if rca_execution_id else ''

    # Zendeskにコメント投稿
    if rca_body:
        status_reason = task.get('statusReason', '')
        comment = f'=== AWS DevOps Agent RCA Report ===\nTask: {task_id}\n'
        if detail_type == 'Investigation Linked' and status_reason:
            comment += f'Note: {status_reason}\n'
        comment += f'Priority: {data.get("priority", "N/A")}\n\n{rca_body}'
    else:
        comment = f'=== AWS DevOps Agent Report ===\nTask: {task_id}\n' \
                  f'Status: {data.get("status", "N/A")}\n' \
                  'RCA summary not yet available.'

    post_to_zendesk(ticket_id, comment)
    return {'statusCode': 200}

ポイント: - Investigation Completed イベントでは自身の executionId からRCA本文を取得 - Investigation Linked イベントでは primaryTaskId で親タスクを辿り、親のRCA結果を取得 - RCA本文は list_journal_records API の investigation_summary_md レコードタイプで取得

補足: 本記事の get_rca_summary 関数では list_journal_records のページネーション処理を省略しています。レコード数が多い場合は NextToken を使ったループ処理が必要になる場合があります。

3つのLambda関数の連携について: createZendeskTicket がZendeskチケット作成とDevOps Agentへの調査依頼を同時に行い、convertZendeskDevOps はZabbix経由のZendeskチケットに対して同様の調査依頼を行います。どちらの経路でも、DevOps Agentが調査を完了すると EventBridge に Investigation Completed / Investigation Linked イベントが発行され、sendBackToZendesk がRCA結果を元のZendeskチケットに書き戻します。この紐付けは、すべてのLambdaが共通して zendesk-{ticket_id}referenceId として使用することで実現しています。

4. EventBridgeルールの設定

本構成で使用するEventBridgeイベントは以下の通りです。

ソース detail-type 説明 トリガー先
aws.securityhub Security Hub Findings - Imported SecurityHub Findingsの検出 createZendeskTicket
aws.cloudwatch CloudWatch Alarm State Change CloudWatchアラームの状態変更 createZendeskTicket
aws.aidevops Investigation Completed DevOps Agent RCA完了 sendBackToZendesk
aws.aidevops Investigation Linked 既存調査へのリンク sendBackToZendesk

4-1. sentBackToZendesk(DevOps Agent → sendBackToZendesk)

命名について: EventBridgeルール名は sentBackToZendesk(過去形: 送り返された)、Lambda関数名は sendBackToZendesk(現在形: 送り返す)としています。ルール名はイベントの発生(=調査が完了した)を表し、Lambda関数名は実行するアクション(=Zendeskに送る)を表しています。

aws events put-rule \
  --name sentBackToZendesk \
  --event-pattern '{
    "source": ["aws.aidevops"],
    "detail-type": ["Investigation Completed", "Investigation Linked"]
  }' \
  --region us-east-1

aws events put-targets \
  --rule sentBackToZendesk \
  --targets '[{"Id":"sendBackToZendesk","Arn":"arn:aws:lambda:us-east-1:{AccountId}:function:sendBackToZendesk"}]' \
  --region us-east-1

4-2. securityHubToZendesk(SecurityHub → createZendeskTicket)

aws events put-rule \
  --name securityHubToZendesk \
  --event-pattern '{
    "source": ["aws.securityhub"],
    "detail-type": ["Security Hub Findings - Imported"],
    "detail": {
      "findings": {
        "Severity": {"Label": ["CRITICAL", "HIGH"]},
        "Workflow": {"Status": ["NEW"]}
      }
    }
  }' \
  --region us-east-1

aws events put-targets \
  --rule securityHubToZendesk \
  --targets '[{"Id":"createZendeskTicket","Arn":"arn:aws:lambda:us-east-1:{AccountId}:function:createZendeskTicket"}]' \
  --region us-east-1

4-3. cloudwatchAlarmToZendesk(CloudWatch Alarm → createZendeskTicket)

aws events put-rule \
  --name cloudwatchAlarmToZendesk \
  --event-pattern '{
    "source": ["aws.cloudwatch"],
    "detail-type": ["CloudWatch Alarm State Change"],
    "detail": {"state": {"value": ["ALARM"]}}
  }' \
  --region us-east-1

aws events put-targets \
  --rule cloudwatchAlarmToZendesk \
  --targets '[{"Id":"createZendeskTicket","Arn":"arn:aws:lambda:us-east-1:{AccountId}:function:createZendeskTicket"}]' \
  --region us-east-1

4-4. Lambdaリソースベースポリシーの追加

EventBridgeからLambdaを呼び出すには、リソースベースポリシーが必要です。これがないとEventBridgeの FailedInvocations になります。

# sendBackToZendesk
aws lambda add-permission \
  --function-name sendBackToZendesk \
  --statement-id EventBridgeInvoke \
  --action lambda:InvokeFunction \
  --principal events.amazonaws.com \
  --source-arn arn:aws:events:us-east-1:{AccountId}:rule/sentBackToZendesk \
  --region us-east-1

# createZendeskTicket(SecurityHub用)
aws lambda add-permission \
  --function-name createZendeskTicket \
  --statement-id SecurityHubEventBridge \
  --action lambda:InvokeFunction \
  --principal events.amazonaws.com \
  --source-arn arn:aws:events:us-east-1:{AccountId}:rule/securityHubToZendesk \
  --region us-east-1

# createZendeskTicket(CloudWatch Alarm用)
aws lambda add-permission \
  --function-name createZendeskTicket \
  --statement-id CloudWatchAlarmEventBridge \
  --action lambda:InvokeFunction \
  --principal events.amazonaws.com \
  --source-arn arn:aws:events:us-east-1:{AccountId}:rule/cloudwatchAlarmToZendesk \
  --region us-east-1

注意: EventBridgeターゲットにLambdaを指定する場合、RoleArn は不要です。Lambda呼び出しはリソースベースポリシーで認可されます。RoleArn を設定するとAssumeRole失敗で FailedInvocations になる場合があります。

5. ハマりどころと対策

5-1. Lambda組み込みboto3にdevops-agentサービスモデルがない

Lambda Runtime の組み込みboto3は古いバージョンのため、devops-agent サービスが含まれていません。Lambda Layerで最新のboto3を追加し、sys.path.insert(0, '/opt/python') で優先読み込みする必要があります。

5-2. EventBridgeからLambdaが呼び出せない(FailedInvocations)

EventBridgeルールを作成しても、Lambda側にリソースベースポリシーがないと呼び出しが失敗します。CloudWatch Logsにもログが残らないため、原因特定が困難です。aws lambda get-policy でポリシーの有無を確認してください。

5-3. DevOps Agentの「Investigation Linked」ではRCAサマリーが生成されない

DevOps Agentは、同一ホスト・同一原因と判断したアラートを既存の調査にリンクする機能を持っています。この場合、リンクされた側のタスクは Investigation Linked ステータスとなり、独自のRCA調査は実行されません。RCAサマリーは親タスク(リンク先)の executionId にのみ生成されます。

そのため、sendBackToZendesk では Investigation Linked イベントを受けた際に、タスクの primaryTaskId フィールドで親タスクを辿り、親タスクの executionId からRCAサマリーを取得する実装が必要です。

6. 動作確認

実際にZabbixで監視しているEC2インスタンス上でCPU負荷を発生させ、アラートからRCA結果のチケット反映までの一連の流れを確認します。

6-1. CPU負荷の発生

Zabbixエージェントが稼働しているEC2インスタンスにSSHで接続し、以下のコマンドでCPUを意図的に高騰させます。

yes > /dev/null & yes > /dev/null & yes > /dev/null & yes > /dev/null &

6-2. Zabbixアラートの発火 → Zendeskチケット作成

ZabbixがCPU使用率の閾値超過を検知し、トリガーが発火します。設定済みのZendesk Webhookメディアタイプにより、Zendeskにインシデントチケットが自動作成されます。

チケットにはZabbixのアラート情報に加え、タグとして zabbixinstance:{インスタンスID}region:{リージョン} が付与されます。

6-3. DevOps Agentによる調査の開始

Zendeskのトリガーが zabbix タグを検知し、convertZendeskDevOps Lambda経由でDevOps AgentのGeneric Webhookにイベントが送信されます。DevOps Agentが自動的にInvestigationを開始し、CloudWatchメトリクス、CloudTrailログ、EC2の状態などを横断的に分析します。

6-4. RCA結果の特定とチケットへの反映

DevOps AgentがRCAを完了すると、EventBridgeに Investigation Completed イベントが発行されます。sendBackToZendesk Lambdaがこのイベントをキャッチし、list_journal_records APIでRCAサマリーを取得、Zendeskチケットにプライベートコメントとして投稿します。

RCAレポートには以下のような内容が含まれます:

  • Symptoms(症状): CPU使用率の急上昇とアラートの発火タイミング
  • Findings(調査結果): 原因となったプロセスの特定、CloudTrailでの操作履歴、インスタンスの状態変化

日本語訳も掲載しておきます。

運用チームはZendeskチケットを開くだけで、AIが分析した根本原因と経緯を即座に確認できます。

6-5. CloudWatch アラームからの連携

前回記事のLambdaを利用して、AWS側で発生したCloudWatch アラームを起点とする調査も検証します。 blog.serverworks.co.jp

下記のコマンドでLambdaを実行すると、Lambda内で意図的にエラーを発生させ、CloudWatch アラームが発火します。 アラームのアクションとして、createZendeskTicketが実行されます。

# シナリオを指定して実行
aws lambda invoke \
  --function-name AWS-DevOpsAgent-test-lambda \
  --cli-binary-format raw-in-base64-out \
  --payload '{"scenario":"db-timeout"}' \
  response.json \
  --region us-east-1

こちらもチケット起票と、RCA完了後の結果の追記がされました。

まとめ

本記事では、以下の自動化パイプラインを構築しました。

経路 フロー
Zabbix経由 Zabbix → Zendesk(チケット作成)→ convertZendeskDevOps → DevOps Agent(RCA)→ sendBackToZendesk → Zendesk(RCA結果投稿)
SecurityHub経由 SecurityHub → EventBridge → createZendeskTicket → Zendesk + DevOps Agent → sendBackToZendesk → Zendesk
CloudWatch経由 CloudWatch Alarm → EventBridge → createZendeskTicket → Zendesk + DevOps Agent → sendBackToZendesk → Zendesk

これにより、アラート発生からRCA結果のチケット反映まで、人手を介さずに自動で完了します。運用チームはZendeskチケットを確認するだけで、AIによる根本原因分析の結果を即座に把握できます。

参考リンク