Skip to content

Commit

Permalink
feat(alertmanager-config): new chart for AlertmanagerConfig objects
Browse files Browse the repository at this point in the history
  • Loading branch information
eevdev committed Dec 5, 2023
1 parent 92545a7 commit ad76140
Show file tree
Hide file tree
Showing 10 changed files with 521 additions and 0 deletions.
32 changes: 32 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Release Charts

on:
push:
branches:
- main

jobs:
release:
# depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions
# see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0

- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "[email protected]"
- name: Install Helm
uses: azure/setup-helm@v3

- name: Run chart-releaser
uses: helm/[email protected]
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/charts/*/charts
.idea
.vscode
.DS_Store
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## Usage

[Helm](https://helm.sh) must be installed to use the charts. Please refer to
Helm's [documentation](https://helm.sh/docs) to get started.

Once Helm has been set up correctly, add the repo as follows:

helm repo add eevdev https://eevdev.github.io/helm-charts

If you had already added this repo earlier, run `helm repo update` to retrieve
the latest versions of the packages. You can then run `helm search repo
eevdev` to see the charts.

To install the <chart-name> chart:

helm install my-<chart-name> eevdev/<chart-name>

To uninstall the chart:

helm delete my-<chart-name>
23 changes: 23 additions & 0 deletions charts/alertmanager-config/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
7 changes: 7 additions & 0 deletions charts/alertmanager-config/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v2
name: alertmanager-config
description: Create namespaced Alertmanager receivers and routes with AlertmanagerConfig objects.
version: 1.0.0
maintainers:
- email: [email protected]
name: Eivind Valderhaug
88 changes: 88 additions & 0 deletions charts/alertmanager-config/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# AlertmanagerConfig

_Warning: the AlertmanagerConfig CRD from Prometheus Operator is not yet stable ([source](https://prometheus-operator.dev/docs/operator/design/#alertmanagerconfig))._

Specify behavior of Alertmanager with the [AlertmanagerConfig](https://prometheus-operator.dev/docs/operator/api/#monitoring.coreos.com/v1alpha1.AlertmanagerConfig) CRD from Prometheus Operator.

This chart makes it easy to provide common default configs while allowing flexibility to override those defaults.

Note: The configuration is scoped to the AlertmanagerConfig namespace; receivers and routes will automatically get a name prefix to avoid conflicts with receivers and routes configured elsewhere. Please see the related configuration options for Alertmanager and Prometheus.

## Configuring Prometheus Operator to support AlertmanagerConfig CRD

### [AlertmanagerSpec](https://prometheus-operator.dev/docs/operator/api/#monitoring.coreos.com/v1.AlertmanagerSpec)
`alertmanagerConfigSelector` and `alertmanagerConfigNamespaceSelector` specifies which AlertmanagerConfigs to include for your Alertmanager instance. If you have several Alertmanagers in your cluster and you don't want others to include certain AlertmanagerConfigs, you'll need to use these options to specify their inclusion behavior.

The operator injects a label matcher matching the namespace of the AlertmanagerConfig object for all its routes and inhibition rules as the default behavior. [AlertmanagerConfigMatcherStrategy](https://prometheus-operator.dev/docs/operator/api/#monitoring.coreos.com/v1.AlertmanagerConfigMatcherStrategy) can be used to disable this constraint.

### [PrometheusSpec](https://prometheus-operator.dev/docs/operator/api/#monitoring.coreos.com/v1.PrometheusSpec)
`enforcedNamespaceLabel` and `excludedFromEnforcement` can be used to enforce a namespace label to alerts etc, which may be useful with the default AlertmanagerConfigMatcherStrategy behavior that applies a namespace label matcher to routes and inhibition rules.

## Chart usage

### Receivers

Receivers can be disabled by setting `enabled: false` to support providing default receivers and overriding those.

You can provide default target type configurations (slackConfigs, pagerdutyConfigs etc.) in `receiverDefaults` (dict) to reduce duplication and support reusability.

By providing a complete configuration as default values for a receiver target type in `receiverDefaults` (eg. for a shared receiver config), it will be possible to enable the target type by setting `- enabled: true` under the related target type config section. Note: there's normally no need to set this `enabled` field in other scenarios, as providing any config will apply the defaults for a given target type, except if you're providing shared AlertmanagerConfig values containing a receiver with multiple target types (eg. send to both Slack and PagerDuty) and you have a case where you want to disable one of the targets for one or more AlertmanagerConfigs.

You can also disable defaults from `receiverDefaults` for certain target type configs with `inheritDefaults: false`, which is useful for uncommon configs, eg. if you use routingKey by default to configure PagerDuty routing but you have a couple of services that use serviceKey instead where you need to remove the defaults.

### Routes

Alerting routes are defined in `alertmanagerConfig.route.routes` either as a list or dict.

Routes can be disabled by setting `enabled: false` to support providing default routes and overriding those.

The motivation for allowing routes as dicts is to override values based on route name rather than list index (ie. by id rather than an arbitrary number that changes when someone reorders the list).

To support routes as dicts, which are unordered (ie. items will not be in a predictable order), the `order` field (int or string) may be added to configure the ordering of any route. The `prioritizeOrderedRoutes` option (bool) controls whether ordered routes (routes with the `order` field) should go before (true, default) or after (false) unordered routes.

Note: The `order` field is sorted using the built-in Helm function sortAlpha because there doesn't seem to be a numeric sorting function available in Helm.

### Example
`values.yaml`:
```yaml
labels:
prometheus: somelabel
alertmanagerConfig:
receivers:
default:
slackConfigs:
- channel: '<channel name or id>'
critical:
slackConfigs:
- channel: '<channel name or id>'
pagerdutyConfigs:
- routingKey:
key: token
name: pagerduty
route:
routes:
critical:
receiver: critical
matchers:
- name: severity
matchType: "="
value: critical
infoInhibitor:
matchers:
- name: alertname
matchType: "="
value: InfoInhibitor
receiver: void
receiverDefaults:
slackConfigs:
apiURL:
key: api-url # https://slack.com/api/chat.postMessage
name: alertmanager-slack
httpConfig:
authorization:
type: Bearer
credentials:
key: bot-token
name: alertmanager-slack
sendResolved: true
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Example of configuring global Alertmanager config with an AlertmanagerConfig object when using the [kube-prometheus-stack](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack) Helm chart.

`alertmanager-config-values.yaml`:
```yaml
fullnameOverride: global-config
alertmanagerConfig: # https://prometheus-operator.dev/docs/operator/api/#monitoring.coreos.com/v1alpha1.AlertmanagerConfigSpec
receivers:
default:
# ...
route:
receiver: default
groupBy:
- alertname
- namespace
- severity
groupWait: 10s
groupInterval: 5m
repeatInterval: 3h
```
`kube-prometheus-stack-values.yaml`:
```yaml
alertmanager:
alertmanagerSpec:
alertmanagerConfiguration: # https://prometheus-operator.dev/docs/operator/api/#monitoring.coreos.com/v1.AlertmanagerConfiguration
name: global-config
# global: # AlertmanagerGlobalConfig: https://prometheus-operator.dev/docs/operator/api/#monitoring.coreos.com/v1.AlertmanagerGlobalConfig
# templates: # SecretOrConfigMap: https://prometheus-operator.dev/docs/operator/api/#monitoring.coreos.com/v1.SecretOrConfigMap
```
146 changes: 146 additions & 0 deletions charts/alertmanager-config/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "alertmanager-config.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "alertmanager-config.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "alertmanager-config.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "alertmanager-config.labels" -}}
helm.sh/chart: {{ include "alertmanager-config.chart" . }}
{{ include "alertmanager-config.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- with .Values.labels }}
{{ toYaml . }}
{{- end }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "alertmanager-config.selectorLabels" -}}
app.kubernetes.io/name: {{ include "alertmanager-config.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{- /* Generate receivers. */}}
{{- define "alertmanager-config.receivers" -}}
{{- $receiverDefaults := .Values.receiverDefaults -}}
{{- $receivers := list -}}
{{- range $name, $receiverConfig := .Values.alertmanagerConfig.receivers }}
{{- /* Allow disabling default receivers. */}}
{{- if or $receiverConfig.enabled (typeIs "<nil>" $receiverConfig.enabled) }}
{{- $receiver := dict -}}
{{- $_ := set $receiver "name" $name }}
{{- /* For each target type (eg. emailConfigs) of receiver, apply default values and add to list of targets. */}}
{{- range $targetType, $targetTypeConfigs := (omit $receiverConfig "enabled") }}
{{- $defaults := (get $receiverDefaults $targetType | default dict) }}
{{- $receiverTargetTypeConfigs := list }}
{{- range $targetTypeConfig := $targetTypeConfigs }}
{{- /* Allow enabling a target type without further config to rely entirely on receiverDefaults. */}}
{{- if or $targetTypeConfig.enabled (typeIs "<nil>" $targetTypeConfig.enabled) }}
{{- if or $targetTypeConfig.inheritDefaults (typeIs "<nil>" $targetTypeConfig.inheritDefaults) }}
{{- $receiverTargetTypeConfigs = append $receiverTargetTypeConfigs (mustMergeOverwrite (deepCopy $defaults) (omit $targetTypeConfig "enabled" "inheritDefaults")) }}
{{- else }}
{{- $receiverTargetTypeConfigs = append $receiverTargetTypeConfigs (omit $targetTypeConfig "enabled" "inheritDefaults") }}
{{- end }}
{{- end }}
{{- end }}
{{- with $receiverTargetTypeConfigs }}
{{- $_ := set $receiver $targetType . }}
{{- end }}
{{- end }}
{{- with $receiver }}
{{- $receivers = append $receivers $receiver }}
{{- end }}
{{- end }}
{{- end }}
{{- with $receivers -}}
receivers:
{{ toYaml . }}
{{- end }}
{{- end }}

