From 72132aa5ce4f6c913ccff4dc8c56cbb324f1b996 Mon Sep 17 00:00:00 2001 From: Haoyu Sun <hasun@redhat.com> Date: Fri, 27 Aug 2021 17:55:25 +0200 Subject: [PATCH] add rule patching function --- examples/rule-patcher.jsonnet | 31 ++++ examples/rule-patches.libsonnet | 26 +++ .../lib/rule-sanitizer.libsonnet | 156 ++++++++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 examples/rule-patcher.jsonnet create mode 100644 examples/rule-patches.libsonnet create mode 100644 jsonnet/kube-prometheus/lib/rule-sanitizer.libsonnet diff --git a/examples/rule-patcher.jsonnet b/examples/rule-patcher.jsonnet new file mode 100644 index 00000000..59d18ddf --- /dev/null +++ b/examples/rule-patcher.jsonnet @@ -0,0 +1,31 @@ +local kp = + (import 'kube-prometheus/main.libsonnet') + + { + values+:: { + common+: { + namespace: 'monitoring', + }, + }, + }; + +local rulePatches = import 'rule-patches.libsonnet'; + +local sanitizePrometheusRules = (import 'kube-prometheus/lib/rule-sanitizer.libsonnet')(rulePatches).sanitizePrometheusRules; + +sanitizePrometheusRules({ 'setup/0namespace-namespace': kp.kubePrometheus.namespace } + + { + ['setup/prometheus-operator-' + name]: kp.prometheusOperator[name] + for name in std.filter((function(name) name != 'serviceMonitor' && name != 'prometheusRule'), std.objectFields(kp.prometheusOperator)) + } + + // serviceMonitor and prometheusRule are separated so that they can be created after the CRDs are ready + { 'prometheus-operator-serviceMonitor': kp.prometheusOperator.serviceMonitor } + + { 'prometheus-operator-prometheusRule': kp.prometheusOperator.prometheusRule } + + { 'kube-prometheus-prometheusRule': kp.kubePrometheus.prometheusRule } + + { ['alertmanager-' + name]: kp.alertmanager[name] for name in std.objectFields(kp.alertmanager) } + + { ['blackbox-exporter-' + name]: kp.blackboxExporter[name] for name in std.objectFields(kp.blackboxExporter) } + + { ['grafana-' + name]: kp.grafana[name] for name in std.objectFields(kp.grafana) } + + { ['kube-state-metrics-' + name]: kp.kubeStateMetrics[name] for name in std.objectFields(kp.kubeStateMetrics) } + + { ['kubernetes-' + name]: kp.kubernetesControlPlane[name] for name in std.objectFields(kp.kubernetesControlPlane) } + { ['node-exporter-' + name]: kp.nodeExporter[name] for name in std.objectFields(kp.nodeExporter) } + + { ['prometheus-' + name]: kp.prometheus[name] for name in std.objectFields(kp.prometheus) } + + { ['prometheus-adapter-' + name]: kp.prometheusAdapter[name] for name in std.objectFields(kp.prometheusAdapter) }) diff --git a/examples/rule-patches.libsonnet b/examples/rule-patches.libsonnet new file mode 100644 index 00000000..65bd863f --- /dev/null +++ b/examples/rule-patches.libsonnet @@ -0,0 +1,26 @@ +{ + excludedRuleGroups: [ + 'alertmanager.rules', + ], + excludedRules: [ + { + name: 'prometheus-operator', + rules: [ + { alert: 'PrometheusOperatorListErrors' }, + ], + }, + ], + patchedRules: [ + { + name: 'prometheus-operator', + rules: [ + { + alert: 'PrometheusOperatorWatchErrors', + labels: { + severity: 'info', + }, + }, + ], + }, + ], +} diff --git a/jsonnet/kube-prometheus/lib/rule-sanitizer.libsonnet b/jsonnet/kube-prometheus/lib/rule-sanitizer.libsonnet new file mode 100644 index 00000000..00cc27ce --- /dev/null +++ b/jsonnet/kube-prometheus/lib/rule-sanitizer.libsonnet @@ -0,0 +1,156 @@ +local defaults = { + /* name of rule groups to exclude */ + excludedRuleGroups: [], + /* Rule match is based on field "alert" or "record" for excludedRules and patchedRules. + * When multiple match is found, we can use a "index" field to distingush each rule, + * which represents their order of appearance. For example, if we have two rules: + * [ + * { + * name: 'alertmanager.rules', + * rules: [ + * { + * alert: 'A', + * field: 'A0 rule', + * labels: { + * severity: 'warning', + * }, + * }, + * { + * alert: 'A', + * field: 'A1 rule', + * labels: { + * severity: 'warning', + * }, + * }, + * ], + * }, + * ] + * We can use index 1 to choose "A1 rule" for patching, as shown in the example below: + * [ + * { + * name: 'alertmanager.rules', + * rules: [ + * { + * alert: 'A', + * index: 1, + * patch: 'A1', + * labels: { + * severity: 'warning', + * }, + * }, + * ], + * }, + * ] + */ + excludedRules: [], + patchedRules: [], +}; + + +local deleteIndex(rule) = { + [k]: rule[k] + for k in std.objectFields(rule) + if k != 'index' +}; + + +local patchOrExcludeRule(rule, ruleSet, operation) = + if std.length(ruleSet) == 0 then + [deleteIndex(rule)] + /* 2 rules match when the name of the patch is a prefix of the name of the rule to patch. */ + else if ((('alert' in rule && 'alert' in ruleSet[0]) && std.startsWith(rule.alert, ruleSet[0].alert)) || + (('record' in rule && 'record' in ruleSet[0]) && std.startsWith(rule.record, ruleSet[0].record))) && + (!('index' in ruleSet[0]) || (('index' in ruleSet[0]) && (ruleSet[0].index == rule.index))) then + if operation == 'patch' then + local patch = { + [k]: ruleSet[0][k] + for k in std.objectFields(ruleSet[0]) + if k != 'alert' && k != 'record' && k != 'index' + }; + [deleteIndex(std.mergePatch(rule, patch))] + else // equivalnt to operation == 'exclude' + [] + + else + [] + patchOrExcludeRule(rule, ruleSet[1:], operation); + + +local sameRuleName(rule1, rule2) = + if ('alert' in rule1 && 'alert' in rule2) then + rule1.alert == rule2.alert + else if ('record' in rule1 && 'record' in rule2) then + rule1.record == rule2.record + else + false; + +local indexRules(lastRule, ruleSet) = + if std.length(ruleSet) == 0 then + [] + else if (lastRule != null) && sameRuleName(lastRule, ruleSet[0]) then + local updatedRule = std.mergePatch(ruleSet[0], { index: lastRule.index + 1 }); + [updatedRule] + indexRules(updatedRule, ruleSet[1:]) + else + local updatedRule = std.mergePatch(ruleSet[0], { index: 0 }); + [updatedRule] + indexRules(updatedRule, ruleSet[1:]); + +local ruleName(rule) = + if ('alert' in rule) then + rule.alert + else if ('record' in rule) then + rule.record + else + assert false : 'rule should have either "alert" or "record" field' + std.toString(rule); + ''; + +local patchOrExcludeRuleGroup(group, groupSet, operation) = + if std.length(groupSet) == 0 then + [group.rules] + else if (group.name == groupSet[0].name) then + local indexedRules = indexRules(null, std.sort( + group.rules, keyF=ruleName + )); + [patchOrExcludeRule(rule, groupSet[0].rules, operation) for rule in indexedRules] + else + [] + patchOrExcludeRuleGroup(group, groupSet[1:], operation); + +function(params) { + local ruleModifications = defaults + params, + assert std.isArray(ruleModifications.excludedRuleGroups) : 'rule-patcher: excludedRuleGroups should be an array', + assert std.isArray(ruleModifications.excludedRules) : 'rule-patcher: excludedRules should be an array', + assert std.isArray(ruleModifications.patchedRules) : 'rule-patcher: patchedRules should be an array', + + local excludeRule(o) = o { + [if (o.kind == 'PrometheusRule') then 'spec']+: { + groups: std.filterMap( + function(group) !std.member(ruleModifications.excludedRuleGroups, group.name), + function(group) + group { + rules: std.flattenArrays( + patchOrExcludeRuleGroup(group, ruleModifications.excludedRules, 'exclude') + ), + }, + super.groups, + ), + }, + }, + + local patchRule(o) = o { + [if (o.kind == 'PrometheusRule') then 'spec']+: { + groups: std.map( + function(group) + group { + rules: std.flattenArrays( + patchOrExcludeRuleGroup(group, ruleModifications.patchedRules, 'patch') + ), + }, + super.groups, + ), + }, + }, + + // shorthand for rule patching, rule excluding + sanitizePrometheusRules(o): { + [k]: patchRule(excludeRule(o[k])) + for k in std.objectFields(o) + }, +} -- GitLab