LabHub

블로그

eBPF 완전 가이드 2025: 커널 프로그래밍, Observability, 네트워킹, 보안의 혁명

한국어English日本語

TL;DR


1. eBPF란 무엇인가?

1.1 한 줄 정의

eBPF는 운영체제 커널을 수정하지 않고도 커널 내부에서 샌드박스화된 프로그램을 실행할 수 있는 기술이다.

전통적으로 커널 기능을 확장하려면 두 가지 방법밖에 없었습니다:

  1. 커널 자체에 코드 추가 → 기여하기 어렵고, 머지되어도 사용자가 새 커널 버전을 기다려야 함
  2. 커널 모듈 로드 → 잘못 작성하면 시스템 크래시 위험. 커널 ABI 변경에 취약

eBPF는 제3의 길을 제시합니다 — 커널 안에서 안전하게 커스텀 코드를 실행.

1.2 어떻게 안전성을 보장하는가?

eBPF 프로그램은 커널에 로드되기 전 BPF Verifier(검증기)의 정적 분석을 통과해야 합니다:

검증기 통과 후 JIT 컴파일러가 네이티브 머신 코드로 변환하여 거의 네이티브 속도로 실행됩니다.

1.3 cBPF에서 eBPF로의 진화

특성cBPF (1992)eBPF (2014~)
용도패킷 필터링만범용 커널 프로그래밍
레지스터2개 (32비트)11개 (64비트)
명령어22개100+
헬퍼 함수없음200+
없음30+ 종류
JIT일부 아키텍처대부분 아키텍처

tcpdump가 사용하던 cBPF는 단순 패킷 필터였지만, eBPF는 소형 가상머신으로 진화했습니다.


2. eBPF 아키텍처 심층 분석

2.1 컴포넌트 개요

┌─────────────────────────────────────────────┐
│  사용자 공간 (User Space)│  ┌──────────┐  ┌──────────┐  ┌──────────┐  │
│  │ bpftool  │  │ bpftrace │  │ Cilium   │  │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘  │
│       │             │             │         │
│       └─────────────┼─────────────┘         │
│                     │ bpf() syscall          │
└─────────────────────┼───────────────────────┘
┌─────────────────────────────────────────────┐
│  커널 공간 (Kernel Space)│  ┌─────────────────────────────────────┐   │
│  │  BPF Verifier (안전성 검증)         │   │
│  └──────────────┬──────────────────────┘   │
│                 ▼                            │
│  ┌─────────────────────────────────────┐   │
│  │  JIT Compiler (네이티브 코드 변환)   │   │
│  └──────────────┬──────────────────────┘   │
│                 ▼                            │
│  ┌─────────────────────────────────────┐   │
│  │  Hooks: kprobe, tracepoint, XDP,    │   │
│  │  perf_event, socket, cgroup, LSM    │   │
│  └─────────────────────────────────────┘   │
└─────────────────────────────────────────────┘

2.2 주요 훅(Hook) 종류

eBPF 프로그램은 다양한 커널 이벤트에 부착(attach)할 수 있습니다:

훅 유형설명예시
kprobe / kretprobe커널 함수 진입/종료do_sys_open 호출 추적
uprobe / uretprobe사용자 공간 함수OpenSSL의 SSL_read 추적
tracepoint커널 정적 추적 포인트sched:sched_switch
perf_event하드웨어/소프트웨어 카운터CPU 사이클, 캐시 미스
XDP네트워크 드라이버DDoS 필터링
TC (Traffic Control)트래픽 셰이핑QoS, 로드 밸런싱
socket소켓 작업소켓 필터
cgroupcgroup 이벤트네트워크 격리
LSM (Linux Security Modules)보안 정책파일 접근 제어

2.3 BPF Maps — 핵심 데이터 구조

eBPF 프로그램과 사용자 공간 간 데이터 공유는 BPF Maps를 통해 이루어집니다:

맵 종류용도
BPF_MAP_TYPE_HASH일반 키-값 저장
BPF_MAP_TYPE_ARRAY배열 (인덱스 기반)
BPF_MAP_TYPE_PERF_EVENT_ARRAY사용자 공간으로 이벤트 스트리밍
BPF_MAP_TYPE_RINGBUF더 효율적인 이벤트 스트리밍 (커널 5.8+)
BPF_MAP_TYPE_LRU_HASHLRU 캐시
BPF_MAP_TYPE_LPM_TRIE최장 접두사 일치 (라우팅에 사용)
BPF_MAP_TYPE_PROG_ARRAYtail call 점프 테이블
// BPF_MAP_TYPE_HASH 예시: 시스템 콜 호출 횟수 카운팅
struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __type(key, __u32);  // PID
    __type(value, __u64); // count
    __uint(max_entries, 10240);
} syscall_count SEC(".maps");

3. 개발 도구 비교

eBPF 프로그램을 작성하는 방법은 여러 가지가 있습니다:

3.1 BCC (BPF Compiler Collection)

# BCC 예시: openat 시스템 콜 추적
from bcc import BPF

prog = """
int hello(void *ctx) {
    bpf_trace_printk("openat called!\\n");
    return 0;
}
"""

b = BPF(text=prog)
b.attach_kprobe(event="do_sys_openat2", fn_name="hello")
b.trace_print()

3.2 bpftrace — DTrace의 후계자

# 프로세스별 시스템 콜 호출 횟수
bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[comm] = count(); }'

# 1초 이상 걸리는 read() 시스템 콜 추적
bpftrace -e '
kprobe:vfs_read { @start[tid] = nsecs; }
kretprobe:vfs_read /@start[tid]/ {
    $duration = nsecs - @start[tid];
    if ($duration > 1000000000) {
        printf("%s pid %d took %d ms\n", comm, pid, $duration / 1000000);
    }
    delete(@start[tid]);
}'

# CPU 사용량이 가장 높은 함수 (on-CPU 프로파일링)
bpftrace -e 'profile:hz:99 { @[ustack] = count(); }'

3.3 libbpf + CO-RE — 프로덕션 표준

// libbpf 예시: openat() 추적
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>

char LICENSE[] SEC("license") = "GPL";

SEC("tracepoint/syscalls/sys_enter_openat")
int handle_openat(struct trace_event_raw_sys_enter *ctx) {
    char comm[16];
    bpf_get_current_comm(&comm, sizeof(comm));
    bpf_printk("openat by %s\n", comm);
    return 0;
}

3.4 도구 비교표

도구학습 곡선성능배포 용이성적합한 용도
BCC중간중간어려움 (LLVM 필요)학습, 일회성 분석
bpftrace쉬움높음중간즉석 트레이싱, 디버깅
libbpf + CO-RE어려움매우 높음쉬움프로덕션 도구 개발
Aya (Rust)어려움매우 높음쉬움Rust 생태계 통합
ebpf-go중간매우 높음쉬움Go 애플리케이션 통합

4. eBPF의 3대 활용 분야

4.1 Observability — 관측성

eBPF는 애플리케이션 코드 변경 없이 시스템 동작을 깊게 관찰합니다.

Pixie — Kubernetes Native Observability

Parca — Continuous Profiling

bpftop — Top for eBPF

4.2 Networking — 네트워킹

Cilium — eBPF 기반 K8s 네트워킹의 표준

# Cilium L7 Network Policy 예시
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: allow-api-only
spec:
  endpointSelector:
    matchLabels:
      app: backend
  ingress:
  - fromEndpoints:
    - matchLabels:
        app: frontend
    toPorts:
    - ports:
      - port: '8080'
      rules:
        http:
        - method: 'GET'
          path: '/api/v1/.*'

XDP (eXpress Data Path) — 초고속 패킷 처리

XDP는 네트워크 드라이버 레벨에서 패킷을 처리합니다 — 커널 네트워크 스택을 거치지 않음.

