Skip to content
Edouard Topin's Blog
Kubernetes security in production / Series 04/04

Runtime security: Falco and Tetragon, and how to actually choose

Falco and Tetragon both collect through eBPF. What separates them lies elsewhere: event scope, rule model, and above all what each one can actually prevent.

Edouard Topin
17 min read
Abstract editorial illustration of a watchful eye motif over container process trails.

The auditor comes back to vks-fret-01 with a fourth question, and it is the most uncomfortable of the four: show me what a process actually did inside a pod you authorised, on a network you opened, from an image you signed. None of the three previous control planes answers it. RBAC authorises API calls. A network policy allows or denies a flow without logging it. A signature attests to provenance, not behaviour. Runtime detection is the first plane that looks at what is happening — and the last net once the other three have let something through.

This article does not compare two products: it compares two sets of documentation. Every axis is filled in by the relevant project’s own page on both sides, or it is declared untieable — and that declaration is a deliverable, not an admission of incomplete research. No lab was run: no agent was deployed, no event was observed, no alert was triaged. Everything below comes from upstream documentation pages that were actually loaded, and whatever is not on them is said out loud.

Kubernetes 1.35 → 1.36Falco 0.44.1 · Tetragon v1.7.0No lab was run

TL;DR

  • The decision. The choice is not between two products but on a question with two answers: do you want to see, or do you want to prevent? Only one of the two projects publishes an enforcement path — and it also publishes that this path guarantees nothing on its own.
  • The trade-off that costs. Preventing requires a kernel build option (CONFIG_BPF_KPROBE_OVERRIDE), accepts an API in cilium.io/v1alpha1 — therefore alpha — and turns a false positive into a production incident rather than an alert.
  • Monday morning. Before any tool discussion, collect three facts from your own nodes: kernel version, BTF exposure, presence of the override option. The first two branches of the decision tree close there, not on a product page.

Version contract

This series stands on a Kubernetes 1.35 floor and a 1.36 target: 1.34 leaves support on 27 October 2026, before this article is published, and 1.36 is the most recent upstream version loaded. No later version has been announced on any loaded page, so none is mentioned here.

Neither Falco nor Tetragon is a Kubernetes API. Their version is not read off a FEATURE STATE banner — you have to go looking for it, and the two projects do not publish it in the same place. That is a method point worth stating.

Item Version State Where it is published
Falco — documentation v0.44 current documentation version documentation site banner
Falco — latest release 0.44.1, published 11 June 2026 stable release, not a prerelease GitHub API of the official repository
Tetragon — latest release v1.7.0, published 29 April 2026 stable release, not a prerelease GitHub API of the official repository
TracingPolicy, TracingPolicyNamespaced cilium.io/v1alpha1 CRD, alpha API version Tetragon reference page
Falco rules rules file, not a Kubernetes object Basic Elements of Falco Rules page

Two version remarks that catch readers out. First, cilium.io/v1alpha1 is an alpha API version, and it is the API on which all of Tetragon’s configuration rests, enforcement included. A team that requires stable APIs for anything touching production needs to know that beforehand, not afterwards; it is not a defect — a CRD outside the Kubernetes tree does not follow the upstream lifecycle — but it deserves naming. Second, a Falco rules file carries no apiVersion at all, because it is not a Kubernetes object: the envelope that carries it does.

What this article will not describe, and why: PodSecurityPolicy (policy/v1beta1), deprecated in v1.21 and removed in v1.25, which is not a subject of this page; Falco’s legacy eBPF driver, which no longer appears on the loaded Kernel Events page; the list of Falco Talon actionners, published on a documentation site that does not resolve in DNS; and any overhead, latency, consumption or alert-volume figure, because no comparable figure was found on any upstream page — that is a result, not a gap.

The framing to drop before comparing

The first service this article renders is to take away a framing the reader has probably already met elsewhere: “Falco does audit, Tetragon does eBPF”. The Kernel Events page on falco.org lists two drivers for Falco and gives the modern eBPF probe as the default driver; the kernel module comes second. The first is built on the CO-RE paradigm and shipped inside the binary.

Both tools collect through eBPF. A reader who picks one “because it’s the eBPF one” is choosing on a criterion that does not exist. The difference lies elsewhere, and that is the whole subject.

