materialize-monitoring Helm Reference#

Unified observability stack for Materialize: Alloy-based pipeline, optional bundled backends (Loki / Thanos / Grafana / Alertmanager), and the dashboards / alerts / rules that consume the Materialize metrics surface.

Source Code#

Requirements#

Kubernetes: >=1.27.0-0

Storage Requirements#

Using Thanos or Loki (both enabled by default) requires object storage (such as an AWS S3 bucket) for long-term storage.

In development, Thanos may use a PVC for less-reliable storage. Loki however will not run without object storage. You may consider Garage or RustFS or MinIO for manually provisioned object storage.

Included Subcharts#

RepositoryNameVersion
https://grafana.github.io/helm-chartsalloy(alloy-agent)^1.8.0
https://grafana.github.io/helm-chartsalloy(alloy-gateway)^1.8.0
https://kubernetes-sigs.github.io/metrics-servermetrics-server^3.13.0
oci://ghcr.io/grafana-community/helm-chartsgrafana^12.3.3
oci://ghcr.io/grafana-community/helm-chartsloki^15.0.0
oci://ghcr.io/grafana/helm-chartsgrafana-operator^5.22.2
oci://ghcr.io/prometheus-community/chartsalertmanager^1.25.0
oci://ghcr.io/prometheus-community/chartskube-state-metrics^7.3.0
oci://ghcr.io/thanos-community/helm-chartsthanos^0.11.0

Values#

Globals#

Values that are passed to all subcharts

KeyTypeDefaultDescription
global.imageRegistrystring""Override the registry for all images in this chart and its subcharts. Leave empty to use upstream defaults.
global.imagePullSecretslist
[]
Image pull secrets applied to every workload in this chart and its subcharts.

Main chart configuration#

Configuration for the main chart

KeyTypeDefaultDescription
nameOverridestring""Standard Helm name override. Note that in umbrella charts, fullname may be shorted. See fullnameOverride.
fullnameOverridestring"mzmon"Standard Helm fullname override. Note that in umbrella charts, this may be shortened to avoid long prefixes. (and have more stable names in resources)
namespaceOverridestring""Namespace override for default workloads.

Subchart enablement (Helm tags)#

Group toggles for subchart enablement. Subchart enablement has two layers, in precedence order:

  1. Per-chart circuit breaker (<chart>.enabled). Each subchart dependency carries a condition: <chart>.enabled in Chart.yaml. The corresponding enabled: key is left commented-out under each subchart block in values.yaml; uncommenting it force-includes (true) or force-excludes (false) that chart regardless of any tag state. Helm evaluates condition: first and only falls through to tags when the path is absent from values, so this is a hard override.
  2. Helm tags (the tags: block). When no <chart>.enabled is set, enablement is decided by tags. Each dependency in Chart.yaml carries the master default tag, a group tag (e.g. bundled-backends), and a per-chart tag (e.g. loki); a dependency is included if any of its tags evaluates true (OR semantics). To opt one chart out of an otherwise-enabled group, set the group tag false and flip the per-chart tags individually — or use the circuit breaker.

Group ↔ chart mapping:

Group tagCharts
defaultpipeline + bundled-backends + managed-grafana
groups + kube-state-metrics (recommended stack)
pipelinealloy-agent, alloy-gateway
bundled-backendsloki, thanos, alertmanager
managed-grafanagrafana, grafana-operator
cluster-metricskube-state-metrics, metrics-server
crdsprometheus-operator-crds, grafana-operator-crds
(in the sibling materialize-monitoring-crds chart)

default is the only group on by default and enables the full recommended stack. --set tags.default=false turns everything off, so you can enable a single group (bundled-backends, managed-grafana, pipeline, cluster-metrics) or individual charts on top. Profile preset values files under profiles/ flip these appropriately. (kube-state-metrics is in default, but metrics-server is not — most clusters already run metrics-server; enable it via tags.cluster-metrics or tags.metrics-server if yours doesn’t.)

KeyTypeDefaultDescription
tags.defaultbooltrueEnable all recommended defaults. You can `--set tags.default=false` to disable all services and explicitly enable others.
tags.pipelineboolfalseEnable both Alloy releases (agent + gateway).
tags.bundled-backendsboolfalseEnable Loki, Thanos, and Alertmanager as a group. (Grafana is in the `managed-grafana` group.)
tags.cluster-metricsboolfalseEnable kube-state-metrics and metrics-server as a group.
tags.alloy-agentboolfalsePer-chart override: enable just the Alloy agent. OR'd with `tags.default` / `tags.pipeline`.
tags.alloy-gatewayboolfalsePer-chart override: enable just the Alloy gateway. OR'd with `tags.default` / `tags.pipeline`.
tags.lokiboolfalsePer-chart override: enable just Loki. OR'd with `tags.default` / `tags.bundled-backends`.
tags.thanosboolfalsePer-chart override: enable just Thanos. OR'd with `tags.default` / `tags.bundled-backends`.
tags.grafana-standaloneboolfalsePer-chart override: enable just Grafana standalone. OR'd with `tags.default` / `tags.managed-grafana`.
tags.grafana-operatorboolfalsePer-chart override: enable just Grafana operator. OR'd with `tags.default` / `tags.managed-grafana`.
tags.alertmanagerboolfalsePer-chart override: enable just Alertmanager. OR'd with `tags.default` / `tags.bundled-backends`.
tags.kube-state-metricsboolfalsePer-chart override: enable just kube-state-metrics. OR'd with `tags.default` / `tags.cluster-metrics`.
tags.metrics-serverboolfalsePer-chart override: enable just metrics-server. OR'd with `tags.cluster-metrics`.

Materialize Integration#

Materialize-specific configuration values.

KeyTypeDefaultDescription
materialize.namespaceslist
[]
Namespaces to scrape Materialize workloads from. Empty list means all namespaces the chart can read.
materialize.environmentd.serviceMonitorobject
{
  "enabled": true,
  "metricEndpoints": [],
  "selector": {},
  "targetLabels": []
}
ServiceMonitor/PodMonitor configuration for environmentd
materialize.environmentd.serviceMonitor.enabledbooltrueDeploy a PodMonitor to collect Materialize metrics.
materialize.environmentd.serviceMonitor.selectorobject
{}
Override for default selector
materialize.environmentd.serviceMonitor.targetLabelslist
[]
Override for default labels to copy from the pod into metrics
materialize.environmentd.serviceMonitor.metricEndpointslist
[]
Override for default metric endpoints
materialize.environmentdSQL.serviceMonitorobject
{
  "enabled": true,
  "metricEndpoints": [],
  "selector": {},
  "targetLabels": []
}
ServiceMonitor/PodMonitor configuration for environmentd-sql
materialize.environmentdSQL.serviceMonitor.enabledbooltrueDeploy a PodMonitor to collect Materialize metrics.
materialize.environmentdSQL.serviceMonitor.selectorobject
{}
Override for default selector
materialize.environmentdSQL.serviceMonitor.targetLabelslist
[]
Override for default labels to copy from the pod into metrics
materialize.environmentdSQL.serviceMonitor.metricEndpointslist
[]
Override for default metric endpoints
materialize.clusterd.serviceMonitorobject
{
  "enabled": true,
  "metricEndpoints": [],
  "selector": {},
  "targetLabels": []
}
ServiceMonitor/PodMonitor configuration for clusterd
materialize.clusterd.serviceMonitor.enabledbooltrueDeploy a PodMonitor to collect Materialize metrics.
materialize.clusterd.serviceMonitor.selectorobject
{}
Override for default selector
materialize.clusterd.serviceMonitor.targetLabelslist
[]
Override for default labels to copy from the pod into metrics
materialize.clusterd.serviceMonitor.metricEndpointslist
[]
Override for default metric endpoints
materialize.deploymentModestring"self-managed"Deployment mode normalization hint. One of: `self-managed`, `cloud`. Drives relabeling rules in the pipeline.
materialize-system.namespacestring"materialize-environment"The namespace materialize-system was deployed into
materialize-system.serviceMonitorobject
{
  "enabled": true
}
ServiceMonitor/PodMonitor configuration TODO: nothing uses this yet
materialize-operator.namespacestring"materialize"The namespace materialize-operator was deployed into
materialize-operator.serviceMonitorobject
{
  "enabled": true,
  "metricEndpoints": [],
  "selector": {},
  "targetLabels": []
}
ServiceMonitor/PodMonitor configuration
materialize-operator.serviceMonitor.selectorobject
{}
Override for default selector
materialize-operator.serviceMonitor.targetLabelslist
[]
Override for default labels to copy from the pod into metrics
materialize-operator.serviceMonitor.metricEndpointslist
[]
Override for default metric endpoints

