Skip to content

Commit

Permalink
fix: Fix ingress panels when multiple controllers present in the cluster
Browse files Browse the repository at this point in the history
When there are multiple ingress controllers in a cluster and ingressclasses are used then
controller names must be different. Instead of detecting nginx based on controller name try to join
controller class and nginx build info to detect ingress controller type.
  • Loading branch information
tiithansen committed Jan 19, 2025
1 parent 2c107de commit fe3659e
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 69 deletions.
90 changes: 61 additions & 29 deletions src/common/promql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,6 @@ enum MatchingModifiers {
ON = 'on',
}

class PromQLMatchingModifier extends PromQLExpression {

constructor(private modifier: MatchingModifiers, private labels: string[], private left: PromQLExpression) {
super();
}

stringify() {
return `${this.left.stringify()} ${this.modifier}(${this.labels.join(', ')}) `;
}

groupLeft(labels: string[], vectorExpr: PromQLVectorExpression) {
return new PromQLGroupModifier(GroupModifiers.GROUP_LEFT, labels, this, vectorExpr);
}

groupRight(labels: string[], vectorExpr: PromQLVectorExpression) {
return new PromQLGroupModifier(GroupModifiers.GROUP_RIGHT, labels, this, vectorExpr);
}
}

enum BinaryOperators {
ADD = '+',
SUBTRACT = '-',
Expand All @@ -50,9 +31,6 @@ enum BinaryOperators {
POW = '^',
}




export abstract class PromQLVectorExpression extends PromQLExpression {

add() {
Expand All @@ -79,19 +57,49 @@ export abstract class PromQLVectorExpression extends PromQLExpression {
return new PromQLBinaryExpression(BinaryOperators.POW, this);
}

or(vectorExpr: PromQLVectorExpression) {
return new PromQLLogicalExpression(LogicalOperators.OR, this, vectorExpr);
or() {
return new PromQLLogicalExpression(LogicalOperators.OR, this);
}

and(vectorExpr: PromQLVectorExpression) {
return new PromQLLogicalExpression(LogicalOperators.AND, this, vectorExpr);
and() {
return new PromQLLogicalExpression(LogicalOperators.AND, this);
}

equals(value: number) {
return new PromQLComparisonExpression(ComparisonOperators.EQUALS, this, new PromQLScalarExpression(value));
}
}

class PromQLMatchingModifier extends PromQLVectorExpression {

private right?: PromQLExpression;

constructor(private modifier: MatchingModifiers, private labels: string[], private left: PromQLExpression) {
super();
}

stringify() {
if (this.right) {
return `${this.left.stringify()} ${this.modifier}(${this.labels.join(', ')}) ${this.right.stringify()}`;
} else {
return `${this.left.stringify()} ${this.modifier}(${this.labels.join(', ')})`;
}
}

groupLeft(labels: string[], vectorExpr: PromQLVectorExpression) {
return new PromQLGroupModifier(GroupModifiers.GROUP_LEFT, labels, this, vectorExpr);
}

groupRight(labels: string[], vectorExpr: PromQLVectorExpression) {
return new PromQLGroupModifier(GroupModifiers.GROUP_RIGHT, labels, this, vectorExpr);
}

withExpression(expr: PromQLExpression) {
this.right = expr;
return PromQL.parenthesis(this);
}
}

class PromQLBinaryExpression extends PromQLVectorExpression {

private right?: PromQLExpression;
Expand Down Expand Up @@ -142,12 +150,36 @@ enum LogicalOperators {
}

class PromQLLogicalExpression extends PromQLVectorExpression {
constructor(private operator: LogicalOperators, private left: PromQLExpression, private right: PromQLExpression) {

private right?: PromQLExpression;

constructor(private operator: LogicalOperators, private left: PromQLExpression) {
super();
}

stringify() {
return `${this.left.stringify()} ${this.operator} (${this.right.stringify()}) `;
if (this.right) {
return `${this.left.stringify()} ${this.operator} ${this.right.stringify()}`;
} else {
return `${this.left.stringify()} ${this.operator}`;
}
}

ignoring(labels: string[]) {
return new PromQLMatchingModifier(MatchingModifiers.IGNORING, labels, this);
}

on(labels: string[]) {
return new PromQLMatchingModifier(MatchingModifiers.ON, labels, this);
}

withScalar(scalar: number) {
return new PromQLScalarExpression(scalar, this);
}

withExpression(expr: PromQLExpression) {
this.right = expr;
return PromQL.parenthesis(this);
}
}

Expand Down Expand Up @@ -482,7 +514,7 @@ export class PromQL {
}

static labelReplace(exp: PromQLVectorExpression, dest: string, sourceLabel: string, replacement: string, regex: string) {
return new PromQLLabelReplaceFunction(exp, dest, sourceLabel, replacement, regex);
return new PromQLLabelReplaceFunction(exp, dest, replacement, sourceLabel, regex);
}

static parenthesis(expr: PromQLVectorExpression) {
Expand Down
7 changes: 7 additions & 0 deletions src/metrics/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,13 @@ export const Metrics = {
controller: 'controller',
}
},
// Nginx Ingress Controller
nginxIngressControllerBuildInfo: {
name: 'nginx_ingress_controller_build_info',
labels:{
controllerClass: 'controller_class',
}
},
// Services
kubeServiceInfo: {
name: 'kube_service_info',
Expand Down
20 changes: 15 additions & 5 deletions src/pages/Clusters/tabs/Nodes/Queries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ export class NodesQueryBuilder implements QueryBuilder<TableRow> {
value: ''
}
})
).or(
)
.or()
.withExpression(
baseQuery.multiply().withScalar(0)
)
)
Expand All @@ -70,7 +72,9 @@ export class NodesQueryBuilder implements QueryBuilder<TableRow> {
...commonCarryOverLabels,
],
this.createCpuRequestsQuery('$cluster', {})
).or(
)
.or()
.withExpression(
baseQuery.multiply().withScalar(0)
)
)
Expand All @@ -87,7 +91,9 @@ export class NodesQueryBuilder implements QueryBuilder<TableRow> {
...commonCarryOverLabels,
],
this.createCoresQuery('$cluster', {})
).or(
)
.or()
.withExpression(
baseQuery.multiply().withScalar(0)
)
)
Expand All @@ -104,7 +110,9 @@ export class NodesQueryBuilder implements QueryBuilder<TableRow> {
...commonCarryOverLabels,
],
this.createPodCountQuery('$cluster', {})
).or(
)
.or()
.withExpression(
baseQuery.multiply().withScalar(0)
)
)
Expand All @@ -121,7 +129,9 @@ export class NodesQueryBuilder implements QueryBuilder<TableRow> {
...commonCarryOverLabels,
],
this.createNodeAgeQuery('$cluster', {})
).or(
)
.or()
.withExpression(
baseQuery.multiply().withScalar(0)
)
)
Expand Down
72 changes: 44 additions & 28 deletions src/pages/Network/pages/ingresses/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ import { usePluginJsonData } from "utils/utils.plugin";
import { createTimeRange, createTopLevelVariables } from "common/variableHelpers";
import { AlertsTable } from "components/AlertsTable";
import { createResourceLabels } from "pages/Workloads/components/ResourceLabels";
import { PromQL } from "common/promql";
import { MatchOperators, PromQL } from "common/promql";
import { Metrics } from "metrics/metrics";
import React, { useMemo } from "react";
import { DataFrameView } from "@grafana/data";
import { Spinner } from "@grafana/ui";
import {
getNginxFailureRatioPanel,
Expand All @@ -45,7 +44,7 @@ import Analytics from "components/Analytics";
// Try connecting kube_ingress_path service_name to pods

interface ConditionalSceneObjectState extends SceneObjectState {
builder: (data: string) => SceneObject<SceneObjectState>;
builder: (rowCounts: Map<string, number>) => SceneObject<SceneObjectState>;
children?: Array<SceneObject<SceneObjectState>>;
}

Expand All @@ -63,16 +62,16 @@ function ConditionalRenderer({ model }: SceneComponentProps<ConditionalSceneObje
return [];
}

const frame = data.series[0];
const view = new DataFrameView<any>(frame);
const rows = view.toArray();

const controller = rows && rows.length > 0 ? rows[0].controller : undefined;
const rowCounts = new Map<string, number>();
// result counts per query
for (const serie of data.series) {
rowCounts.set(serie.refId || 'unknown', serie.length);
}

// By setting it via state we can trigger render but also grafana connects the model to the scene graph
// so that all nested objects could use the variables ...
model.setState({
children: [builder(controller)]
children: [builder(rowCounts)]
});

return;
Expand All @@ -92,19 +91,33 @@ function ConditionalRenderer({ model }: SceneComponentProps<ConditionalSceneObje
}
}

