171 lines
7.8 KiB
JSON
171 lines
7.8 KiB
JSON
{
|
||
"name": "SLO 服務等級監控",
|
||
"nodes": [
|
||
{
|
||
"parameters": {
|
||
"rule": {
|
||
"interval": [
|
||
{
|
||
"field": "cronExpression",
|
||
"expression": "0 * * * *"
|
||
}
|
||
]
|
||
}
|
||
},
|
||
"id": "schedule",
|
||
"name": "每小時執行",
|
||
"type": "n8n-nodes-base.scheduleTrigger",
|
||
"typeVersion": 1.2,
|
||
"position": [240, 300]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"url": "https://mo.wooo.work/health",
|
||
"options": { "timeout": 10000 }
|
||
},
|
||
"id": "uat-check",
|
||
"name": "UAT 可用性",
|
||
"type": "n8n-nodes-base.httpRequest",
|
||
"typeVersion": 4.2,
|
||
"position": [460, 200],
|
||
"continueOnFail": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"url": "https://momo.wooo.work/health",
|
||
"options": { "timeout": 10000 }
|
||
},
|
||
"id": "gcp-check",
|
||
"name": "GCP 可用性",
|
||
"type": "n8n-nodes-base.httpRequest",
|
||
"typeVersion": 4.2,
|
||
"position": [460, 400],
|
||
"continueOnFail": true
|
||
},
|
||
{
|
||
"parameters": {
|
||
"mode": "combine",
|
||
"combineBy": "combineAll",
|
||
"options": {}
|
||
},
|
||
"id": "merge",
|
||
"name": "合併結果",
|
||
"type": "n8n-nodes-base.merge",
|
||
"typeVersion": 3,
|
||
"position": [680, 300]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"jsCode": "const results = $input.all();\nconst now = new Date();\nconst hour = now.getHours();\nconst dateKey = now.toISOString().slice(0, 10);\n\n// 從 static data 讀取 SLO 數據\nconst sloData = $getWorkflowStaticData('global');\nif (!sloData.metrics) {\n sloData.metrics = {\n uat: { checks: 0, success: 0, latency: [] },\n gcp: { checks: 0, success: 0, latency: [] },\n daily: {}\n };\n}\n\n// UAT 結果\nconst uatResult = results[0]?.json;\nconst uatSuccess = uatResult && uatResult.status === 'healthy';\nconst uatLatency = results[0]?.json?.$responseTime || 0;\n\nsloData.metrics.uat.checks++;\nif (uatSuccess) sloData.metrics.uat.success++;\nif (uatLatency > 0) {\n sloData.metrics.uat.latency.push(uatLatency);\n if (sloData.metrics.uat.latency.length > 168) { // 保留 7 天\n sloData.metrics.uat.latency.shift();\n }\n}\n\n// GCP 結果\nconst gcpResult = results[1]?.json;\nconst gcpSuccess = gcpResult && gcpResult.status === 'healthy';\nconst gcpLatency = results[1]?.json?.$responseTime || 0;\n\nsloData.metrics.gcp.checks++;\nif (gcpSuccess) sloData.metrics.gcp.success++;\nif (gcpLatency > 0) {\n sloData.metrics.gcp.latency.push(gcpLatency);\n if (sloData.metrics.gcp.latency.length > 168) {\n sloData.metrics.gcp.latency.shift();\n }\n}\n\n// 計算 SLO 指標\nconst uatAvailability = (sloData.metrics.uat.success / sloData.metrics.uat.checks * 100).toFixed(2);\nconst gcpAvailability = (sloData.metrics.gcp.success / sloData.metrics.gcp.checks * 100).toFixed(2);\n\nconst avgUatLatency = sloData.metrics.uat.latency.length > 0 \n ? (sloData.metrics.uat.latency.reduce((a, b) => a + b, 0) / sloData.metrics.uat.latency.length).toFixed(0)\n : 0;\nconst avgGcpLatency = sloData.metrics.gcp.latency.length > 0\n ? (sloData.metrics.gcp.latency.reduce((a, b) => a + b, 0) / sloData.metrics.gcp.latency.length).toFixed(0)\n : 0;\n\n// SLO 目標\nconst SLO_AVAILABILITY = 99.5; // 可用性目標 99.5%\nconst SLO_LATENCY = 3000; // 延遲目標 3 秒內\n\nconst uatMeetsSLO = parseFloat(uatAvailability) >= SLO_AVAILABILITY;\nconst gcpMeetsSLO = parseFloat(gcpAvailability) >= SLO_AVAILABILITY;\n\n// 每日報告(每天 09:00)\nconst shouldReport = hour === 9;\n\nreturn [{\n json: {\n uat: {\n currentStatus: uatSuccess ? 'healthy' : 'unhealthy',\n availability: uatAvailability + '%',\n avgLatency: avgUatLatency + 'ms',\n meetsSLO: uatMeetsSLO,\n totalChecks: sloData.metrics.uat.checks\n },\n gcp: {\n currentStatus: gcpSuccess ? 'healthy' : 'unhealthy',\n availability: gcpAvailability + '%',\n avgLatency: avgGcpLatency + 'ms',\n meetsSLO: gcpMeetsSLO,\n totalChecks: sloData.metrics.gcp.checks\n },\n sloTarget: {\n availability: SLO_AVAILABILITY + '%',\n latency: SLO_LATENCY + 'ms'\n },\n shouldReport,\n timestamp: now.toISOString()\n }\n}];"
|
||
},
|
||
"id": "calculate-slo",
|
||
"name": "計算 SLO 指標",
|
||
"type": "n8n-nodes-base.code",
|
||
"typeVersion": 2,
|
||
"position": [900, 300]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"conditions": {
|
||
"options": {
|
||
"caseSensitive": true,
|
||
"leftValue": "",
|
||
"typeValidation": "strict"
|
||
},
|
||
"conditions": [
|
||
{
|
||
"id": "should-report",
|
||
"leftValue": "={{ $json.shouldReport }}",
|
||
"rightValue": true,
|
||
"operator": {
|
||
"type": "boolean",
|
||
"operation": "equals"
|
||
}
|
||
}
|
||
],
|
||
"combinator": "and"
|
||
},
|
||
"options": {}
|
||
},
|
||
"id": "if-report",
|
||
"name": "每日報告?",
|
||
"type": "n8n-nodes-base.if",
|
||
"typeVersion": 2,
|
||
"position": [1120, 300]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"jsCode": "const data = $input.first().json;\nconst now = new Date().toLocaleString('zh-TW', { timeZone: 'Asia/Taipei' });\n\nlet message = `📊 <b>每日 SLO 報告</b>\\n\\n`;\nmessage += `<b>服務等級目標</b>\\n`;\nmessage += `├ 可用性目標: ${data.sloTarget.availability}\\n`;\nmessage += `└ 延遲目標: < ${data.sloTarget.latency}\\n\\n`;\n\nmessage += `<b>🟦 UAT 環境</b>\\n`;\nmessage += `├ 可用性: ${data.uat.availability} ${data.uat.meetsSLO ? '✅' : '❌'}\\n`;\nmessage += `├ 平均延遲: ${data.uat.avgLatency}\\n`;\nmessage += `└ 檢查次數: ${data.uat.totalChecks}\\n\\n`;\n\nmessage += `<b>🟥 GCP 環境</b>\\n`;\nmessage += `├ 可用性: ${data.gcp.availability} ${data.gcp.meetsSLO ? '✅' : '❌'}\\n`;\nmessage += `├ 平均延遲: ${data.gcp.avgLatency}\\n`;\nmessage += `└ 檢查次數: ${data.gcp.totalChecks}\\n\\n`;\n\nmessage += `⏰ 報告時間: ${now}`;\n\nreturn [{ json: { message } }];"
|
||
},
|
||
"id": "format-report",
|
||
"name": "格式化報告",
|
||
"type": "n8n-nodes-base.code",
|
||
"typeVersion": 2,
|
||
"position": [1340, 200]
|
||
},
|
||
{
|
||
"parameters": {
|
||
"url": "https://api.telegram.org/bot<TELEGRAM_BOT_TOKEN>/sendMessage",
|
||
"sendBody": true,
|
||
"specifyBody": "json",
|
||
"jsonBody": "={\n \"chat_id\": \"5619078117\",\n \"parse_mode\": \"HTML\",\n \"text\": \"{{ $json.message }}\"\n}",
|
||
"options": {}
|
||
},
|
||
"id": "telegram",
|
||
"name": "發送報告",
|
||
"type": "n8n-nodes-base.httpRequest",
|
||
"typeVersion": 4.2,
|
||
"position": [1560, 200]
|
||
}
|
||
],
|
||
"connections": {
|
||
"每小時執行": {
|
||
"main": [
|
||
[
|
||
{ "node": "UAT 可用性", "type": "main", "index": 0 },
|
||
{ "node": "GCP 可用性", "type": "main", "index": 0 }
|
||
]
|
||
]
|
||
},
|
||
"UAT 可用性": {
|
||
"main": [
|
||
[{ "node": "合併結果", "type": "main", "index": 0 }]
|
||
]
|
||
},
|
||
"GCP 可用性": {
|
||
"main": [
|
||
[{ "node": "合併結果", "type": "main", "index": 1 }]
|
||
]
|
||
},
|
||
"合併結果": {
|
||
"main": [
|
||
[{ "node": "計算 SLO 指標", "type": "main", "index": 0 }]
|
||
]
|
||
},
|
||
"計算 SLO 指標": {
|
||
"main": [
|
||
[{ "node": "每日報告?", "type": "main", "index": 0 }]
|
||
]
|
||
},
|
||
"每日報告?": {
|
||
"main": [
|
||
[{ "node": "格式化報告", "type": "main", "index": 0 }],
|
||
[]
|
||
]
|
||
},
|
||
"格式化報告": {
|
||
"main": [
|
||
[{ "node": "發送報告", "type": "main", "index": 0 }]
|
||
]
|
||
}
|
||
},
|
||
"settings": {
|
||
"executionOrder": "v1"
|
||
},
|
||
"staticData": null,
|
||
"tags": [],
|
||
"triggerCount": 0,
|
||
"updatedAt": "2026-02-07T17:00:00.000Z",
|
||
"versionId": "1"
|
||
}
|