Deployment¶
The deploy/ directory contains four manifests, applied in order by
kubectl apply -f deploy/.
| File | Contents |
|---|---|
00-namespace.yaml |
The oom-oracle namespace, labelled for Pod Security |
10-rbac.yaml |
ServiceAccount, ClusterRole, ClusterRoleBinding |
20-daemonset.yaml |
The agent itself |
30-service.yaml |
A headless Service |
Two ways to install¶
helm install oci://ghcr.io/ethan-kane-ops/charts/oom-oracle |
The supported path. Values are documented in the chart README |
kubectl apply -f deploy/ |
The same objects as plain YAML, for reading and for clusters with no Helm |
deploy/ is not a lesser copy: CI renders both and checks every daemon flag they
pass actually exists on the binary, and the nightly e2e installs the chart on
kind and asserts the probe attaches. They can still differ in defaults, so treat
the chart as authoritative.
What it needs, and why¶
| Grant | Why |
|---|---|
CAP_BPF |
Creating the maps and loading the verified program |
CAP_PERFMON |
Attaching the kprobe through perf_event_open. Loading succeeds without it; attaching is what fails |
runAsUser: 0 |
A non-root process starts with an empty effective capability set regardless of its bounding set, so bpf() returns EPERM and the daemon silently degrades |
hostPID: true |
/proc must show the node's processes, or there is no victim to identify and no process list to build |
/sys/fs/cgroup, /proc |
Read-only host mounts. The daemon never writes to the node |
pods: get,list,watch |
The only cluster access it has. Read-only, and only to turn a pod UID into a name |
The daemon never writes to the node or to the API server.
runAsUser: 0 is not redundant
Capabilities alone are not enough. The distroless base image runs as a
non-root user, and a non-root process starts with an empty effective
capability set no matter how permissive its bounding set is. Populating it
for a non-root UID needs ambient capabilities, which a pod spec cannot
request. The result is not an error: bpf() returns EPERM and the daemon
quietly falls back to polling. This was found by the e2e suite, not by a
unit test.
The container therefore runs as UID 0 with drop: [ALL] and exactly two
capabilities added, rather than as a non-root user holding two capabilities
it could never use.
privileged: true is no longer requested
It was, until the narrower set was measured on kind. Dropping it did break
the process listing at first, and the cause was not a capability: containerd
puts a privileged container in the host cgroup namespace and everything else
in a private one, and /proc/<pid>/cgroup is written relative to the
reader's. The daemon now reads cgroup membership from the kernel's
cgroup.procs, which reads the same from any namespace.
Pod Security Admission¶
The namespace carries the privileged PSA level on all three of enforce, audit
and warn:
pod-security.kubernetes.io/enforce: privileged
pod-security.kubernetes.io/audit: privileged
pod-security.kubernetes.io/warn: privileged
This is still required after dropping privileged: true. Baseline forbids host
namespaces, hostPath volumes, and adding any capability outside its default
set, and the agent needs all three: hostPID, the two read-only host mounts, and
CAP_BPF with CAP_PERFMON. Pod Security must be told so, or admission rejects
the DaemonSet outright on any cluster running baseline or restricted.
RBAC¶
The ClusterRole is one rule:
The informer narrows this to one node with a spec.nodeName field selector, but
RBAC cannot express that: field selectors are not authorization, so the grant is
necessarily cluster-wide on pods. There is deliberately nothing else. The node
name comes from the downward API rather than a nodes read, precisely so this
list stays one line long.
Resource sizing¶
No CPU limit. The daemon's work is a ring buffer read and a periodic cgroup scan, and throttling a diagnostic agent during the incident it exists to explain is the wrong trade.
The 128Mi memory limit is what drives two design choices upstream of it. Pods
are trimmed before they enter the informer cache, dropping managed fields and
the last-applied annotation, which are routinely larger than the rest of the
object. Reports are capped by --retain, default 256. An OOM diagnostic that
OOMs on its own cache would be a poor advertisement.
If you raise --history or --retain, raise the limit with them.
Probes¶
| Probe | Path | Meaning |
|---|---|---|
| Liveness | /healthz |
The process is up |
| Readiness | /readyz |
The detector is attached and history is being kept |
Readiness is deliberately not gated on the pod cache. See Correlation.
The headless Service¶
Each agent holds only its own node's reports. A load-balanced VIP would answer a question about one node's pod from a different node's daemon, which is worse than not answering: it looks like a correct empty result.
The Service is therefore headless, and you query a specific agent:
The API is unauthenticated¶
Fine bound to a node. Not fine exposed. Anything beyond the node needs authentication in front of it. See Security.