Files
deprecated-helm-charts/stable/prometheus-operator/hack/sync_prometheus_rules.py
T
Brian NuttandKubernetes Prow Robot 610e47e34c [stable/prometheus-operator] Disable AlertmanagerDown alert if alertmanager is disabled (#12027)
In the case alertingEndpoints is used, not every deployment may require an alertmanager with it. Prometheus will throw an alert in this case unless the rule is manually removed. This will just exlude the rule automatically should alertmanager be disabled.

Signed-off-by: Brian Nutt <brian@slice.com>
2019-03-18 13:00:28 -07:00

237 lines
9.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""Fetch alerting and aggregation rules from provided urls into this chart."""
import textwrap
from os import makedirs
import requests
import yaml
from yaml.representer import SafeRepresenter
# https://stackoverflow.com/a/20863889/961092
class LiteralStr(str):
pass
def change_style(style, representer):
def new_representer(dumper, data):
scalar = representer(dumper, data)
scalar.style = style
return scalar
return new_representer
# Source files list
charts = [
{
'source': 'https://raw.githubusercontent.com/coreos/prometheus-operator/master/contrib/kube-prometheus/manifests/prometheus-rules.yaml',
'destination': '../templates/prometheus/rules'
},
{
'source': 'https://raw.githubusercontent.com/etcd-io/etcd/master/Documentation/op-guide/etcd3_alert.rules.yml',
'destination': '../templates/prometheus/rules'
},
]
# Additional conditions map
condition_map = {
'alertmanager.rules': ' .Values.defaultRules.rules.alertmanager',
'general.rules': ' .Values.defaultRules.rules.general',
'k8s.rules': ' .Values.defaultRules.rules.k8s',
'kube-apiserver.rules': ' .Values.kubeApiServer.enabled .Values.defaultRules.rules.kubeApiserver',
'kube-prometheus-node-alerting.rules': ' .Values.defaultRules.rules.kubePrometheusNodeAlerting',
'kube-prometheus-node-recording.rules': ' .Values.defaultRules.rules.kubePrometheusNodeRecording',
'kube-scheduler.rules': ' .Values.kubeScheduler.enabled .Values.defaultRules.rules.kubeScheduler',
'kubernetes-absent': ' .Values.defaultRules.rules.kubernetesAbsent',
'kubernetes-resources': ' .Values.defaultRules.rules.kubernetesResources',
'kubernetes-storage': ' .Values.defaultRules.rules.kubernetesStorage',
'kubernetes-system': ' .Values.defaultRules.rules.kubernetesSystem',
'node.rules': ' .Values.nodeExporter.enabled .Values.defaultRules.rules.node',
'prometheus-operator': ' .Values.defaultRules.rules.prometheusOperator',
'prometheus.rules': ' .Values.defaultRules.rules.prometheus',
'kubernetes-apps': ' .Values.kubeStateMetrics.enabled .Values.defaultRules.rules.kubernetesApps',
'etcd': ' .Values.kubeEtcd.enabled .Values.defaultRules.rules.etcd',
}
alert_condition_map = {
'KubeAPIDown': '.Values.kubeApiServer.enabled', # there are more alerts which are left enabled, because they'll never fire without metrics
'KubeControllerManagerDown': '.Values.kubeControllerManager.enabled',
'KubeSchedulerDown': '.Values.kubeScheduler.enabled',
'KubeStateMetricsDown': '.Values.kubeStateMetrics.enabled', # there are more alerts which are left enabled, because they'll never fire without metrics
'KubeletDown': '.Values.prometheusOperator.kubeletService.enabled', # there are more alerts which are left enabled, because they'll never fire without metrics
'PrometheusOperatorDown': '.Values.prometheusOperator.enabled',
'NodeExporterDown': '.Values.nodeExporter.enabled',
'CoreDNSDown': '.Values.kubeDns.enabled',
'AlertmanagerDown': '.Values.alertmanager.enabled',
}
replacement_map = {
'job="prometheus-operator"': {
'replacement': 'job="{{ $operatorJob }}"',
'init': '{{- $operatorJob := printf "%s-%s" (include "prometheus-operator.fullname" .) "operator" }}'},
'job="prometheus-k8s"': {
'replacement': 'job="{{ $prometheusJob }}"',
'init': '{{- $prometheusJob := printf "%s-%s" (include "prometheus-operator.fullname" .) "prometheus" }}'},
'job="alertmanager-main"': {
'replacement': 'job="{{ $alertmanagerJob }}"',
'init': '{{- $alertmanagerJob := printf "%s-%s" (include "prometheus-operator.fullname" .) "alertmanager" }}'},
'namespace="monitoring"': {
'replacement': 'namespace="{{ $namespace }}"',
'init': '{{- $namespace := .Release.Namespace }}'},
'alertmanager-$1': {
'replacement': '$1',
'init': ''},
}
# standard header
header = '''# Generated from '%(name)s' group from %(url)s
# Do not change in-place! In order to change this file first read following link:
# https://github.com/helm/charts/tree/master/stable/prometheus-operator/hack
{{- if and .Values.defaultRules.create%(condition)s }}%(init_line)s
apiVersion: {{ printf "%%s/v1" (.Values.prometheusOperator.crdApiGroup | default "monitoring.coreos.com") }}
kind: PrometheusRule
metadata:
name: {{ printf "%%s-%%s" (include "prometheus-operator.fullname" .) "%(name)s" | trunc 63 | trimSuffix "-" }}
labels:
app: {{ template "prometheus-operator.name" . }}
{{ include "prometheus-operator.labels" . | indent 4 }}
{{- if .Values.defaultRules.labels }}
{{ toYaml .Values.defaultRules.labels | indent 4 }}
{{- end }}
{{- if .Values.defaultRules.annotations }}
annotations:
{{ toYaml .Values.defaultRules.annotations | indent 4 }}
{{- end }}
spec:
groups:
-'''
def init_yaml_styles():
represent_literal_str = change_style('|', SafeRepresenter.represent_str)
yaml.add_representer(LiteralStr, represent_literal_str)
def escape(s):
return s.replace("{{", "{{`{{").replace("}}", "}}`}}")
def fix_expr(rules):
"""Remove trailing whitespaces and line breaks, which happen to creep in
due to yaml import specifics;
convert multiline expressions to literal style, |-"""
for rule in rules:
rule['expr'] = rule['expr'].rstrip()
if '\n' in rule['expr']:
rule['expr'] = LiteralStr(rule['expr'])
def yaml_str_repr(struct, indent=4):
"""represent yaml as a string"""
text = yaml.dump(
struct,
width=1000, # to disable line wrapping
default_flow_style=False # to disable multiple items on single line
)
text = escape(text) # escape {{ and }} for helm
text = textwrap.indent(text, ' ' * indent)[indent - 1:] # indent everything, and remove very first line extra indentation
return text
def add_rules_conditions(rules, indent=4):
"""Add if wrapper for rules, listed in alert_condition_map"""
rule_condition = '{{- if %s }}\n'
for alert_name in alert_condition_map:
line_start = ' ' * indent + '- alert: '
if line_start + alert_name in rules:
rule_text = rule_condition % alert_condition_map[alert_name]
# add if condition
index = rules.index(line_start + alert_name)
rules = rules[:index] + rule_text + rules[index:]
# add end of if
try:
next_index = rules.index(line_start, index + len(rule_text) + 1)
except ValueError:
# we found the last alert in file if there are no alerts after it
next_index = len(rules)
# depending on the rule ordering in alert_condition_map it's possible that an if statement from another rule is present at the end of this block.
found_block_end = False
last_line_index = next_index
while not found_block_end:
last_line_index = rules.rindex('\n', index, last_line_index - 1) # find the starting position of the last line
last_line = rules[last_line_index + 1:next_index]
if last_line.startswith('{{- if'):
next_index = last_line_index + 1 # move next_index back if the current block ends in an if statement
continue
found_block_end = True
rules = rules[:next_index] + '{{- end }}\n' + rules[next_index:]
return rules
def write_group_to_file(group, url, destination):
fix_expr(group['rules'])
# prepare rules string representation
rules = yaml_str_repr(group)
# add replacements of custom variables and include their initialisation in case it's needed
init_line = ''
for line in replacement_map:
if line in rules:
rules = rules.replace(line, replacement_map[line]['replacement'])
if replacement_map[line]['init']:
init_line += '\n' + replacement_map[line]['init']
# append per-alert rules
rules = add_rules_conditions(rules)
# initialize header
lines = header % {
'name': group['name'],
'url': url,
'condition': condition_map.get(group['name'], ''),
'init_line': init_line,
}
# rules themselves
lines += rules
# footer
lines += '{{- end }}'
filename = group['name'] + '.yaml'
new_filename = "%s/%s" % (destination, filename)
# make sure directories to store the file exist
makedirs(destination, exist_ok=True)
# recreate the file
with open(new_filename, 'w') as f:
f.write(lines)
print("Generated %s" % new_filename)
def main():
init_yaml_styles()
# read the rules, create a new template file per group
for chart in charts:
print("Generating rules from %s" % chart['source'])
response = requests.get(chart['source'])
if response.status_code != 200:
print('Skipping the file, response code %s not equals 200' % response.status_code)
continue
raw_text = response.text
yaml_text = yaml.load(raw_text)
# etcd workaround, their file don't have spec level
groups = yaml_text['spec']['groups'] if yaml_text.get('spec') else yaml_text['groups']
for group in groups:
write_group_to_file(group, chart['source'], chart['destination'])
print("Finished")
if __name__ == '__main__':
main()