Добавлен модуль Kubernetes MCP с DI и диагностикой ошибок
This commit is contained in:
166
LazyBear.MCP/Services/Kubernetes/K8sPodsTools.cs
Normal file
166
LazyBear.MCP/Services/Kubernetes/K8sPodsTools.cs
Normal file
@@ -0,0 +1,166 @@
|
||||
using System.ComponentModel;
|
||||
using k8s;
|
||||
using k8s.Autorest;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace LazyBear.MCP.Services.Kubernetes;
|
||||
|
||||
[McpServerToolType]
|
||||
public sealed class K8sPodsTools(K8sClientProvider clientProvider, IConfiguration configuration)
|
||||
{
|
||||
private readonly IKubernetes? _client = clientProvider.Client;
|
||||
private readonly string? _clientInitializationError = clientProvider.InitializationError;
|
||||
private readonly string _defaultNamespace = configuration["Kubernetes:DefaultNamespace"] ?? "default";
|
||||
|
||||
[McpServerTool, Description("Список подов в namespace")]
|
||||
public async Task<string> ListPods(
|
||||
[Description("Namespace Kubernetes")] string? @namespace = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var ns = ResolveNamespace(@namespace);
|
||||
if (!TryGetClient(out var client, out var clientError))
|
||||
{
|
||||
return clientError;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var pods = await client.CoreV1.ListNamespacedPodAsync(ns, cancellationToken: cancellationToken);
|
||||
|
||||
if (pods.Items.Count == 0)
|
||||
{
|
||||
return $"В namespace '{ns}' поды не найдены.";
|
||||
}
|
||||
|
||||
var lines = pods.Items.Select(pod =>
|
||||
{
|
||||
var podName = pod.Metadata?.Name ?? "unknown";
|
||||
var phase = pod.Status?.Phase ?? "Unknown";
|
||||
var node = pod.Spec?.NodeName ?? "-";
|
||||
var restarts = pod.Status?.ContainerStatuses?.Sum(s => s.RestartCount) ?? 0;
|
||||
return $"{podName}: phase={phase}, restarts={restarts}, node={node}";
|
||||
});
|
||||
|
||||
return $"Поды namespace '{ns}':\n{string.Join('\n', lines)}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return FormatError("list_pods", ns, ex);
|
||||
}
|
||||
}
|
||||
|
||||
[McpServerTool, Description("Статус pod по имени")]
|
||||
public async Task<string> GetPodStatus(
|
||||
[Description("Имя pod")] string name,
|
||||
[Description("Namespace Kubernetes")] string? @namespace = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var ns = ResolveNamespace(@namespace);
|
||||
if (!TryGetClient(out var client, out var clientError))
|
||||
{
|
||||
return clientError;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var pod = await client.CoreV1.ReadNamespacedPodAsync(name, ns, cancellationToken: cancellationToken);
|
||||
|
||||
var phase = pod.Status?.Phase ?? "Unknown";
|
||||
var hostIp = pod.Status?.HostIP ?? "-";
|
||||
var podIp = pod.Status?.PodIP ?? "-";
|
||||
var conditions = pod.Status?.Conditions?.Select(c => $"{c.Type}={c.Status}") ?? [];
|
||||
|
||||
return $"Pod '{name}' в namespace '{ns}': phase={phase}, hostIp={hostIp}, podIp={podIp}, conditions=[{string.Join(", ", conditions)}]";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return FormatError("get_pod_status", ns, ex, name);
|
||||
}
|
||||
}
|
||||
|
||||
[McpServerTool, Description("Логи pod")]
|
||||
public async Task<string> GetPodLogs(
|
||||
[Description("Имя pod")] string name,
|
||||
[Description("Namespace Kubernetes")] string? @namespace = null,
|
||||
[Description("Имя контейнера")]
|
||||
string? container = null,
|
||||
[Description("Количество строк с конца")]
|
||||
int? tailLines = 100,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var ns = ResolveNamespace(@namespace);
|
||||
if (!TryGetClient(out var client, out var clientError))
|
||||
{
|
||||
return clientError;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await using var logStream = await client.CoreV1.ReadNamespacedPodLogAsync(
|
||||
name,
|
||||
ns,
|
||||
container: container,
|
||||
tailLines: tailLines,
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
using var reader = new StreamReader(logStream);
|
||||
var logs = await reader.ReadToEndAsync(cancellationToken);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(logs))
|
||||
{
|
||||
return $"Логи pod '{name}' в namespace '{ns}' пустые.";
|
||||
}
|
||||
|
||||
return logs;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return FormatError("get_pod_logs", ns, ex, name);
|
||||
}
|
||||
}
|
||||
|
||||
private string ResolveNamespace(string? @namespace)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(@namespace) ? _defaultNamespace : @namespace;
|
||||
}
|
||||
|
||||
private static string BuildClientInitializationError()
|
||||
{
|
||||
return "Kubernetes клиент не инициализирован. Проверьте Kubernetes:KubeconfigPath или in-cluster окружение (KUBERNETES_SERVICE_HOST/KUBERNETES_SERVICE_PORT).";
|
||||
}
|
||||
|
||||
private bool TryGetClient(out IKubernetes client, out string error)
|
||||
{
|
||||
if (_client is null)
|
||||
{
|
||||
client = null!;
|
||||
var details = string.IsNullOrWhiteSpace(_clientInitializationError)
|
||||
? string.Empty
|
||||
: $" Детали: {_clientInitializationError}";
|
||||
error = BuildClientInitializationError() + details;
|
||||
return false;
|
||||
}
|
||||
|
||||
client = _client;
|
||||
error = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string FormatError(string toolName, string @namespace, Exception exception, string? resourceName = null)
|
||||
{
|
||||
var resourcePart = string.IsNullOrWhiteSpace(resourceName) ? string.Empty : $", resource='{resourceName}'";
|
||||
|
||||
if (exception is HttpOperationException httpException)
|
||||
{
|
||||
var statusCode = httpException.Response?.StatusCode;
|
||||
var reason = httpException.Response?.ReasonPhrase;
|
||||
var body = string.IsNullOrWhiteSpace(httpException.Response?.Content)
|
||||
? "-"
|
||||
: httpException.Response!.Content;
|
||||
|
||||
return $"Ошибка Kubernetes в tool '{toolName}' (namespace='{@namespace}'{resourcePart}): status={(int?)statusCode ?? 0} {reason}, details={body}";
|
||||
}
|
||||
|
||||
return $"Ошибка Kubernetes в tool '{toolName}' (namespace='{@namespace}'{resourcePart}): {exception.GetType().Name}: {exception.Message}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user