Pipeline configuration#

Pipeline configuration values that drive Alloy behavior and defaults.

The Alloy pipeline is the flagship deliverable of this chart. Pipeline configuration is expressed in values, rendered into ConfigMaps under templates/pipelines/, and consumed by the Alloy agent and gateway release instances.

KeyTypeDefaultDescription
pipeline.preValidateJob.enabledbooltrueRun a hook/dependency to validate rendered configs before rolling them out
pipeline.preValidateJob.annotationsobject
{
  "helm.sh/hook": "pre-install,pre-upgrade",
  "helm.sh/hook-delete-policy": "before-hook-creation"
}
Job specific annotations This should be used to control when the job is executed. The default is as a helm hook, but setting any other annotation overrides this.
pipeline.preValidateJob.backoffLimitint1Number of times to retry the job before failing the release.
pipeline.preValidateJob.podSecurityContextobject
{
  "fsGroup": 473,
  "runAsGroup": 473,
  "runAsUser": 473
}
Security context for the pre-validate job pod. This is the hardened recommendation with the alloy user.
pipeline.preValidateJob.containerSecurityContextobject
{
  "allowPrivilegeEscalation": false,
  "capabilities": {
    "drop": [
      "ALL"
    ]
  },
  "readOnlyRootFilesystem": true,
  "runAsGroup": 473,
  "runAsNonRoot": true,
  "runAsUser": 473
}
Security context for the pre-validate job container. This is the hardened recommendation with the alloy user.
pipeline.envobject
{}
Environment variables to set in agent and gateway pods. These support tpl rendering. Since these are injected into an envFrom, any env entries take precedence.
pipeline.env.CLUSTER_NAMEstring"default"Name of the cluster to discriminate workloads from different sources.
pipeline.env.GATEWAY_LOG_LEVELstring"info"Level for the gateway logs.

Log configuration#

Configuration for log behavior

KeyTypeDefaultDescription
pipeline.logging.agent.rateLimitint5000Rate limit for alloy agent incoming pod logs. This is per agent.
pipeline.logging.agent.burstint20000Burst limit for alloy agent incoming pod logs.
pipeline.logging.agent.destination.loki.urlstring"http://alloy-gateway.{{ include \"mzmon.alloyGateway.namespace\" $ }}.svc:3100/loki/api/v1/push"alloy-gateway push endpoint URL.
pipeline.logging.agent.destination.loki.retriesobject
{
  "maxBackoffPeriod": "5m",
  "maxBackoffRetries": 10,
  "minBackoffPeriod": "1s",
  "retryOnHttp429": true
}
Retry configuration.
pipeline.logging.agent.destination.loki.authTypestring"none"Type of authentication to use with the alloy-gateway endpoint. Use none if no authentication is required.
pipeline.logging.agent.destination.loki.tls.enabledboolfalseWhether to enable TLS for alloy-gateway dest.
pipeline.logging.agent.destination.loki.tls.verifybooltrueWhether to verify the TLS certificate for the alloy-gateway dest.
pipeline.logging.agent.destination.loki.tls.castring""Certificate Authority (CA) PEM contents for TLS.
pipeline.logging.agent.destination.loki.tls.certstring""Client certificate PEM contents for TLS.
pipeline.logging.agent.destination.loki.tls.keystring""Client private key PEM contents for TLS.
pipeline.logging.agent.destination.loki.tls.serverNamestring""Alternative SNI (Server Name Indication) to specify.
pipeline.logging.agent.destination.loki.tls.minVersionstring"TLS13"Minimum TLS version to allow. Use TLS12 if you need better compat. TLS11 and TLS10 are not recommended for production.
pipeline.logging.gateway.destination.loki.enabledbooltrueEnable writing to a loki destination. By default, we use the in-cluster loki
pipeline.logging.gateway.destination.loki.urlstring"http://loki-distributor.{{ include \"mzmon.loki.namespace\" $ }}.svc:3100/loki/api/v1/push"Loki push endpoint URL.
pipeline.logging.gateway.destination.loki.retriesobject
{
  "maxBackoffPeriod": "5m",
  "maxBackoffRetries": 10,
  "minBackoffPeriod": "1s",
  "retryOnHttp429": true
}
Retry configuration.
pipeline.logging.gateway.destination.loki.authTypestring"none"Type of authentication to use with the loki endpoint. Use none if no authentication is required. Use basicAuth for username/password. Use bearer for bearer token. Use oauth2 for OAuth2 client credentials.
pipeline.logging.gateway.destination.loki.basicAuthobject
{
  "password": "",
  "passwordEnv": "GATEWAY_LOKI_DEST_PASSWORD",
  "username": "",
  "usernameEnv": "GATEWAY_LOKI_DEST_USERNAME"
}
Configuration for auth when using authType=basicAuth You will need to provide alloy-gateway.alloy.agent.extraEnv TODO: add a check for this
pipeline.logging.gateway.destination.loki.bearerobject
{
  "token": "",
  "tokenEnv": "GATEWAY_LOKI_DEST_BEARER_TOKEN"
}
Configuration for bearer token when using authType=bearer This is used for bearer type tokens.
pipeline.logging.gateway.destination.loki.oauth2object
{
  "clientId": "",
  "clientIdEnv": "GATEWAY_LOKI_DEST_OAUTH2_CLIENT_ID",
  "clientSecret": "",
  "clientSecretEnv": "GATEWAY_LOKI_DEST_OAUTH2_CLIENT_SECRET",
  "scopes": [],
  "tokenUrl": "",
  "tokenUrlEnv": "GATEWAY_LOKI_DEST_OAUTH2_TOKEN_URL"
}
Configuration for OAuth2 when using authType=oauth2
pipeline.logging.gateway.destination.loki.tls.enabledboolfalseWhether to enable TLS for the loki destination.
pipeline.logging.gateway.destination.loki.tls.verifybooltrueWhether to verify the TLS certificate for the loki destination.
pipeline.logging.gateway.destination.loki.tls.castring""Certificate Authority (CA) PEM contents for TLS.
pipeline.logging.gateway.destination.loki.tls.certstring""Client certificate PEM contents for TLS.
pipeline.logging.gateway.destination.loki.tls.keystring""Client private key PEM contents for TLS.
pipeline.logging.gateway.destination.loki.tls.serverNamestring""Alternative SNI (Server Name Indication) to specify.
pipeline.logging.gateway.destination.loki.tls.minVersionstring"TLS13"Minimum TLS version to allow. Use TLS12 if you need better compat. TLS11 and TLS10 are not recommended for production.
pipeline.logging.gateway.destination.otel.enabledboolfalseEnable writing to an OpenTelemetry/OTLP destination. By default, we do not use the OTLP destination. NOTE: This is configured in the pipeline.metrics.gateway.destination.otlp block, not the logging block.
pipeline.logging.tenancy.staticTenantstring"loki"Default tenant to write logs to. This is used when tenantMap values is set to `static`.
pipeline.logging.tenancy.tenantMapobject
{
  "audit": "static",
  "default": "static",
  "environment": "static",
  "infra": "static"
}
Type of tenancy used to write logs. Use static to write to the staticTenant value (recommended). Use byEnvironment to separate by the environment name. Use byNamespace to use the namespace of the source pod as the tenant. Use byLabel to use a label (tenantLabel) to specifically identify the tenant. Use none to disable tenancy (only use if loki does not have tenancy).

