POOOLING FOREST
eBPF 검증기의 악몽에서 벗어나기: Rust로 작성하는 커널 확장 Rex - eBPF 검증기의 제약에서 벗어나 Rust의 컴파일 타임 안전성을 활용해 리눅스 커널 확장을 작성하는 프레임
Engineering & Tech

eBPF 검증기의 악몽에서 벗어나기: Rust로 작성하는 커널 확장 Rex

eBPF 검증기의 제약에서 벗어나 Rust의 컴파일 타임 안전성을 활용해 리눅스 커널 확장을 작성하는 프레임워크 'Rex'를 소개합니다.

김영태

테크리드

안녕하세요. 풀링포레스트 테크리드 김영태입니다.

최근 우리 팀은 마이크로서비스 아키텍처(MSA) 환경에서 네트워크 가시성을 확보하기 위해 eBPF 도입을 적극적으로 검토하고 있었습니다. 트래픽을 커널 레벨에서 모니터링하고 제어한다는 아이디어는 언제나 매력적이니까요. 하지만 실무에서 eBPF를 직접 다뤄본 분들이라면 공감하실 겁니다. 그놈의 'Verifier(검증기)'가 얼마나 사람을 지치게 만드는지를요.

솔직히 고백하자면, 저는 지난주 내내 단순한 패킷 로직 하나를 추가하려다 eBPF 검증기와 씨름하며 머리를 쥐어뜯었습니다. 로직 자체는 완벽했습니다. 하지만 루프가 조금만 복잡해지거나 코드 길이가 길어지면 검증기는 가차 없이 "Unsafe" 딱지를 붙이며 프로그램을 거부해버립니다. 안전을 위한 장치라는 건 알지만, 개발자 입장에서는 코드를 최적화하는 게 아니라 검증기 비위를 맞추기 위해 코드를 비틀어야 하는 주객전도 상황이 벌어지곤 하죠.

그러다 문득, "꼭 eBPF 바이트코드여야만 할까?"라는 의문이 들었고, 흥미로운 프로젝트 하나를 발견했습니다. 바로 Rex입니다.

Rex는 eBPF 대신 Rust를 사용하여 리눅스 커널 확장을 작성하고 실행할 수 있게 해주는 프레임워크입니다. 가장 충격적이었던 점은 Rex로 작성한 프로그램은 커널 내 eBPF Verifier를 거치지 않는다는 것입니다.

"잠깐, 검증기를 안 거치면 위험한 거 아냐?"라는 생각이 가장 먼저 드실 겁니다. 저도 그랬으니까요. 하지만 Rex는 검증기 대신 Rust의 컴파일 타임 안전성을 믿는 전략을 취합니다. Rust가 자랑하는 소유권 모델과 타입 시스템을 통해 컴파일 단계에서 메모리 안전성을 보장하고, 이를 통과한 코드만을 네이티브 기계어로 변환해 커널에 로드하는 방식입니다.

이 접근 방식이 주는 이점은 명확합니다.

첫째, 복잡성 제약에서의 해방입니다. 기존 eBPF는 프로그램의 명령어 수나 복잡도(Complexity)에 엄격한 제한이 있었습니다. 반면 Rex는 Rust의 안전한 부분집합(Safe Subset)을 사용하는 한, 개발자가 원하는 대로 로직을 구현할 수 있습니다. 검증기를 통과하기 위해 직관적이지 않은 코드를 짤 필요가 없어진 것이죠.

둘째, 성능 최적화입니다. eBPF는 바이트코드를 커널 내 JIT 컴파일러가 변환하는 구조지만, Rex는 LLVM 같은 성숙한 컴파일러 백엔드를 통해 미리 최적화된 네이티브 코드를 생성합니다.

실제 코드를 보면 그 매력이 더 잘 느껴집니다. 다음은 Rex를 이용해 특정 프로세스의 시스템 콜에 에러를 주입하는 `kprobe` 예제입니다.

#[rex_kprobe]
pub fn err_injector(obj: &kprobe, ctx: &mut PtRegs) -> Result {
    obj.bpf_get_current_task()
       .map(|t| t.get_pid())
       .and_then(|p| obj.bpf_map_lookup_elem(&pid_to_errno, &p).cloned())
       .map(|e| obj.bpf_override_return(ctx, e))
       .ok_or(0)
}

보시다시피 `RexHashMap` 같은 자료구조나 헬퍼 함수들을 일반적인 Rust 코드처럼 자연스럽게 사용하고 있습니다. Aya 같은 기존 eBPF 프레임워크도 훌륭하지만, 결국 바이트코드로 변환되어 검증기의 심판을 받아야 한다는 한계가 있었습니다. Rex는 그 굴레를 벗어던진 느낌입니다.

물론 Rex도 만능은 아닙니다. 아직 초기 단계의 프로젝트이고, 지원하는 프로그램 타입이 kprobe, perf_event, tracepoint, xdp, tc 정도로 제한적입니다. 하지만 Memcached 가속이나 복잡한 패킷 필터링처럼 eBPF의 제약 때문에 구현이 막막했던 기능을 구현해야 할 때, Rex는 훌륭한 대안이 될 수 있습니다. 커널 패닉 시 콜 스택을 추적해 정리해주는 안전장치도 마련되어 있어, '커널을 깨먹을까 봐' 막연히 두려워할 필요도 줄어들었습니다.

우리는 기술을 선택할 때 항상 '트레이드오프'를 고민해야 합니다. 검증기의 엄격함이 주는 절대적인 안전함이 필요할 때도 있지만, 때로는 그 엄격함이 혁신을 가로막는 벽이 되기도 합니다. Rust의 강력한 타입 시스템을 믿고 조금 더 유연한 커널 프로그래밍을 시도해보고 싶다면, 이번 주말에는 Rex를 한번 들여다보시는 건 어떨까요?

저도 당분간은 이 녀석을 데리고 그동안 미뤄뒀던 복잡한 트래픽 제어 로직을 다시 짜볼 생각입니다. eBPF 검증기와 싸우느라 지친 모든 시스템 개발자분들, 오늘도 화이팅입니다.

지금 읽으신 내용, 귀사에 적용해보고 싶으신가요?

상황과 목표를 알려주시면 가능한 옵션과 현실적인 도입 경로를 제안해드립니다.