# k8spacket, and the cluster traffic graph nobody sees by default

> k8spacket collects TCP connection and TLS handshake metadata inside a Kubernetes cluster with eBPF and renders it in Grafana. Version 2 dropped the packet capture library for eBPF entirely, which set a kernel floor of 5.4 with BTF and moved the whole implementation into three small BPF programs.

**k8spacket/k8spacket** — k8spacket - collects TCP traffic and TLS connection metadata in the Kubernetes cluster using eBPF and visualizes in Grafana

- Repository: https://github.com/k8spacket/k8spacket
- Stars: 1,097 · Forks: 60
- Language: Go
- License: Apache-2.0
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/k8spacket-k8spacket

## Two eBPF hooks, one TCP graph and one TLS graph

k8spacket collects TCP traffic and TLS connection metadata in a Kubernetes cluster using eBPF and visualizes the result in Grafana, and the two data sources come from two different places in the kernel.

The first is the inet_sock_set_state tracepoint, which is used to get information about TCP connections inside the cluster. A tracepoint on socket state changes tells you that a connection was established, that it was closed, and what the addresses and ports are, without copying a single byte of payload.

The second is traffic control and queueing discipline filters on ingress and egress, which is how the TLS handshake information is collected. The handshake is where the interesting metadata lives: the certificate chain the server presented, the TLS version and the cipher suite on each side. A tc filter sees the packets at the point they enter and leave the interface, which is where a TLS ClientHello and ServerHello are still visible as plaintext before encryption begins.

That combination is the whole design. The socket tracepoint gives you the graph of who talks to whom, and the tc filters give you what was negotiated. Together they answer the question a cluster operator cannot answer from metrics alone: which of my services is talking to the outside world, and over what kind of TLS.

The list of things the tool does with that data is stated plainly. It shows traffic between workloads in the cluster, informs where the traffic is routed outside the cluster, displays information about closing sockets by connections, shows how many bytes are sent and received by workloads, calculates how long the connections are established, and displays the net of connections between workloads in the whole cluster.

## Version 2 dropped gopacket, and the kernel floor came with it

The most important line in the project description is the one about version 2.0.0. k8spacket doesn't use the gopacket library nowadays and is fully based on eBPF now, and there is a long article on medium about that change.

The architectural consequence is stated without softening: to use k8spacket v2.x.x, the minimum required kernel version is 5.4 with enabled BTF, and the project points at the e2e tests README for details.

BTF, the BPF Type Format, is what lets a BPF program be written in C and compiled against kernel types, and what lets the loader find those types on a running kernel. It is available on the mainstream distributions now, but enabled and complete is a different matter, and a cluster running an older node image, or a managed offering with a restricted kernel, will simply not work.

Going from gopacket to eBPF is not a small change in implementation. The old approach relied on the packet capture library and therefore on the ability to see packets at all, which means permissions, a network tap or a promiscuous interface, and overhead proportional to traffic volume. An eBPF based approach attaches programs to kernel hook points, so the cost is proportional to the events you care about, connections and handshakes, rather than to the bytes on the wire.

The 2.0.0 release also brought a new feature set on top of the rewrite. The dashboards for the node graph and for TLS connections and certificates became sensitive to the Grafana time range control, so you can show the last five minutes of cluster traffic or TLS connections rather than a fixed window.

## The Helm install, and the three Grafana plugins you will need

k8spacket is installed with a Helm chart from a separate repository.

```bash
  helm repo add k8spacket https://k8spacket.github.io/k8spacket-helm-chart
  helm repo update
  
  helm install k8spacket --namespace k8spacket k8spacket/k8spacket --create-namespace
```

After the chart is in, the Grafana side needs work, and this is the part of the installation that trips people up, because the dashboards are not self contained. You have to add the Node Graph API and JSON API plugins and datasources to your Grafana instance, either by hand or by changing the Helm values for the Grafana chart.

```yaml
grafana:
  env:
    GF_INSTALL_PLUGINS: hamedkarbasi93-nodegraphapi-datasource,marcusolsson-json-datasource,marcusolsson-dynamictext-panel
```

Three plugins, and each has a job. The node graph datasource is what renders the connection graph, the JSON API datasource serves the TLS parser data that the certificate and handshake dashboards read, and the dynamic text panel is used by the certificate chain display.

The datasources point at the in-cluster service, at the nodegraph path and at the tlsparser api data path respectively, both on port 8080 of the k8spacket service in the k8spacket namespace.

Then the dashboards themselves are added as a configmap applied to the Grafana namespace.

```bash
  kubectl -n $GRAFANA_NS apply --recursive -f ./dashboards
```

So the installation is a chart, three plugins, two datasources, one configmap and one Prometheus scrape job, and the project is honest about each of those steps rather than pretending the chart does everything.

## Prometheus metrics, and the four dashboards

k8spacket exposes Prometheus metrics, and the scrape config is part of the installation steps rather than something you have to discover.

```yaml
    - job_name: "k8spacket-metrics"
      metrics_path: /metrics
      scrape_interval: 25s
      static_configs:
      - targets: [k8spacket.k8spacket.svc.cluster.local:8080]
```

A 25 second scrape interval against the service in the cluster, with the metrics path and the target both spelled out.

On the Grafana side there are four dashboards, and the naming tells you what each is for. The node graph dashboard shows the connection net between workloads, and the TLS connections and certificates dashboard shows the TLS side. The TLS metrics dashboard holds the raw metrics, and the TLS certificate expiration dashboard is the one that turns the tool into an operations tool rather than a curiosity: it shows the remaining time until the certificates expire, with a variable for the past interval to analyze so you can narrow or expand the scope of data, sorted by expiring earliest first, which is the order you want when you are building an alert on certificate expiry.

