No introduction found. Create it?
alloy from home-operations/charts-mirror/alloy is more popular with 18 repositories.
Install with:
helm repo add grafana oci://ghcr.io/grafana-operator/helm-charts/
helm install alloy grafana/alloy -f values.yamlSee examples from other people.
| Name | Repo | Stars | Version | Timestamp |
|---|---|---|---|---|
| alloy | Pumba98/flux2-gitops | 70 | 1.14.2 | 6 hours ago |
| alloy | anthr76/infra | 107 | 1.14.2 | a day ago |
| alloy | gabe565/home-ops | 38 | 1.13.0 | 25 days ago |
See the most popular values for this chart:
| Key | Types |
|---|---|
controller.type (14) daemonset | string |
| string | |
controller.tolerations[].effect (4) NoSchedule | string |
controller.tolerations[].key (1) node-role.kubernetes.io/control-plane | string |
| string | |
controller.volumes.extra[].hostPath.path (1) /var/lib/alloy | string |
controller.volumes.extra[].hostPath.type (1) DirectoryOrCreate | string |
| string | |
| string | |
| string | |
| string | |
| string | |
alloy.configMap.content (11) logging {
level = "info"
format = "logfmt"
}
// Discover only pods running on this node — Alloy runs as a
// DaemonSet, so each pod scopes discovery via the K8S_NODE_NAME
// env var the chart injects (spec.nodeName).
discovery.kubernetes "pods" {
role = "pod"
selectors {
role = "pod"
field = "spec.nodeName=" + sys.env("K8S_NODE_NAME")
}
}
// Derive our target label set from pod metadata.
// namespace <- __meta_kubernetes_namespace
// app <- pod label app.kubernetes.io/name (fallback: `app`)
// container <- __meta_kubernetes_pod_container_name
// pod <- __meta_kubernetes_pod_name (structured metadata)
// node <- __meta_kubernetes_pod_node_name (structured metadata)
//
// Defaulting to "unknown" for missing namespace/app is done HERE
// (via an empty-match relabel rule), NOT in loki.process. Reason:
// loki.process stages operate on the per-entry `extracted` map,
// which starts empty for each log line and is NOT seeded from the
// incoming stream labels. If we ran `stage.template { source =
// "namespace" ... else "unknown" }` there, .Value would always be
// empty, template would always write "unknown" into extracted, and
// the subsequent `stage.labels { namespace = "" }` would promote
// that "unknown" back over the good stream label from discovery.
// (See labels.go processLabelsConfigs — it reads only from
// extracted, never from labels.) This exact mistake was the root
// cause of the LokiIngestionDiscardsHigh flapping fixed by #3395.
discovery.relabel "pod_logs" {
targets = discovery.kubernetes.pods.targets
rule {
source_labels = ["__meta_kubernetes_namespace"]
target_label = "namespace"
}
// Default namespace to "unknown" if somehow empty (edge-case AC).
rule {
source_labels = ["namespace"]
regex = "^quot;
target_label = "namespace"
replacement = "unknown"
}
// Preferred workload label.
rule {
source_labels = ["__meta_kubernetes_pod_label_app_kubernetes_io_name"]
target_label = "app"
}
// Fallback: bare `app` label (older charts / hand-written manifests).
rule {
source_labels = ["app", "__meta_kubernetes_pod_label_app"]
separator = ";"
regex = "^;(.+)quot;
target_label = "app"
replacement = "$1"
}
// Default app to "unknown" if no workload label is set at all
// (edge-case AC — never drop the line).
rule {
source_labels = ["app"]
regex = "^quot;
target_label = "app"
replacement = "unknown"
}
rule {
source_labels = ["__meta_kubernetes_pod_container_name"]
target_label = "container"
}
rule {
source_labels = ["__meta_kubernetes_pod_name"]
target_label = "pod"
}
rule {
source_labels = ["__meta_kubernetes_pod_node_name"]
target_label = "node"
}
}
// Tail container logs for the discovered pods via the K8s API.
// Requires pods/log RBAC (granted above).
loki.source.kubernetes "pods" {
targets = discovery.relabel.pod_logs.output
forward_to = [loki.process.normalise.receiver]
}
// Normalise the level field and enforce the approved label set.
// See #3347 comment ("label cardinality strategy") for the rules:
// * stream/index labels: namespace, app, container, level
// * structured metadata: pod, node
// * unmatched level -> "unknown" (never drop the line)
//
// IMPORTANT: loki.process stages read/write the per-entry
// `extracted` map, which starts empty for each log line. It is
// NOT seeded from the incoming stream labels. So `stage.template`
// + `stage.labels` are only appropriate for values genuinely
// extracted from the log content here (i.e. `level`). For values
// that already arrive as stream labels (namespace, app, container),
// defaulting must happen upstream in discovery.relabel — running
// template+labels on them would overwrite the good values with
// "unknown". `stage.structured_metadata` is different: it falls
// back to reading from e.Labels when the extracted key is missing
// (see stages/structured_metadata.go), which is why pod/node work.
loki.process "normalise" {
// Drop a line that is more than 45 minutes old when Alloy reads it
// (#3892). Each hour, loki.source.kubernetes re-opens the log
// stream of each container and sends the last line it read again.
// If that line is more than 1h behind the newest line of its Loki
// stream (max_chunk_age / 2), or older than
// reject_old_samples_max_age, Loki rejects it. Loki stores the
// other lines of the push but returns HTTP 400, and loki.write
// then counts every entry of the batch as dropped (reason
// ingester_error).
//
// In steady state the tailer follows the stream and reads each
// line when the container writes it, so only a re-sent line is
// this old.
// 45m is the 1h Loki window minus about 10m, the longest time
// that loki.write retries one batch with its default settings.
// As a result, a line that passes this stage is still inside the
// Loki window when the push arrives.
//
// Cost: after an Alloy or Loki outage of more than 45m, Alloy
// drops the part of the backlog that is older than 45m.
//
// Keep this stage first. If it moves below the zigbee2mqtt
// stage.match, a re-sent failure line older than 45m increments
// that counter again.
stage.drop {
older_than = "45m"
drop_counter_reason = "older_than_45m"
}
// Pre-seed extracted[level] = "unknown" for every entry. The
// regex/klog stages below will overwrite this if they extract
// a level from the log line; if none match, the seed survives
// and satisfies the edge-case AC (line with no recognisable
// level still ingests with level="unknown").
//
// Why a static template value ("unknown") instead of a Go
// template that would say "if .Value use it else unknown":
// this whole configMap.content string is fed through Helm's
// `tpl` function by the alloy chart (templates/configmap.yaml),
// which evaluates any Go-template action inside at chart-render
// time in Helm's own context (where .Value is nil), collapsing
// the Alloy-side Go template into the literal string "unknown"
// BEFORE the ConfigMap is even written. Escaping the braces
// (backtick-raw-string form, the double-quoted string-brace
// idiom, etc.) is either eaten by the outer tpl or unsupported
// by its lexer, AND ANY Go-template action sequence inside
// this configMap.content string — including ones sitting
// harmlessly inside `//` comments — is parsed by tpl, so we
// avoid the whole syntax in this file. Pre-seeding sidesteps
// the problem: no Go-template syntax inside stage.template,
// nothing for Helm to interpret. This latent Helm-tpl
// collision was the second half of the #3395 root cause: the
// original #3347 pipeline had the same collision for
// namespace/app/level, all three rendering as `template =
// "unknown"` in the deployed ConfigMap.
stage.template {
source = "level"
template = "unknown"
}
// Extract a level token from the log line if present. Best-effort;
// structured JSON logs, klog-style prefixes (I0101 ...), and plain
// words are all common. On no match, stage.regex is a silent
// no-op (see stages/regex.go Process) so the "unknown" seed above
// survives.
stage.regex {
expression = "(?i)(?:^|[^A-Za-z])(?P<level>trace|debug|info|warn(?:ing)?|err(?:or)?|fatal|critical|panic)(?:[^A-Za-z]|$)"
}
// klog-style single-letter prefixes at line start (I0101 ..., W0101 ...).
stage.regex {
expression = "^(?P<level>[IWED])[0-9]{4}"
}
// Canonicalise to lowercase {info, warn, error, debug}. Anchored
// so partial matches don't collide.
//
// IMPORTANT: `stage.replace` only substitutes the CAPTURED groups,
// not the full match (see stages/replace.go getReplacedEntry).
// So the entire input we want to overwrite MUST be wrapped in a
// capture group, otherwise the un-captured prefix survives and we
// end up with values like "Iinfo", "wwarn", "eerror". This was a
// latent bug in the original #3347 pipeline, only unmasked once
// #3395 stopped `stage.template` from clobbering level to
// "unknown" before anyone could see the real value.
stage.replace {
source = "level"
expression = "^(?i)(i|info)quot;
replace = "info"
}
stage.replace {
source = "level"
expression = "^(?i)(w|warn(?:ing)?)quot;
replace = "warn"
}
stage.replace {
source = "level"
expression = "^(?i)(e|err(?:or)?)quot;
replace = "error"
}
stage.replace {
source = "level"
expression = "^(?i)(d|debug)quot;
replace = "debug"
}
stage.replace {
source = "level"
expression = "^(?i)(fatal|critical|panic)quot;
replace = "error"
}
stage.replace {
source = "level"
expression = "^(?i)(trace)quot;
replace = "debug"
}
// zigbee2mqtt failed-command counter (#3885). Only the zigbee2mqtt
// stream enters this block, and only failed Publish lines pass the
// line filter, so no other entry changes. stage.metrics reads its
// labels from the entry label set, so stage.labels promotes device
// and reason first. The final stage.label_keep below drops both
// from the Loki stream labels.
stage.match {
selector = "{app=\"zigbee2mqtt\"} |~ \"Publish .* failed\""
stage.regex {
expression = "Publish '[^']*' '[^']*' to '(?P<device>[^']*)' failed"
}
// Seed the reason, then overwrite it for the two known causes.
stage.template {
source = "reason"
template = "other"
}
stage.regex {
expression = "(?P<reason>failed to send ZCL request|No ZCL response received)"
}
stage.replace {
source = "reason"
expression = "(failed to send ZCL request)"
replace = "send"
}
stage.replace {
source = "reason"
expression = "(No ZCL response received)"
replace = "timeout"
}
stage.labels {
values = {
device = "",
reason = "",
}
}
stage.metrics {
metric.counter {
name = "zigbee2mqtt_command_failures_total"
description = "Failed zigbee2mqtt Publish commands, by device and reason."
source = "device"
action = "inc"
// Failures are rare. An expired series restarts at 1, and
// Prometheus increase() does not count the first sample of a
// new series. A short idle time hides most single failures.
max_idle_duration = "8760h"
}
}
}
// Promote extracted level -> stream label. namespace/app/container
// already arrive as stream labels via discovery.relabel and are
// preserved by stage.label_keep below; running them through
// stage.labels would be a no-op at best (extracted map is empty
// for them) and destructive at worst.
stage.labels {
values = {
level = "",
}
}
// Attach pod + node as structured metadata (Loki 3.x). NOT stream
// labels — see the cardinality decision in #3347.
stage.structured_metadata {
values = {
pod = "",
node = "",
}
}
// Final gate: drop everything else from the stream index.
// Exact list per the Phase 2 spec.
stage.label_keep {
values = ["app", "container", "level", "namespace"]
}
forward_to = [loki.write.default.receiver]
}
// Push to the in-cluster Loki. auth_enabled=false on the Loki side,
// so no tenant header / credential is required for intra-cluster
// pushes.
loki.write "default" {
endpoint {
url = "http://loki.monitoring.svc:3100/loki/api/v1/push"
}
}
| string |
| boolean | |
alloy.configMap.key (3) config.alloy | string |
alloy.configMap.name (3) alloy-config | string |
| boolean | |
| string | |
| string | |
| string | |
| boolean | |
| string | |
| string | |
| boolean | |
| string | |
alloy.extraPorts[].name (1) otlp-grpc | string |
| number | |
| string | |
| number | |
alloy.listenPort (1) 12345 | number |
| boolean | |
| string | |
| boolean | |
alloy.storagePath (1) /var/lib/alloy | string |
| boolean | |
rbac.create (3) true | boolean |
| string | |
rbac.clusterRules[].resources[] (2) - namespaces | string |
rbac.clusterRules[].verbs[] (2) - get | string |
| string | |
rbac.rules[].resources[] (2) - pods | string |
rbac.rules[].verbs[] (2) - get | string |
fullnameOverride (2) alloy | string |
| string | |
| string | |
| string | |
| string | |
crds.create (1) false | boolean |
global.podSecurityContext.fsGroup (1) ${SECURITY_CONTEXT_RUN_AS_GROUP} | string |
global.podSecurityContext.fsGroupChangePolicy (1) OnRootMismatch | string |
global.podSecurityContext.runAsGroup (1) ${SECURITY_CONTEXT_RUN_AS_GROUP} | string |
| boolean | |
global.podSecurityContext.runAsUser (1) ${SECURITY_CONTEXT_RUN_AS_USER} | string |
global.podSecurityContext.seccompProfile.type (1) RuntimeDefault | string |
image.repository (1) grafana/alloy | string |
image.tag (1) v1.21.2 | string |
| boolean | |
| string |