From 02263445c21c872ffe1db72c86aa8b7bbda7162a Mon Sep 17 00:00:00 2001 From: OG T Date: Sun, 19 Apr 2026 16:31:26 +0800 Subject: [PATCH] =?UTF-8?q?fix(asset=5Fscanner):=20kubectl=20=E6=94=B9=20s?= =?UTF-8?q?ubprocess=20=E2=80=94=20K8sProvider=20=E4=B8=8D=E6=94=AF?= =?UTF-8?q?=E6=8F=B4=20--all-namespaces?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5b9b36f 部署後 asset_scanner 跑 3 次但 total=0, new=0: - asset_inventory 仍 0 筆 - Pod 手動 kubectl get pods --all-namespaces -o json 可取 JSON - 真因: K8sProvider._kubectl_get 把 namespace 參數塞進 '-n $ns', 所以 '--all-namespaces' 變成 '-n --all-namespaces' (kubectl 拒絕) 修復: - 不走 K8sProvider,直接 asyncio.create_subprocess_exec - kubectl get pods --all-namespaces -o json - 30s timeout,rc != 0 拋 RuntimeError 觸發 aol status='failed' 驗證: 部署後 asset_inventory 應在 1 分鐘內開始有 pods 寫入 Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/api/src/jobs/asset_scanner_job.py | 38 ++++++++++++-------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/apps/api/src/jobs/asset_scanner_job.py b/apps/api/src/jobs/asset_scanner_job.py index fa3d2313..320018d7 100644 --- a/apps/api/src/jobs/asset_scanner_job.py +++ b/apps/api/src/jobs/asset_scanner_job.py @@ -169,34 +169,30 @@ async def scan_once( async def _collect_k8s_assets() -> list[dict[str, Any]]: """ - 用 K8sProvider 列出全 namespace 的 pods,轉成 asset_inventory 結構。 + 直接 subprocess 執行 kubectl get pods --all-namespaces. + + 2026-04-19 ogt + Claude Opus 4.7 v2: 不走 K8sProvider._kubectl_get, + 因為它把 namespace 參數當 -n 旗標,無法處理 '--all-namespaces'. + 直接 subprocess 最可靠 + Pod 內 /usr/local/bin/kubectl 已確認可用. 回傳每筆: {asset_key, asset_type, host, namespace, name, metadata, tags} """ - from src.plugins.mcp.providers.k8s_provider import K8sProvider - - provider = K8sProvider() - result = await asyncio.wait_for( - provider.execute( - tool_name="kubectl_get", - parameters={"namespace": "--all-namespaces", "resource": "pods"}, + proc = await asyncio.wait_for( + asyncio.create_subprocess_exec( + "kubectl", "get", "pods", "--all-namespaces", "-o", "json", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, ), timeout=_KUBECTL_TIMEOUT_SEC, ) - if not result.success: - raise RuntimeError(f"kubectl get pods failed: {result.error}") + stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=_KUBECTL_TIMEOUT_SEC) + if proc.returncode != 0: + raise RuntimeError(f"kubectl failed rc={proc.returncode}: {stderr.decode('utf-8', errors='replace')[:500]}") - raw = result.output - # k8s_provider _kubectl_get 回傳 stdout 字串 (line 299) - if isinstance(raw, str): - try: - payload = _json.loads(raw) - except _json.JSONDecodeError as e: - raise RuntimeError(f"kubectl JSON parse failed: {e}") from e - elif isinstance(raw, dict): - payload = raw - else: - raise RuntimeError(f"unexpected kubectl output type: {type(raw)}") + try: + payload = _json.loads(stdout.decode("utf-8", errors="replace")) + except _json.JSONDecodeError as e: + raise RuntimeError(f"kubectl JSON parse failed: {e}") from e items = payload.get("items", []) if isinstance(payload, dict) else [] assets: list[dict[str, Any]] = []