Files
LazyBearWorks/LazyBear.MCP/Services/Kubernetes/K8sPodsTools.cs

167 lines
6.1 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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}";
}
}