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など)は別途料金が発生する場合があります。
関連ワークフロー
Telegram AIチャンネルボット - テキストと画像両方に対応したTGPTジェネレータ
GPT-4とTGPTを使ってTelegramチャネルでテキストと画像の応答を生成
If
Set
Code
+
If
Set
Code
22 ノードVigh Sandor
Robot Framework、ArgoCD、そして完全な KinD ライフサイクルを用いた Kubernetes 自動化テスト
Robot Framework、ArgoCD、および完全な KinD ライフサイクルを基にした Kubernetes テストの自動化
If
Set
Gitlab
+
If
Set
Gitlab
73 ノードVigh Sandor
DevOps
パスワード認証とアラートシステムを基にした自動化Rsyncバックアップ
パスワード認証とアラートシステムを備えた自動化Rsyncバックアップ
If
Set
Manual Trigger
+
If
Set
Manual Trigger
21 ノードVigh Sandor
AI駆動型動画制作&Instagram/TikTok/YouTubeへの自動アップロード
クラウドドライブからAI駆動の動画作成およびInstagram、TikTok、YouTubeへのアップロード
If
Set
Code
+
If
Set
Code
53 ノードDevCode Journey
コンテンツ作成
毎日のスポーツ要約
Google Gemini、Kokoro TTS、FFmpegを使用してRSSフィードをポッドキャストに変換
If
Set
Code
+
If
Set
Code
34 ノードJonas
コンテンツ作成
RSSフィードとElevenLabs音声を統合したAIポッドキャストジェネレーター
AIポッドキャストジェネレーター:RSSフィードとElevenLabs音声の統合
If
Code
Gmail
+
If
Code
Gmail
11 ノードDavid Olusola
人工知能