Skip to main content

Deploy the System Alloy

Inspect 1.36

The System Alloy is the one agent that runs on every node, and it collects everything the node itself knows: the control-plane metrics on loopback, the kubelet and cadvisor series, the Cilium and cluster component endpoints, and the container, systemd, and audit logs. One Helm release covers all of it. Application metrics are deliberately absent, for the reason in .

Prerequisites: a metrics backend that accepts remote-write and a Loki that accepts pushes, both reachable from inside the cluster ( and ), plus Helm and kubectl access.

Warning

This DaemonSet is a privileged collector. On the host network it reaches every loopback listener on the node, including the unauthenticated ones (etcd on 2381, KubeGate on 8080, syself-agent on 20257), and it reads root-owned audit files. Keep it in a namespace only your platform team can write to, run no application workloads there, and review its config like a firewall rule.

What the config has to solve#

Four things make this config specific to Syself Autopilot rather than a stock Kubernetes install.

  1. Loopback binding. Most platform metrics endpoints bind to 127.0.0.1, so the agent shares the node's network namespace with hostNetwork: true and dials loopback directly. dnsPolicy: ClusterFirstWithHostNet keeps cluster DNS resolvable from there, which the backend Service names need.

  2. A per-node identity. Every agent dials the same 127.0.0.1:<port>, so without intervention every node reports the same instance label. A remote-write receiver then sees many agents writing one series, interleaves the samples, and rejects the overlap as out of order (err-mimir-sample-out-of-order on Grafana Mimir). The config stamps the node name as instance and node on every target, injected through the Downward API.

  3. Node-role gating. etcd, kube-controller-manager, kube-scheduler, KubeGate, and syself-tunnel-server run only on control planes; syself-proxy only on workers; the CSI node plugin only on cloud workers. Give every node the same target list and the nodes missing a component report connection refused forever, behind a TargetDown alert that never clears. The config reads the local node's labels and keeps each job only where that component actually runs. Control-plane nodes carry node-role.kubernetes.io/control-plane with an empty value, so the gate checks that the label is there rather than what it says; workers carry node.kubernetes.io/worker=true, and cloud nodes carry autopilot.syself.com/machine-type=hcloud. On a cluster with bare-metal workers, the CSI node job needs both labels: a worker-only gate keeps the bare-metal nodes, where that port is never listening.

  4. Self-signed certificates on loopback. kube-controller-manager, kube-scheduler, and the kubelet serve HTTPS with a certificate the agent cannot verify from loopback: self-signed, or with the node IP as its only SAN. Those jobs set insecure_skip_verify. The connection never leaves the node, so there is nothing on the wire to intercept. The API server is the exception: it listens on loopback too and its serving certificate names 127.0.0.1, so that job verifies against the ServiceAccount CA bundle rather than skipping verification.

Deploy it#

Write the values file #

The chart's own RBAC covers everything this agent needs. rbac.create: true grants the discovery permissions plus nodes/metrics and get on the /metrics non-resource URL, which is exactly what the token-authenticated control-plane and kubelet jobs authorize against, so no extra ClusterRole is required.

mounts.varlog: true mounts the host's whole /var/log, which is where the container logs, the journal, and all three audit streams live, so one flag replaces a list of hostPath volumes. All three audit streams are mode 0600, owned by root except the KubeGate log, which belongs to the unprivileged user KubeGate runs as. Either way no unprivileged reader can open them, so the container runs as root.

Install the chart #

		$ helm repo add grafana https://grafana.github.io/helm-charts
$ helm upgrade -i system-alloy grafana/alloy \
  --namespace monitoring --create-namespace \
  --version <chart-version> \
  --values system-alloy-values.yaml
	

Pin --version to a chart release you have tested, so an upgrade does not change collection behavior underneath you.

Confirm a pod runs on every node #

		$ kubectl -n monitoring get pods -l app.kubernetes.io/instance=system-alloy -o wide
	

Expect one pod per node, control planes included. A control-plane node with no pod means the toleration did not apply.

Check the targets #

Alloy serves a UI that lists every component and its targets, which is the fastest way to see what a single node is actually collecting:

		$ kubectl -n monitoring port-forward daemonset/system-alloy 12345:12345
	

Open http://localhost:12345 and look at the prometheus.scrape components. On a control-plane pod the etcd, kubegate, controller_manager, scheduler, and tunnel_server components each show one target up, and csi_node and syself_proxy show none. On a worker pod that reverses. A component with no targets on the node where the component does not belong is the gating working, not a fault.

Then confirm the samples arrive at the backend, one series per node rather than one shared series:

text
		count(up{job="etcd"}) by (instance)
count(up{job="cilium-agent"}) by (instance)
	

Add the Hubble flow log#

If you turned on Hubble flow export ( ), the verdict log is a file on the node like any other, and the values file above already mounts its directory. Add a source for it:

terraform
		local.file_match "hubble_flows" {
  path_targets = [{
    __path__ = "/var/run/cilium/hubble/events.log",
    job      = "hubble-flows",
    node     = sys.env("NODE_NAME"),
  }]
}
 
loki.source.file "hubble_flows" {
  targets    = local.file_match.hubble_flows.targets
  forward_to = [loki.write.central.receiver]
}
	

Keep the log volume down#

The journald rule above keeps kubelet, containerd, and the syself-* units and drops the rest, which is most of the volume for none of the value. Container logs are the other large contributor and vary entirely with your workloads, so drop or sample the high-volume namespaces rather than paying to store what you will not query. When Loki is slow or an incident floods logs, Alloy's disk buffer at storagePath absorbs the burst; size that volume so a spike degrades into delay instead of taking the node's memory with it. has the per-node estimates to size against.

With the node signals flowing, add for your workloads' metrics and traces. For what the series you just started collecting actually tell you, see and . If a target will not come up, covers the failures worth knowing by name.