Configuring Kube-prometheus-stack Dashboards and Alerts for K3s Compatibility

Published on November 18, 2024, 17:21 UTC 951 views
6 min read

The kube-prometheus-stack Helm chart, which deploys the kubernetes-mixin, targets standard Kubernetes setups, often pre-configured for specific cloud environments. However, these configurations aren’t directly compatible with k3s, a lightweight Kubernetes distribution. Since k3s lacks many of the default cloud integrations, issues arise, such as missing metrics, broken graphs, and unavailable endpoints (example issue). This blog post guides you through adapting the kube-prometheus-stack Helm chart and the kubernetes-mixin to work seamlessly in k3s environments, ensuring functional dashboards and alerts tailored to k3s.

Adjusting the kube-prometheus-stack Helm chart

First, take a look at how to turn off the alerts and dashboards that don’t work for k3s. Add the following configuration to your values.yaml file for the kube-prometheus-stack Helm chart:

grafana:
  # The default dashboards are not working for `k3s`, so we disable them.
  defaultDashboardsEnabled: false
defaultRules:
  # The default rules are not working for `k3s`, so we disable them.
  create: false
# Source for issues/solutions: https://github.com/k3s-io/k3s/issues/3619#issuecomment-1425852034
# `k3s` exposes all metrics combined for each component, so we don't need to scrape them separately
# We'll only scrape kubelet, otherwise we'd get duplicate metrics.
kubelet:
  enabled: true
# Kubernetes API server collects data from master nodes, while kubelet collects data from master and worker nodes
# To not duplicate metrics we'll only scrape Kubelet
kubeApiServer: {}
kubeControllerManager: {}
kubeProxy: {}
kubeScheduler: {}
# `k3s` runs SQLite by default and not etcd, so we don't need to scrape etcd.
kubeEtcd: {}

The preceding configuration disables the default dashboards and rules while ensuring only the kubelet metrics gets scraped. This adjustment is necessary because k3s combines all metrics for its components into a single endpoint. Scraping these metrics separately would result in duplicates, leading to inconsistencies in your monitoring data.

Configuring the kubernetes-mixin

The kube-prometheus-stack Helm chart uses the kube-prometheus project as a baseline for the Helm chart. The kube-prometheus project is a collection of Kubernetes manifests, Grafana dashboards and Prometheus rules combined with Jsonnet libraries to generate them. The kube-prometheus project uses monitoring mixins to generate alerts and dashboards. Monitoring mixins are a collection of Jsonnet libraries that generate dashboards and alerts for Kubernetes. The kubernetes-mixin is a mixin that generates dashboards and alerts for Kubernetes. The node-exporter, coredns, grafana, prometheus and prometheus-operator mixins are also used to generate dashboards and alerts for the Kubernetes cluster.

Since the kube-prometheus-stack Helm chart with the new values turns off the alerts and dashboard, you need to generate the dashboards yourself by using jsonnet. Install jsonnet and the jsonnet-bundler to initialize a new Jsonnet project:

go install github.com/google/go-jsonnet/cmd/jsonnet@latest
go install github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb@latest

Next, initialize a new Jsonnet project:

jb init

This creates a jsonnetfile.json file. Now install the mixins:

jb install github.com/kubernetes-monitoring/kubernetes-mixin@master
jb install github.com/prometheus-operator/kube-prometheus/jsonnet/kube-prometheus@main
jb install github.com/povilasv/coredns-mixin@master # Install the core-dns mixin if you use CoreDNS

This creates a jsonnetfile.lock.json file and a vendor directory. The other mixins pull in from the kubernetes-mixin mixin since their dependencies. The kube-prometheus project installs since helper functions from the project generate the dashboards and alerts.

Next, create a new file called main.jsonnet and add the following content:

# We use helper functions from kube-prometheus to generate dashboards and alerts for Kubernetes.
local addMixin = (import 'kube-prometheus/lib/mixin.libsonnet');

local kubernetesMixin = addMixin({
  name: 'kubernetes',
  dashboardFolder: 'Kubernetes',
  mixin: (import 'kubernetes-mixin/mixin.libsonnet') + {
    _config+:: {
      cadvisorSelector: 'job="kubelet"',
      kubeletSelector: 'job="kubelet"',
      kubeSchedulerSelector: 'job="kubelet"',
      kubeControllerManagerSelector: 'job="kubelet"',
      kubeApiserverSelector: 'job="kubelet"',
      kubeProxySelector: 'job="kubelet"',
    },
  },
});