Metrics configuration#

Configuration for metrics behavior

KeyTypeDefaultDescription
pipeline.metrics.gateway.denyMetricslist
[]
Denylist of metrics that are excluded from being exported These are |'d in a regex pattern
pipeline.metrics.gateway.destination.prometheusRemoteWrite.enabledbooltrueEnable writing to a Prometheus remote write destination. We default to in-cluster Thanos.
pipeline.metrics.gateway.destination.prometheusRemoteWrite.urlstring"http://thanos-receive.{{ include \"mzmon.thanos.namespace\" $ }}.svc:10908/api/v1/receive"Prometheus remote write endpoint URL.
pipeline.metrics.gateway.destination.prometheusRemoteWrite.minMetricImportancestring"all"Only export metrics with the specified importance level. Values are "essential", "recommended", "extended", "diagnostic", "all"
pipeline.metrics.gateway.destination.prometheusRemoteWrite.authTypestring"none"Type of authentication to use with the loki endpoint. Use none if no authentication is required. Use basicAuth for username/password. Use bearer for bearer token. Use oauth2 for OAuth2 client credentials. Use sigv4 for AWS SigV4 signing (For AMP).
pipeline.metrics.gateway.destination.prometheusRemoteWrite.basicAuthobject
{
  "password": "",
  "passwordEnv": "GATEWAY_PROMETHEUS_DEST_PASSWORD",
  "username": "",
  "usernameEnv": "GATEWAY_PROMETHEUS_DEST_USERNAME"
}
Configuration for auth when using authType=basicAuth
pipeline.metrics.gateway.destination.prometheusRemoteWrite.bearerobject
{
  "token": "",
  "tokenEnv": "GATEWAY_PROMETHEUS_DEST_BEARER_TOKEN"
}
Configuration for bearer token when using authType=bearer This is used for bearer type tokens.
pipeline.metrics.gateway.destination.prometheusRemoteWrite.oauth2object
{
  "clientId": "",
  "clientIdEnv": "GATEWAY_PROMETHEUS_DEST_OAUTH2_CLIENT_ID",
  "clientSecret": "",
  "clientSecretEnv": "GATEWAY_PROMETHEUS_DEST_OAUTH2_CLIENT_SECRET",
  "scopes": [],
  "tokenUrl": "",
  "tokenUrlEnv": "GATEWAY_PROMETHEUS_DEST_OAUTH2_TOKEN_URL"
}
Configuration for OAuth2 when using authType=oauth2
pipeline.metrics.gateway.destination.prometheusRemoteWrite.sigv4object
{
  "region": "",
  "roleArn": ""
}
Configuration for AWS signatures when using authType=sigv4 This generally does not need to be set (it is derived from IRSA).
pipeline.metrics.gateway.destination.prometheusRemoteWrite.tls.enabledboolfalseWhether to enable TLS for the prometheus destination.
pipeline.metrics.gateway.destination.prometheusRemoteWrite.tls.verifybooltrueWhether to verify the TLS certificate for the prometheus destination.
pipeline.metrics.gateway.destination.prometheusRemoteWrite.tls.castring""Certificate Authority (CA) PEM contents for TLS.
pipeline.metrics.gateway.destination.prometheusRemoteWrite.tls.certstring""Client certificate PEM contents for TLS.
pipeline.metrics.gateway.destination.prometheusRemoteWrite.tls.keystring""Client private key PEM contents for TLS.
pipeline.metrics.gateway.destination.prometheusRemoteWrite.tls.serverNamestring""Alternative SNI (Server Name Indication) to specify.
pipeline.metrics.gateway.destination.prometheusRemoteWrite.tls.minVersionstring"TLS13"Minimum TLS version to allow. Use TLS12 if you need better compat. TLS11 and TLS10 are not recommended for production.
OpenTelemetry/OTLP destinations.#

OpenTelemetry/OTLP destination configuration for metrics (and logging).

This supports several components and allows further customization of endpoints. Multiple exporters can be enabled at once.