Seven axes, and the rule that admits them

One rule, stated before the comparison: an axis enters the matrix only if it is filled in by both projects’ documentation. An axis documented on one side only is not an advantage, it is a blind spot — it leaves the matrix. This is not caution: it is the only protection against a vendor shoot-out. Falco and Tetragon are backed by two distinct organisations that each publish positioning content; taking an axis from one project’s page means letting it write the rules of the match.

Axis Falco — what the project publishes Tetragon — what the project publishes
1. Collection syscalls captured by a driver, stream asserted against a rules engine, alert; two drivers, modern eBPF by default hook points declared in a policy, filtering in the kernel; base install with no policy: process_exec and process_exit
2. Event scope two families — kernel and plugin; named plugins for Kubernetes audit (k8saudit, k8saudit-aks, k8saudit-eks, k8saudit-gke, k8saudit-ovh), CloudTrail, Okta, GitHub, Docker, Nomad process event types PROCESS_EXEC, PROCESS_EXIT, PROCESS_KPROBE, PROCESS_UPROBE, PROCESS_TRACEPOINT, PROCESS_LOADER; listed domains: capabilities, execution, file integrity, network, OS integrity, privileges
3. Rule model a rules language — rules, macros, lists; five mandatory fields, eight priorities from EMERGENCY to DEBUG; default rule set shipped, only maturity_stable rules enabled a declarative Kubernetes resource; eleven selector filters, including matchArgs, matchBinaries, matchNamespaces, matchWorkloads; published limits, notably “each hook can contain up to 5 selectors”; policy library presented as examples to apply
4. Enforcement no enforcement section anywhere in the concepts tree; response delegated to Falco Talon, a separate repository badged Incubating two published actions, Override return value and Signals; three policy modes monitoring, enforcement, monitor_only
5. Kernel dependencies modern eBPF: “all versions >=5.8 are enough”, BTF exposed, BPF ring buffer, capabilities CAP_SYS_BPF, CAP_SYS_PERFMON, CAP_SYS_RESOURCE, CAP_SYS_PTRACE — kernel module: >= 3.10, full privileges “Tetragon needs Linux kernel version 4.19 or greater”; BTF required for CO-RE; CONFIG_BPF, CONFIG_BPF_JIT, CONFIG_BPF_EVENTS, CONFIG_BPF_SYSCALL; CONFIG_BPF_KPROBE_OVERRIDE under the Enforcement section; LTS kernels listed 4.19, 5.4, 5.10, 5.15, bpf-next
6. Volume and destination loss at the entry point when per-CPU buffers saturate; levers buf_size_preset, cpus_for_each_buffer, base_syscalls; counters scap.n_drops*, falco.outputs_queue_num_drops; five output channels per-cgroup throttling via --cgroup-rate, THROTTLE_START / THROTTLE_STOP events; counters tetragon_bpf_missed_events_total, tetragon_observer_ringbuf_events_lost_total; JSON export, log or gRPC endpoint
7. Governance CNCF project, Graduated on 29 February 2024, accepted on 10 October 2018 — trajectory published by the foundation no maturity statement on the official repository; Tetragon is named nowhere on Cilium’s CNCF project page

Two asymmetries show up in that table and must not be smoothed over. Falco publishes the Linux capabilities its driver requires, Tetragon does not — on the loaded pages. Tetragon publishes the list of LTS kernels it is exercised against, Falco does not. These are absences of publication, not absences of property. The same caution applies to row 7: nothing that was loaded establishes Tetragon’s status with respect to the CNCF, neither as a project of its own nor as a subproject; the only citable fact is the publication asymmetry.

One last point of honesty on axis 2. The argument that filtering in the kernel beats filtering in user space, because the latter happens after the call has executed, is published by the Tetragon project. It is a project argument, not a neutral finding, and no loaded falco.org page answers it. That absence of an answer is not a concession: it is an axis documented on one side only.

Which one actually prevents anything

This is the axis that carries the conclusion of the article, and of the whole series.

