8
n8n 中文网amn8n.com

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)可能需要您自行付费。

工作流信息
难度等级
中级
节点数量14
分类-
节点类型7
难度说明

适合有一定经验的用户,包含 6-15 个节点的中等复杂度工作流

外部链接
在 n8n.io 查看

分享此工作流