Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[cTENcf] eBPF and Go: The Core of Modern Kubern...

[cTENcf] eBPF and Go: The Core of Modern Kubernetes Networking

Explore how eBPF and Cilium are revolutionising Kubernetes networking — improving observability, security, and scalability. Learn how Go simplifies working with eBPF and unlocks practical use cases for modern cloud environments.

Avatar for Donia Chaiehloudj

Donia Chaiehloudj

November 01, 2025
Tweet

More Decks by Donia Chaiehloudj

Other Decks in Technology

Transcript

  1. eBPF and Go: The Core of Modern Kubernetes Networking Donia

    Chaiehloudj | @doniacld Software Engineer, Isovalent at Cisco Co-author of the book “Learn Go with Pocket-Sized Projectsˮ Nov 1, 2025 10
  2. Overview of @doniacld Program App Linux Kernel Userspace syscalls event

    Events HookPoints) • tracepoints • kprobes (kernel) • uprobes (user) • TC Traffic Control) • XDP Express Datapath)
  3. is safe @doniacld Program App Linux Kernel Userspace syscalls eBPF

    verifier eBPF verifier guarantees: • No Infinite Loops • Memory Safety • Kernel Integrity
  4. is safe @doniacld Program App Linux Kernel Userspace syscalls eBPF

    verifier approved eBPF verifier guarantees: • No Infinite Loops • Memory Safety • Kernel Integrity
  5. is safe and performant @doniacld Program App Linux Kernel Userspace

    syscalls eBPF verifier eBPF JIT Compiler approved eBPF verifier guarantees: • No Infinite Loops • Memory Safety • Kernel Integrity
  6. is safe and performant @doniacld Program App Linux Kernel Userspace

    syscalls eBPF verifier eBPF JIT Compiler approved eBPF verifier guarantees: • No Infinite Loops • Memory Safety • Kernel Integrity eBPF Just In Time Compiler: • eBPF bytecode ⟶ native machine code • ~ same speed as native kernel code
  7. @doniacld Communicating with Program App Linux Kernel Userspace syscalls Maps

    BPF_MAP_TYPE_HASH BPF_MAP_TYPE_ARRAY BPF_MAP_TYPE_PERCPU_HASH ...
  8. @doniacld Communicating with Program App Linux Kernel Userspace syscalls Maps

    BPF_MAP_TYPE_HASH BPF_MAP_TYPE_ARRAY BPF_MAP_TYPE_PERCPU_HASH ... Use Cases: • kernel → userspace read write
  9. @doniacld Communicating with App Linux Kernel Userspace syscalls Maps BPF_MAP_TYPE_HASH

    BPF_MAP_TYPE_ARRAY BPF_MAP_TYPE_PERCPU_HASH ... Use Cases: • kernel → userspace • userspace → kernel write read Program
  10. @doniacld Communicating with App Linux Kernel Userspace Program syscalls Maps

    Use Cases: • kernel → userspace • userspace → kernel • kernel → kernel Program write read
  11. Why for Kubernetes? @doniacld pod Linux Kernel Userspace pod pods

    One kernel for all the containers syscalls
  12. Why for Kubernetes? @doniacld Program pod Linux Kernel Userspace pod

    pods syscalls One kernel for all the containers
  13. Why for Kubernetes? @doniacld Program pod Linux Kernel Userspace event

    pod pods One kernel for all the containers syscalls
  14. @doniacld Go is the 💎 in Cloud-native ecosystem • eBPF

    is kernel-level magic → But hard to interact with directly C, bytecode). • Most cloud-native projects are written in Go → Kubernetes, Docker, Prometheus, Cilium, etc. • Go offers simplicity + performance → Ideal for distributed systems and networking. • Go + eBPF = productive + powerful → SDKs like cilium/ebpf let you write networking/security tooling
  15. @doniacld 1 Write eBPF C program 2 Generate Go Objects

    & Methods 3 Write Go main program 4 Run your program Development cycle and
  16. 1. Write C program: Trace when files are open #include

    <linux/bpf.h> // BPF header for definitions. #include <bpf/bpf_helpers.h> // BPF helper functions. // Attach to sys_enter_openat tracepoint. SEC("tracepoint/syscalls/sys_enter_openat") int trace_openat(void *ctx) { char msg[] = "File opening..."; // Message to log. bpf_trace_printk(msg, sizeof(msg)); // Log message to trace pipe. return 0; } char LICENSE[] SEC("license") = "GPL"; @doniacld
  17. 1. Write C program: Trace when files are open #include

    <linux/bpf.h> // BPF header for definitions. #include <bpf/bpf_helpers.h> // BPF helper functions. // Attach to sys_enter_openat tracepoint. SEC("tracepoint/syscalls/sys_enter_openat") int trace_openat(void *ctx) { char msg[] = "File opening..."; // Message to log. bpf_trace_printk(msg, sizeof(msg)); // Log message to trace pipe. return 0; } char LICENSE[] SEC("license") = "GPL"; @doniacld
  18. 1. Write C program: Trace when files are open #include

    <linux/bpf.h> // BPF header for definitions. #include <bpf/bpf_helpers.h> // BPF helper functions. // Attach to sys_enter_openat tracepoint. SEC("tracepoint/syscalls/sys_enter_openat") int trace_openat(void *ctx) { char msg[] = "File opening..."; // Message to log. bpf_trace_printk(msg, sizeof(msg)); // Log message to trace pipe. return 0; } char LICENSE[] SEC("license") = "GPL"; @doniacld $ sudo cat /sys/kernel/debug/tracing/trace_pipe systemd-journal-326 [000] ...21 53765.037027: bpf_trace_printk: File opening... systemd-journal-326 [000] ...21 53765.037048: bpf_trace_printk: File opening... cat-35252 [000] ...21 53765.037948: bpf_trace_printk: File opening... cat-35252 [000] ...21 53765.037966: bpf_trace_printk: File opening... cat-35252 [000] ...21 53765.038107: bpf_trace_printk: File opening... process id
  19. 2. Compile & Generate Go Objects: bpf2go @doniacld //go:generate go

    run github.com/cilium/ebpf/cmd/bpf2go openfile openfile.c call clang under the hood
  20. @doniacld 2. Generated Go Objects // openfilePrograms contains all programs

    after they have been loaded into the kernel. //type openfilePrograms struct { TraceOpenat *ebpf.Program `ebpf:"trace_openat"` } Objects to manipulate // openfileObjects contains all objects after they have been loaded into the kernel. type openfileObjects struct { openfilePrograms openfileMaps openfileVariables } //go:generate go run github.com/cilium/ebpf/cmd/bpf2go openfile openfile.c eBPF program bytecodes
  21. @doniacld 3. Main Go Program: Load the eBPF program func

    main() { // Allow the current process to lock memory for eBPF resources. if err := rlimit.RemoveMemlock(); err != nil { log.Fatalf("failed to remove memlock: %v", err) } var objs openfileObjects err := loadOpenfileObjects(&objs, nil) if err != nil { log.Fatalf("failed to load objects: %v", err) } // Attach the eBPF program to the tracepoint. tp, err := link.Tracepoint("syscalls", "sys_enter_openat", objs.TraceOpenat, nil) if err != nil { log.Fatalf("failed to attach tracepoint: %v", err) } defer tp.Close() log.Println("eBPF program attached. Waiting for events...") // ... // Log “/sys/kernel/debug/tracing/trace_pipe" output // ... }
  22. @doniacld 3. Main Go Program: Attach the eBPF program to

    tracepoint func main() { // Allow the current process to lock memory for eBPF resources. if err := rlimit.RemoveMemlock(); err != nil { log.Fatalf("failed to remove memlock: %v", err) } var objs openfileObjects err := loadOpenfileObjects(&objs, nil) if err != nil { log.Fatalf("failed to load objects: %v", err) } // Attach the eBPF program to the tracepoint tp, err := link.Tracepoint("syscalls", "sys_enter_openat", objs.TraceOpenat, nil) if err != nil { log.Fatalf("failed to attach tracepoint: %v", err) } defer tp.Close() log.Println("eBPF program attached. Waiting for events...") // ... // Log “/sys/kernel/debug/tracing/trace_pipe" output // ... }
  23. 1. Write C Program: Define a Map to store the

    counter #include <linux/bpf.h> #include <bpf/bpf_helpers.h> struct { __uint(type, BPF_MAP_TYPE_ARRAY); __type(key, __u32); __type(value, __u64); __uint(max_entries, 1); } pkt_count SEC(".maps"); // count_packets atomically increases a packet counter on every invocation. SEC("xdp") int count_packets() { __u32 key = 0; __u64 *count = bpf_map_lookup_elem(&pkt_count, &key); if (count) { __sync_fetch_and_add(count, 1); } return XDP_PASS; } char __license[] SEC("license") = "Dual MIT/GPL";
  24. 1. Write C Program: Increment packet counter #include <linux/bpf.h> #include

    <bpf/bpf_helpers.h> struct { __uint(type, BPF_MAP_TYPE_ARRAY); __type(key, __u32); __type(value, __u64); __uint(max_entries, 1); } pkt_count SEC(".maps"); // count_packets atomically increases a packet counter on every invocation. SEC("xdp") int count_packets() { __u32 key = 0; __u64 *count = bpf_map_lookup_elem(&pkt_count, &key); if (count) { __sync_fetch_and_add(count, 1); } return XDP_PASS; } char __license[] SEC("license") = "Dual MIT/GPL";
  25. 3. Main Go Program: Load the eBPF program package main

    import ( // … "github.com/cilium/ebpf/link" "github.com/cilium/ebpf/rlimit" ) func main() { err := rlimit.RemoveMemlock() var objs counterObjects err := loadCounterObjects(&objs, nil) if err != nil { //… } defer objs.Close() ifname := "eth0" iface, err := net.InterfaceByName(ifname) if err != nil {//…} link, err := link.AttachXDP(link.XDPOptions{ Program: objs.CountPackets, Interface: iface.Index, }) defer link.Close()
  26. 3. Main Go Program: Choose an interface package main import

    ( // … "github.com/cilium/ebpf/link" "github.com/cilium/ebpf/rlimit" ) func main() { err := rlimit.RemoveMemlock() var objs counterObjects err := loadCounterObjects(&objs, nil) if err != nil { //… } defer objs.Close() ifname := "eth0" iface, err := net.InterfaceByName(ifname) if err != nil {//…} link, err := link.AttachXDP(link.XDPOptions{ Program: objs.CountPackets, Interface: iface.Index, }) defer link.Close()
  27. 3. Main Go Program: Attach the program to a hook

    package main import ( // … "github.com/cilium/ebpf/link" "github.com/cilium/ebpf/rlimit" ) func main() { err := rlimit.RemoveMemlock() var objs counterObjects err := loadCounterObjects(&objs, nil) if err != nil { //… } defer objs.Close() ifname := "eth0" iface, err := net.InterfaceByName(ifname) if err != nil {//…} link, err := link.AttachXDP(link.XDPOptions{ Program: objs.CountPackets, Interface: iface.Index, }) defer link.Close() ...
  28. 3. Main Go Program: Read from the map // Periodically

    fetch the packet counter from PktCount tick := time.Tick(time.Second) stop := make(chan os.Signal, 5) signal.Notify(stop, os.Interrupt) for { select { case <-tick: var count uint64 err := objs.PktCount.Lookup(uint32(0), &count) if err != nil { log.Fatal("Map lookup:", err) } log.Printf("Received %d packets", count) case <-stop: log.Print("Received signal, exiting..") return } } }
  29. 3. Main Go Program: Log // Periodically fetch the packet

    counter from PktCount tick := time.Tick(time.Second) stop := make(chan os.Signal, 5) signal.Notify(stop, os.Interrupt) for { select { case <-tick: var count uint64 err := objs.PktCount.Lookup(uint32(0), &count) if err != nil { log.Fatal("Map lookup:", err) } log.Printf("Received %d packets", count) case <-stop: log.Print("Received signal, exiting..") return } }
  30. Example: XDP Drop packets SEC("xdp") int xdp_drop(struct xdp_md *ctx) {

    char msg[] = "Hello, World!"; bpf_trace_printk(msg, sizeof(msg)); // Print to trace pipe void *data = (void *)(long)ctx->data; void *data_end = (void *)(long)ctx->data_end; // Check if the packet's destination IP is blocked if (isBlockedIP(data, data_end)) { char msg[] = "Got ping packet"; bpf_trace_printk(msg, sizeof(msg)); // Print to trace pipe return XDP_DROP; } return XDP_PASS; // Allow the packet to pass } // License declaration char _license[] SEC("license") = "GPL"; @doniacld
  31. Example: XDP Drop packets SEC("xdp") int xdp_drop(struct xdp_md *ctx) {

    char msg[] = "Hello, World!"; bpf_trace_printk(msg, sizeof(msg)); // Print to trace pipe void *data = (void *)(long)ctx->data; void *data_end = (void *)(long)ctx->data_end; // Check if the packet's destination IP is blocked if (isBlockedIP(data, data_end)) { char msg[] = "Got ping packet"; bpf_trace_printk(msg, sizeof(msg)); // Print to trace pipe return XDP_DROP; } return XDP_PASS; // Allow the packet to pass } // License declaration char _license[] SEC("license") = "GPL"; @doniacld
  32. Cilium Network Policy YAML @doniacld apiVersion: "cilium.io/v2" kind: CiliumNetworkPolicy metadata:

    name: "deny-external" spec: endpointSelector: matchLabels: app: "backend" ingress: - fromEndpoints: - matchLabels: app: "frontend" frontend pod backend pod frontend pod frontend pods backend pod backend pods external pod external pods 💡 Endpoint = Pod
  33. From Endpoint to Identity Go @doniacld type Endpoint struct {

    ID int Labels map[string]string Identity int } 💡 Endpoint = Pod Endpoints 1: {“app”:”frontend”} 2: {“app”:”backend”} type Policy struct { SourceIdentity int DestinationIdentity int Allowed bool } Policy src: 1 dst: 2 true
  34. Store Policies @doniacld struct bpf_map_def SEC("maps") policy_map = { .type

    = BPF_MAP_TYPE_HASH, .key_size = sizeof(int), .value_size = sizeof(int), .max_entries = 1024, }; eBPF map to store policies
  35. Store Policies @doniacld func loadPoliciesToMap(policies []Policy, policyMap *ebpf.Map) error {

    for _, policy := range policies { err := policyMap.Put(policy.SourceIdentity, policy.DestinationIdentity) if err != nil { return err } } return nil } struct bpf_map_def SEC("maps") policy_map = { .type = BPF_MAP_TYPE_HASH, .key_size = sizeof(int), .value_size = sizeof(int), .max_entries = 1024, }; eBPF map to store policies Function to load policies into the eBPF map
  36. eBPF program to enforce policies @doniacld // eBPF map to

    store policies. struct bpf_map_def SEC("maps") policy_map = { .type = BPF_MAP_TYPE_HASH, .key_size = sizeof(int), .value_size = sizeof(int), .max_entries = 1024, }; // eBPF program to enforce policies. SEC("xdp") int enforce_policy(struct xdp_md *ctx) { int src_identity = get_source_identity(ctx); int dst_identity = get_destination_identity(ctx); int *allowed = bpf_map_lookup_elem(&policy_map, &src_identity); if (allowed && *allowed == dst_identity) { // Allow the packet. return XDP_PASS; } // Drop the packet. return XDP_DROP; }
  37. eBPF and Cilium everywhere! @doniacld Amazon EKS Anywhere Anthos GKE

    Dataplane v2 Azure Kubernetes Service MetaKube Managed Kubernetes Service source: https://cilium.io/use-cases/cni/ Alibaba Cloud Container Service
  38. 📝 Key Takeaways @doniacld • eBPF lets you run safe,

    fast programs in the Linux kernel • You can use it for: ◦ 🔍 Tracing (e.g. file open events) ◦ 📊 Observability (e.g. counting packets by IP ◦ 🛡 Security (e.g. dropping packets with XDP ◦ 🌐 Networking (e.g. packets routing with Cilium)
  39. 📝 Key Takeaways @doniacld • eBPF lets you run safe,

    fast programs in the Linux kernel • You can use it for: ◦ 🔍 Tracing (e.g. file open events) ◦ 📊 Observability (e.g. counting packets by IP ◦ 🛡 Security (e.g. dropping packets with XDP ◦ 🌐 Networking (e.g. packets routing with Cilium) • Go is the language of the cloud-native ecosystem
  40. 📝 Key Takeaways @doniacld • eBPF lets you run safe,

    fast programs in the Linux kernel • You can use it for: ◦ 🔍 Tracing (e.g. file open events) ◦ 📊 Observability (e.g. counting packets by IP ◦ 🛡 Security (e.g. dropping packets with XDP and Tetragon) ◦ 🌐 Networking (e.g. packets routing with Cilium) • Go is the language of the cloud-native ecosystem • With libraries like cilium/ebpf, you can use Go to: ◦ Load eBPF programs & Interact with kernel data safely • Perfect fit for building Kubernetes CNI, agents, operators, and tools, etc