// XDP 예시: 특정 IP 차단
SEC("xdp")
int xdp_drop_blocked_ip(struct xdp_md *ctx) {
    void *data_end = (void *)(long)ctx->data_end;
    void *data = (void *)(long)ctx->data;
    struct ethhdr *eth = data;

    if ((void *)eth + sizeof(*eth) > data_end)
        return XDP_PASS;

    if (eth->h_proto != htons(ETH_P_IP))
        return XDP_PASS;

    struct iphdr *ip = (void *)eth + sizeof(*eth);
    if ((void *)ip + sizeof(*ip) > data_end)
        return XDP_PASS;

    // 특정 IP 차단 (예: 1.2.3.4)
    if (ip->saddr == htonl(0x01020304))
        return XDP_DROP;

    return XDP_PASS;
}

4.3 Security — 보안

Falco — 런타임 보안 모니터링

# Falco rule: 컨테이너에서 셸 실행 탐지
- rule: Terminal shell in container
  desc: A shell was used as the entrypoint/exec point into a container
  condition: >
    spawned_process and container
    and shell_procs and proc.tty != 0
  output: >
    Shell spawned in container (user=%user.name container=%container.name
    shell=%proc.name pid=%proc.pid)
  priority: WARNING

Tetragon — Cilium 팀의 차세대 보안

Aqua Security, Sysdig Secure


5. 실전: bpftrace로 디버깅하기

5.1 시나리오: 디스크 I/O가 갑자기 느려졌다

# 1. 어떤 프로세스가 디스크 I/O를 일으키는가?
sudo bpftrace -e '
tracepoint:block:block_rq_issue {
    @[comm] = count();
}
interval:s:5 {
    print(@);
    clear(@);
}'

# 2. I/O 지연시간 분포 (히스토그램)
sudo bpftrace -e '
kprobe:blk_account_io_start { @start[arg0] = nsecs; }
kprobe:blk_account_io_done /@start[arg0]/ {
    @ms = hist((nsecs - @start[arg0]) / 1000000);
    delete(@start[arg0]);
}'

5.2 시나리오: 알 수 없는 파일이 열리고 있다

sudo bpftrace -e '
tracepoint:syscalls:sys_enter_openat {
    printf("%s opened: %s\n", comm, str(args->filename));
}'

5.3 시나리오: TCP 연결이 끊긴다

sudo bpftrace -e '
kprobe:tcp_close {
    $sk = (struct sock *)arg0;
    $state = $sk->__sk_common.skc_state;
    if ($state != 7) { // TCP_CLOSE
        printf("PID %d (%s) closed TCP connection in state %d\n",
               pid, comm, $state);
    }
}'

6. CO-RE: 한 번 컴파일, 어디서든 실행

6.1 문제: 커널 버전마다 구조체가 다르다

// 커널 5.10에서:
struct task_struct {
    int pid;
    char comm[16];
    // ...
};

// 커널 5.15에서 새 필드 추가:
struct task_struct {
    int pid;
    int tgid;  // 새로 추가
    char comm[16];
};

전통적인 BCC는 실행 시점에 커널 헤더를 사용해 컴파일했습니다. 하지만 컨테이너에서는 호스트 커널 헤더에 접근하기 어렵고, 컴파일 시간도 길었습니다.

6.2 해결책: BTF + CO-RE

BTF (BPF Type Format): 커널이 자신의 구조체 정보를 /sys/kernel/btf/vmlinux에 노출합니다.

CO-RE: 컴파일러가 구조체 필드 접근을 "심볼릭"하게 표현하고, 로더가 런타임에 BTF 정보로 재배치합니다.

// CO-RE 매크로 사용
#include <bpf/bpf_core_read.h>

SEC("kprobe/do_unlinkat")
int handle_unlinkat(struct pt_regs *ctx) {
    struct task_struct *task = (struct task_struct *)bpf_get_current_task();
    
    // BPF_CORE_READ: BTF 기반 안전한 필드 읽기
    pid_t pid = BPF_CORE_READ(task, pid);
    
    bpf_printk("unlink by pid %d\n", pid);
    return 0;
}

효과: 한 번 빌드한 .o 파일이 5.4부터 6.5까지 모든 커널에서 동작.


7. 프로덕션 사례

7.1 Netflix — bpftrace로 성능 디버깅

