Monitoreo y alertas de Kubernetes - Estado de implementación y Pods - Alertas de Telegram

Intermedio

Este es unautomatización que contiene 14 nodos.Utiliza principalmente nodos como If, Code, Telegram, ExecuteCommand, ScheduleTrigger. Despliegue en Kubernetes y monitoreo de Pods con alertas por Telegram

Requisitos previos
  • Bot Token de Telegram

Categoría

-
Vista previa del flujo de trabajo
Visualización de las conexiones entre nodos, con soporte para zoom y panorámica
Exportar flujo de trabajo
Copie la siguiente configuración JSON en n8n para importar y usar este flujo de trabajo
{
  "id": "cM2tIagg9TD8Jc0l",
  "meta": {
    "instanceId": "5c7ce220523e8664f49208a8be668a8dc6fab5f747ce4de865fa1309727919f1"
  },
  "name": "Kubernetes Monitoring & Alert - Deplyoment & Pods Status - Telegram Alert",
  "tags": [],
  "nodes": [
    {
      "id": "f0b0f7f0-b5e1-4bfd-85a0-6c0f90336a6e",
      "name": "Disparador Programado",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -480,
        208
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "976ee100-553e-4b40-8b9f-09a3afd980a9",
      "name": "Configuración de 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": "Obtener 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": "Obtener Deployments",
      "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": "Procesar y Generar Reporte",
      "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": "¿Hay Alertas?",
      "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": "Enviar Alerta 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": "Guardar Reporte",
      "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": "Nota Adhesiva",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -752,
        0
      ],
      "parameters": {
        "width": 360,
        "height": 180,
        "content": "## Kubernetes Monitoring Workflow\n\nAutomatically monitors your K8s cluster and sends Telegram alerts when workloads have zero ready pods.\n\nReports are saved as markdown files for every execution."
      },
      "typeVersion": 1
    },
    {
      "id": "da5b3125-2dfd-4ee5-9732-27404ef98455",
      "name": "Nota Adhesiva1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -416,
        352
      ],
      "parameters": {
        "width": 300,
        "height": 240,
        "content": "## CONFIGURATION REQUIRED\n\n1. Paste your kubeconfig content\n2. Set target namespace (default: 'production')\n\nThe workflow will automatically download kubectl during execution."
      },
      "typeVersion": 1
    },
    {
      "id": "848a775e-d3c7-4c5b-8b1b-6ad4200b9687",
      "name": "Nota Adhesiva2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -96,
        -208
      ],
      "parameters": {
        "width": 280,
        "height": 188,
        "content": "## Data Collection\n\nBoth nodes run in parallel to fetch:\n- All pods\n- All deployments\n\nfrom the specified namespace"
      },
      "typeVersion": 1
    },
    {
      "id": "5e94eb86-fb7a-4cde-a2b5-3778f057c316",
      "name": "Nota Adhesiva3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        192,
        384
      ],
      "parameters": {
        "width": 280,
        "content": "## Processing & Alert Detection\n\nGroups pods by owner (Deployment, DaemonSet, StatefulSet, Node)\n\nDetects alerts: workloads with 0 ready pods\n\nGenerates comprehensive markdown report"
      },
      "typeVersion": 1
    },
    {
      "id": "83b169f1-7bf5-497d-bdb8-9b999baaa980",
      "name": "Nota Adhesiva4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        704,
        -224
      ],
      "parameters": {
        "width": 464,
        "height": 216,
        "content": "## TELEGRAM CONFIGURATION\n\n1. Create bot via @BotFather\n2. Get bot token & add as credential\n3. Replace YOUR_TELEGRAM_CHAT_ID with your actual chat ID\n\nFind chat ID: message your bot, then visit:\nhttps://api.telegram.org/bot<TOKEN>/getUpdates"
      },
      "typeVersion": 1
    },
    {
      "id": "00a9cb9f-6e0b-4537-adee-b2b6cef02f86",
      "name": "Nota Adhesiva5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        704,
        624
      ],
      "parameters": {
        "width": 280,
        "height": 220,
        "content": "## Report Output\n\nSaves markdown report with timestamp:\nk8s-report-YYYY-MM-DD-HHmmss.md\n\nExecutes on every run regardless of alert status"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "0f3068a8-25a9-42af-b277-7deb8c6844f1",
  "connections": {
    "b95b994d-00b2-4bb7-9d4b-9f417f8bd1e1": {
      "main": [
        [
          {
            "node": "f0a55a90-5bcf-42dc-96b0-3a21bd83c12e",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "22af800d-d130-4d13-9e6b-6a9176b886b9": {
      "main": [
        [
          {
            "node": "deb746c1-42b4-4e12-8530-d0340cf52e90",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "c8cf22c0-7954-4a5c-84fc-3513ece1e376",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "0b5b3388-2de0-479e-a9d3-a1b136b0cf56": {
      "main": [
        [
          {
            "node": "f0a55a90-5bcf-42dc-96b0-3a21bd83c12e",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "976ee100-553e-4b40-8b9f-09a3afd980a9": {
      "main": [
        [
          {
            "node": "b95b994d-00b2-4bb7-9d4b-9f417f8bd1e1",
            "type": "main",
            "index": 0
          },
          {
            "node": "0b5b3388-2de0-479e-a9d3-a1b136b0cf56",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "f0b0f7f0-b5e1-4bfd-85a0-6c0f90336a6e": {
      "main": [
        [
          {
            "node": "976ee100-553e-4b40-8b9f-09a3afd980a9",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "deb746c1-42b4-4e12-8530-d0340cf52e90": {
      "main": [
        [
          {
            "node": "c8cf22c0-7954-4a5c-84fc-3513ece1e376",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "f0a55a90-5bcf-42dc-96b0-3a21bd83c12e": {
      "main": [
        [
          {
            "node": "22af800d-d130-4d13-9e6b-6a9176b886b9",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Preguntas frecuentes

¿Cómo usar este flujo de trabajo?

Copie el código de configuración JSON de arriba, cree un nuevo flujo de trabajo en su instancia de n8n y seleccione "Importar desde JSON", pegue la configuración y luego modifique la configuración de credenciales según sea necesario.

¿En qué escenarios es adecuado este flujo de trabajo?

Intermedio

¿Es de pago?

Este flujo de trabajo es completamente gratuito, puede importarlo y usarlo directamente. Sin embargo, tenga en cuenta que los servicios de terceros utilizados en el flujo de trabajo (como la API de OpenAI) pueden requerir un pago por su cuenta.

Información del flujo de trabajo
Nivel de dificultad
Intermedio
Número de nodos14
Categoría-
Tipos de nodos7
Descripción de la dificultad

Adecuado para usuarios con experiencia intermedia, flujos de trabajo de complejidad media con 6-15 nodos

Enlaces externos
Ver en n8n.io

Compartir este flujo de trabajo

Categorías

Categorías: 34