k8s(raspberry pi)のpodのログをチャットで観れるか実験してみました。
この後、エラーがログに出てないかも聞いてみました
最新の M4 Max搭載 Mac Studio を導入しました。この圧倒的なパワーをローカルLLMの実行だけに使うのはもったいない。そこで、長年運用している 自作のラズパイ・クラスター(3台構成) を、VS CodeのAIエージェント「Roo Code」から直接操作・管理できる環境を構築することにしました。
AIに「インフラの目と手」を与えるための架け橋として、GoogleやAnthropicが推進する Model Context Protocol (MCP) を活用します。
Client: Roo Code (VS Code Extension)
Protocol: Model Context Protocol (MCP)
Runtime: Node.js
Infrastructure: Kubernetes (Raspberry Pi Cluster), kubectl
[構成図]
AIとKubernetesを繋ぐ既成のMCPサーバーが見当たらなかったため、Node.jsで簡易ブリッジ k8s-bridge.js を自作しました。
ターミナルでは動くのに、Roo Code経由だと kubectl が見つからない問題が発生。VS Codeから起動されるMCPプロセスには、普段のシェル(.zshrc等)の PATH が引き継がれないのが原因でした。
対策: * kubectl をフルパス(/opt/homebrew/bin/kubectl)で指定。
mcp_settings.json の env セクションにも明示的に PATH を記述。
Roo Codeの設定画面でランプが「黄色(再試行中)」から変わらず、接続に苦労しました。
MCPは標準入出力を介した JSON-RPC 通信です。AIからの initialize リクエストに対し、仕様に沿ったレスポンスを即座に返さないと、AIは接続先が生きていると認識してくれません。
対策: 最小限の initialize 応答と tools/list 応答を実装。ついにランプが 「緑(Connected)」 へ変わり、AIがクラスターを認識しました。
当初は「ノード一覧を取得するだけ」のReadOnlyなツールでしたが、これではPodの生成ができません。そこで、引数を自由に渡せる汎用ツール run_kubectl へ刷新しました。
Node.jsの spawn を使い、stdout(標準出力)と stderr(標準エラー)の両方をバッファリングしてAIに返すロジックを実装しました。これにより、AIが「自分でYAMLを書き、自分でapplyし、エラーが出れば自分で修正する」という自律的なループが可能になりました。
実験指示: 「NginxのPodを作って」 結果: AIが run_kubectl を使って default namespaceに my-web Podを生成。
感動ポイント: get pods -A を実行した際、自分が長年運用してきた Argo CD や Cilium と並んで、AIが生成した my-web が Running になっている のを見た瞬間、インフラ運用の新しい時代を感じました。
M4 Maxのパワーがあれば、ローカルでAIを回しながら、物理インフラの運用(ログ解析、トラブルシュート、デプロイ)を対話形式で自動化できます。
インフラエンジニアにとってのMCPは、単なる便利ツールではありません。「自分の長年の経験とスキルを、AIという強力な相棒に移植するためのプラグ」 なのだと確信しました。
k8s-bridge.js標準モジュールのみで構成しているため、npm install 不要で動作します。
-------------------------------------------------
#!/usr/bin/env node
const { spawn } = require('child_process');
// Mac (Homebrew) のデフォルトパス
const KUBECTL_PATH = '/opt/homebrew/bin/kubectl';
const HELM_PATH = '/opt/homebrew/bin/helm';
let buffer = '';
process.stdin.on('data', (chunk) => {
buffer += chunk.toString();
let lines = buffer.split('\n');
buffer = lines.pop();
for (let line of lines) {
if (!line.trim()) continue;
try {
handleMessage(JSON.parse(line));
} catch (e) {
console.error("JSON Parse Error:", e);
}
}
});
function handleMessage(message) {
const respond = (result) => {
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: message.id, result }) + '\n');
};
// 1. 初期化
if (message.method === 'initialize') {
respond({
protocolVersion: "2024-11-05",
capabilities: { tools: {} },
serverInfo: { name: "k8s-helm-bridge", version: "1.2.0" }
});
// 2. ツール一覧の定義
} else if (message.method === 'tools/list') {
respond({
tools: [
{
name: "run_kubectl",
description: "kubectl コマンドを実行します(例: get pods, logsなど)",
inputSchema: {
type: "object",
properties: {
args: { type: "array", items: { type: "string" } }
},
required: ["args"]
}
},
{
name: "run_helm",
description: "helm コマンドを実行します(例: list, status, installなど)",
inputSchema: {
type: "object",
properties: {
args: { type: "array", items: { type: "string" } }
},
required: ["args"]
}
}
]
});
// 3. 実行ロジック
} else if (message.method === 'tools/call') {
const toolName = message.params.name;
const args = message.params.arguments.args;
// ツール名によってバイナリを選択
let commandPath;
if (toolName === 'run_kubectl') {
commandPath = KUBECTL_PATH;
} else if (toolName === 'run_helm') {
commandPath = HELM_PATH;
} else {
return respond({ content: [{ type: "text", text: "Unknown tool" }], isError: true });
}
// コマンド実行
const proc = spawn(commandPath, args, {
env: { ...process.env } // kubeconfigやHelmのパスを通すために重要
});
let stdout = '';
let stderr = '';
proc.stdout.on('data', (d) => { stdout += d.toString(); });
proc.stderr.on('data', (d) => { stderr += d.toString(); });
proc.on('close', (code) => {
const isError = code !== 0;
respond({
content: [{
type: "text",
text: isError ? stderr.trim() || `Error: Exit code ${code}` : stdout.trim() || "Success"
}],
isError: isError
});
});
}
}
----------------------------------------------------------------------
mcp_settings.json設定ファイルの場所: ~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json
{
"mcpServers": {
"kubernetes": {
"command": "node",
"args": ["/Users/hidenari/src/k8s-bridge.js"],
"env": {
"KUBECONFIG": "/Users/hidenari/.kube/config",
"PATH": "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
}
}
}
}
[用途]
ルール対象外のマニフェストをデプロイさせないための
防止策で利用するイメージになる
[検証]
以下のコマンドを投入する
kubectl label ns default pod-security.kubernetes.io/warn=baseline
[各種コマンドの意味]
1. kubectl label ns default
特に、AI とローカル環境を繋ぐブリッジとして MCP (Model Context Protocol) サーバーを自作 した過程を記録します。 1. 構築したアーキテクチャ 単に Obsidian を使うだけでなく、AI(LLM)がローカルのファイルを自在に検索・閲覧できる「...