カスタムメールアラートとエスカレーション:高度なインシデントルーティング
適切なタイミングで適切な担当者を呼び出すエスカレーションワークフローを設計しましょう。アラートルーティング、オンコール連携、エスカレーションポリシーのガイドです。 — Nova Uptimeはアップタイム、SSL、メール健全性、リンク変更を1つのダッシュボードで監視します。
エスカレーションの問題
午前3時。ウェブサイトがダウンします。誰かがすぐに対応する必要があります。
エスカレーションがない場合:
- アラートがオンコールのSlackチャンネルに通知される
- 誰も気づかない(全員が就寝中、Slack通知はオフ)
- 45分後、顧客からの苦情で誰かが目を覚ます
- MTTR: 45分
スマートなエスカレーションがある場合:
- アラートがSlackに通知される(低優先度)
- 2分以内に確認がない場合 → オンコールのプライマリ担当者にSMS送信
- 5分以内に応答がない場合 → オンコールのバックアップ担当者に通話
- MTTR: 5分
このガイドでは、インテリジェントなエスカレーションワークフローの構築方法を紹介します。
インシデントの重大度を理解する
まず、インシデントを重大度別に分類します。
Tier 1: 重大(即時呼び出し)#
- 本番ウェブサイトのダウン(売上に影響)
- 決済ゲートウェイの障害
- APIが5xxエラーを返す
- データベースのレプリケーション停止
アクション:
- Slack #critical-incidents(常時監視)
- プライマリオンコール担当者へのSMS
- SMSへの応答がない場合は電話
- Jiraチケットの自動作成
- ステータスページの更新
Tier 2: 警告(営業時間内に呼び出し)#
- レスポンス時間の劣化
- 重大ではないサービスエラー
- メール配信ヘルスの問題
- データベースクエリの遅延
アクション:
- Slack #alerts(営業時間中に確認)
- Jiraチケット作成
- 営業終了時のメールダイジェスト
Tier 3: 情報(ログのみ)#
- 30日以内のドメイン有効期限
- 90日以内のSSL有効期限
- 週次トレンドレポート
- 重大ではないメトリクスの閾値
アクション:
- 週次メールダイジェスト
- ダッシュボード通知(呼び出しなし)
エスカレーションポリシーの構築
ステップ1: オンコールローテーションの定義#
誰がいつオンコールかを示すローテーションを作成します。
月曜~金曜 9 AM - 5 PM: Alice (プライマリ), Bob (バックアップ)
月曜~金曜 5 PM - 9 AM: Charlie (プライマリ), Diana (バックアップ)
土曜~日曜 24時間: Eve (プライマリ), Frank (バックアップ)
休日: George (終日オンコール)
ステップ2: エスカレーション時間の定義#
T+0: アラートがSlackに通知
T+2分: 確認なし → プライマリにSMS
T+5分: 応答なし → プライマリに通話
T+10分: 応答なし → バックアップにSMS
T+15分: それでも応答なし → チーム全員を呼び出し
ステップ3: エスカレーションロジックの実装#
Nova Uptimeで(対応している場合):
- ドメイン設定 → アラート
- 重大度を設定: 重大
- エスカレーションを構成:
- ステップ1: Slack #critical-incidents
- ステップ2 (2分): オンコールにSMS
- ステップ3 (5分): 電話
- ステップ4 (10分): チーム全員
Webhook + カスタムシステム経由:
async function handleCriticalIncident({ domain, detectedAt }) {
const oncall = await getOnCallEngineer(new Date());
// Step 1: Immediate Slack alert
const slackMessage = await postToSlack({
channel: '#critical-incidents',
text: `🚨 CRITICAL: ${domain} is down`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `@${oncall.slackHandle}: ${domain} is down. Acknowledge with reaction.`
}
}
]
});
// Step 2: Wait 2 minutes for acknowledgment
const acknowledged = await waitForAcknowledgment(slackMessage, 2 * 60 * 1000);
if (!acknowledged) {
// Step 2: Send SMS
await sendSMS({
to: oncall.phone,
message: `CRITICAL: ${domain} down. Reply OK to acknowledge.`
});
}
// Step 3: Wait 5 minutes total
const smsAcknowledged = await waitForSMS(oncall.phone, 5 * 60 * 1000);
if (!smsAcknowledged) {
// Step 3: Phone call
await makePhoneCall({
to: oncall.phone,
message: `Critical incident. Website ${domain} is down. Press 1 to acknowledge.`
});
}
// ... Continue escalation chain
}
応用編: スマートなアラートルーティング
パターン1: 時間帯によるルーティング#
時間帯ごとに異なる担当者がオンコールにつきます。
async function getOnCallEngineer(timestamp) {
const hour = new Date(timestamp).getHours();
const dayOfWeek = new Date(timestamp).getDay();
// Business hours (9 AM - 5 PM weekdays)
if (dayOfWeek >= 1 && dayOfWeek <= 5 && hour >= 9 && hour < 17) {
return {
name: 'Alice',
slackHandle: 'alice',
phone: '+1-555-0100',
email: 'alice@company.com'
};
}
// After hours (weekdays)
if (dayOfWeek >= 1 && dayOfWeek <= 5 && (hour < 9 || hour >= 17)) {
return {
name: 'Charlie',
slackHandle: 'charlie',
phone: '+1-555-0102',
email: 'charlie@company.com'
};
}
// Weekend
if (dayOfWeek === 0 || dayOfWeek === 6) {
return {
name: 'Eve',
slackHandle: 'eve',
phone: '+1-555-0104',
email: 'eve@company.com'
};
}
}
パターン2: ドメインによるルーティング#
異なるチームが異なるドメインを所有します。
async function getTeamForDomain(domain) {
// Engineering team owns api.*, backend.*
if (domain.startsWith('api.') || domain.startsWith('backend.')) {
return 'engineering';
}
// Infrastructure team owns server, monitoring, infra
if (domain.includes('server') || domain.includes('monitor')) {
return 'infrastructure';
}
// Support team owns customer-facing domains
if (domain.startsWith('support.') || domain.startsWith('customer.')) {
return 'support';
}
// Default: DevOps
return 'devops';
}
async function handleIncident({ domain, severity }) {
const team = await getTeamForDomain(domain);
const oncall = await getOnCallEngineer(team, new Date());
if (severity === 'critical') {
await escalate(oncall);
}
}
パターン3: インシデント種別によるルーティング#
障害の種類によって異なる専門知識が必要です。
async function getExpertForIncident(domain, incidentType) {
if (incidentType === 'database_down') {
// Page database expert
return await getOnCallExpert('database');
} else if (incidentType === 'api_errors') {
// Page API lead
return await getOnCallExpert('backend');
} else if (incidentType === 'email_delivery_failing') {
// Page email ops
return await getOnCallExpert('email');
} else if (incidentType === 'ssl_expired') {
// Page security
return await getOnCallExpert('security');
}
// Default: on-call engineer
return await getOnCallEngineer(new Date());
}
パターン4: 条件付きエスカレーション#
インシデントの特性に基づいて異なるエスカレーション経路を取ります。
async function escalateIncident({ domain, severity, duration }) {
const oncall = await getOnCallEngineer(new Date());
if (severity === 'critical' && duration > 5 * 60 * 1000) {
// Critical for >5 minutes: Aggressive escalation
await Promise.all([
postSlack({ channel: '#critical-incidents', text: 'CRITICAL ESCALATION' }),
sendSMS(oncall.phone),
makePhoneCall(oncall.phone),
pageBackup(oncall.backup)
]);
} else if (severity === 'critical') {
// Critical but recent: Gentle escalation
await Promise.all([
postSlack({ channel: '#critical-incidents' }),
sendSMS(oncall.phone)
]);
} else if (severity === 'warning') {
// Just log
await postSlack({ channel: '#alerts' });
}
}
PagerDutyとの連携#
高度なオンコール管理には、PagerDutyと連携します。
const PagerDutyClient = require('pagerduty');
async function pageOnCallVia PagerDuty(domain, severity) {
const client = new PagerDutyClient({
token: process.env.PAGERDUTY_TOKEN
});
// Create incident
const incident = await client.incidents.create({
type: 'incident_reference',
incident: {
type: 'incident',
title: `${domain} is down`,
body: {
type: 'incident_body',
description: `Website ${domain} is down. Response: Down. Severity: ${severity}`
},
urgency: severity === 'critical' ? 'high' : 'low',
service: {
id: process.env.PAGERDUTY_SERVICE_ID,
type: 'service_reference'
}
}
});
console.log(`Created PagerDuty incident: ${incident.id}`);
}
確認と引き継ぎ
確認パターン
オンコールエンジニアはインシデントを確認しなければなりません。
// Via Slack reaction
async function waitForSlackAcknowledgment(messageId, maxWait) {
const startTime = Date.now();
while (Date.now() - startTime < maxWait) {
// Poll for reactions
const reactions = await getSlackMessageReactions(messageId);
if (reactions.includes('white_check_mark')) {
return true; // Acknowledged
}
await sleep(10 * 1000); // Check every 10 seconds
}
return false; // Not acknowledged within max wait
}
// Via SMS
async function waitForSmsAcknowledgment(phone, maxWait) {
const startTime = Date.now();
while (Date.now() - startTime < maxWait) {
// Poll SMS responses
const responses = await getSmsResponses(phone);
if (responses.some(m => m.text.toUpperCase().includes('OK'))) {
return true; // Acknowledged
}
await sleep(5 * 1000); // Check every 5 seconds
}
return false; // Not acknowledged
}
チーム間の引き継ぎ
あるオンコールエンジニアが別の担当者に引き継ぐ必要がある場合:
async function handoffIncident(incident, fromEngineer, toEngineer) {
// Update incident
incident.assignedTo = toEngineer;
incident.handoffAt = new Date();
await incident.save();
// Notify both
await sendMessage({
to: fromEngineer.slack,
text: `Handing off ${incident.domain} to ${toEngineer.name}`
});
await sendMessage({
to: toEngineer.slack,
text: `Taking over incident: ${incident.domain}. See: ${incident.dashboard}`
});
// Update status page
await updateStatusPage({
message: `Working with ${toEngineer.name}'s team on investigation`
});
}
エスカレーションの効果測定
エスカレーションメトリクスを追跡します。
async function analyzeEscalationMetrics() {
const incidents = await Incident.find({
createdAfter: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) // Last 30 days
});
const metrics = {
totalIncidents: incidents.length,
avgTimeToAcknowledgment: calculateAvg(
incidents.map(i => i.acknowledgedAt - i.alertedAt)
),
avgTimeToEscalation: calculateAvg(
incidents.map(i => i.escalatedAt - i.alertedAt)
),
escalationRate: incidents.filter(i => i.escalatedAt).length / incidents.length,
avgMTTR: calculateAvg(
incidents.map(i => i.resolvedAt - i.alertedAt)
)
};
console.log('Escalation Metrics (Last 30 days):');
console.log(`Total Incidents: ${metrics.totalIncidents}`);
console.log(`Avg Time to Acknowledgment: ${metrics.avgTimeToAcknowledgment / 60}m`);
console.log(`Escalation Rate: ${(metrics.escalationRate * 100).toFixed(1)}%`);
console.log(`Avg MTTR: ${metrics.avgMTTR / 60}m`);
}
エスカレーションでよくある間違い
間違い1: アラート疲れによる無視#
問題: アラートが多すぎる → チームが対応をやめる → 本当の問題を見逃す
解決策: 厳格な閾値を使用しましょう。本当に重大な問題のみ呼び出します。
間違い2: 過度に積極的なエスカレーション#
問題: すべてのインシデントでチーム全員を呼び出す → 燃え尽き → 退職者が出る
解決策: 段階的にエスカレーションしましょう。バックアップを呼び出す前に、プライマリに5分の猶予を与えます。
間違い3: エスカレーションが遅すぎる#
問題: 重大インシデントが30分間気づかれない → 大きな影響
解決策: 重大インシデントの場合、2~5分以内にエスカレーションしましょう。
間違い4: 引き継ぎプロセスがない#
問題: プライマリオンコール担当者が引き継ぎを知らなかった → 混乱
解決策: Slackやメールで明示的に引き継ぎを伝えましょう。
まとめ: エスカレーション設定チェックリスト
- ✅ インシデントの重大度階層を定義(重大/警告/情報)
- ✅ オンコールローテーションスケジュールを作成
- ✅ エスカレーションのタイミングを定義(2分 → 5分 → 10分 → 全員)
- ✅ エスカレーションチャンネルを設定(Slack → SMS → 電話 → チーム全員)
- ✅ 時間帯によるルーティングを構成
- ✅ ドメイン/チーム所有者によるルーティング
- ✅ インシデント種別によるルーティング(データベース vs API vs メール)
- ✅ PagerDutyと連携(該当する場合)
- ✅ 確認メカニズムを設定(Slackのリアクション、SMS応答)
- ✅ 引き継ぎ手順を定義
- ✅ エスカレーションメトリクスを追跡
- ✅ 月次でエスカレーションの効果を見直し
今すぐ始めましょう
シンプルに始めましょう。最初はSlackとSMSだけで十分です。インシデントの量が増えるにつれて、複雑さ(PagerDutyや条件付きルーティング)を追加していきます。
エスカレーションポリシーをチームのwikiに文書化しましょう。すべてのチームメンバーと共有します。四半期ごとにテストを行い、すべてが正しく動作することを確認します。
エスカレーションポリシーは、「インシデントが5分で解決した」と「障害が3時間続いた」の違いを生みます。これを正しく構築することに投資しましょう。
Monitor Your Website Before It Goes Down
Get uptime monitoring, SSL tracking, domain expiry alerts, and email health checks. Free plan — no credit card required.
Start Monitoring Free関連記事
ケーススタディ:アップタイムモニタリングが$50万の損失を防いだ方法
プロアクティブなアップタイムモニタリングが事業への壊滅的な影響を防いだ実例。あるSaaS企業のインシデント対応ストーリーから学びましょう。 — Nova Uptimeはアップタイム、SSL、メール健全性、リンク変更を1つのダッシュボードで監視します。
障害サービスのスクリーンショット証拠:アップタイム問題のデバッグ
障害時の自動スクリーンショットがウェブサイトのダウン原因の特定にどう役立つか。視覚的なデバッグとインシデント分析。 — Nova Uptimeはアップタイム、SSL、メール健全性、リンク変更を1つのダッシュボードで監視します。 無料トライアル、登録不要、クレジットカード不要で今すぐ試せます。
アラート疲労を実践的に減らす方法:本当の問題を見逃さないために
アラート疲労はチームの67%に影響します。誤検知を減らし、適切なしきい値を設定し、本当のインシデントに対応するための8つの戦略を学びましょう。 — Nova Uptimeはアップタイム、SSL、メール健全性、リンク変更を1つのダッシュボードで監視します。