show lens-metrics on cluster settings

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen committed 2021-05-06 17:00:39 +03:00
1 parent 7b1b8e6321
commit 8b0b7fcbb2
18 files changed
+311 -84

No files matched your search

+21 -49
View File
@@ -1,55 +1,27 @@
import { LensRendererExtension, Interface, Component, Catalog} from "@k8slens/extensions";
import { MetricsFeature } from "./src/metrics-feature";
import React from "react";
import { LensRendererExtension, Catalog } from "@k8slens/extensions";
import { MetricsSettings } from "./src/metrics-settings";
export default class ClusterMetricsFeatureExtension extends LensRendererExtension {
onActivate() {
const category = Catalog.catalogCategories.getForGroupKind<Catalog.KubernetesClusterCategory>("entity.k8slens.dev", "KubernetesCluster");
entitySettings = [
{
apiVersions: ["entity.k8slens.dev/v1alpha1"],
kind: "KubernetesCluster",
title: "Lens Metrics",
priority: 5,
components: {
View: (props: {entity: Catalog.KubernetesCluster}) => { // eslint-disable-line
const cluster = props.entity; // eslint-disable-line
if (!category) {
return;
}
category.on("contextMenuOpen", this.clusterContextMenuOpen.bind(this));
}
async clusterContextMenuOpen(cluster: Catalog.KubernetesCluster, ctx: Interface.CatalogEntityContextMenuContext) {
if (!cluster.status.active) {
return;
}
const metricsFeature = new MetricsFeature();
await metricsFeature.updateStatus(cluster);
if (metricsFeature.status.installed) {
if (metricsFeature.status.canUpgrade) {
ctx.menuItems.unshift({
icon: "refresh",
title: "Upgrade Lens Metrics stack",
onClick: async () => {
metricsFeature.upgrade(cluster);
}
});
return (
<section>
<section>
<MetricsSettings cluster={cluster} />
</section>
</section>
);
}
}
ctx.menuItems.unshift({
icon: "toggle_off",
title: "Uninstall Lens Metrics stack",
onClick: async () => {
await metricsFeature.uninstall(cluster);
Component.Notifications.info(`Lens Metrics has been removed from ${cluster.metadata.name}`, { timeout: 10_000 });
}
});
} else {
ctx.menuItems.unshift({
icon: "toggle_on",
title: "Install Lens Metrics stack",
onClick: async () => {
metricsFeature.install(cluster);
Component.Notifications.info(`Lens Metrics is now installed to ${cluster.metadata.name}`, { timeout: 10_000 });
}
});
}
}
];
}
@@ -1,3 +1,4 @@
{{#if prometheus.enabled}}
apiVersion: v1
kind: Service
metadata:
@@ -14,3 +15,4 @@ spec:
protocol: TCP
port: 80
targetPort: 9090
{{/if}}
@@ -1,3 +1,4 @@
{{#if prometheus.enabled}}
apiVersion: apps/v1
kind: StatefulSet
metadata:
@@ -53,7 +54,7 @@ spec:
mountPath: /var/lib/prometheus
containers:
- name: prometheus
image: quay.io/prometheus/prometheus:v2.19.3
image: quay.io/prometheus/prometheus:v2.20.1
args:
- --web.listen-address=0.0.0.0:9090
- --config.file=/etc/prometheus/prometheus.yaml
@@ -114,3 +115,4 @@ spec:
requests:
storage: {{persistence.size}}
{{/if}}
{{/if}}
@@ -4,6 +4,9 @@ import * as path from "path";
export interface MetricsConfiguration {
// Placeholder for Metrics config structure
prometheus: {
enabled: boolean;
};
persistence: {
enabled: boolean;
storageClass: string;
@@ -29,20 +32,23 @@ export class MetricsFeature extends ClusterFeature.Feature {
latestVersion = "v2.19.3-lens1";
templateContext: MetricsConfiguration = {
prometheus: {
enabled: false
},
persistence: {
enabled: false,
storageClass: null,
size: "20G",
},
nodeExporter: {
enabled: true,
enabled: false,
},
retention: {
time: "2d",
size: "5GB",
},
kubeStateMetrics: {
enabled: true,
enabled: false,
},
alertManagers: null,
replicas: 1,
@@ -59,7 +65,7 @@ export class MetricsFeature extends ClusterFeature.Feature {
sc.metadata?.annotations?.["storageclass.beta.kubernetes.io/is-default-class"] === "true"
));
super.applyResources(cluster, path.join(__dirname, "../resources/"));
await super.applyResources(cluster, path.join(__dirname, "../resources/"));
}
async upgrade(cluster: Catalog.KubernetesCluster): Promise<void> {
@@ -75,6 +81,13 @@ export class MetricsFeature extends ClusterFeature.Feature {
this.status.installed = true;
this.status.currentVersion = prometheus.spec.template.spec.containers[0].image.split(":")[1];
this.status.canUpgrade = semver.lt(this.status.currentVersion, this.latestVersion, true);
const pvcTemplate = prometheus.spec.volumeClaimTemplates[0];
if (pvcTemplate) {
this.templateContext.persistence.enabled = true;
this.templateContext.persistence.size = pvcTemplate.spec.resources.requests.storage;
}
} else {
this.status.installed = false;
}
@@ -0,0 +1,213 @@
import React from "react";
import { Component, Catalog, K8sApi } from "@k8slens/extensions";
import { observer } from "mobx-react";
import { observable } from "mobx";
import { MetricsFeature } from "./metrics-feature";
interface Props {
cluster: Catalog.KubernetesCluster;
}
@observer
export class MetricsSettings extends React.Component<Props> {
@observable featureStates = {
prometheus: false,
kubeStateMetrics: false,
nodeExporter: false
};
@observable canUpgrade = false;
@observable upgrading = false;
pvcOptions = [
{ value: "", label: "Use EmptyDir (no PVC)" },
{ value: "5GB", label: "5GB" },
{ value: "10GB", label: "10GB" },
{ value: "20GB", label: "20GB" },
{ value: "40GB", label: "40GB" },
{ value: "80GB", label: "80GB" },
];
featureSet: MetricsFeature;
async componentDidMount() {
this.featureSet = new MetricsFeature();
await this.updateFeatureStates();
}
async updateFeatureStates() {
await this.featureSet.updateStatus(this.props.cluster);
this.canUpgrade = this.featureSet.status.canUpgrade;
this.featureStates.prometheus = this.featureSet.status.installed;
const deployment = K8sApi.forCluster(this.props.cluster, K8sApi.Deployment);
try {
await deployment.get({name: "kube-state-metrics", namespace: "lens-metrics"});
this.featureStates.kubeStateMetrics = true;
} catch(e) {
if (e?.error?.code === 404) {
this.featureStates.kubeStateMetrics = false;
} else {
this.featureStates.kubeStateMetrics = undefined;
}
}
const daemonSet = K8sApi.forCluster(this.props.cluster, K8sApi.DaemonSet);
try {
await daemonSet.get({name: "node-exporter", namespace: "lens-metrics"});
this.featureStates.nodeExporter = true;
} catch(e) {
if (e?.error?.code === 404) {
this.featureStates.nodeExporter = false;
} else {
this.featureStates.nodeExporter = undefined;
}
}
}
async save() {
const ctx = this.featureSet.templateContext;
ctx.prometheus.enabled = !!this.featureStates.prometheus;
ctx.kubeStateMetrics.enabled = !!this.featureStates.kubeStateMetrics;
ctx.nodeExporter.enabled = !!this.featureStates.nodeExporter;
if (!ctx.prometheus.enabled && !ctx.kubeStateMetrics.enabled && !ctx.nodeExporter.enabled) {
await this.featureSet.uninstall(this.props.cluster);
} else {
await this.featureSet.install(this.props.cluster);
}
}
async togglePrometheus(enabled: boolean) {
this.featureStates.prometheus = enabled;
this.featureSet.templateContext.prometheus.enabled = this.featureStates.prometheus;
try {
await this.save();
} catch(error) {
this.featureStates.prometheus = !enabled;
Component.Notifications.error(`Failed to ${enabled ? "enable" : "disable"} Prometheus: ${error}`);
}
}
async toggleKubeStateMetrics(enabled: boolean) {
this.featureStates.kubeStateMetrics = enabled;
this.featureSet.templateContext.kubeStateMetrics.enabled = this.featureStates.kubeStateMetrics;
try {
await this.save();
} catch(error) {
this.featureStates.kubeStateMetrics = !enabled;
Component.Notifications.error(`Failed to ${enabled ? "enable" : "disable"} kube-state-metrics: ${error}`);
}
}
async toggleNodeExporter(enabled: boolean) {
this.featureStates.nodeExporter = enabled;
this.featureSet.templateContext.nodeExporter.enabled = this.featureStates.nodeExporter;
try {
await this.save();
} catch(error) {
this.featureStates.nodeExporter = !enabled;
Component.Notifications.error(`Failed to ${enabled ? "enable" : "disable"} node-exporter: ${error}`);
}
}
async updateStack() {
this.upgrading = true;
await this.save();
setTimeout(() => {
Component.Notifications.info("Lens Metrics stack updated!", {timeout: 5_000});
this.upgrading = false;
this.canUpgrade = false;
}, 1000);
}
render() {
return (
<>
{ !this.props.cluster.status.active && (
<section>
<p style={ {color: "var(--colorError)"} }>
Lens Metrics settings requires established connection to the cluster.
</p>
</section>
)}
{ this.canUpgrade && (
<section>
<Component.SubTitle title="Software Update" />
<Component.Button label={this.upgrading ? "Updating ..." : "Update Now"} primary onClick={() => this.updateStack() } waiting={this.upgrading} />
<small className="hint">
An update is available for installed metrics components.
</small>
</section>
)}
<section>
<Component.SubTitle title="Prometheus" />
<Component.FormSwitch
control={
<Component.Switcher
disabled={this.featureStates.kubeStateMetrics === undefined || !this.props.cluster.status.active}
checked={!!this.featureStates.prometheus && this.props.cluster.status.active}
onChange={v => this.togglePrometheus(v.target.checked)}
name="prometheus"
/>
}
label="Install built-in Prometheus metrics stack"
/>
<small className="hint">
Enable timeseries data visualization (Prometheus stack) for your cluster.
</small>
</section>
<section>
<Component.SubTitle title="Kube State Metrics" />
<Component.FormSwitch
control={
<Component.Switcher
disabled={this.featureStates.kubeStateMetrics === undefined || !this.props.cluster.status.active}
checked={!!this.featureStates.kubeStateMetrics && this.props.cluster.status.active}
onChange={v => this.toggleKubeStateMetrics(v.target.checked)}
name="node-exporter"
/>
}
label="Install built-in kube-state-metrics stack"
/>
<small className="hint">
Enable Kubernetes API object metrics for your cluster.
Install this only if you don&apos;t have existing kube-state-metrics stack installed.
</small>
</section>
<section>
<Component.SubTitle title="Node Exporter" />
<Component.FormSwitch
control={
<Component.Switcher
disabled={this.featureStates.nodeExporter === undefined || !this.props.cluster.status.active}
checked={!!this.featureStates.nodeExporter && this.props.cluster.status.active}
onChange={v => this.toggleNodeExporter(v.target.checked)}
name="node-exporter"
/>
}
label="Install built-in node-exporter stack"
/>
<small className="hint">
Enable node level metrics for your cluster.
Install this only if you don&apos;t have existing node-exporter stack installed.
</small>
</section>
</>
);
}
}
@@ -16,8 +16,8 @@
"jsx": "react"
},
"include": [
"./*.ts",
"./*.tsx"
"./**/*.ts",
"./**/*.tsx"
],
"exclude": [
"node_modules",