{{- /* Generate routes from .Values.alertmanagerConfig.route.routes. */}}
{{- define "alertmanager-config.routes" -}}
{{- with .Values.alertmanagerConfig.route -}}
routes:
{{- /*
Convert .routes to list if type is dict/map.
Motivation for supporting dict type is to be able to override values by name.
*/}}
{{- if (kindIs "map" .routes) }}{{ $_ := set . "routes" (values .routes) }}{{ end }}
{{- $orderedRoutes := dict }}
{{- $unorderedRoutes := list }}
{{- /*
$route.enabled must be true/non-empty or nil/missing to be included, ie. routes are enabled unless this is false.
$route.order may be set to specify sorting order for each route.
If order is not provided for a route it will be put before or after ordered items based on the value of prioritizeOrderedRoutes.
*/}}
{{- range $route := (.routes | default list) }}
{{- if or $route.enabled (typeIs "<nil>" $route.enabled) }}
{{- if (typeIs "<nil>" $route.order) }}
{{- $unorderedRoutes = append $unorderedRoutes (omit $route "enabled" "order") }}
{{- else }}
{{- /*
Work-around for missing numeric ordering in Helm (at least I couldn't find a proper way).
As keys are sorted alphabetically with sortAlpha, we need to prefix lower numbers with zeros to get correct
sorting (otherwise 2 would come after 10 etc.).
Currently sort order up to 999 is supported, but it could be increased if necessary.
*/}}
{{- if and (not (kindIs "string" $route.order)) (lt (int $route.order) 100) }}
{{- $_ := set $route "order" (ternary (printf "00%d" (int $route.order)) (printf "0%d" (int $route.order)) (lt (int $route.order) 10)) }}
{{- end }}
{{- if hasKey $orderedRoutes (toString $route.order) }}
{{- fail (printf "Conflicting order '%s' for routes.\nRoute 1: %s.\nRoute 2: %s." $route.order (omit $route "enabled" "order") (get $orderedRoutes (toString $route.order))) }}
{{- end }}
{{- $_ := set $orderedRoutes (toString $route.order) (omit $route "enabled" "order") }}
{{- end }}
{{- end }}
{{- end }}
{{- if not $.Values.prioritizeOrderedRoutes }}
{{- with $unorderedRoutes }}
{{- toYaml . | nindent 0 }}
{{- end }}
{{- end }}
{{- /* Sort ordered routes alphabetically and print. */}}
{{- range $key := (keys $orderedRoutes | sortAlpha) }}
{{- toYaml (list (get $orderedRoutes $key)) | nindent 0 }}
{{- end }}
{{- if $.Values.prioritizeOrderedRoutes }}
{{- with $unorderedRoutes }}
{{- toYaml . | nindent 0 }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
21 changes: 21 additions & 0 deletions charts/alertmanager-config/templates/alertmanagerconfig.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
apiVersion: {{ .Values.apiVersion }}
kind: AlertmanagerConfig
metadata:
name: {{ include "alertmanager-config.fullname" . }}
labels:
{{- include "alertmanager-config.labels" . | nindent 4 }}
spec:
{{- with (omit .Values.alertmanagerConfig "receivers" "route") }}
{{- toYaml . | nindent 2 }}
{{- end }}
{{- with (include "alertmanager-config.receivers" .) }}
{{- . | nindent 2 }}
{{- end }}
{{- with (required "Missing required alertmanagerConfig.route" .Values.alertmanagerConfig.route) }}
route:
receiver: {{ required "Missing receiver for root route." .receiver | quote }}
{{- toYaml (omit . "receiver" "routes") | nindent 4 }}
{{- with .routes }}
{{- include "alertmanager-config.routes" $ | nindent 4 }}
{{- end }}
{{- end }}
Loading

0 comments on commit ad76140

Please sign in to comment.