On the Falco side the fact is documentary, not polemical: the Concepts page publishes five entries — Event Sources, Rules, Outputs, Plugins, Metrics. No enforcement, response or blocking section. The vocabulary on the landing page is parsing, asserting, alerting. Automated response does exist, but in a separate repository: falcosecurity/falco-talon, described as “a Response Engine for managing threats in your Kubernetes”, fed by Falco or Falcosidekick, and badged Incubating. Writing “Falco can kill the pod” attributes to a graduated project a capability carried by an incubating component.

On the Tetragon side, two actions are published. Override return value: “the function will never be executed and, instead, a value (typically an error) will be returned to the caller”. Signals: sending a SIGKILL to a process matching criteria. And the page immediately publishes the limit of the second one, which has to be quoted verbatim: “Sending a SIGKILL signal does not always stop the operation being performed by the process that triggered the operation.” The example given is a write(): the signal does not guarantee the data will not be written. The published recommendation is to combine the two actions.

On top of that comes a mode that changes everything in operations: monitoring — “enforcement operations are elided”; enforcement — “enforcement operations are respected and performed”; monitor_only — “the policy has no enforcement actions, thus it cannot be set to enforcement mode”. The mode can be set in the policy through spec.options, at load time, or live.

The accurate wording, not to be rounded off: Tetragon publishes an enforcement path and its limit; Falco publishes an alerting path and delegates response to a separate, still-incubating component.

A starting manifest, explicitly non-blocking. The fd_install hook point and the argument structure follow the example published on the Tracing Policy page, adapted to the running scenario:

apiVersion: cilium.io/v1alpha1
kind: TracingPolicyNamespaced
metadata:
  name: webshop-sensitive-file-watch
  namespace: webshop-app
spec:
  options:
    - name: "policy-mode"
      value: "monitor"          # no enforcement — actions are elided
  podSelector:
    matchLabels:
      app: REPLACE_WITH_APP_LABEL
  kprobes:
    - call: "fd_install"
      syscall: false
      args:
        - index: 0
          type: "int"
        - index: 1
          type: "file"
      selectors:
        - matchArgs:
            - index: 1
              operator: "Equal"
              values:
                - "REPLACE_WITH_WATCHED_PATH"
          matchActions:
            - action: Sigkill      # not executed while policy-mode is monitor

The watched path stays a placeholder on purpose: an example path copied straight into production watches the wrong file.

The equivalent Falco rule in intent is not a Kubernetes object and therefore carries no apiVersion. Structure and fields follow the Basic Elements of Falco Rules page:

# Falco rules file — not a Kubernetes object, no apiVersion
- rule: shell_in_webshop_container
  desc: notice shell activity within a webshop container
  condition: >
    (evt.type in (execve, execveat)) and
    container.id != host and
    proc.name = bash
  output: >
    shell in a container |
    user=%user.name container_id=%container.id
    container_name=%container.name shell=%proc.name
    parent=%proc.pname cmdline=%proc.cmdline
  priority: WARNING

The Kubernetes envelope that carries that file does carry its apiVersion — and it is the envelope that falls under the RBAC set up in the first article of the series:

apiVersion: v1
kind: ConfigMap
metadata:
  name: falco-rules-webshop
  namespace: REPLACE_WITH_FALCO_NAMESPACE
data:
  webshop_rules.yaml: |
    - rule: shell_in_webshop_container
      # … content of the rules file above

The two axes the documentation cannot settle

This table is a deliverable in the same way the matrix is. It is also, precisely, where the vendor shoot-out gets in: an overhead figure or a detection rate is what the reader is looking for, what makes an article shareable — and what exists in no loaded upstream source.

Untieable axis Why What this article publishes instead
Comparative detection coverage neither project publishes an evaluation methodology, and no common methodology was found on any loaded page what each one observes (axis 2), to be set against your own use cases — never “X detects better than Y”
Overhead, latency, consumption, alert volume zero comparable figure found on any loaded upstream page, across two verification passes the counters to collect on your side (axis 6) — importing a figure from a third-party post would borrow a stranger’s credibility under a real byline

What axis 6 contributes to the choice fits in one sentence: both projects publish an event-loss mechanism and the counters to measure it, neither publishes a comparable reference value. Both give you the means to measure on your own platform; neither excuses you from doing it.

