eBPF(Extended Berkeley Packet Filter)란 리눅스 커널의 소스 코드를 수정하거나 커널 모듈(LKM)을 로드하지 않고도 커널 내부에서 샌드박스 형태로 안전하게 프로그램을 실행해 기능을 확장하는 기술이다. 과거 네트워크 패킷 필터링에 제한되었던 BPF를 업그레이드 하여, 현재는 시스템 전반의 가시성과 제어권을 확보하는 기술이 되었다. 최근 컨테이너와 Kubernets 기반의 마이크로 서비스(MSA)에서는 eBPF의 중요성은 더욱 부각되고 있다. 기존 모니터링은 에이전트로 수행하여 컨테이너마다 리소스를 점유하기 때문에 약간의 오버헤드가 있었지만, eBPF를 사용한다면 오버헤드가 없으며, 런타임 보안 정책을 효율적으로 적용할 수 있게 된다. 시장에서는 eBPF 기반의 솔루션들이 자주 등장하고 있으며, 나도 이런 eBPF를 사용하여 프로젝트를 해보고 싶어서 개념적으로 정리해보려고 한다.

BPF vs eBPF
1992년부터 리눅스에서는 네트워크 패킷 필터링을 위한 BPF가 있었지만 확장된(Extended)이라는 뜻을 붙인 eBPF로 확장되었다. 이렇게 이름을 eBPF로 붙이면서 기존의 BPF는 이를 구별하기 위해 cBPF라고 부르는 경우도 있다. 이 cBPF는 tcpdump에서 특정 포트만 캡처하는 것처럼 네트워크 패킷을 효율적으로 필터링하기 위한 목적으로 2개의 레지스터(Accumulator, Index Register)만 가졌다. 이런 cBPF의 한계는 32비트 아키텍처 기반여서 요즘 64비트 아키텍처에서는 구시대로 남았다는 것과 오직 네트워크 스택 내의 특정 지점에서만 작동했기에 범용적으로 사용하기가 어려웠었다.
2014년에 리눅스 커널 3.18부터 eBPF가 생기게 되었다. 기존의 cBPF의 단순한 필터링을 넘어 커널의 기능을 안전하게 확장할 수 있게 되었다. eBPF는 네트워크뿐만 아니라 시스템 콜(System Call), 커널 함수(kprobes), 유저 공간 함수(uprobes), 추적(tracing) 등 시스템 전반에서 사용 가능한 BPF로 바뀐 것이다. JIT(Just-In-Time) 컴파일러를 통해 바이트코드를 실제 머신 코드로 변환하여 실행하므로 커널 네이티브 코드에 가까운 속도를 발휘하며(이는 밑에서 살펴보기로 하자), 검증기(Verifier)가 있어 무한 루프나 잘못된 메모리 접근이 있는 코드는 아예 실행되지도 않아 커널 패닉을 방지할 수 있었다.
작동 원리
eBPF 프로그램이 커널 내부에서 실행되는 과정은 크게 세 단계로 나뉘게 된다. 일단 기존에 유저 공간에서 Clang/LLVM으로 소스코드를 eBPF 바이트코드로 바꾸고, 컴파일된 바이트코드를 커널로 보냈다.(bpf() syscall) 다음 소개하는 과정은 그 이후에 커널에서 작동되는 동작들이다.
1. 검증기(Verifier)
사용자가 작성한 eBPF 코드는 커널에 로드되기 전 반드시 검증기를 거쳐야 한다. 검증기는 다음과 같은 사항을 정적으로 분석하여 시스템에 해를 끼치지 않는지 확인하게 된다.
- 종료 보장: 무한 루프가 있어 커널을 멈추게 하지 않는가?
- 메모리 안정성: 허용되지 않은 메모리 영역에 접근하거나 Null 포인터를 참조하지 않는가?
- 권한 확인: 프로그램을 로드하는 프로세스가 적절한 권한을 가지고 있는가?
- 프로그램 크기 제한: 너무 긴 코드는 커널 성능에 영향을 주므로 적절한지 확인
- 도달 불가능한 코드: 데드 코드가 있는지 확인
2. JIT(Just-In-Time) 컴파일러
검증을 통과한 eBPF 바이트코드는 JIT 컴파일러를 통해 해당 서버의 CPU가 이해할 수 있는 기계어로 변환된다.
3. 이벤트 기반 실행과 Hooks
eBPF 프로그램은 아무 때나 실행되는 것이 아니라 특정 이벤트가 발생했을 때만 작동하는 이벤트 기반 모델이다. 프로그램이 부착되는 지점을 후크(Hook)라고 부르며, 시스템 콜(파일 열기, 프로세스 생성 등 애플리케이션의 요청 시점), 네트워크 이벤트(패킷이 들어오거나 나가는 시점), 커널/유저 함수(특정 함수의 시작이나 종료 시점(kprobes/uprobes))과 같은 곳에 부착할 수 있다.
이렇게 3가지 단계가 있는데 eBPF의 검증기는 매우 보수적이고 까다롭기 때문에, 개발자들 사이에서는 검증기와 싸우며 코딩한다는 말이 나올 정도로 엄격하다고 한다.
커널과 eBPF 프로그램이 소통하는 방법
eBPF 프로그램은 커널 내부의 격리된 샌드박스에서 실행되므로, 외부와의 데이터 교환을 위해 두가지 특수한 구조가 필요하다.
첫 번째로는 Maps이다. eBPF Maps이란 커널 공간에 위치한 키-값(Key-Value) 저장소이다. eBPF 프로그램이 수집한 통계 데이터를 저장하면, 사용자 공간에 있는 애플리케이션이 이 데이터를 읽어 시각화하거나 분석할 수 있다.
두 번째는 헬퍼 함수(Helper Fuctions)이다. eBPF 프로그램이 커널의 기능을 안전하게 활용할 수 있도록 미리 정의된 API 세트이다. 현재 시간을 가져오거나, 난수를 생성하거나, 네트워크 패킷을 수정하는 등의 작업을 수행할 때 사용된다.
eBPF의 사용
eBPF는 현재 클라우드 인프라의 핵심 기술로 자리 잡게 되었다. DevSecOps를 공부하면서 이런 eBPF 이야기를 자주 들었고, 보안 분야에서 프로젝트를 진행한다고 하면 eBPF가 한번씩은 이야기가 들리게 된다. 그만큼 아직 더 확장 가능한 분야이며, 최근 핫한 클라우드 보안 분야와 합쳐져서 더 시너지가 나온다고 생각한다. 그 외에도 모니터링이나 런타인 보안, 네트워크 등 다양한 분야에서 사용되고 있으며, 대표 사례와 함께 현재 eBPF가 어떻게 쓰이는지 간략하게 살펴보겠다.
1. 클라우드 쿠버네티스 네트워킹 - Cilium(https://cilium.io/)

쿠버네티스 환경에서 기존의 iptables는 룰이 많아질수록 성능이 급격하게 저하된다. eBPF는 커널 레벨에서 패킷을 직접 처리하여 로드 밸런싱과 네트워크 정책을 수행하므로, 수만 개의 컨테이너가 있는 환경에서도 병목 현상 없는 통신을 가능하게 한다.
2. 시스템 런타임 보안 - Falco(https://github.com/falcosecurity/falco), Tetragon(https://tetragon.io/)

특정 파일 접근, 비정상적인 프로세스 실행, 권한 상승 시도 등을 시스템 콜 레벨에서 실시간으로 감시한다. 기존 보안 도구와 달리 커널 내부에서 위협을 탐지하므로 우회하기가 어렵고, 위협 탐지 시 프로세스를 차단하는 등 능동적인 방어가 가능하다.
3. 시스템 모니터링 - Pixie(https://px.dev/), Parca(https://www.parca.dev/)

애플리케이션 코드를 수정하지 않고도 CPU 사용량, 메모리 누수, 함수 호출 지연 시간 등을 측정한다. 특히 Continuous Profiling을 통해 운영 중인 서비스의 성능 병목 지점을 실시간으로 찾아낼 수 있다고 한다.
결론
eBPF는 이제 리눅스 커널을 브라우저가 JS를 샌드박스 형식으로 가두듯 프로그래밍 가능한 영역으로 바꾸게 되었다. 시스템의 커널을 제어할 수 있다는 점은 보안과 인프라 엔지니어들에게는 아직 공부 가능한 영역이 남아있다고 생각한다. AI가 현재 대체하고 있지만 커널과 같은 분야는 AI의 위협에도 아직은 공부할만한 분야인 것 같다. (하지만 어렵다...) 이렇게 이론적으로 공부한 eBPF를 사용해 클라우드 혹은 컨테이너 보안 프로젝트를 진행해 보고 싶다는 생각으로 이렇게 작성하게 되었는데 알게된 개념을 바탕으로 어떤 주제를 잡고 도전해봐야 할지 고민해봐야겠다.