function ingressInfoQuery(namespace: string, ingress: string) {
return PromQL.metric(Metrics.kubeIngressInfo.name)
.withLabelEquals('namespace', namespace)
.withLabelEquals('ingress', ingress)
.withLabelEquals('cluster', '$cluster')
.multiply()
.on([
Metrics.kubeIngressInfo.labels.ingressClass
])
.groupLeft([
Metrics.kubeIngressClassInfo.labels.controller
], PromQL.metric(Metrics.kubeIngressClassInfo.name)
.withLabelEquals('cluster', '$cluster')
function nginxBuildInfoQuery(namespace: string, ingress: string) {

return PromQL.metric(Metrics.nginxIngressControllerBuildInfo.name)
.withLabel('cluster', MatchOperators.EQUALS, '$cluster')
.and()
.on(['controller_class', 'cluster'])
.withExpression(
PromQL.labelReplace(
PromQL.metric(
Metrics.kubeIngressInfo.name
)
.withLabel('cluster', MatchOperators.EQUALS, '$cluster')
.withLabel('ingress', MatchOperators.EQUALS, ingress)
.multiply()
.on(['ingressclass', 'cluster'])
.groupLeft(
[
'controller'
],
PromQL.metric(Metrics.kubeIngressClassInfo.name)
.withLabel('cluster', MatchOperators.EQUALS, '$cluster')
),
'controller_class',
'controller',
'$1',
'(.*)'
)
)
}

Expand Down Expand Up @@ -201,8 +214,11 @@ function displayBasicNginxMetrics(ingress: string, namespace: string) {
});
}