Collect three facts from your nodes before choosing

The VKr release notes publish the kernel versions of the node images, VKr by VKr. For v1.36.1: Photon 5.0 on 6.1.170-1.ph5, Ubuntu 22.04 LTS on 5.15.0-181.191, Ubuntu 24.04 LTS on 6.17.0-35.35~24.04.1. For v1.35.5, the series floor: Photon 5.0 on 6.1.170-1.ph5, Ubuntu 22.04 on 5.15.0.179.163 (generic), Ubuntu 24.04 on 6.17.0-29.29~24.04.1 (hwe). The default image is published: “By default, VKS cluster nodes use the PhotonOS edition of the specified VKr”, the switch to Ubuntu being made through the run.tanzu.vmware.com/resolve-os-image: os-name=ubuntu annotation.

The consequence is clear-cut: all three published kernels clear the >= 5.8 floor of Falco’s modern eBPF driver and Tetragon’s 4.19 floor. On that version criterion, and on that one alone, neither tool is ruled out by the platform.

The three readings to take, and which were not taken here. The outputs are not reproduced, precisely because they were not observed:

# 1. kernel version actually in service, per node
kubectl get nodes -o wide

# 2. BTF exposure — the file must exist on the node
ls -l /sys/kernel/btf/vmlinux

# 3. kprobe override option — gates Tetragon's Override action
zcat /proc/config.gz | grep -E 'CONFIG_BPF_KPROBE_OVERRIDE|CONFIG_DEBUG_INFO_BTF'

The REPLACE_WITH_NODE_KERNEL_VERSION placeholder in the running scenario deliberately stays empty, even though the catalogue value is now known: the version published for a VKr image is not proof of what a given cluster runs, which depends on the image chosen and the patch level applied.

The rest of the procedure is a decision procedure, not an installation one. Write the question before comparing the answers — see or prevent, and answering “both” means deciding under pressure at the first incident. Set the alert destination before the agent: who receives it, at what hour, and what that person does at 3 a.m.; the grp-secops group is the recipient, and both tools expose Prometheus metrics, which makes supervising the agent itself possible from day one. Start without preventing anything in either case — Falco by construction, Tetragon through the monitoring mode. Only then switch over, policy by policy, with the rollback command written in the ticket before activation:

# explicit switch to enforcement, policy by policy
tetra tp set-mode --namespace webshop-app webshop-sensitive-file-watch enforce

# back to observation — same command, opposite mode
tetra tp set-mode --namespace webshop-app webshop-sensitive-file-watch monitor

Rollback, incidentally, is not symmetric, and that is a selection criterion in its own right. Removing Falco removes visibility and nothing else, since it prevents nothing: the cost is a blind spot, not an outage. Removing a Tetragon policy in monitoring mode is the same. Removing a policy in enforcement becomes a production operation — and no loaded page describes what happens to an in-flight operation at that moment. That is a gap stated out loud: the mode switch is the rollback to prefer, because it is the only one explicitly documented.

Pitfalls

  • matchNamespaces is not about Kubernetes. In Tetragon selectors, matchNamespaces filters on Linux namespaces; matchWorkloads filters on Kubernetes workloads, and selection by Kubernetes identity goes through podSelector and containerSelector. Two homonyms, two different effects.
  • A monitor_only mode does not become blocking. Published: the policy has no enforcement actions, “thus it cannot be set to enforcement mode”, and attempting the live switch “will result in an error”. The mode does not add an action the policy does not contain.
  • A Falco rule’s maturity is not a promise of quiet. Published in black and white: “The maturity level of the rules, however, does not directly reflect their potential for generating noise in the adopters’ environment.” A stable rule can be noisy on your platform.
  • Not supervising the agent itself. Both projects publish an event-loss mechanism and the matching counters. An agent that loses events silently produces apparent coverage, which is more dangerous than an acknowledged absence.
  • A throttled cgroup is a blind spot. Tetragon’s throttling “stops posting its events” beyond the threshold, and the published caveat specifies that only base-sensor events, PROCESS_EXEC and PROCESS_EXIT, are monitored for this purpose. It is a protection, and it is also a loss of visibility on the busiest cgroup — hence potentially the most interesting one.
  • A hook point present on only one side of the documentation. Tetragon’s concepts page lists three hook point types (kprobes, tracepoints, uprobes); the reference page lists five (the same plus LSM hooks and USDTs). Two pages of the same project, loaded on the same day, do not say the same thing: the discrepancy is not settled here, and until it is settled against a precise version, do not build a production policy on a hook point present on one side only.
  • A Falco alert will not always carry its Kubernetes context. The k8s.* fields are extracted from the container runtime socket, with published race conditions and sandbox processes that have no image. An alert without a pod name reaches the on-call rota like an alert without an address.
  • What the documentation does not publish. Tetragon publishes an explicit threat model — “Tetragon’s current design does not offer comprehensive protection against all forms of kernel-level exploitation”, and “no security observability data from a root-compromised node can be considered trustworthy”. No equivalent page was found on the Falco side: that is an absence of publication, not an absence of limits. Likewise, the Linux privileges required by Tetragon were not found on the loaded pages, whereas Falco publishes its own.

