✨ Add env, secrets and CRDs
This commit is contained in:
parent
282a12111c
commit
855e48f6b2
6
src/main/java/dev/dinauer/EnvVar.java
Normal file
6
src/main/java/dev/dinauer/EnvVar.java
Normal file
@ -0,0 +1,6 @@
|
||||
package dev.dinauer;
|
||||
|
||||
public record EnvVar(String key, String value)
|
||||
{
|
||||
|
||||
}
|
||||
7
src/main/java/dev/dinauer/KubernetesLog.java
Normal file
7
src/main/java/dev/dinauer/KubernetesLog.java
Normal file
@ -0,0 +1,7 @@
|
||||
package dev.dinauer;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
public record KubernetesLog(LocalDateTime timestamp, String message)
|
||||
{
|
||||
}
|
||||
@ -12,7 +12,12 @@ import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.jboss.resteasy.reactive.common.NotImplementedYet;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@ -28,16 +33,44 @@ public class LogResource
|
||||
@Inject
|
||||
PodService podService;
|
||||
|
||||
@Inject
|
||||
ProcessRunner processRunner;
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<String> getLogs(@PathParam("pod-id") String podId)
|
||||
public List<KubernetesLog> getLogs(@PathParam("pod-id") String podId)
|
||||
{
|
||||
Optional<Pod> podOptional = podService.findPodById(podId);
|
||||
if(podOptional.isPresent())
|
||||
{
|
||||
Pod pod = podOptional.get();
|
||||
return List.of(clientProvider.getClient().pods().inNamespace(pod.getMetadata().getNamespace()).withName(pod.getMetadata().getName()).getLog().split("\\r?\\n"));
|
||||
return getLogs(pod, null);
|
||||
}
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
public List<KubernetesLog> getLogs(Pod pod, LocalDateTime from)
|
||||
{
|
||||
String command = String.format("kubectl logs %s -n %s --timestamps", pod.getMetadata().getName(), pod.getMetadata().getNamespace());
|
||||
List<KubernetesLog> result = new ArrayList<>();
|
||||
List<String> logs = processRunner.runToLines(command);
|
||||
for (String log : logs)
|
||||
{
|
||||
int indexFirstSpace = log.indexOf(" ");
|
||||
if (indexFirstSpace != -1)
|
||||
{
|
||||
String timestampRaw = log.substring(0, indexFirstSpace);
|
||||
String message = log.substring(indexFirstSpace).trim();
|
||||
try
|
||||
{
|
||||
result.add(new KubernetesLog(LocalDateTime.parse(timestampRaw, DateTimeFormatter.ISO_DATE_TIME), message));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,25 +14,42 @@ import jakarta.ws.rs.core.MediaType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Path("/pods")
|
||||
@Startup
|
||||
@ApplicationScoped
|
||||
@Blocking
|
||||
@Authenticated
|
||||
public class PodResource
|
||||
{
|
||||
@Inject
|
||||
PodService podService;
|
||||
|
||||
@DELETE
|
||||
@Produces
|
||||
@Consumes
|
||||
@Path("/{namespace}/{name}")
|
||||
@RolesAllowed({"admin", "maintainer"})
|
||||
public void getEnv()
|
||||
{
|
||||
@Inject
|
||||
ProcessRunner processRunner;
|
||||
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@Consumes
|
||||
@Blocking
|
||||
@Path("/{namespace}/{name}/env")
|
||||
public List<EnvVar> getEnv(@PathParam("namespace") String namespace, @PathParam("name") String name)
|
||||
{
|
||||
return getVars(podService.findByNameAndNamespace(name, namespace));
|
||||
}
|
||||
|
||||
private List<EnvVar> getVars(Pod pod)
|
||||
{
|
||||
List<EnvVar> result = new ArrayList<>();
|
||||
List<String> lines = processRunner.runToLines(String.format("kubectl exec -it %s -n %s -- env", pod.getMetadata().getName(), pod.getMetadata().getNamespace()));
|
||||
for (String line : lines)
|
||||
{
|
||||
int indexOfFirstEquals = line.indexOf("=");
|
||||
if (indexOfFirstEquals != -1)
|
||||
{
|
||||
result.add(new EnvVar(line.substring(0, indexOfFirstEquals), line.substring(indexOfFirstEquals + 1)));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ApplicationScoped
|
||||
public class ProcessRunner
|
||||
@ -18,14 +17,30 @@ public class ProcessRunner
|
||||
@Inject
|
||||
Logger LOG;
|
||||
|
||||
public String run(String command) throws IOException, InterruptedException
|
||||
public String runToText(String command)
|
||||
{
|
||||
return String.join("\n", runToLines(command));
|
||||
}
|
||||
|
||||
public List<String> runToLines(String command)
|
||||
{
|
||||
LOG.infof("Running command: %s", command);
|
||||
ProcessBuilder pb = new ProcessBuilder(command.split("\\s+"));
|
||||
pb.redirectErrorStream(true);
|
||||
|
||||
try
|
||||
{
|
||||
Process p = pb.start();
|
||||
return runAndCollectLogs(p);
|
||||
}
|
||||
catch (IOException | InterruptedException e)
|
||||
{
|
||||
throw new RuntimeException("Failed to run command " + command);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> runAndCollectLogs(Process p) throws InterruptedException
|
||||
{
|
||||
List<String> text = new ArrayList<>();
|
||||
try(BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())))
|
||||
{
|
||||
@ -35,20 +50,19 @@ public class ProcessRunner
|
||||
text.add(line);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
boolean endedInTime = p.waitFor(10, TimeUnit.SECONDS);
|
||||
p.destroy();
|
||||
if (endedInTime)
|
||||
{
|
||||
int exitCode = p.exitValue();
|
||||
if(exitCode == 0)
|
||||
{
|
||||
return String.join("\n", text);
|
||||
return text;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG.error("Process did not end in time.");
|
||||
}
|
||||
throw new RuntimeException("Error executing command: " + command);
|
||||
throw new InterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,7 +6,6 @@ import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.*;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.jboss.resteasy.reactive.common.NotImplementedYet;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@ -38,6 +37,9 @@ public class ResourceResource
|
||||
@Inject
|
||||
NodeService nodeService;
|
||||
|
||||
@Inject
|
||||
SecretService secretService;
|
||||
|
||||
@GET
|
||||
public List<?> get(@PathParam("resource") String resourceType)
|
||||
{
|
||||
@ -122,6 +124,10 @@ public class ResourceResource
|
||||
{
|
||||
return nodeService;
|
||||
}
|
||||
case ResourceType.SECRET ->
|
||||
{
|
||||
return secretService;
|
||||
}
|
||||
default ->
|
||||
{
|
||||
LOG.errorf("Invalid resource type %s.", resourceType);
|
||||
|
||||
@ -9,4 +9,5 @@ public class ResourceType
|
||||
public static final String POD = "pods";
|
||||
public static final String CUSTOM_RESOURCE_DEFINITION = "custom-resource-definitions";
|
||||
public static final String NODE = "nodes";
|
||||
public static final String SECRET = "secrets";
|
||||
}
|
||||
|
||||
73
src/main/java/dev/dinauer/monitoring/TopNodesService.java
Normal file
73
src/main/java/dev/dinauer/monitoring/TopNodesService.java
Normal file
@ -0,0 +1,73 @@
|
||||
package dev.dinauer.monitoring;
|
||||
|
||||
import dev.dinauer.ProcessRunner;
|
||||
import dev.dinauer.monitoring.nodes.NodeStats;
|
||||
import dev.dinauer.utils.ClientProvider;
|
||||
import io.fabric8.kubernetes.api.model.Node;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ApplicationScoped
|
||||
public class TopNodesService
|
||||
{
|
||||
@Inject
|
||||
ClientProvider clientProvider;
|
||||
|
||||
@Inject
|
||||
ProcessRunner processRunner;
|
||||
|
||||
public List<NodeStats> findAll()
|
||||
{
|
||||
List<NodeStats> result = new ArrayList<>();
|
||||
|
||||
List<String> stats = runTopNodesCommand();
|
||||
|
||||
|
||||
for(String nodeName : stats)
|
||||
{
|
||||
String[] parts = nodeName.split("\\s+");
|
||||
if(parts.length == 5)
|
||||
{
|
||||
String name = parts[0];
|
||||
Node node = clientProvider.getClient().nodes().withName(name).get();
|
||||
Integer absoluteCpu = extractInteger(parts[1]);
|
||||
Integer relativeCpu = extractInteger(parts[2]);
|
||||
Integer absoluteMemory = extractMemory(parts[3]);
|
||||
Integer relativeMemory = extractInteger(parts[4]);
|
||||
result.add(new NodeStats(node, absoluteCpu, relativeCpu, Integer.parseInt(node.getStatus().getAllocatable().get("cpu").getAmount()) * 1000, absoluteMemory, relativeMemory, extractMemory(node.getStatus().getAllocatable().get("memory").getAmount())));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<String> runTopNodesCommand()
|
||||
{
|
||||
String command = String.format("kubectl --kubeconfig=%s top nodes --no-headers", clientProvider.pathToKubeconfig());
|
||||
return processRunner.runToLines(command);
|
||||
}
|
||||
|
||||
private Integer extractInteger(String input)
|
||||
{
|
||||
return Integer.valueOf(input.replace("m", "").replace("%", ""));
|
||||
}
|
||||
|
||||
private Integer extractMemory(String input)
|
||||
{
|
||||
if(input.contains("Ki"))
|
||||
{
|
||||
return Integer.parseInt(input.replace("Ki", ""));
|
||||
}
|
||||
if(input.contains("Mi"))
|
||||
{
|
||||
return Integer.parseInt(input.replace("Mi", "")) * 1024;
|
||||
}
|
||||
if(input.contains("Gi"))
|
||||
{
|
||||
return Integer.parseInt(input.replace("Gi", "")) * 1024 * 1024;
|
||||
}
|
||||
return Integer.parseInt(input);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,9 @@
|
||||
package dev.dinauer.monitoring.nodes;
|
||||
|
||||
import dev.dinauer.ProcessRunner;
|
||||
import dev.dinauer.monitoring.TopNodesService;
|
||||
import dev.dinauer.monitoring.indexing.IndexingService;
|
||||
import dev.dinauer.service.NodeService;
|
||||
import dev.dinauer.utils.ClientProvider;
|
||||
import io.smallrye.common.annotation.Blocking;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
@ -15,65 +17,22 @@ import java.util.Map;
|
||||
public class NodeMonitoringService
|
||||
{
|
||||
@Inject
|
||||
ClientProvider clientProvider;
|
||||
|
||||
@Inject
|
||||
ProcessRunner processRunner;
|
||||
TopNodesService topNodesService;
|
||||
|
||||
@Inject
|
||||
IndexingService indexingService;
|
||||
|
||||
public void run() throws IOException, InterruptedException
|
||||
{
|
||||
List<NodeStats> result = new ArrayList<>();
|
||||
|
||||
String[] stats = getTopNodes().split("\n");
|
||||
|
||||
for(String nodeName : stats)
|
||||
List<NodeStats> nodes = topNodesService.findAll();
|
||||
for (NodeStats node : nodes)
|
||||
{
|
||||
String[] parts = nodeName.split("\\s+");
|
||||
if(parts.length == 5)
|
||||
{
|
||||
String name = parts[0];
|
||||
String node = clientProvider.getClient().nodes().withName(name).get().getMetadata().getUid();
|
||||
int absoluteCpu = extractInteger(parts[1]);
|
||||
int relativeCpu = extractInteger(parts[2]);
|
||||
int absoluteMemory = extractMemory(parts[3]);
|
||||
int relativeMemory = extractInteger(parts[4]);
|
||||
Map<String, Long> metrics = Map.ofEntries(
|
||||
Map.entry("RELATIVE_CPU", (long) relativeCpu),
|
||||
Map.entry("RELATIVE_MEMORY", (long) relativeMemory),
|
||||
Map.entry("ABSOLUTE_MEMORY", (long) absoluteMemory));
|
||||
Map.entry("RELATIVE_CPU", (long) node.relativeCpuUsage()),
|
||||
Map.entry("RELATIVE_MEMORY", (long) node.relativeMemory()),
|
||||
Map.entry("ABSOLUTE_MEMORY", (long) node.absoluteMemory()),
|
||||
Map.entry("ABSOLUTE_CPU", (long) node.absoluteCpuUsage()));
|
||||
indexingService.index(String.format("NODE-%s", node), "NODE_METRICS", metrics);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getTopNodes() throws IOException, InterruptedException
|
||||
{
|
||||
String command = String.format("kubectl --kubeconfig=%s top nodes --no-headers", clientProvider.pathToKubeconfig());
|
||||
return processRunner.run(command);
|
||||
}
|
||||
|
||||
private Integer extractInteger(String input)
|
||||
{
|
||||
return Integer.valueOf(input.replace("m", "").replace("%", ""));
|
||||
}
|
||||
|
||||
private Integer extractMemory(String input)
|
||||
{
|
||||
if(input.contains("Ki"))
|
||||
{
|
||||
return Integer.parseInt(input.replace("Ki", ""));
|
||||
}
|
||||
if(input.contains("Mi"))
|
||||
{
|
||||
return Integer.parseInt(input.replace("Mi", "")) * 1024;
|
||||
}
|
||||
if(input.contains("Gi"))
|
||||
{
|
||||
return Integer.parseInt(input.replace("Gi", "")) * 1024 * 1024;
|
||||
}
|
||||
return Integer.parseInt(input);
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ public class CustomResourceDefinitionService implements ResourceService<CustomRe
|
||||
@Override
|
||||
public List<CustomResourceDefinition> findByNamespace(String namespace)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
return findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -1,122 +1,22 @@
|
||||
package dev.dinauer.service;
|
||||
|
||||
import dev.dinauer.monitoring.TopNodesService;
|
||||
import dev.dinauer.monitoring.nodes.NodeStats;
|
||||
import dev.dinauer.utils.ClientProvider;
|
||||
import io.fabric8.kubernetes.api.model.Node;
|
||||
import io.smallrye.common.annotation.Blocking;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Produces;
|
||||
import jakarta.ws.rs.core.MediaType;
|
||||
import org.jboss.resteasy.reactive.common.NotImplementedYet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@ApplicationScoped
|
||||
public class NodeService implements ResourceService<NodeStats>
|
||||
{
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NodeService.class);
|
||||
|
||||
@Inject
|
||||
ClientProvider clientProvider;
|
||||
TopNodesService topNodesService;
|
||||
|
||||
@GET
|
||||
@Blocking
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public List<NodeStats> findAll()
|
||||
{
|
||||
List<NodeStats> result = new ArrayList<>();
|
||||
|
||||
List<String> stats = null;
|
||||
try
|
||||
{
|
||||
stats = getTopNodes();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
for(String nodeName : stats)
|
||||
{
|
||||
String[] parts = nodeName.split("\\s+");
|
||||
if(parts.length == 5)
|
||||
{
|
||||
String name = parts[0];
|
||||
Node node = clientProvider.getClient().nodes().withName(name).get();
|
||||
Integer absoluteCpu = extractInteger(parts[1]);
|
||||
Integer relativeCpu = extractInteger(parts[2]);
|
||||
Integer absoluteMemory = extractMemory(parts[3]);
|
||||
Integer relativeMemory = extractInteger(parts[4]);
|
||||
result.add(new NodeStats(node, absoluteCpu, relativeCpu, Integer.parseInt(node.getStatus().getAllocatable().get("cpu").getAmount()) * 1000, absoluteMemory, relativeMemory, extractMemory(node.getStatus().getAllocatable().get("memory").getAmount())));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<String> getTopNodes() throws IOException, InterruptedException
|
||||
{
|
||||
List<String> commands = List.of("kubectl", String.format("--kubeconfig=%s", clientProvider.pathToKubeconfig()), "top", "nodes", "--no-headers");
|
||||
LOG.info("Executing command: {}", String.join(" ", commands));
|
||||
ProcessBuilder pb = new ProcessBuilder(commands);
|
||||
Process p = pb.start();
|
||||
|
||||
List<String> text = new ArrayList<>();
|
||||
try(BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())))
|
||||
{
|
||||
String line;
|
||||
while((line = br.readLine()) != null)
|
||||
{
|
||||
text.add(line);
|
||||
}
|
||||
}
|
||||
boolean hasEndedInTime = p.waitFor(10, TimeUnit.SECONDS);
|
||||
p.destroy();
|
||||
if (hasEndedInTime)
|
||||
{
|
||||
int exitCode = p.waitFor();
|
||||
if(exitCode == 0)
|
||||
{
|
||||
LOG.info("Found {} nodes", text.size());
|
||||
return text;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Failed to retrieve top nodes.");
|
||||
}
|
||||
|
||||
private Integer extractInteger(String input)
|
||||
{
|
||||
return Integer.valueOf(input.replace("m", "").replace("%", ""));
|
||||
}
|
||||
|
||||
private Integer extractMemory(String input)
|
||||
{
|
||||
if(input.contains("Ki"))
|
||||
{
|
||||
return Integer.parseInt(input.replace("Ki", ""));
|
||||
}
|
||||
if(input.contains("Mi"))
|
||||
{
|
||||
return Integer.parseInt(input.replace("Mi", "")) * 1024;
|
||||
}
|
||||
if(input.contains("Gi"))
|
||||
{
|
||||
return Integer.parseInt(input.replace("Gi", "")) * 1024 * 1024;
|
||||
}
|
||||
return Integer.parseInt(input);
|
||||
return topNodesService.findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -134,7 +34,7 @@ public class NodeService implements ResourceService<NodeStats>
|
||||
@Override
|
||||
public List<NodeStats> findByNamespace(String namespace)
|
||||
{
|
||||
throw new NotImplementedYet();
|
||||
return findAll();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
47
src/main/java/dev/dinauer/service/SecretService.java
Normal file
47
src/main/java/dev/dinauer/service/SecretService.java
Normal file
@ -0,0 +1,47 @@
|
||||
package dev.dinauer.service;
|
||||
|
||||
import dev.dinauer.utils.ClientProvider;
|
||||
import io.fabric8.kubernetes.api.model.Secret;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.inject.Inject;
|
||||
import org.jboss.resteasy.reactive.common.NotImplementedYet;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@ApplicationScoped
|
||||
public class SecretService implements ResourceService<Secret>
|
||||
{
|
||||
@Inject
|
||||
ClientProvider clientProvider;
|
||||
|
||||
@Override
|
||||
public void delete(String name, String namespace)
|
||||
{
|
||||
throw new NotImplementedYet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Secret findByNameAndNamespace(String name, String namespace)
|
||||
{
|
||||
throw new NotImplementedYet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Secret> findByNamespace(String namespace)
|
||||
{
|
||||
return clientProvider.getClient().secrets().inNamespace(namespace).list().getItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Secret> findAll()
|
||||
{
|
||||
return clientProvider.getClient().secrets().inAnyNamespace().list().getItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Secret> findOptionalByNameAndNamespace(String name, String namespace)
|
||||
{
|
||||
throw new NotImplementedYet();
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user