Netflix의 Brendan Gregg는 eBPF의 가장 영향력 있는 옹호자입니다. Netflix는 수만 개의 EC2 인스턴스에서 BCC와 bpftrace를 활용해:

7.2 Cloudflare — XDP로 DDoS 방어

7.3 Meta (Facebook) — 모든 곳에 eBPF

7.4 Google — Cilium으로 GKE 데이터플레인 V2


8. eBPF의 한계와 도전

8.1 검증기의 제약

8.2 커널 버전 의존성

8.3 디버깅의 어려움

8.4 보안 우려


9. eBPF의 미래

9.1 BPF in Windows

Microsoft가 eBPF for Windows를 개발 중. Windows에서도 같은 eBPF 프로그램 실행 가능 → 진정한 크로스 플랫폼 커널 프로그래밍.

9.2 sched_ext — 스케줄러를 eBPF로

커널 6.12+에서 도입. CPU 스케줄러 자체를 eBPF로 작성 가능 → 워크로드별 맞춤 스케줄링.

9.3 BPF FUSE — 파일시스템

eBPF로 파일시스템 작성 가능성 탐구 중.

9.4 Confidential Computing

eBPF를 활용한 워크로드 격리와 attestation.


10. eBPF 시작하기 — 학습 로드맵

Week 1: 기초

Week 2-3: BCC

Week 4-6: libbpf + CO-RE

Week 7+: 실전 프로젝트


퀴즈

1. eBPF 프로그램이 안전하다고 보장되는 이유는 무엇인가요?

: 커널에 로드되기 전 BPF Verifier가 정적 분석을 수행합니다. 무한 루프 금지, 메모리 접근 검증, 허용된 헬퍼 함수만 호출, 명령어 수 제한 등을 확인합니다. 검증을 통과하면 JIT 컴파일러가 네이티브 머신 코드로 변환하여 거의 네이티브 속도로 실행됩니다.

2. CO-RE(Compile Once, Run Everywhere)가 해결하는 문제는?

: 전통적인 BCC는 실행 시점에 커널 헤더로 컴파일해야 했습니다. 이는 LLVM 의존성, 긴 시작 시간, 컨테이너 환경에서의 어려움을 야기했습니다. CO-RE는 BTF(BPF Type Format)를 활용해 컴파일러가 구조체 필드 접근을 "심볼릭"하게 표현하고, 로더가 런타임에 BTF 정보로 재배치합니다. 한 번 빌드한 .o 파일이 다양한 커널 버전에서 동작합니다.

3. XDP가 iptables보다 빠른 이유는?

: XDP는 네트워크 드라이버 수준에서 패킷을 처리합니다. 패킷이 커널 네트워크 스택(skb 할당, conntrack 등)을 거치기 전에 처리하므로 오버헤드가 매우 적습니다. Cloudflare는 XDP로 단일 서버에서 초당 1천만+ 패킷을 처리하며, iptables 대비 10배 이상 효율적입니다.

4. Cilium이 kube-proxy를 어떻게 대체하나요?

: kube-proxy는 iptables 또는 IPVS 규칙으로 Service ClusterIP를 백엔드 Pod로 라우팅합니다. 클러스터가 커지면 iptables 규칙이 수천 개로 늘어나 성능이 저하됩니다. Cilium은 eBPF 프로그램이 소켓 작업과 패킷 처리에서 직접 백엔드를 선택하므로 iptables 규칙이 필요 없습니다. 결과적으로 일정한 성능을 유지하며 CPU 사용량이 낮습니다.

5. bpftrace와 libbpf의 차이는 언제 어떤 것을 사용해야 하나요?

: bpftrace는 즉석 트레이싱과 디버깅에 적합합니다. DTrace 같은 고수준 DSL로 한 줄로 강력한 분석이 가능합니다. libbpf + CO-RE는 프로덕션 도구 개발에 적합합니다. 작은 바이너리, 컴파일러 의존성 없음, 빠른 시작, 다양한 커널 호환성을 제공합니다. 개인 디버깅은 bpftrace, 배포할 도구는 libbpf로 작성하는 것이 일반적입니다.


참고 자료

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다