Conclusion

No winner. The conclusion is a question you put to your own platform, in three branches, each backed by a published page. Are all your sources of interest on the machine? If the Kubernetes audit log, a cloud log or an identity provider fall within scope, only one of the two projects publishes plugins for those sources. Do you need to prevent, and do your kernels allow it? If so, only one of the two publishes an enforcement path — and it also publishes that this path guarantees nothing without combining actions, and that it depends on a build option. Who writes the rules, and with what tooling? A rules language with a shipped library and maturity levels, or a declarative Kubernetes resource subject to RBAC and bounded by published numeric limits.

None of those three branches is decided by reading a comparison table. All three are decided by collecting a fact from your own cluster.

And the series closes on an admission rather than a ticked box. RBAC allows or denies an API call, not the behaviour of a process already running. Pod Security Admission’s enforce mode rejects the pod, but does not apply to workload resources: the Deployment goes through. NetworkPolicy isolates by selection, with no explicit deny rule and no logging — that is published in its own list of non-capabilities. A signature attests to provenance, not behaviour, and requires an out-of-tree component. And on this fourth plane, Falco does not block, while Tetragon’s SIGKILL does not guarantee the operation did not take place. Four nets, each holed at a point its own documentation publishes. The fifth plane is not a tool: it is someone reading the alerts.

Collect before choosing

Kernel version, BTF exposure, presence of CONFIG_BPF_KPROBE_OVERRIDE. The published VKr kernels clear both floors; the other two facts are published nowhere and are read off a node.

See or prevent

One question, two answers. Not deciding at selection time means deciding under pressure at the first incident — and discovering the published SIGKILL limit on that day.

Write down the limit

One sentence in the audit file: neither tool guarantees that an operation did not take place. That holds up better than a ticked box that does not.

The four articles of the series, in order: 1 — Kubernetes RBAC, 2 — Network policies and Cilium, 3 — Supply chain security: Sigstore, SBOM, admission control, and this one.

Related reading: Deploying your first VKS cluster on VCF 9 for the platform all of this runs on, Day-2 ops on VKS: lifecycle, upgrades, observability for the VKr lifecycle and therefore the node kernels, Prometheus & Grafana on VKS for the destination of the agent metrics, and VCF 9.1: security & resilience for the platform hardening context.

Get the next one by email

New articles and series, sent when they are published. No other mail.

One click to unsubscribe, any time.

Back to blog
Share

Related articles

  1. 23 min read

    Network policies and Cilium: building a defensible default-deny

    The NetworkPolicy API ships with Kubernetes; enforcing it is the CNI's job. What Cilium adds, what stays standard, and how to reach default-deny by watching real flows before blocking any.

  2. 18 min read

    Kubernetes RBAC: the foundations, and the pitfalls that survive an audit

    Every one of these pitfalls is published on kubernetes.io. What is missing is the ordering — and the path that leads from a vSphere Namespace straight to cluster-admin.

  3. 17 min read

    Supply chain security: Sigstore, SBOM, admission control

    Kubernetes verifies no image signature on its own. Signing with Sigstore, inventorying with an SBOM, refusing at admission — and what each of those verbs actually covers.

Follow along

New articles, thoughts, and updates.