KeyTypeDefaultDescription
pipeline.metrics.gateway.destination.otel.enabledboolfalseEnable writing metrics to an OpenTelemetry/OTLP destination. By default, we do not use the OTel destination. WARNING: if logging otel destination is enabled, this block will still be used for configuration! (just not for metrics)
pipeline.metrics.gateway.destination.otel.googleCloudExporter.enabledboolfalseEnable writing to a Google Cloud Monitoring / Cloud Logging destination.
pipeline.metrics.gateway.destination.otel.googleCloudExporter.compressionstring"gzip"Compression for logs/metrics Only gzip is supported for Google Cloud Monitoring / Logging.
pipeline.metrics.gateway.destination.otel.googleCloudExporter.minMetricImportancestring"recommended"Only export metrics with the specified importance level. Values are "essential", "recommended", "extended", "diagnostic", "all"
pipeline.metrics.gateway.destination.otel.googleCloudExporter.handlerslist
[
  "otelcol.exporter.googlecloud.destination.input"
]
Handlers to use for the Google Cloud exporter.
pipeline.metrics.gateway.destination.otel.googleCloudExporter.configstringAn `otelcol.exporter.googlecloud.destination` definition.Raw configuration for an otelcol.exporter.googlecloud block. The default config uses Workload Identity Federation (WIF) to authenticate to GCP.
pipeline.metrics.gateway.destination.otel.datadogExporter.enabledboolfalseEnable writing to a Datadog destination.
pipeline.metrics.gateway.destination.otel.datadogExporter.compressionstring"gzip"Compression for logs/metrics Only gzip is supported for Datadog.
pipeline.metrics.gateway.destination.otel.datadogExporter.minMetricImportancestring"recommended"Only export metrics with the specified importance level. Values are "essential", "recommended", "extended", "diagnostic", "all"
pipeline.metrics.gateway.destination.otel.datadogExporter.hostMetadata.enabledboolfalseWhether to include host metadata in the Datadog exporter. FIXME: how do we support this in an Agent->Gateway architecture?
pipeline.metrics.gateway.destination.otel.datadogExporter.handlerslist
[
  "otelcol.exporter.datadog.destination.input"
]
Handlers to use for the Datadog exporter.
pipeline.metrics.gateway.destination.otel.datadogExporter.configstringAn `otelcol.exporter.datadog.destination` definition.Raw configuration for an otelcol.exporter.datadog block.
pipeline.metrics.gateway.destination.otel.otlpExporter.enabledboolfalseEnable writing to an OpenTelemetry/OTLP destination. This is the generic fallback for other destinations. You can also use this if you need to set a custom destination block.
pipeline.metrics.gateway.destination.otel.otlpExporter.urlstring""OTLP push endpoint URL. This does not need a protocol prefix (http:// or https://)
pipeline.metrics.gateway.destination.otel.otlpExporter.protocolstring"grpc"Protocol to use for OTLP. Use grpc for gRPC protocol. Use http for HTTP protocol.
pipeline.metrics.gateway.destination.otel.otlpExporter.compressionstring"gzip"Compression to use Use gzip for better compatibility. Use snappy for better performance.
pipeline.metrics.gateway.destination.otel.otlpExporter.minMetricImportancestring"all"Only export metrics with the specified importance level. Values are "essential", "recommended", "extended", "diagnostic", "all"
pipeline.metrics.gateway.destination.otel.otlpExporter.handlerslist
[
  "otelcol.exporter.otlp[http].destination.input"
]
Handlers to use for the OTLP exporter. Update this if your config was customized.
pipeline.metrics.gateway.destination.otel.otlpExporter.configstring
{{- $exporterType := ternary "otlp" "otlphttp" ( eq .Values.pipeline.metrics.gateway.destination.otel.otlpExporter.protocol "grpc" ) }}
otelcol.exporter.{{ $exporterType }} "destination" {
    client {
        endpoint = {{ .Values.pipeline.metrics.gateway.destination.otel.otlpExporter.url | required "destination.otlp.url must be set" | quote }}
        compression = {{ .Values.pipeline.metrics.gateway.destination.otel.otlpExporter.compression | quote }}
{{- if ( include "mzmon.alloyGateway.otelDest.authEnabled" $ ) }}
        auth = {{ include "mzmon.alloyGateway.otelDest.authHandler" $ }}
{{- end }}
    }
}
Raw configuration for an otelcol.exporter.otlp block. Use this if you need to configure multiple destinations or use an alternative endpoint entirely.
OpenTelemetry Authentication Configuration#

Configuration for OpenTelemetry/OTLP destinations. This is only needed for destinations that require an auth handler (like otlpExporter).

KeyTypeDefaultDescription
pipeline.metrics.gateway.destination.otel.auth.authTypestring"none"Type of authentication to use with the OpenTelemetry destination. Valid values are: `none`, `basic`, `bearer`, `awsSigv4`, and `custom`.
pipeline.metrics.gateway.destination.otel.auth.basic.configstringAn `otelcol.auth.basic.oteldest` definition.Raw configuration for an otelcol.auth.basic block. This uses the GATEWAY_OTEL_DEST_USERNAME/GATEWAY_OTEL_DEST_PASSWORD env vars.
pipeline.metrics.gateway.destination.otel.auth.bearer.configstringAn `otelcol.auth.bearer.oteldest` definition.Raw configuration for an otelcol.auth.bearer block. This uses the GATEWAY_OTEL_DEST_BEARER_TOKEN env var.
pipeline.metrics.gateway.destination.otel.auth.awsSigv4.regionstring""Override the region to sign requests for.
pipeline.metrics.gateway.destination.otel.auth.awsSigv4.roleArnstring""Override the role ARN to assume for signing requests.
pipeline.metrics.gateway.destination.otel.auth.awsSigv4.configstringAn `otelcol.auth.sigv4.oteldest` definition.Raw configuration for an otelcol.auth.sigv4 block.
pipeline.metrics.gateway.destination.otel.auth.custom.handlerstring"{{ fail \"Be sure to set this\" }}"Handler for a custom auth handler (Advanced escape hatch). This should point to the definition you used. Most auth types use `.handler` for their capsule export. If you need multiple handlers, you can skip this field and instead modify your otlpExporter.config to not use "mzmon.alloyGateway.otelDest.authHandler".
pipeline.metrics.gateway.destination.otel.auth.custom.configstring
// THIS IS AN EXAMPLE
otelcol.auth.basic "oteldest" {
    client_auth {
        username = sys.env("GATEWAY_OTEL_DEST_USERNAME")
        password = sys.env("GATEWAY_OTEL_DEST_PASSWORD")
    }
}
Raw configuration for a custom auth handler.

Monitoring configurations#

Configuration for dashboards, rules, and alerts

Underlying content is generated into pre-rendered/ from the sources under packages/ and embedded via .Files.Get.

KeyTypeDefaultDescription
dashboards.config.grafana.enabledbooltrueInstall the bundled Grafana dashboards. Requires the Grafana operator or a writable Grafana instance.
dashboards.config.grafana.modestring"operator"Grafana deployment mode, either "standalone" (the bundled Grafana chart) or "operator" (a separate Grafana Operator instance).
dashboards.config.grafana.manifest.resyncPeriodstring"5m"Time to sync the dashboard from the manifest
dashboards.config.grafana.manifest.instanceSelectorobject
{}
Non-default label selector for a Grafana-operator Grafana instance. Defaults to the labels on the `Grafana` instance this chart creates (see `connections.grafana.labels`), so the two cannot drift.
dashboards.config.grafana.manifest.allowCrossNamespaceImportstringinferredAllow dashboards to match a Grafana instance outside their own namespace. Left unset, this is inferred — it turns on only when the `Grafana` resource lands in a different namespace than the dashboards, as it does under the `split-namespace` profile. Set it explicitly when pointing `instanceSelector` at an instance this chart does not create. Note that the CRDs forbid turning this back off in place; the resource has to be recreated.
dashboards.config.grafana.manifest.apiTargetstring"dashboard.grafana.app/v2"Dashboard API Version (v2 or v2beta1)
dashboards.config.datadog.enabledboolfalseInstall the bundled Datadog dashboards. Requires Datadog API credentials configured out-of-band.
dashboards.selectedlist
[
  "env-*"
]
List of dashboard patterns to render

Rule configuration#

Configuration for rules

KeyTypeDefaultDescription
config.rules.prometheus.enabledbooltrueInstall the bundled Prometheus recording and alerting rules as PrometheusRule resources.
config.rules.loki.enabledboolfalseInstall the bundled Loki rules.
config.rules.thanos.enabledboolfalseInstall the bundled Thanos rules.

Alert configuration#

Configuration for alerts

KeyTypeDefaultDescription
config.alerts.enabledbooltrueInstall the bundled Alertmanager routing and templates.

Scraper configuration#

Configuration for scrapers

KeyTypeDefaultDescription
config.scrapers.enabledbooltrueInstall ServiceMonitors / Alloy scrape configs for Materialize and adjacent components.

Grafana connection configuration#

How to talk to a grafana instance

KeyTypeDefaultDescription
connections.grafana.modestring"bundled"How this establishes its connection to Grafana. `bundled` (default) targets the Grafana deployed by the bundled `grafana` subchart; the URL and admin-credential Secret are derived from it. `external` targets a Grafana you already run — Grafana Cloud, a shared platform Grafana, another cluster — and requires `external.url` plus either `external.apiKey` or `external.adminUser` + `external.adminPassword`. `operator` hands the instance lifecycle to grafana-operator itself, which builds it from the operator's own defaults and is not yet production-ready.
connections.grafana.labelsobject
{}
Additional labels applied to the Grafana instance, and to the `instanceSelector` of every Grafana resource this chart targets at it. Merged over a static `monitoring.materialize.cloud/grafana-instance: mzmon` label, which is what keeps the selector non-empty — grafana-operator reads an empty `matchLabels` as *every* instance, not none. Add to this to narrow the selector further, e.g. to scope per release when two `materialize-monitoring` releases share a cluster.
connections.grafana.external.urlstring""External grafana uri
connections.grafana.external.adminPasswordobject
{}
Secret for Grafana admin password
connections.grafana.external.adminUserobject
{}
Secret for Grafana admin user
connections.grafana.external.apiKeyobject
{}
Secret for Grafana API key

Datasource configuration#

Datasources provisioned into the Grafana instance

Provisioned as GrafanaDatasource resources, pushed into the same instance the dashboards target. url values are rendered with tpl, so they may reference chart helpers.

KeyTypeDefaultDescription
connections.datasources.enabledbooltrueInstall the bundled datasources. Requires the Grafana operator.
connections.datasources.resyncPeriodstring"5m"How often the operator re-pushes each datasource.
connections.datasources.editableboolfalseWhether the datasources can be edited in the Grafana UI. Edits are reverted on the next resync either way; this only hides the controls so the reversion is not a surprise.
connections.datasources.thanos.enabledstringfollows `thanos.enabledProvision the Thanos datasource. Unset follows whether the bundled Thanos is enabled. Set it explicitly to point Grafana at metrics storage this chart does not deploy.
connections.datasources.thanos.namestring"Thanos"Datasource name, as shown in Grafana.
connections.datasources.thanos.uidstring"mzmon-thanos"Stable datasource UID.
connections.datasources.thanos.urlstring"http://thanos-query.{{ include \"mzmon.thanos.namespace\" $ }}.svc:9090"Thanos Query endpoint. Rendered with `tpl`.
connections.datasources.thanos.isDefaultbooltrueMake this Grafana's default datasource. The bundled dashboards deliberately do not pin a datasource: their `${metricsDatasource}` variable resolves to whichever Prometheus-type datasource is default. With no default, every panel renders empty and Grafana reports no error. Only turn this off if something else in the instance is already the default Prometheus datasource.
connections.datasources.thanos.jsonDataobject
{}
Extra `jsonData`, merged over the chart's defaults (`prometheusType: Thanos`, `httpMethod: POST`).
connections.datasources.thanos.secureJsonDataobject
{}
Inline `secureJsonData`. Prefer `valuesFrom` for real secrets — this renders into the release manifest.
connections.datasources.thanos.valuesFromlist
[]
Secret- or ConfigMap-sourced field injection, passed through to the `GrafanaDatasource`. This is the supported way to supply credentials.
valuesFrom:
  - targetPath: secureJsonData.basicAuthPassword
    valueFrom:
      secretKeyRef:
        name: thanos-basic-auth
        key: password
connections.datasources.loki.enabledstringfollows `loki.enabledProvision the Loki datasource. Unset follows whether the bundled Loki is enabled.
connections.datasources.loki.namestring"Loki"Datasource name, as shown in Grafana.
connections.datasources.loki.uidstring"mzmon-loki"Stable datasource UID.
connections.datasources.loki.urlstring"http://loki-query-frontend.{{ include \"mzmon.loki.namespace\" $ }}.svc:3100"Loki read endpoint. Rendered with `tpl`. The Loki gateway is disabled by default, so reads go to the query frontend directly (see `loki.gateway.enabled`).
connections.datasources.loki.tenantstringfollows `pipeline.logging.tenancy.staticTenantTenant to read as, sent in the `X-Scope-OrgID` header. The bundled Loki runs `auth_enabled: true`, so reads without this header fail with `no org id`. Unset follows the tenant the pipeline writes to. Set to `""` to send no header, which is only correct against a Loki with `auth_enabled: false`.
connections.datasources.loki.jsonDataobject
{}
Extra `jsonData`, merged over the chart's defaults (the tenant header name, and `timeout`).
connections.datasources.loki.secureJsonDataobject
{}
Inline `secureJsonData`. Prefer `valuesFrom` for real secrets — this renders into the release manifest.
connections.datasources.loki.valuesFromlist
[]
Secret- or ConfigMap-sourced field injection, passed through to the `GrafanaDatasource`. This is the supported way to supply credentials.

Bundled subchart configurations#

Configuration for bundled subcharts

Alloy Agent#

Alloy collector instance running close to scrape targets. Pre-egress shaping happens here.

Upstream reference:

KeyTypeDefaultDescription
alloy-agent.fullnameOverridestring"alloy-agent"Standard Helm full-name override. We use a static name for deterministic relations.
alloy-agent.namespaceOverridestringnilNamespace override.
alloy-agent.crdsobject
{
  "create": false
}
Control for the PodLogs crd.
alloy-agent.global.podSecurityContextobject
{
  "fsGroup": 473,
  "runAsGroup": 473,
  "runAsUser": 473
}
Security context for the alloy agent pods.
alloy-agent.alloy.stabilityLevelstring"generally-available"Stability level of alloy components.
alloy-agent.alloy.extraEnvlist
[]
Extra environment variables to pass to the alloy agent pod.
alloy-agent.alloy.envFromlist
[
  {
    "configMapRef": {
      "name": "mzmon-alloy-agent-env"
    }
  },
  {
    "secretRef": {
      "name": "mzmon-alloy-agent-env",
      "optional": true
    }
  }
]
Environment variable configmaps/secrets to pass to the alloy agent pod.
alloy-agent.alloy.mountsobject
{
  "extra": [
    {
      "mountPath": "/tmp",
      "name": "tmp"
    }
  ],
  "varlog": true
}
Volume mounts to expose to alloy agent.
alloy-agent.alloy.securityContextobject
{
  "allowPrivilegeEscalation": false,
  "capabilities": {
    "drop": [
      "ALL"
    ]
  },
  "readOnlyRootFilesystem": true,
  "runAsGroup": 473,
  "runAsNonRoot": false,
  "runAsUser": 0
}
Security context for the alloy agent containers. The agent MUST run as root in order to be able to read container logs.
alloy-agent.alloy.resourcesobject
{
  "limits": {
    "cpu": "100m",
    "memory": "200Mi"
  },
  "requests": {
    "cpu": "100m",
    "memory": "200Mi"
  }
}
Resources for the alloy agent containers.
alloy-agent.controller.extraAnnotationsobject
{}
Extra annotations to apply to the alloy agent pod. If you are using pulumi, be sure to add `config.kubernetes.io/depends-on: job/mzmon-validate-agent`

Alloy Gateway#

Alloy gateway instance. Cardinality reduction and backend-specific egress happen here.

Upstream reference:

KeyTypeDefaultDescription
alloy-gateway.fullnameOverridestring"alloy-gateway"Standard Helm full-name override. We use a static name for deterministic relations.
alloy-gateway.namespaceOverridestringnilNamespace override.
alloy-gateway.crdsobject
{
  "create": false
}
Control for the PodLogs crd.
alloy-gateway.global.podSecurityContextobject
{
  "fsGroup": 473,
  "runAsGroup": 473,
  "runAsUser": 473
}
Security context for the alloy gateway pods.
alloy-gateway.alloy.stabilityLevelstring"generally-available"Stability level of alloy components.
alloy-gateway.alloy.extraEnvlist
[]
Extra environment variables to pass to the alloy gateway pod.
alloy-gateway.alloy.envFromlist
[
  {
    "configMapRef": {
      "name": "mzmon-alloy-gateway-env"
    }
  },
  {
    "secretRef": {
      "name": "mzmon-alloy-gateway-env",
      "optional": true
    }
  }
]
Environment variable configmaps/secrets to pass to the alloy gateway pod.
alloy-gateway.alloy.extraPortslist
[
  {
    "name": "loki",
    "port": 3100,
    "protocol": "TCP",
    "targetPort": 3100
  },
  {
    "name": "otlp-grpc",
    "port": 4317,
    "protocol": "TCP",
    "targetPort": 4317
  },
  {
    "name": "otlp-http",
    "port": 4318,
    "protocol": "TCP",
    "targetPort": 4318
  },
  {
    "name": "prom",
    "port": 9090,
    "protocol": "TCP",
    "targetPort": 9090
  }
]
Ports to expose from alloy-gateway.
alloy-gateway.alloy.mountsobject
{
  "extra": [
    {
      "mountPath": "/tmp",
      "name": "tmp"
    }
  ],
  "varlog": false
}
Volume mounts to expose to alloy gateway.
alloy-gateway.alloy.securityContextobject
{
  "allowPrivilegeEscalation": false,
  "capabilities": {
    "drop": [
      "ALL"
    ]
  },
  "readOnlyRootFilesystem": true,
  "runAsGroup": 473,
  "runAsNonRoot": true,
  "runAsUser": 473
}
Security context for the alloy gateway containers.
alloy-gateway.alloy.resourcesobject
{
  "limits": {
    "cpu": "500m",
    "memory": "512Mi"
  },
  "requests": {
    "cpu": "500m",
    "memory": "512Mi"
  }
}
Resources for the alloy gateway containers.
alloy-gateway.controller.extraAnnotationsobject
{}
Extra annotations to apply to the alloy gateway pod. If you are using pulumi, be sure to add `config.kubernetes.io/depends-on: job/mzmon-validate-gateway`
alloy-gateway.serviceAccount.createbooltrueCreate a service account for alloy-gateway.
alloy-gateway.serviceAccount.annotationsobject
{}
Extra annotations to set on the alloy-gateway service account. Use `eks.amazonaws.com/role-arn` to set up IRSA. Use `iam.gke.io/gcp-service-account` to set up Workload Identity Federation.

Loki#

Bundled Loki backend for logs.

Upstream reference:

KeyTypeDefaultDescription
loki.fullnameOverridestring"loki"Standard Helm full-name override. We use a static name for deterministic relations.
loki.namespaceOverridestringnilNamespace override.
loki.deploymentModestring"Distributed"How loki is deployed. We prefer to run in Distributed/Microservices mode.
loki.networkPolicy.enabledbooltrueWhether to enable a network policy for loki In production, this is recommended to be enabled.
loki.networkPolicy.metricsobject
{
  "namespaceSelector": {}
}
Selector for incoming traffic to metric endpoints. This must be configured manually (usually set to `kubernetes.io/metadata.name: monitoring`).
loki.networkPolicy.ingressobject
{
  "namespaceSelector": {}
}
Selector for incoming traffic to the read/write endpoints. This must be configured manually (usually set to `kubernetes.io/metadata.name: monitoring`).
loki.networkPolicy.externalStorageobject
{
  "cidrs": [
    "0.0.0.0/0"
  ],
  "ports": [
    443,
    80
  ]
}
Outgoing traffic from loki to the object store. This is usually unrestricted, even in many production settings. Adjust if you have a VPCEndpoint in front of S3/STS or are using a non-standard port.
loki.loki.storage.bucketNamesobject
{
  "chunks": "\u003cREPLACE-ME\u003e",
  "ruler": "\u003cREPLACE-ME\u003e"
}
Bucket names for object storage. These are required to be populated.
loki.loki.storage.use_thanos_objstorebooltrueUse the thanos object store client
loki.loki.storage.object_storeobject
{
  "type": "s3"
}
Object storage configuration. Modify as needed.
loki.loki.schemaConfigobject
{
  "configs": [
    {
      "from": "2024-01-01",
      "index": {
        "period": "24h",
        "prefix": "loki_index_"
      },
      "object_store": "s3",
      "schema": "v13",
      "store": "tsdb"
    }
  ]
}
Schema configuration for the loki TSDB. This is append-only and MUST be copied into projects and mutated on upgrades (if v13 stops being in use).
loki.loki.commonConfigobject
{
  "replication_factor": 3
}
Cluster-wide replication factor. Three is our HA floor and the reason at least three ingesters are required. (This matches the upstream default; surfaced here because it is load-bearing.)
loki.loki.limits_configobject
{
  "ingestion_burst_size_mb": 20,
  "ingestion_rate_mb": 10,
  "max_global_streams_per_user": 10000,
  "reject_old_samples": true,
  "reject_old_samples_max_age": "168h",
  "retention_period": "30d",
  "volume_enabled": true
}
Per-tenant limits, sized for a medium install. These are protective caps (per environment), not the expected volume — see Operating > Production Best Practices for the throughput envelope.
loki.loki.limits_config.retention_periodstring"30d"Default retention before the compactor deletes logs. Upstream defaults to infinite retention; we set a real bound.
loki.loki.limits_config.ingestion_rate_mbint10Per-tenant ingestion rate / burst, in MB. Per environment, not the fleet aggregate.
loki.loki.limits_config.max_global_streams_per_userint10000Active-stream ceiling per tenant; a cardinality guard.
loki.loki.limits_config.reject_old_samplesbooltrueReject writes too far in the past.
loki.loki.limits_config.volume_enabledbooltrueEnable the log-volume endpoints Grafana's logs drilldown relies on.
loki.loki.compactorobject
{
  "delete_request_store": "s3",
  "retention_delete_delay": "8h",
  "retention_enabled": true
}
Compactor *configuration* (distinct from the compactor deployment below). Retention is enforced by the compactor and is OFF in upstream defaults.
loki.loki.compactor.delete_request_storestring"s3"Where delete requests are stored; matches the object-store backend.
loki.loki.compactor.retention_delete_delaystring"8h"Grace period before retention/deletes actually remove data.
loki.gateway.enabledboolfalseDisable gateway by default. We recommend using alloy-gateway for loki writes. Use the query-frontend for loki reads.
Loki Microservice Configuration#

Configuration for each loki microservice.

https://grafana.com/docs/loki/latest/get-started/components/

KeyTypeDefaultDescription
loki.distributor.enabledbooltrueEnable the distributor microservice. Distributor is required. The Distributor is the stateless front door for writes. It validates incoming streams against per-tenant limits, enforces rate limits, and normalizes labels, then splits the batch into individual streams and forwards each to the owning ingesters.
loki.distributor.replicasstringnilNumber of replicas for the distributor microservice. If autoscaling is enabled, this should be set to null.
loki.distributor.kindstring"Deployment"Type of workload for the distributor.
loki.distributor.autoscalingobject
{
  "enabled": true,
  "maxReplicas": 4,
  "minReplicas": 2,
  "targetCPUUtilizationPercentage": 60,
  "targetMemoryUtilizationPercentage": 75
}
Configuration for autoscaling of distributor For production, this is recommended to be enabled. We provide opinionated defaults.
loki.distributor.service.typestring"ClusterIP"Service type for the distributor microservice. Without a gateway, this is the exposed write component.
loki.distributor.podDisruptionBudgetobject
{
  "enabled": true,
  "minAvailable": 1
}
Configuration for pod disruption budget for the distributor microservice.
loki.distributor.resourcesobject
{
  "requests": {
    "cpu": "150m",
    "memory": "256Mi"
  }
}
Resource requests (medium install). Tune per profile.
loki.ingester.enabledbooltrueEnable the ingester microservice. Ingester is required. The Ingester is the stateful heart of the write path, and it also serves the most recent reads. It buffers incoming entries into per-stream in-memory chunks, compresses them, and periodically flushes those chunks and their index to object storage.
loki.ingester.replicasint3Number of ingester replicas. Three is the floor for replication_factor 3. Scale UP on memory / stream-cardinality (so streams shard across the ring), not on bytes — with replicas == replication_factor every ingester holds every stream.
loki.ingester.kindstring"StatefulSet"Ingesters are stateful.
loki.ingester.persistence.enabledboolfalseRun ingesters ephemerally: no PVC, node-local emptyDir for the WAL and not-yet-flushed chunks. Durability comes from replication_factor 3, not from disk — a rescheduled ingester starts fresh and the ring backfills from its peers. This avoids EBS zonal pinning and the slow volume detach/attach that PVCs incur during node replacement.
loki.ingester.terminationGracePeriodSecondsint60Termination grace period for ingesters. flush-on-shutdown is best-effort; if the node force-kills sooner, the other replicas still flush their copies, so do not depend on a long grace period for durability. Kept modest to survive enterprise force-kill windows.
loki.ingester.affinityobject
{
  "podAntiAffinity": {
    "requiredDuringSchedulingIgnoredDuringExecution": null
  }
}
Pod affinity for ingesters. Drop the chart's default *hard* per-host anti-affinity so host spread can be soft (see topologySpreadConstraints); zone spread stays hard. We null the nested list rather than the whole `affinity` map: `affinity: {}` is a no-op against the subchart default, and `affinity: null` clears it but makes helm-unittest log a noisy "cannot overwrite table" warning — nulling the list avoids both.
loki.ingester.topologySpreadConstraintslist
[
  {
    "labelSelector": {
      "matchLabels": {
        "app.kubernetes.io/component": "ingester",
        "app.kubernetes.io/instance": "{{ .Release.Name }}",
        "app.kubernetes.io/name": "{{ include \"loki.name\" . }}"
      }
    },
    "matchLabelKeys": [
      "controller-revision-hash"
    ],
    "maxSkew": 1,
    "minDomains": 2,
    "nodeTaintsPolicy": "Honor",
    "topologyKey": "topology.kubernetes.io/zone",
    "whenUnsatisfiable": "DoNotSchedule"
  },
  {
    "labelSelector": {
      "matchLabels": {
        "app.kubernetes.io/component": "ingester",
        "app.kubernetes.io/instance": "{{ .Release.Name }}",
        "app.kubernetes.io/name": "{{ include \"loki.name\" . }}"
      }
    },
    "matchLabelKeys": [
      "controller-revision-hash"
    ],
    "maxSkew": 1,
    "topologyKey": "kubernetes.io/hostname",
    "whenUnsatisfiable": "ScheduleAnyway"
  }
]
Topology spread for ingesters: hard across zones, soft across hosts. A pod that cannot satisfy the hard zone rule goes Pending, which is the signal Karpenter (or the cluster-autoscaler) uses to add a node in the deficient zone — soft rules cannot summon capacity that way. Host spread is soft so pods still schedule when nodes are momentarily scarce.
loki.ingester.zoneAwareReplication.enabledboolfalseEnable zone-aware replication for the ingester microservice. Not needed at our size: topologySpreadConstraints give the AZ spread without the complexity.
loki.ingester.podDisruptionBudgetobject
{
  "enabled": true,
  "maxUnavailable": 1
}
PDB for the ingester. Protect ingest quorum across rollouts and node drains. Do not set maxUnavailable >= 2 to avoid potential quorum loss.
loki.ingester.resourcesobject
{
  "requests": {
    "cpu": "500m",
    "memory": "1Gi"
  }
}
Resource requests (medium install). The memory *limit* is intentionally left unset: an OOM-kill drops in-memory / WAL-buffered logs, so we alert on usage rather than cap hard.
loki.querier.enabledbooltrueEnable the querier microservice. Querier is required. It executes LogQL, reading recent data from ingesters and historical data from object storage.
loki.querier.replicasstringnilReplicas are managed by autoscaling; leave null.
loki.querier.autoscalingobject
{
  "enabled": true,
  "maxReplicas": 4,
  "minReplicas": 2,
  "targetCPUUtilizationPercentage": 60,
  "targetMemoryUtilizationPercentage": 75
}
Autoscale the stateless read workers with query load.
loki.querier.podDisruptionBudgetobject
{
  "enabled": true,
  "minAvailable": 1
}
PDB for querier.
loki.querier.resourcesobject
{
  "requests": {
    "cpu": "250m",
    "memory": "512Mi"
  }
}
Resource requests. This assumes a medium install by default.
loki.queryFrontend.enabledbooltrueEnable the query-frontend microservice. Query-frontend is required. It queues and splits queries and caches results.
loki.queryFrontend.replicasstringnilStatic replicas for query-frontend when autoscaling is not enabled.
loki.queryFrontend.kindstring"Deployment"Kind of workload for query-frontend. Deployments are recommended for stateless components.
loki.queryFrontend.autoscalingobject
{
  "enabled": true,
  "maxReplicas": 4,
  "minReplicas": 2,
  "targetCPUUtilizationPercentage": 60,
  "targetMemoryUtilizationPercentage": 75
}
Configure autoscaling for query-frontend.
loki.queryFrontend.podDisruptionBudgetobject
{
  "enabled": true,
  "minAvailable": 1
}
PDB for query-frontend.
loki.queryFrontend.resourcesobject
{
  "requests": {
    "cpu": "100m",
    "memory": "256Mi"
  }
}
Resource requests for query-frontend.
loki.queryScheduler.enabledbooltrueEnable the query-scheduler microservice. Recommended for scaled deployments: it decouples the read queue from the query-frontend so the two scale independently. Two replicas for availability.
loki.queryScheduler.podDisruptionBudgetobject
{
  "enabled": true,
  "minAvailable": 1
}
PDB for query-scheduler.
loki.queryScheduler.resourcesobject
{
  "requests": {
    "cpu": "100m",
    "memory": "256Mi"
  }
}
Resources for the query-scheduler microservice.
loki.compactor.enabledbooltrueEnable the compactor. Compactor is required and MUST be a singleton: it compacts the index and enforces retention against shared object storage.
loki.compactor.replicasint1Compactor replicas. This MUST be one, since it runs as a singleton.
loki.compactor.kindstring"StatefulSet"Kind of workload for the compactor. This must be a statefulset.
loki.compactor.persistence.enabledboolfalseRun the compactor ephemerally (no PVC, node-local emptyDir). Its local dir is just a working copy of the object-store index, and compaction is idempotent and off the critical path — losing it only restarts the current cycle. Ephemeral also lets the singleton float freely between zones instead of being pinned by a PVC. Our production index is ~30MB, so re-downloading each cycle is negligible.
loki.compactor.resourcesobject
{
  "requests": {
    "cpu": "250m",
    "memory": "512Mi"
  }
}
Resources for the compactor.
loki.indexGateway.enabledbooltrueEnable the index-gateway. Index-gateway is required: it serves index lookups so queriers do not each download the whole index from object storage.
loki.indexGateway.persistenceobject
{
  "enabled": false
}
Run the index-gateway ephemerally (no PVC, node-local emptyDir). The local index is a read-through cache of object storage, not authoritative — a fresh pod just re-downloads what it queries. This keeps the read path off EBS zonal pinning; the only cost is a little cold-start warmup and a burst of index reads after a reschedule.
loki.indexGateway.podDisruptionBudgetobject
{
  "enabled": true,
  "minAvailable": 1
}
PDB for index-gateway.
loki.indexGateway.resourcesobject
{
  "requests": {
    "cpu": "200m",
    "memory": "512Mi"
  }
}
Resources for the index-gateway.
loki.ruler.enabledbooltrueEnable the ruler. The ruler evaluates LogQL alerting and recording rules. Recording-rule samples are remote-written back through alloy-gateway to the metric store.
loki.ruler.persistence.enabledbooltrueKeep a PVC for the ruler (unlike the other components, which are ephemeral). Rule definitions come from object storage, but the ruler's remote-write WAL buffers recording-rule samples when the metric store is unreachable — genuinely useful durability in the run-up to an incident, exactly when you don't want to drop derived signals.
loki.ruler.podDisruptionBudgetobject
{
  "enabled": true,
  "minAvailable": 1
}
PDB for the ruler.
loki.ruler.resourcesobject
{
  "requests": {
    "cpu": "100m",
    "memory": "256Mi"
  }
}
Resources for the ruler.
loki.chunksCacheh5{"allocatedMemory":2048}Chunk cache (memcached). Default allocation is sized for very large installs; we shrink it to match our volumes. The results cache keeps its upstream default.
loki.monitoring.serviceMonitor.enabledbooltrueEnable a ServiceMonitor for the loki microservices.
loki.lokiCanaryh5{"enabled":true, "kind":"Deployment", "lokiurl":"loki-query-frontend:3100", "push":false}End-to-end write→read canary for meta-monitoring. On by default upstream; surfaced here because self-monitoring the log store is a first-class requirement for us.

Thanos#

Bundled Thanos backend for long-term metrics.

Upstream reference:

KeyTypeDefaultDescription
thanos.fullnameOverridestring"thanos"Standard Helm full-name override. We use a static name for deterministic relations.
thanos.namespaceOverridestringnilNamespace override.
thanos.global.pdbobject
{
  "enabled": true,
  "maxUnavailable": 1
}
PodDisruptionBudgets for every Thanos component.

Upstream ships these off. One switch turns them on for all components, and the per-component pdb.enabled cannot opt back out — the subchart templates test or <component>.pdb.enabled global.pdb.enabled.

maxUnavailable rather than minAvailable, deliberately:

  • It scales with replica count instead of pinning an absolute floor.
  • On the single-replica Compactor, minAvailable: 1 would block every voluntary eviction and hang node drains indefinitely. maxUnavailable: 1 permits the eviction, so the singleton is a harmless no-op rather than a drain deadlock.
  • On Receive it must stay within what write quorum tolerates (replicationFactor - ((replicationFactor / 2) + 1)), which is 1 at the replication factor of 3 set below. A validator enforces this.

This matches the Loki convention (ingester maxUnavailable: 1).

thanos.queryobject
{
  "autoscaling": {
    "enabled": true,
    "maxReplicas": 5,
    "minReplicas": 2,
    "targetCPUUtilizationPercentage": 80
  },
  "enabled": true
}
Thanos Query configuration. Query provides a PromQL query endpoint.
thanos.query.autoscalingobject
{
  "enabled": true,
  "maxReplicas": 5,
  "minReplicas": 2,
  "targetCPUUtilizationPercentage": 80
}
Horizontal autoscaling for Query. Query is a stateless PromQL fan-out, so it is the natural place to autoscale: no local state, no ring membership, no PVC.
thanos.receive.enabledbooltrueEnable Thanos receiver. Receive provides a Prometheus remote_write-compatible endpoint.
thanos.receive.modestring"standalone"Whether to split receive distributors from ingesters. mode=split is not very stable in the helm chart
thanos.receive.replicaCountint3Number of receive replicas.
thanos.storegatewayobject
{
  "enabled": true
}
Thanos Store Gateway configuration. Store Gateway provides historical block querying.

Autoscaling is available upstream but left off deliberately. Store Gateway is a PVC-backed StatefulSet that syncs the bucket’s block index on startup, so scale-up is slow to become useful (it serves nothing until the index is warm) and CPU-triggered scaling reacts long after the load that triggered it. Scale-down also leaves orphaned PVCs behind, since StatefulSet volumes are not reclaimed. Size it deliberately instead.

thanos.compactorobjectenabled with default retention policiesThanos Compactor configuration. Compactor provides block compaction and downsampling.
thanos.compactor.retentionobject
{
  "resolution1h": "365d",
  "resolution5m": "90d",
  "resolutionRaw": "30d"
}
Retention policies for Thanos Compactor downsampled data
thanos.queryFrontendobject
{
  "autoscaling": {
    "enabled": true,
    "maxReplicas": 5,
    "minReplicas": 2,
    "targetCPUUtilizationPercentage": 80
  },
  "enabled": false
}
Thanos Query Frontend configuration. Query Frontend provides query parallelization and result caching. Only required for production.

Note that enabling this is not sufficient on its own: point connections.datasources.thanos.url at the query-frontend Service too, or reads keep going straight to Query and the cache is never consulted. A validator warns when the two disagree.

thanos.queryFrontend.autoscalingobject
{
  "enabled": true,
  "maxReplicas": 5,
  "minReplicas": 2,
  "targetCPUUtilizationPercentage": 80
}
Horizontal autoscaling for Query Frontend. Stateless like Query, so the same reasoning applies. Inert until `queryFrontend.enabled` is true.
thanos.rulerobject
{
  "enabled": false
}
Thanos Ruler configuration. Ruler provides alerting and recording rules evaluation.

Grafana Operator#

Bundled Grafana-operator for managing Grafana instances.

Upstream references:

KeyTypeDefaultDescription
grafana-operator.fullnameOverridestring"grafana-operator"Standard Helm full-name override. We use a static name for deterministic relations.
grafana-operator.namespaceOverridestringnilNamespace override.
grafana-operator.crdsobject
{
  "immutable": true
}
CRD behavior. The Grafana Operator CRDs are owned by the `materialize-monitoring-crds` chart, which vendors a deflated copy of them. The operator chart offers no way to skip its own CRDs outright — `immutable` only chooses where they come from — so keep this `true`: that keeps them out of this chart's release manifest and leaves them install-only, which `helm install --skip-crds` drops entirely. Setting it `false` makes this chart template and upgrade the CRDs itself, fighting the CRDs chart for ownership.

Grafana Instance#

Bundled Grafana for dashboard rendering.

Upstream reference:

KeyTypeDefaultDescription
grafana.fullnameOverridestring"grafana"Standard Helm full-name override. We use a static name for deterministic relations. `connections.grafana.mode: bundled` derives the URL it hands grafana-operator from this, so a release-name-derived name would leave the operator dialing a host that does not resolve.
grafana.namespaceOverridestringnilNamespace override.

Alertmanager#

Bundled Alertmanager for routing alerts emitted by the rule packages.

KeyTypeDefaultDescription
alertmanager.persistence.sizestring"4Gi"Volume for silences and the notification log. Sized by cloud disk minimums, not by Alertmanager, which needs kilobytes. The subchart default of 50Mi is below the 4 GiB floor on GCP Hyperdisk and Azure managed disks, so provisioning fails there.

Kube State Metrics#

kube-state-metrics for Kubernetes resource-state metrics consumed by Materialize-adjacent dashboards.

Upstream reference:

Metrics Server#

metrics-server for pod and node resource usage; only needed when the cluster does not already ship one.

Upstream reference:

KeyTypeDefaultDescription
metrics-server.replicasint1Number of replicas for metrics-server.