1. はじめに(背景)
最新の 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
2. フェーズ1:MCPブリッジの構築(疎通編)
AIとKubernetesを繋ぐ既成のMCPサーバーが見当たらなかったため、Node.jsで簡易ブリッジ k8s-bridge.js を自作しました。
ハマりポイント:環境変数PATHの壁
ターミナルでは動くのに、Roo Code経由だと kubectl が見つからない問題が発生。VS Codeから起動されるMCPプロセスには、普段のシェル(.zshrc等)の PATH が引き継がれないのが原因でした。
対策: * kubectl をフルパス(/opt/homebrew/bin/kubectl)で指定。
mcp_settings.jsonのenvセクションにも明示的にPATHを記述。
3. フェーズ2:JSONとハンドシェイクの格闘(接続編)
Roo Codeの設定画面でランプが「黄色(再試行中)」から変わらず、接続に苦労しました。
ハマりポイント:JSON-RPCプロトコルの厳密さ
MCPは標準入出力を介した JSON-RPC 通信です。AIからの initialize リクエストに対し、仕様に沿ったレスポンスを即座に返さないと、AIは接続先が生きていると認識してくれません。
対策: 最小限の initialize 応答と tools/list 応答を実装。ついにランプが 「緑(Connected)」 へ変わり、AIがクラスターを認識しました。
4. フェーズ3:万能ツールへの進化(実行編)
当初は「ノード一覧を取得するだけ」のReadOnlyなツールでしたが、これではPodの生成ができません。そこで、引数を自由に渡せる汎用ツール run_kubectl へ刷新しました。
実装の肝
Node.jsの spawn を使い、stdout(標準出力)と stderr(標準エラー)の両方をバッファリングしてAIに返すロジックを実装しました。これにより、AIが「自分でYAMLを書き、自分でapplyし、エラーが出れば自分で修正する」という自律的なループが可能になりました。
5. 動作確認:AIにPodを作らせてみた
実験指示: 「NginxのPodを作って」 結果: AIが run_kubectl を使って default namespaceに my-web Podを生成。
感動ポイント: get pods -A を実行した際、自分が長年運用してきた Argo CD や Cilium と並んで、AIが生成した my-web が Running になっている のを見た瞬間、インフラ運用の新しい時代を感じました。
6. まとめ:AI時代のインフラ管理
M4 Maxのパワーがあれば、ローカルでAIを回しながら、物理インフラの運用(ログ解析、トラブルシュート、デプロイ)を対話形式で自動化できます。
インフラエンジニアにとってのMCPは、単なる便利ツールではありません。「自分の長年の経験とスキルを、AIという強力な相棒に移植するためのプラグ」 なのだと確信しました。
🛠 実装リソース
万能型MCPブリッジ:k8s-bridge.js
標準モジュールのみで構成しているため、npm install 不要で動作します。
#!/usr/bin/env node
const { spawn } = require('child_process');
// ※各自の環境に合わせて kubectl の絶対パスを確認してください
const KUBECTL_PATH = '/opt/homebrew/bin/kubectl';
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');
};
if (message.method === 'initialize') {
respond({
protocolVersion: "2024-11-05",
capabilities: { tools: {} },
serverInfo: { name: "k8s-universal-bridge", version: "1.1.1" }
});
} else if (message.method === 'tools/list') {
respond({
tools: [{
name: "run_kubectl",
description: "任意の kubectl コマンドを実行し結果を返します",
inputSchema: {
type: "object",
properties: {
args: { type: "array", items: { type: "string" } }
},
required: ["args"]
}
}]
});
} else if (message.method === 'tools/call' && message.params.name === 'run_kubectl') {
const k8s = spawn(KUBECTL_PATH, message.params.arguments.args);
let output = '';
k8s.stdout.on('data', (d) => { output += d.toString(); });
k8s.stderr.on('data', (d) => { output += d.toString(); });
k8s.on('close', (code) => {
respond({ content: [{ type: "text", text: output.trim() || `Code: ${code}` }] });
});
}
}
Roo Code設定: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"
}
}
}
}
実際の設定の様子。
0 件のコメント:
コメントを投稿