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 Monitoring & Alert - Deplyoment & Pods Status - Telegram Alert",
  "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": "Pod 목록 가져오기",
      "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": "Send Telegram Alert",
      "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 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": "참고 사항1",
      "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": "참고 사항2",
      "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": "참고 사항3",
      "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": "참고 사항4",
      "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": "참고 사항5",
      "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
          }
        ]
      ]
    }
  }
}
자주 묻는 질문

이 워크플로우를 어떻게 사용하나요?

위의 JSON 구성 코드를 복사하여 n8n 인스턴스에서 새 워크플로우를 생성하고 "JSON에서 가져오기"를 선택한 후, 구성을 붙여넣고 필요에 따라 인증 설정을 수정하세요.

이 워크플로우는 어떤 시나리오에 적합한가요?

중급

유료인가요?

이 워크플로우는 완전히 무료이며 직접 가져와 사용할 수 있습니다. 다만, 워크플로우에서 사용하는 타사 서비스(예: OpenAI API)는 사용자 직접 비용을 지불해야 할 수 있습니다.

워크플로우 정보
난이도
중급
노드 수14
카테고리-
노드 유형7
난이도 설명

일정 경험을 가진 사용자를 위한 6-15개 노드의 중간 복잡도 워크플로우

외부 링크
n8n.io에서 보기

이 워크플로우 공유

카테고리

카테고리: 34