local nodeExporterMixin = addMixin({
  name: 'node-exporter',
  dashboardFolder: 'General',
  mixin: (import 'node-mixin/mixin.libsonnet') + {
    _config+:: {},
  },
});

local corednsMixin = addMixin({
  name: 'coredns',
  dashboardFolder: 'DNS',
  mixin: (import 'coredns-mixin/mixin.libsonnet') + {
    _config+:: {
      corednsSelector: 'job="coredns"',
    },
  },
});

local grafanaMixin = addMixin({
  name: 'grafana',
  dashboardFolder: 'Grafana',
  mixin: (import 'grafana-mixin/mixin.libsonnet') + {
    _config+:: {},
  },
});

local prometheusMixin = addMixin({
  name: 'prometheus',
  dashboardFolder: 'Prometheus',
  mixin: (import 'prometheus/mixin.libsonnet') + {
    _config+:: {},
  },
});

local prometheusOperatorMixin = addMixin({
  name: 'prometheus-operator',
  dashboardFolder: 'Prometheus Operator',
  mixin: (import 'prometheus-operator-mixin/mixin.libsonnet') + {
    _config+:: {},
  },
});

local stripJsonExtension(name) =
  local extensionIndex = std.findSubstr('.json', name);
  local n = if std.length(extensionIndex) < 1 then name else std.substr(name, 0, extensionIndex[0]);
  n;

local grafanaDashboardConfigMap(folder, name, json) = {
  apiVersion: 'v1',
  kind: 'ConfigMap',
  metadata: {
    name: 'grafana-dashboard-%s' % stripJsonExtension(name),
    namespace: 'monitoring',
    labels: {
      grafana_dashboard: '1',
    },
  },
  data: {
    [name]: std.manifestJsonEx(json, '    '),
  },
};

local generateGrafanaDashboardConfigMaps(mixin) = if std.objectHas(mixin, 'grafanaDashboards') && mixin.grafanaDashboards != null then {
  ['grafana-dashboard-' + stripJsonExtension(name)]: grafanaDashboardConfigMap(folder, name, mixin.grafanaDashboards[folder][name])
  for folder in std.objectFields(mixin.grafanaDashboards)
  for name in std.objectFields(mixin.grafanaDashboards[folder])
} else {};

local nodeExporterMixinHelmGrafanaDashboards = generateGrafanaDashboardConfigMaps(nodeExporterMixin);
local kubernetesMixinHelmGrafanaDashboards = generateGrafanaDashboardConfigMaps(kubernetesMixin);
local corednsMixinHelmGrafanaDashboards = generateGrafanaDashboardConfigMaps(corednsMixin);
local grafanaMixinHelmGrafanaDashboards = generateGrafanaDashboardConfigMaps(grafanaMixin);
local prometheusMixinHelmGrafanaDashboards = generateGrafanaDashboardConfigMaps(prometheusMixin);
local prometheusOperatorMixinHelmGrafanaDashboards = generateGrafanaDashboardConfigMaps(prometheusOperatorMixin);

local grafanaDashboards =
  kubernetesMixinHelmGrafanaDashboards +
  nodeExporterMixinHelmGrafanaDashboards +
  corednsMixinHelmGrafanaDashboards +
  grafanaMixinHelmGrafanaDashboards +
  prometheusMixinHelmGrafanaDashboards +
  prometheusOperatorMixinHelmGrafanaDashboards;


local prometheusAlerts = {
  'kubernetes-mixin-rules': kubernetesMixin.prometheusRules,
  'node-exporter-mixin-rules': nodeExporterMixin.prometheusRules,
  'coredns-mixin-rules': corednsMixin.prometheusRules,
  'grafana-mixin-rules': grafanaMixin.prometheusRules,
  'prometheus-mixin-rules': prometheusMixin.prometheusRules,
  'prometheus-operator-mixin-rules': prometheusOperatorMixin.prometheusRules,
};

grafanaDashboards + prometheusAlerts

