Kubernetes监控与告警 - 部署和Pod状态 - Telegram告警
中级
这是一个自动化工作流,包含 14 个节点。主要使用 If, Code, Telegram, ExecuteCommand, ScheduleTrigger 等节点。 Kubernetes部署和Pod监控,带Telegram告警
前置要求
- •Telegram Bot Token
分类
-
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
"id": "cM2tIagg9TD8Jc0l",
"meta": {
"instanceId": "5c7ce220523e8664f49208a8be668a8dc6fab5f747ce4de865fa1309727919f1"
},
"name": "Kubernetes 监控与告警 - 部署和 Pod 状态 - Telegram 告警",
"tags": [],
"nodes": [
{
"id": "f0b0f7f0-b5e1-4bfd-85a0-6c0f90336a6e",
"name": "计划触发器",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-480,
208
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes"
}
]
}
},
"typeVersion": 1
},
{
"id": "976ee100-553e-4b40-8b9f-09a3afd980a9",
"name": "Kubeconfig 设置",
"type": "n8n-nodes-base.code",
"position": [
-224,
208
],
"parameters": {
"jsCode": "// PASTE YOUR KUBECONFIG CONTENT HERE\nconst kubeconfigContent = `\napiVersion: v1\nkind: Config\nclusters:\n- cluster:\n certificate-authority-data: YOUR_CA_DATA\n server: https://your-k8s-api-server:6443\n name: your-cluster\ncontexts:\n- context:\n cluster: your-cluster\n user: your-user\n name: your-context\ncurrent-context: your-context\nusers:\n- name: your-user\n user:\n token: YOUR_TOKEN\n`;\n\n// Configuration\nconst namespace = 'production';\n\nreturn [{\n json: {\n kubeconfig: kubeconfigContent,\n namespace: namespace\n }\n}];"
},
"typeVersion": 2
},
{
"id": "b95b994d-00b2-4bb7-9d4b-9f417f8bd1e1",
"name": "获取 Pods",
"type": "n8n-nodes-base.executeCommand",
"position": [
0,
0
],
"parameters": {
"command": "=apk add curl\nKUBECONFIG_PATH=\"/tmp/kubeconfig-$RANDOM.yaml\"\necho '{{$json.kubeconfig}}' > $KUBECONFIG_PATH\ncurl -LO https://dl.k8s.io/release/v1.34.0/bin/linux/amd64/kubectl\nchmod +x ./kubectl\n./kubectl --kubeconfig=$KUBECONFIG_PATH get pods -n {{$json.namespace}} -o json\nrm -f $KUBECONFIG_PATH"
},
"typeVersion": 1
},
{
"id": "0b5b3388-2de0-479e-a9d3-a1b136b0cf56",
"name": "获取部署",
"type": "n8n-nodes-base.executeCommand",
"position": [
-16,
432
],
"parameters": {
"command": "=apk add curl\nKUBECONFIG_PATH=\"/tmp/kubeconfig-$RANDOM.yaml\"\necho '{{$json.kubeconfig}}' > $KUBECONFIG_PATH\ncurl -LO https://dl.k8s.io/release/v1.34.0/bin/linux/amd64/kubectl\nchmod +x ./kubectl\n./kubectl --kubeconfig=$KUBECONFIG_PATH get deployments -n {{$json.namespace}} -o json\nrm -f $KUBECONFIG_PATH"
},
"typeVersion": 1
},
{
"id": "f0a55a90-5bcf-42dc-96b0-3a21bd83c12e",
"name": "处理并生成报告",
"type": "n8n-nodes-base.code",
"position": [
208,
224
],
"parameters": {
"jsCode": "// Debug: Check inputs\nconst inputs = $input.all();\n\n// Try to identify which input is which\nlet podsData, deploymentsData;\n\nfor (let i = 0; i < inputs.length; i++) {\n try {\n const input = inputs[i];\n \n if (!input.json?.stdout) continue;\n \n const data = JSON.parse(input.json.stdout);\n \n if (data.items && data.items.length > 0) {\n const firstKind = data.items[0].kind;\n \n if (firstKind === 'Pod') {\n podsData = data;\n } else if (firstKind === 'Deployment') {\n deploymentsData = data;\n }\n }\n } catch (e) {\n console.log(`Error parsing input ${i}:`, e.message);\n }\n}\n\nconst pods = podsData?.items || [];\nconst deployments = deploymentsData?.items || [];\nconst namespace = podsData?.items?.[0]?.metadata?.namespace || deploymentsData?.items?.[0]?.metadata?.namespace || 'N/A';\n\nconst deploymentStatus = {};\nconst otherWorkloads = {};\nconst standalonePods = [];\nconst alerts = [];\n\n// Process deployments from deployment objects\ndeployments.forEach(deployment => {\n const name = deployment.metadata.name;\n const ns = deployment.metadata.namespace;\n const replicas = deployment.spec.replicas || 0;\n const readyReplicas = deployment.status.readyReplicas || 0;\n const availableReplicas = deployment.status.availableReplicas || 0;\n \n deploymentStatus[name] = {\n name: name,\n namespace: ns,\n replicas: replicas,\n readyReplicas: readyReplicas,\n availableReplicas: availableReplicas,\n pods: []\n };\n \n if (readyReplicas < 1) {\n alerts.push({\n workload: name,\n kind: 'Deployment',\n issue: `No ready pods available! (Ready: ${readyReplicas}/${replicas})`\n });\n }\n});\n\n// Process pods - group by owner\npods.forEach(pod => {\n const podName = pod.metadata.name;\n const phase = pod.status.phase;\n const nodeName = pod.spec.nodeName || 'N/A';\n const conditions = pod.status.conditions || [];\n const readyCondition = conditions.find(c => c.type === 'Ready');\n const isReady = readyCondition?.status === 'True';\n const restartCount = pod.status.containerStatuses?.reduce((sum, c) => sum + c.restartCount, 0) || 0;\n \n const podInfo = {\n name: podName,\n phase: phase,\n ready: isReady,\n node: nodeName,\n restarts: restartCount\n };\n \n // Find owner from ownerReferences\n const ownerRefs = pod.metadata.ownerReferences || [];\n let ownerName = null;\n let ownerKind = null;\n \n for (const owner of ownerRefs) {\n if (owner.kind === 'ReplicaSet') {\n // Extract deployment name from ReplicaSet name\n const parts = owner.name.split('-');\n if (parts.length > 1) {\n parts.pop();\n ownerName = parts.join('-');\n ownerKind = 'Deployment';\n }\n break;\n } else if (['DaemonSet', 'StatefulSet', 'Node'].includes(owner.kind)) {\n ownerName = owner.name;\n ownerKind = owner.kind;\n break;\n }\n }\n \n // Add pod to appropriate owner\n if (ownerKind === 'Deployment') {\n // If deployment exists in deploymentStatus, add there\n if (deploymentStatus[ownerName]) {\n deploymentStatus[ownerName].pods.push(podInfo);\n } else {\n // Otherwise create a deployment entry (discovered from pods)\n if (!deploymentStatus[ownerName]) {\n const readyCount = 0; // Will be calculated later\n deploymentStatus[ownerName] = {\n name: ownerName,\n namespace: namespace,\n replicas: 0,\n readyReplicas: 0,\n availableReplicas: 0,\n pods: [],\n discoveredFromPods: true\n };\n }\n deploymentStatus[ownerName].pods.push(podInfo);\n }\n } else if (ownerName) {\n // Group by other owner types\n const key = `${ownerKind}/${ownerName}`;\n if (!otherWorkloads[key]) {\n otherWorkloads[key] = {\n kind: ownerKind,\n name: ownerName,\n pods: []\n };\n }\n otherWorkloads[key].pods.push(podInfo);\n } else {\n standalonePods.push(podInfo);\n }\n});\n\n// Calculate stats for deployments discovered from pods\nObject.values(deploymentStatus).forEach(dep => {\n if (dep.discoveredFromPods) {\n const totalPods = dep.pods.length;\n const readyPods = dep.pods.filter(p => p.ready).length;\n dep.replicas = totalPods;\n dep.readyReplicas = readyPods;\n dep.availableReplicas = readyPods;\n \n if (readyPods < 1) {\n alerts.push({\n workload: dep.name,\n kind: 'Deployment',\n issue: `No ready pods available! (Ready: ${readyPods}/${totalPods})`\n });\n }\n }\n});\n\n// Check for alerts in non-deployment workloads\nObject.values(otherWorkloads).forEach(owner => {\n const readyCount = owner.pods.filter(p => p.ready).length;\n if (readyCount < 1) {\n alerts.push({\n workload: owner.name,\n kind: owner.kind,\n issue: `No ready pods available! (Ready: ${readyCount}/${owner.pods.length})`\n });\n }\n});\n\n// Generate report\nlet markdown = `# Kubernetes Cluster Status Report\\n\\n`;\nmarkdown += `**Namespace:** ${namespace}\\n`;\nmarkdown += `**Timestamp:** ${new Date().toISOString()}\\n\\n`;\n\nconst deploymentCount = Object.keys(deploymentStatus).length;\nconst otherWorkloadCount = Object.keys(otherWorkloads).length;\nmarkdown += `**Total:** ${deploymentCount} deployments, ${otherWorkloadCount} other workloads, ${pods.length} pods\\n\\n`;\n\n// Deployments section\nif (deploymentCount > 0) {\n markdown += `## Deployments\\n\\n`;\n Object.values(deploymentStatus).forEach(dep => {\n const status = dep.readyReplicas >= 1 ? '✅' : '❌';\n markdown += `### ${status} ${dep.name}\\n`;\n markdown += `- **Replicas:** ${dep.readyReplicas}/${dep.replicas}\\n`;\n markdown += `- **Available:** ${dep.availableReplicas}\\n`;\n markdown += `- **Pods:**\\n`;\n \n if (dep.pods.length === 0) {\n markdown += ` - *No pods*\\n`;\n } else {\n dep.pods.forEach(pod => {\n const podStatus = pod.ready ? '✅' : '❌';\n markdown += ` - ${podStatus} ${pod.name} (${pod.phase}) - Node: ${pod.node}, Restarts: ${pod.restarts}\\n`;\n });\n }\n markdown += `\\n`;\n });\n}\n\n// Other workloads section (DaemonSets, StatefulSets, Static Pods)\nif (otherWorkloadCount > 0) {\n markdown += `## Other Workloads (DaemonSets, StatefulSets, Static Pods)\\n\\n`;\n Object.values(otherWorkloads).forEach(owner => {\n const readyCount = owner.pods.filter(p => p.ready).length;\n const totalCount = owner.pods.length;\n const status = readyCount >= 1 ? '✅' : '❌';\n \n markdown += `### ${status} ${owner.name} (${owner.kind})\\n`;\n markdown += `- **Ready:** ${readyCount}/${totalCount}\\n`;\n markdown += `- **Pods:**\\n`;\n \n owner.pods.forEach(pod => {\n const podStatus = pod.ready ? '✅' : '❌';\n markdown += ` - ${podStatus} ${pod.name} (${pod.phase}) - Node: ${pod.node}, Restarts: ${pod.restarts}\\n`;\n });\n markdown += `\\n`;\n });\n}\n\n// Standalone pods\nif (standalonePods.length > 0) {\n markdown += `## Standalone Pods\\n\\n`;\n standalonePods.forEach(pod => {\n const podStatus = pod.ready ? '✅' : '❌';\n markdown += `- ${podStatus} ${pod.name} (${pod.phase}) - Node: ${pod.node}, Restarts: ${pod.restarts}\\n`;\n });\n markdown += `\\n`;\n}\n\n// Alerts section\nif (alerts.length > 0) {\n markdown += `## ⚠️ Alerts\\n\\n`;\n alerts.forEach(alert => {\n markdown += `- **${alert.workload}** (${alert.kind}): ${alert.issue}\\n`;\n });\n}\n\n// Convert markdown to binary data\nconst buffer = Buffer.from(markdown, 'utf-8');\nconst binaryData = {\n data: buffer.toString('base64'),\n mimeType: 'text/markdown',\n fileName: 'report.md'\n};\n\nreturn [{\n json: {\n markdown: markdown,\n hasAlerts: alerts.length > 0,\n alerts: alerts,\n deploymentCount: deploymentCount,\n podCount: pods.length,\n namespace: namespace\n },\n binary: {\n data: binaryData\n }\n}];"
},
"typeVersion": 2
},
{
"id": "22af800d-d130-4d13-9e6b-6a9176b886b9",
"name": "有告警吗?",
"type": "n8n-nodes-base.if",
"position": [
432,
224
],
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json.hasAlerts}}",
"value2": true
}
]
}
},
"typeVersion": 1
},
{
"id": "deb746c1-42b4-4e12-8530-d0340cf52e90",
"name": "发送 Telegram 告警",
"type": "n8n-nodes-base.telegram",
"position": [
704,
16
],
"webhookId": "ad590700-edca-4911-84c0-bbfd3f1971a0",
"parameters": {
"text": "=🚨 *Kubernetes Alert*\\n\\n*Namespace:* {{$json.namespace}}\\n\\n{{$json.alerts.map(a => `⚠️ *${a.deployment}*: ${a.issue}`).join('\\n')}}\\n\\n---\\n\\n{{$json.markdown}}",
"chatId": "YOUR_TELEGRAM_CHAT_ID",
"additionalFields": {
"parse_mode": "Markdown"
}
},
"credentials": {
"telegramApi": {
"id": "FUXl519hpM0FsK8j",
"name": "Telegram account"
}
},
"typeVersion": 1
},
{
"id": "c8cf22c0-7954-4a5c-84fc-3513ece1e376",
"name": "保存报告",
"type": "n8n-nodes-base.writeBinaryFile",
"position": [
704,
480
],
"parameters": {
"options": {},
"fileName": "=k8s-report-{{$now.format('YYYY-MM-DD-HHmmss')}}.md",
"dataPropertyName": "=data"
},
"typeVersion": 1
},
{
"id": "277ca9f2-b6bf-4c87-8d56-1734c0fecae0",
"name": "便签",
"type": "n8n-nodes-base.stickyNote",
"position": [
-752,
0
],
"parameters": {
"width": 360,
"height": 180,
"content": "## Kubernetes 监控工作流"
},
"typeVersion": 1
},
{
"id": "da5b3125-2dfd-4ee5-9732-27404ef98455",
"name": "便签1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-416,
352
],
"parameters": {
"width": 300,
"height": 240,
"content": "## 需要配置"
},
"typeVersion": 1
},
{
"id": "848a775e-d3c7-4c5b-8b1b-6ad4200b9687",
"name": "便签2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-96,
-208
],
"parameters": {
"width": 280,
"height": 188,
"content": "## 数据收集"
},
"typeVersion": 1
},
{
"id": "5e94eb86-fb7a-4cde-a2b5-3778f057c316",
"name": "便签3",
"type": "n8n-nodes-base.stickyNote",
"position": [
192,
384
],
"parameters": {
"width": 280,
"content": "## 处理与告警检测"
},
"typeVersion": 1
},
{
"id": "83b169f1-7bf5-497d-bdb8-9b999baaa980",
"name": "便签4",
"type": "n8n-nodes-base.stickyNote",
"position": [
704,
-224
],
"parameters": {
"width": 464,
"height": 216,
"content": "## TELEGRAM 配置"
},
"typeVersion": 1
},
{
"id": "00a9cb9f-6e0b-4537-adee-b2b6cef02f86",
"name": "便签5",
"type": "n8n-nodes-base.stickyNote",
"position": [
704,
624
],
"parameters": {
"width": 280,
"height": 220,
"content": "## 报告输出"
},
"typeVersion": 1
}
],
"active": false,
"pinData": {},
"settings": {
"executionOrder": "v1"
},
"versionId": "0f3068a8-25a9-42af-b277-7deb8c6844f1",
"connections": {
"Get Pods": {
"main": [
[
{
"node": "Process & Generate Report",
"type": "main",
"index": 0
}
]
]
},
"Has Alerts?": {
"main": [
[
{
"node": "Send Telegram Alert",
"type": "main",
"index": 0
}
],
[
{
"node": "Save Report",
"type": "main",
"index": 0
}
]
]
},
"Get Deployments": {
"main": [
[
{
"node": "Process & Generate Report",
"type": "main",
"index": 0
}
]
]
},
"Kubeconfig Setup": {
"main": [
[
{
"node": "Get Pods",
"type": "main",
"index": 0
},
{
"node": "Get Deployments",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Kubeconfig Setup",
"type": "main",
"index": 0
}
]
]
},
"Send Telegram Alert": {
"main": [
[
{
"node": "Save Report",
"type": "main",
"index": 0
}
]
]
},
"Process & Generate Report": {
"main": [
[
{
"node": "Has Alerts?",
"type": "main",
"index": 0
}
]
]
}
}
}常见问题
如何使用这个工作流?
复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。
这个工作流适合什么场景?
中级
需要付费吗?
本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。
相关工作流推荐
Telegram AI频道机器人 - 支持文本和图像响应的TGPT生成器
使用GPT-4和TGPT在Telegram频道中生成文本和图像响应
If
Set
Code
+8
22 节点Vigh Sandor
PKI证书与CRL监控器 - 自动过期警报系统
监控PKI证书和CRL过期情况并发送Telegram和SMS警报
If
Set
Code
+9
44 节点Vigh Sandor
使用Robot Framework、ArgoCD和完整KinD生命周期实现自动化Kubernetes测试
基于 Robot Framework、ArgoCD 和完整 KinD 生命周期的自动化 Kubernetes 测试
If
Set
Gitlab
+10
73 节点Vigh Sandor
开发运维
基于密码认证和警报系统的自动化Rsync备份
密码认证与警报系统的自动化Rsync备份
If
Set
Manual Trigger
+3
21 节点Vigh Sandor
Proxmox监控报告 - VM状态、主机资源使用情况、温度控制 - 通过Telegram定时发送
Proxmox系统监控 - 通过Telegram发送虚拟机状态、主机资源和温度警报
Set
Ssh
Code
+4
18 节点Vigh Sandor
AI驱动视频创作与上传至Instagram、TikTok和YouTube
从云端硬盘进行AI驱动视频创作并上传至Instagram、TikTok和YouTube
If
Set
Code
+14
53 节点DevCode Journey
内容创作