function buildRequestsPanels(controller: string, ingress: string, namespace: string) {
if (controller === 'k8s.io/ingress-nginx') {
function buildRequestsPanels(rowCounts: Map<string, number>, ingress: string, namespace: string) {

const nginxBuildInfo = rowCounts.get('nginx_ingress_controller_build_info') || 0;

if (nginxBuildInfo > 0) {
return displayBasicNginxMetrics(ingress, namespace);
} else {
return new SceneFlexLayout({
Expand All @@ -222,8 +238,8 @@ function getScene(namespace: string, ingress: string) {
},
queries: [
{
refId: 'ingresses',
expr: ingressInfoQuery(namespace, ingress).stringify(),
refId: 'nginx_ingress_controller_build_info',
expr: nginxBuildInfoQuery(namespace, ingress).stringify(),
instant: true,
format: 'table'
},
Expand Down Expand Up @@ -289,8 +305,8 @@ function getScene(namespace: string, ingress: string) {
width: '100%',
body: new ConditionalSceneObject({
$data: ingressInfoData,
builder: (controller: string) => {
return buildRequestsPanels(controller, ingress, namespace)
builder: (rowCounts: Map<string, number>) => {
return buildRequestsPanels(rowCounts, ingress, namespace)
}
}),
}),
Expand Down
8 changes: 6 additions & 2 deletions src/pages/Workloads/tabs/DaemonSets/Queries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ export class DaemonSetsQueryBuilder implements QueryBuilder<TableRow> {
}
})
).by(['namespace', 'daemonset'])
).or(
)
.or()
.withExpression(
baseQuery.multiply().withScalar(0)
)
)
Expand All @@ -94,7 +96,9 @@ export class DaemonSetsQueryBuilder implements QueryBuilder<TableRow> {
.groupRight(
[],
createReplicasQuery('$cluster', {})
).or(
)
.or()
.withExpression(
baseQuery.multiply().withScalar(0)
)
)
Expand Down
8 changes: 6 additions & 2 deletions src/pages/Workloads/tabs/Deployments/Queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ export class DeploymentQueryBuilder implements QueryBuilder<TableRow> {
}
})
).by(['namespace', 'deployment'])
).or(
)
.or()
.withExpression(
baseQuery.multiply().withScalar(0)
)
)
Expand All @@ -128,7 +130,9 @@ export class DeploymentQueryBuilder implements QueryBuilder<TableRow> {
.groupRight(
[],
createReplicasQuery('$cluster', {})
).or(
)
.or()
.withExpression(
baseQuery.multiply().withScalar(0)
)
)
Expand Down
4 changes: 3 additions & 1 deletion src/pages/Workloads/tabs/Pods/Queries.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit fe3659e

Please sign in to comment.