This file generates the dashboards for Grafana and alerts for Prometheus. The addMixin helper function from the kube-prometheus project generates the dashboards and alerts. The addMixin function accepts a mixin as an argument and generates a new object with dashboards and alerts included. The alerts appear as CustomResourceDefinition (CRD) PrometheusRules, making them compatible with Prometheus, while the dashboards output as json files. Convert the dashboards to ConfigMap objects to use them in Kubernetes. The grafanaDashboardConfigMap function takes a folder, name, and json as arguments and returns a ConfigMap object.

For the kubernetes-mixin, the job selector kubelet applies to all components. This is because the k3s kubelet exposes all metrics combined for each component, so there is no need to scrape them separately. This is necessary; otherwise, dashboard, and alerts won’t work as expected. Now, run the following command to generate the dashboards and alerts:

jsonnet main.jsonnet -J vendor > generated.json

This generates a generated.json file with the dashboards and alerts. However, the goal is to output the generated json to individual files and also convert the files to yaml for better readability. Lastly, it’s necessary to escape brackets in the yaml files similar to how the kube-prometheus-stack Helm chart does it.

First, install gojsontoyaml:

go install github.com/brancz/gojsontoyaml@latest

Now, create a new file called generate.sh and add the following content:

#!/bin/bash

set -e # Exit on any error
set -u # Treat unset variables as an error

# Define paths
MIXINS_DIR="./templates"

# Function to escape YAML content
escape_yaml() {
  local file_path="$1"
  echo "Escaping $file_path..."
  # Read the file content, process, and overwrite it
  sed -i \
    -e 's/{{/{{`{{/g' \
    -e 's/}}/}}`}}/g' \
    -e 's/{{`{{/{{`{{`}}/g' \
    -e 's/}}`}}/{{`}}`}}/g' \
    "$file_path"
  echo "Escaped $file_path."
}

# Clean the templates directory
echo "Cleaning templates directory..."
rm -rf ${MIXINS_DIR}/*
echo "Templates directory cleaned."

# Convert Jsonnet to YAML
echo "Converting Jsonnet to YAML..."
jsonnet main.jsonnet -J vendor -m ${MIXINS_DIR} | xargs -I{} sh -c 'cat {} | gojsontoyaml > {}.yaml' -- {}
echo "Jsonnet conversion completed."

# Remove all non-YAML files
echo "Removing non-YAML files..."
find ${MIXINS_DIR} -type f ! -name "*.yaml" -exec rm {} +
echo "Non-YAML files removed."

# Escape YAML files
echo "Escaping YAML files..."
find ${MIXINS_DIR} -name '*.yaml' | while read -r file; do
  escape_yaml "$file"
done
echo "YAML files escaped."

echo "Processing completed successfully!"

This script generates the dashboards and alerts and convert them to yaml files. It also escapes the brackets in the yaml files. Now you can run the following command to generate the dashboards and alerts:

chmod +x generate.sh
./generate.sh

This generates the dashboards and alerts in the templates directory. You can now apply the dashboards and alerts to your Kubernetes cluster by running the following command:

kubectl apply -f templates

Now, all alerts and dashboards work as expected in k3s environments. You can now access the dashboards in Grafana and see the alerts in Prometheus.

Summary

In this blog post, this guide explores how to configure the kube-prometheus-stack Helm chart and the kubernetes-mixin to work out-of-the-box with k3s. Tools like jsonnet, jsonnet-bundler, and monitoring mixins are introduced, highlighting how they simplify configuring dashboards and alerts for Kubernetes monitoring.


Similar Posts

Configuring VPA to Use Historical Metrics for Recommendations and Expose Them in Kube-state-metrics

6 min read

The Vertical Pod Autoscaler (VPA) can manage both your pods’ resource requests but also recommend what the limits and requests for a pod should be. Recently, the kube-state-metrics project removed built-in support for VPA recommendation metrics, which made the VPA …


Django Monitoring with Prometheus and Grafana

6 min read

The Prometheus package for Django provides a great Prometheus integration, but the open source dashboards and alerts that exist are not that great. The to-go Grafana dashboard does not use a large portion of metrics provided by the Django-Prometheus package, …


Celery Monitoring with Prometheus and Grafana

5 min read

Celery is a python project used for asynchronous job processing and task scheduling in web applications or distributed systems. It is very commonly used together with Django, Celery as the asynchronous job processor and Django as the web framework. Celery …