182 lines
5.6 KiB
C#
182 lines
5.6 KiB
C#
using System.Text;
|
|
using EntKube.Clusters.Domain;
|
|
using k8s;
|
|
using k8s.Models;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace EntKube.Clusters.Features.AdoptCluster.Components.Istio;
|
|
|
|
/// <summary>
|
|
/// Checks whether Istio service mesh with Gateway API support is installed.
|
|
/// The Terraform bootstrap installs three layers:
|
|
/// 1. Gateway API CRDs (standard + experimental)
|
|
/// 2. istio-base (Istio CRDs + namespace)
|
|
/// 3. istiod (control plane with PILOT_ENABLE_GATEWAY_API=true)
|
|
///
|
|
/// This check verifies all three layers are present and healthy.
|
|
/// </summary>
|
|
public class IstioCheck : IAdoptionCheck
|
|
{
|
|
private readonly ILogger<IstioCheck> logger;
|
|
|
|
public string ComponentName => "istio";
|
|
public string DisplayName => "Istio Service Mesh + Gateway API";
|
|
|
|
public IstioCheck(ILogger<IstioCheck> logger)
|
|
{
|
|
this.logger = logger;
|
|
}
|
|
|
|
public async Task<ComponentCheckResult> CheckAsync(KubernetesCluster cluster, CancellationToken ct = default)
|
|
{
|
|
Kubernetes client = BuildClient(cluster);
|
|
List<string> details = new();
|
|
List<string> missing = new();
|
|
|
|
// Check 1: Gateway API CRDs installed (gateways.gateway.networking.k8s.io)
|
|
|
|
bool gatewayApiCrd = await CrdExists(client, "gateways.gateway.networking.k8s.io", ct);
|
|
|
|
if (gatewayApiCrd)
|
|
{
|
|
details.Add("Gateway API CRDs present");
|
|
}
|
|
else
|
|
{
|
|
missing.Add("Gateway API CRDs not installed (gateways.gateway.networking.k8s.io)");
|
|
}
|
|
|
|
// Check 2: istio-system namespace with istiod pods running
|
|
|
|
List<string> istiodPods = await FindIstiodPods(client, ct);
|
|
|
|
if (istiodPods.Count > 0)
|
|
{
|
|
details.AddRange(istiodPods.Select(p => $"Pod running: {p}"));
|
|
}
|
|
else
|
|
{
|
|
missing.Add("No istiod pods found in istio-system namespace");
|
|
}
|
|
|
|
// Check 3: Istio CRDs (VirtualService or Gateway) — confirms istio-base installed
|
|
|
|
bool istioCrd = await CrdExists(client, "virtualservices.networking.istio.io", ct);
|
|
|
|
if (istioCrd)
|
|
{
|
|
details.Add("Istio CRDs present (networking.istio.io)");
|
|
}
|
|
else
|
|
{
|
|
missing.Add("Istio base CRDs not installed (networking.istio.io)");
|
|
}
|
|
|
|
// Determine status
|
|
|
|
if (istiodPods.Count == 0 && !istioCrd)
|
|
{
|
|
return new ComponentCheckResult(ComponentName, ComponentStatus.NotInstalled, details, missing);
|
|
}
|
|
|
|
// Discover configuration from running pods
|
|
|
|
string? version = await GetIstiodVersion(client, ct);
|
|
|
|
DiscoveredConfiguration config = new(
|
|
Version: version,
|
|
Namespace: "istio-system",
|
|
HelmReleaseName: "istiod",
|
|
Values: new Dictionary<string, string>
|
|
{
|
|
["pilotReplicas"] = istiodPods.Count.ToString(),
|
|
["gatewayApiEnabled"] = gatewayApiCrd.ToString(),
|
|
["istioCrdsInstalled"] = istioCrd.ToString()
|
|
});
|
|
|
|
if (missing.Count > 0)
|
|
{
|
|
return new ComponentCheckResult(ComponentName, ComponentStatus.Degraded, details, missing, config);
|
|
}
|
|
|
|
return new ComponentCheckResult(ComponentName, ComponentStatus.Installed, details, missing, config);
|
|
}
|
|
|
|
private async Task<string?> GetIstiodVersion(Kubernetes client, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
V1PodList podList = await client.CoreV1.ListNamespacedPodAsync(
|
|
"istio-system", labelSelector: "app=istiod", cancellationToken: ct);
|
|
|
|
foreach (V1Pod pod in podList.Items)
|
|
{
|
|
if (pod.Spec?.Containers?.Count > 0)
|
|
{
|
|
string? image = pod.Spec.Containers[0].Image;
|
|
|
|
if (image?.Contains(':') == true)
|
|
{
|
|
return image.Split(':').Last().TrimStart('v');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch { }
|
|
|
|
return null;
|
|
}
|
|
|
|
private async Task<List<string>> FindIstiodPods(Kubernetes client, CancellationToken ct)
|
|
{
|
|
List<string> pods = new();
|
|
|
|
try
|
|
{
|
|
V1PodList podList = await client.CoreV1.ListNamespacedPodAsync(
|
|
"istio-system",
|
|
labelSelector: "app=istiod",
|
|
cancellationToken: ct);
|
|
|
|
foreach (V1Pod pod in podList.Items)
|
|
{
|
|
if (pod.Status?.Phase == "Running")
|
|
{
|
|
pods.Add(pod.Metadata.Name);
|
|
}
|
|
}
|
|
}
|
|
catch (k8s.Autorest.HttpOperationException ex) when (ex.Response.StatusCode == System.Net.HttpStatusCode.NotFound)
|
|
{
|
|
logger.LogDebug("istio-system namespace not found");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogWarning(ex, "Error checking for istiod pods");
|
|
}
|
|
|
|
return pods;
|
|
}
|
|
|
|
private async Task<bool> CrdExists(Kubernetes client, string crdName, CancellationToken ct)
|
|
{
|
|
try
|
|
{
|
|
await client.ApiextensionsV1.ReadCustomResourceDefinitionAsync(crdName, cancellationToken: ct);
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static Kubernetes BuildClient(KubernetesCluster cluster)
|
|
{
|
|
byte[] kubeConfigBytes = Encoding.UTF8.GetBytes(cluster.KubeConfig);
|
|
using MemoryStream stream = new(kubeConfigBytes);
|
|
KubernetesClientConfiguration config = KubernetesClientConfiguration.BuildConfigFromConfigFile(stream, cluster.ContextName);
|
|
return new Kubernetes(config);
|
|
}
|
|
}
|