too much for one commit
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user