mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Auto-detect prometheus installation (#343)
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
parent
6412d73a5a
commit
5c1974f07b
@ -95,7 +95,6 @@ export class Cluster implements ClusterInfo {
|
||||
this.contextName = kc.currentContext
|
||||
this.url = this.contextHandler.url
|
||||
this.apiUrl = kc.getCurrentCluster().server
|
||||
await this.contextHandler.init()
|
||||
}
|
||||
|
||||
public stopServer() {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { KubeConfig } from "@kubernetes/client-node"
|
||||
import { KubeConfig, CoreV1Api } from "@kubernetes/client-node"
|
||||
import { readFileSync } from "fs"
|
||||
import * as http from "http"
|
||||
import { ServerOptions } from "http-proxy"
|
||||
@ -7,6 +7,9 @@ import logger from "./logger"
|
||||
import { getFreePort } from "./port"
|
||||
import { KubeAuthProxy } from "./kube-auth-proxy"
|
||||
import { Cluster, ClusterPreferences } from "./cluster"
|
||||
import { prometheusProviders } from "../common/prometheus-providers"
|
||||
import { PrometheusService, PrometheusProvider } from "./prometheus/provider-registry"
|
||||
import { PrometheusLens } from "./prometheus/lens"
|
||||
|
||||
export class ContextHandler {
|
||||
public contextName: string
|
||||
@ -28,6 +31,7 @@ export class ContextHandler {
|
||||
protected defaultNamespace: string
|
||||
protected proxyPort: number
|
||||
protected kubernetesApi: string
|
||||
protected prometheusProvider: string
|
||||
protected prometheusPath: string
|
||||
protected clusterName: string
|
||||
|
||||
@ -56,7 +60,6 @@ export class ContextHandler {
|
||||
this.defaultNamespace = kc.getContextObject(kc.currentContext).namespace
|
||||
this.url = `http://${this.id}.localhost:${cluster.port}/`
|
||||
this.kubernetesApi = `http://127.0.0.1:${cluster.port}/${this.id}`
|
||||
this.setClusterPreferences(cluster.preferences)
|
||||
this.kc.clusters = [
|
||||
{
|
||||
name: kc.getCurrentCluster().name,
|
||||
@ -64,14 +67,17 @@ export class ContextHandler {
|
||||
skipTLSVerify: true
|
||||
}
|
||||
]
|
||||
this.setClusterPreferences(cluster.preferences)
|
||||
}
|
||||
|
||||
public setClusterPreferences(clusterPreferences?: ClusterPreferences) {
|
||||
this.prometheusProvider = clusterPreferences.prometheusProvider?.type
|
||||
|
||||
if (clusterPreferences && clusterPreferences.prometheus) {
|
||||
const prom = clusterPreferences.prometheus
|
||||
this.prometheusPath = `${prom.namespace}/services/${prom.service}:${prom.port}`
|
||||
} else {
|
||||
this.prometheusPath = "lens-metrics/services/prometheus:80"
|
||||
this.prometheusPath = null
|
||||
}
|
||||
if(clusterPreferences && clusterPreferences.clusterName) {
|
||||
this.clusterName = clusterPreferences.clusterName;
|
||||
@ -80,28 +86,48 @@ export class ContextHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public getPrometheusPath() {
|
||||
return this.prometheusPath
|
||||
protected async resolvePrometheusPath(): Promise<string> {
|
||||
const service = await this.getPrometheusService()
|
||||
return `${service.namespace}/services/${service.service}:${service.port}`
|
||||
}
|
||||
|
||||
public async init() {
|
||||
const currentCluster = this.kc.getCurrentCluster()
|
||||
if (currentCluster.caFile) {
|
||||
this.certData = readFileSync(currentCluster.caFile).toString()
|
||||
} else if (currentCluster.caData) {
|
||||
this.certData = Buffer.from(currentCluster.caData, "base64").toString("ascii")
|
||||
public async getPrometheusProvider() {
|
||||
if (!this.prometheusProvider) {
|
||||
const service = await this.getPrometheusService()
|
||||
logger.info(`using ${service.id} as prometheus provider`)
|
||||
this.prometheusProvider = service.id
|
||||
}
|
||||
const user = this.kc.getCurrentUser()
|
||||
if (user.authProvider && user.authProvider.name === "oidc") {
|
||||
const authConfig = user.authProvider.config
|
||||
if (authConfig["idp-certificate-authority"]) {
|
||||
this.authCertData = readFileSync(authConfig["idp-certificate-authority"]).toString()
|
||||
} else if (authConfig["idp-certificate-authority-data"]) {
|
||||
this.authCertData = Buffer.from(authConfig["idp-certificate-authority-data"], "base64").toString("ascii")
|
||||
return prometheusProviders.find(p => p.id === this.prometheusProvider)
|
||||
}
|
||||
|
||||
public async getPrometheusService(): Promise<PrometheusService> {
|
||||
const providers = this.prometheusProvider ? prometheusProviders.filter((p, _) => p.id == this.prometheusProvider) : prometheusProviders
|
||||
const prometheusPromises: Promise<PrometheusService>[] = providers.map(async (provider: PrometheusProvider): Promise<PrometheusService> => {
|
||||
const apiClient = this.kc.makeApiClient(CoreV1Api)
|
||||
return await provider.getPrometheusService(apiClient)
|
||||
})
|
||||
const resolvedPrometheusServices = await Promise.all(prometheusPromises)
|
||||
const service = resolvedPrometheusServices.filter(n => n)[0]
|
||||
if (service) {
|
||||
return service
|
||||
} else {
|
||||
return {
|
||||
id: "lens",
|
||||
namespace: "lens-metrics",
|
||||
service: "prometheus",
|
||||
port: 80
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async getPrometheusPath(): Promise<string> {
|
||||
if (this.prometheusPath) return this.prometheusPath
|
||||
|
||||
this.prometheusPath = await this.resolvePrometheusPath()
|
||||
|
||||
return this.prometheusPath
|
||||
}
|
||||
|
||||
public async getApiTarget(isWatchRequest = false) {
|
||||
if (this.apiTarget && !isWatchRequest) {
|
||||
return this.apiTarget
|
||||
|
||||
@ -1,10 +1,29 @@
|
||||
import { PrometheusLens } from "./lens"
|
||||
import { CoreV1Api } from "@kubernetes/client-node"
|
||||
import { PrometheusService } from "./provider-registry";
|
||||
import logger from "../logger"
|
||||
|
||||
export class PrometheusHelm extends PrometheusLens {
|
||||
constructor() {
|
||||
super()
|
||||
this.id = "helm"
|
||||
this.name = "Helm"
|
||||
this.rateAccuracy = "5m"
|
||||
id = "helm"
|
||||
name = "Helm"
|
||||
rateAccuracy = "5m"
|
||||
|
||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService> {
|
||||
const labelSelector = "app=prometheus,component=server,heritage=Helm"
|
||||
try {
|
||||
const serviceList = await client.listServiceForAllNamespaces(false, "", null, labelSelector)
|
||||
const service = serviceList.body.items[0]
|
||||
if (!service) return
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
namespace: service.metadata.namespace,
|
||||
service: service.metadata.name,
|
||||
port: service.spec.ports[0].port
|
||||
}
|
||||
} catch(error) {
|
||||
logger.warn(`PrometheusHelm: failed to list services: ${error.toString()}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,28 @@
|
||||
import { PrometheusProvider, PrometheusQueryOpts, PrometheusClusterQuery, PrometheusNodeQuery, PrometheusPodQuery, PrometheusPvcQuery, PrometheusIngressQuery } from "./provider-registry";
|
||||
import { PrometheusProvider, PrometheusQueryOpts, PrometheusQuery, PrometheusService } from "./provider-registry";
|
||||
import { CoreV1Api } from "@kubernetes/client-node";
|
||||
import logger from "../logger"
|
||||
|
||||
export class PrometheusLens implements PrometheusProvider {
|
||||
id = "lens"
|
||||
name = "Lens"
|
||||
rateAccuracy = "1m"
|
||||
|
||||
public getQueries(opts: PrometheusQueryOpts): PrometheusNodeQuery | PrometheusClusterQuery | PrometheusPodQuery | PrometheusPvcQuery | PrometheusIngressQuery {
|
||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService> {
|
||||
try {
|
||||
const resp = await client.readNamespacedService("prometheus", "lens-metrics")
|
||||
const service = resp.body
|
||||
return {
|
||||
id: this.id,
|
||||
namespace: service.metadata.namespace,
|
||||
service: service.metadata.name,
|
||||
port: service.spec.ports[0].port
|
||||
}
|
||||
} catch(error) {
|
||||
logger.warn(`PrometheusLens: failed to list services: ${error.toString()}`)
|
||||
}
|
||||
}
|
||||
|
||||
public getQueries(opts: PrometheusQueryOpts): PrometheusQuery {
|
||||
switch(opts.category) {
|
||||
case 'cluster':
|
||||
return {
|
||||
|
||||
@ -1,11 +1,36 @@
|
||||
import { PrometheusProvider, PrometheusQueryOpts, PrometheusClusterQuery, PrometheusNodeQuery, PrometheusPodQuery, PrometheusPvcQuery, PrometheusIngressQuery } from "./provider-registry";
|
||||
import { PrometheusProvider, PrometheusQueryOpts, PrometheusQuery, PrometheusService } from "./provider-registry";
|
||||
import { CoreV1Api, V1Service } from "@kubernetes/client-node";
|
||||
import logger from "../logger";
|
||||
|
||||
export class PrometheusOperator implements PrometheusProvider {
|
||||
rateAccuracy = "1m"
|
||||
id = "operator"
|
||||
name = "Prometheus Operator"
|
||||
|
||||
public getQueries(opts: PrometheusQueryOpts): PrometheusNodeQuery | PrometheusClusterQuery | PrometheusPodQuery | PrometheusPvcQuery | PrometheusIngressQuery {
|
||||
public async getPrometheusService(client: CoreV1Api): Promise<PrometheusService> {
|
||||
try {
|
||||
let service: V1Service
|
||||
for (const labelSelector of ["operated-prometheus=true", "self-monitor=true"]) {
|
||||
if (!service) {
|
||||
const serviceList = await client.listServiceForAllNamespaces(null, null, null, labelSelector)
|
||||
service = serviceList.body.items[0]
|
||||
}
|
||||
}
|
||||
if (!service) return
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
namespace: service.metadata.namespace,
|
||||
service: service.metadata.name,
|
||||
port: service.spec.ports[0].port
|
||||
}
|
||||
} catch(error) {
|
||||
logger.warn(`PrometheusOperator: failed to list services: ${error.toString()}`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
public getQueries(opts: PrometheusQueryOpts): PrometheusQuery {
|
||||
switch(opts.category) {
|
||||
case 'cluster':
|
||||
return {
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { CoreV1Api } from "@kubernetes/client-node"
|
||||
|
||||
export type PrometheusClusterQuery = {
|
||||
memoryUsage: string;
|
||||
memoryRequests: string;
|
||||
@ -48,8 +50,20 @@ export type PrometheusQueryOpts = {
|
||||
[key: string]: string | any;
|
||||
};
|
||||
|
||||
export type PrometheusQuery = PrometheusNodeQuery | PrometheusClusterQuery | PrometheusPodQuery | PrometheusPvcQuery | PrometheusIngressQuery
|
||||
|
||||
export type PrometheusService = {
|
||||
id: string;
|
||||
namespace: string;
|
||||
service: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export interface PrometheusProvider {
|
||||
getQueries(opts: PrometheusQueryOpts): PrometheusNodeQuery | PrometheusClusterQuery | PrometheusPodQuery | PrometheusPvcQuery | PrometheusIngressQuery;
|
||||
id: string;
|
||||
name: string;
|
||||
getQueries(opts: PrometheusQueryOpts): PrometheusQuery;
|
||||
getPrometheusService(client: CoreV1Api): Promise<PrometheusService>;
|
||||
}
|
||||
|
||||
export type PrometheusProviderList = {
|
||||
|
||||
@ -83,8 +83,6 @@ export class LensProxy {
|
||||
}, (250 * retryCount))
|
||||
}
|
||||
}
|
||||
|
||||
//return
|
||||
}
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'text/plain'
|
||||
|
||||
@ -13,7 +13,6 @@ class MetricsRoute extends LensApi {
|
||||
const { response, cluster} = request
|
||||
const query: MetricsQuery = request.payload;
|
||||
const serverUrl = `http://127.0.0.1:${cluster.port}/api-kube`
|
||||
const metricsUrl = `${serverUrl}/api/v1/namespaces/${cluster.contextHandler.getPrometheusPath()}/proxy/api/v1/query_range`
|
||||
const headers = {
|
||||
"Host": `${cluster.id}.localhost:${cluster.port}`,
|
||||
"Content-type": "application/json",
|
||||
@ -23,10 +22,12 @@ class MetricsRoute extends LensApi {
|
||||
queryParams[key] = value
|
||||
})
|
||||
|
||||
const prometheusInstallationSource = cluster.preferences.prometheusProvider?.type || "lens"
|
||||
let metricsUrl: string
|
||||
let prometheusProvider: PrometheusProvider
|
||||
try {
|
||||
prometheusProvider = PrometheusProviderRegistry.getProvider(prometheusInstallationSource)
|
||||
const prometheusPath = await cluster.contextHandler.getPrometheusPath()
|
||||
metricsUrl = `${serverUrl}/api/v1/namespaces/${prometheusPath}/proxy/api/v1/query_range`
|
||||
prometheusProvider = await cluster.contextHandler.getPrometheusProvider()
|
||||
} catch {
|
||||
this.respondJson(response, {})
|
||||
return
|
||||
|
||||
@ -20,17 +20,7 @@
|
||||
<div class="cluster-settings-section">
|
||||
<b>Prometheus</b>
|
||||
<p>Use pre-installed Prometheus service for metrics. Please refer to the <a href="https://github.com/lensapp/lens/blob/master/troubleshooting/custom-prometheus.md">guide</a> for possible configuration changes.</p>
|
||||
<b-form-group
|
||||
label="Prometheus service address."
|
||||
description="A path to an existing Prometheus installation (<namespace>/<service>:<port>)."
|
||||
>
|
||||
<b-form-input
|
||||
v-model="prometheusPath"
|
||||
placeholder="lens-metrics/prometheus:80"
|
||||
id="input-prometheuspath"
|
||||
@blur="onPrometheusSave"
|
||||
/>
|
||||
</b-form-group>
|
||||
|
||||
<b-form-group
|
||||
label="Prometheus installation method."
|
||||
description="What query format is used to fetch metrics from Prometheus"
|
||||
@ -41,6 +31,18 @@
|
||||
@change="onPrometheusProviderSave"
|
||||
/>
|
||||
</b-form-group>
|
||||
<b-form-group
|
||||
label="Prometheus service address."
|
||||
description="An address to an existing Prometheus installation (<namespace>/<service>:<port>). Lens tries to auto-detect address if left empty."
|
||||
v-if="canEditPrometheusPath"
|
||||
>
|
||||
<b-form-input
|
||||
v-model="prometheusPath"
|
||||
placeholder="<namespace>/<service>:<port>"
|
||||
id="input-prometheuspath"
|
||||
@blur="onPrometheusSave"
|
||||
/>
|
||||
</b-form-group>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
@ -84,13 +86,25 @@ export default {
|
||||
},
|
||||
prometheusPath: "",
|
||||
prometheusProvider: "",
|
||||
prometheusProviders: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
prometheusProviders: function() {
|
||||
const providers = prometheusProviders.map((provider) => {
|
||||
return { text: provider.name, value: provider.id }
|
||||
})
|
||||
providers.unshift({text: "Auto detect", value: ""})
|
||||
|
||||
return providers;
|
||||
},
|
||||
canEditPrometheusPath: function() {
|
||||
if (this.prometheusProvider === "") return false
|
||||
if (this.prometheusProvider === "lens") return false
|
||||
|
||||
return true
|
||||
}
|
||||
},
|
||||
mounted: async function() {
|
||||
this.prometheusProviders = prometheusProviders.map((provider) => {
|
||||
return { text: provider.name, value: provider.id }
|
||||
})
|
||||
this.updateValues()
|
||||
},
|
||||
methods: {
|
||||
@ -104,7 +118,7 @@ export default {
|
||||
if (this.cluster.preferences.prometheusProvider) {
|
||||
this.prometheusProvider = this.cluster.preferences.prometheusProvider.type
|
||||
} else {
|
||||
this.prometheusProvider = "lens"
|
||||
this.prometheusProvider = ""
|
||||
}
|
||||
},
|
||||
parsePrometheusPath: function(path) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user