The usage section then explains how to work with the graph dashboard: a control for the graph mode, which is connection, bytes or duration, a filter by namespace, and a filter that includes or excludes by workflow name. That last one is the one that makes the graph usable in a large cluster, because the default view of every connection in every namespace is a hairball and the interesting question is always about a subset.

The project also links a try-on-killercoda scenario, which is the fastest way to see the whole thing working without building a cluster yourself.

## The plugin architecture and the TLS certificate chain

The 1.0.0 release changed the architecture of k8spacket to use go plugins, with the available plugins in a separate plugins repository, and that change is what made the TLS features possible.

The first plugin added in 1.0.0 collects metrics about the TLS handshake process inside and outside the cluster, including the TLS version and the cipher suite used. The dashboards that came with it show the IP and name of the TLS client, the domain, IP and port of the TLS server, the supported TLS versions and cipher suites by the client, and the chosen TLS version and cipher suite by the server.

The 1.1.0 release extended that: the tls-parser plugin can get information about the server certificate chain, for TLS versions less than 1.3, and the TLS connections dashboard changed to show the server certificate chain details, which depends on the marcusolsson-json-datasource and the marcusolsson-dynamictext-panel Grafana plugins.

Two details in that are worth noting. The chain information is limited to TLS versions below 1.3, because in TLS 1.3 the server certificate is encrypted, and the tool is reading the handshake from the wire, so there is nothing to read. And the chain display depends on two specific third party Grafana plugins, which is why the installation section insists on adding all three plugins rather than just the graph one.

The module dependencies show where the data goes after collection. There is a certificate info library for parsing the certificates, a whois client, a GeoIP2 library for mapping addresses, a bolthold and bbolt pair for key value storage, netlink for network configuration, the Prometheus client for the metrics, and the Kubernetes client libraries for resolving the workload names behind the addresses.

## Building it: three BPF programs, a distroless image, and root

The Dockerfile and Makefile together describe a build that is more involved than a normal Go project, and the reason is the eBPF programs.

The image is built in three stages. The first stage, on ubuntu:26.04, installs the development libraries for elf, pcap, bfd and ssl, the build tools, and then builds bpftool from source rather than using a package, with a comment explaining that this is because of a bpftool not found for kernel error. A libbpf.sh script in the repository root does the library build.

The second stage, on golang:1.26.5, installs clang and llvm, copies the generated BPF headers into three separate directories, inet, tc and socketfilter, and runs go generate in each so the BPF C is compiled to Go bindings with bpf2go. The final binary is built with CGO_ENABLED=0.

The third stage is gcr.io/distroless/static-debian13, with the binary and the fields.json file copied in, and a comment explaining that it needs to run as root because of the use of kernel tracing.

The Makefile mirrors that for a local build. The prepare target runs libbpf.sh in each of the three BPF directories, the generate target runs bpf2go for each program, and there are build, test, run, run_local, docker_build_local and docker_run_local targets.

The run_local target is the one to read for a local run, because the environment variables show the knobs: the TCP listener port, the loader source, the certificate cache time to live, the command used to list interfaces and the refresh period for it, and a switch that disables the Kubernetes resources.

The docker_run_local target shows the privileges the container needs, the tracing filesystem mount, host networking, privileged mode, and the CAP_SYS_ADMIN, CAP_NET_ADMIN and CAP_NET_RAW capabilities.

## Conclusion

Adopt k8spacket if you run a Kubernetes cluster and need to see which workloads talk to which, how much they move, and when the certificates in your internal TLS terminate. Do not adopt it if you need full packet payloads, because the project collects connection and handshake metadata rather than packet content, and do not deploy it on clusters whose kernels are older than 5.4 or lack BTF, since the eBPF programs need both. Before you install, read the kernel requirement, add the three Grafana plugins the dashboards depend on, and run the e2e tests referenced in the project documentation against a cluster that resembles production.

## FAQ

### What kernel version does k8spacket 2.x need?

The minimum required kernel version is 5.4 with BTF enabled, because version 2 is fully based on eBPF and no longer uses the gopacket library. The project points at the e2e tests README for details.

### How do I install k8spacket?

With Helm, adding the k8spacket chart repository, updating it and installing the chart into the k8spacket namespace. You then have to add the Node Graph API and JSON API Grafana plugins and datasources, apply the dashboards configmap and add the Prometheus scrape config for the metrics endpoint.

### Which Grafana plugins does k8spacket need?

Three: hamedkarbasi93-nodegraphapi-datasource for the connection graph, marcusolsson-json-datasource for the TLS parser data, and marcusolsson-dynamictext-panel for the certificate chain display. They can be installed by hand or through the Grafana chart values.

### Can k8spacket tell me when my certificates expire?

Yes. The dashboard named k8spacket - TLS certificate expiration shows the remaining time until certificates expire, with a variable to narrow or expand the analysis interval, and the data sorted by expiring earliest first, which is what an alert on expiry needs.

### Does k8spacket capture packet payloads?

The project collects TCP connection and TLS handshake metadata: connection information from the inet_sock_set_state tracepoint, and handshake information from traffic control filters on ingress and egress. The TLS certificate chain is available for TLS versions less than 1.3.

## Sources

- [Issues](https://github.com/k8spacket/k8spacket/issues)
- [k8spacket/k8spacket on GitHub](https://github.com/k8spacket/k8spacket)
- [License: Apache-2.0](https://github.com/k8spacket/k8spacket/blob/master/LICENSE)
- [README](https://github.com/k8spacket/k8spacket/blob/master/README.md)
- [Releases](https://github.com/k8spacket/k8spacket/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/k8spacket-k8spacket
