TECH 으로 돌아가기
TECH HACKER NEWS 오늘 7분 읽기 27 READS

eBPF 보안 에이전트, 메모이제이션으로 커널 CPU 90% 절감한 사례

eBPF 보안 에이전트, 메모이제이션으로 커널 CPU 90% 절감한 사례
SOURCE IMAGE · HACKER NEWS

리눅스 커널에서 동작하는 보안 에이전트는 성능이 곧 채택 여부를 좌우한다. 파일 접근처럼 초당 수만 건씩 발생하는 이벤트마다 검사 로직이 끼어들면, 아무리 정교한 정책이라도 운영 환경에서는 부담으로 돌아오기 때문이다. 오픈소스로 공개된 Bomfather 에이전트 개발자는 자신들이 만든 eBPF 기반 보안 에이전트를 프로파일링하다가 의외의 사실을 발견했다. 정작 무거운 작업은 접근을 허용할지 차단할지 결정하는 정책 집행 자체가 아니라, '이 파일 열기에 어떤 정책이 적용되는가'를 알아내는 과정이었다는 것이다. 이 병목을 메모이제이션(캐싱)으로 해소해 커널 측 CPU 비용을 약 90% 줄였다는 것이 이번 사례의 핵심이다.

왜 경로 탐색이 병목이었나

이 에이전트의 정책은 파일 경로를 기준으로 한다. 그래서 파일이 열릴 때 트리거되는 LSM 훅을 활용해, 경로를 재구성하고 상위 디렉터리의 dentry를 하나씩 거슬러 올라가며 해당 파일이나 조상 디렉터리에 매칭되는 정책이 있는지 확인한다. 문제는 이 경로 탐색이 매번 반복된다는 점이다. 예를 들어 Postgres가 /var/lib/postgres 하위에만 접근하도록 정책을 걸어두면, Postgres가 data/base/123, data/base/234, data/base/345 같은 파일을 열 때마다 사실상 같은 상위 경로를 처음부터 다시 걸어 올라가야 한다. 개발자는 이 비효율적인 전체 경로 탐색을 '슬로 패스'라고 부른다. 데이터베이스처럼 같은 경로를 반복적으로 재접근하는 워크로드에서 특히 낭비가 컸다.

dentry 대신 inode를 캐시 키로 삼은 이유

해법은 결과를 캐시해 재사용하는 것이지만, eBPF 환경에는 제약이 있다. 처음에는 dentry를 키로 쓰려 했으나 dentry는 포인터이고, 포인터는 eBPF 맵에 저장할 수 없다. dentry의 내용을 구조체에 담아 키로 쓰는 방법도 있지만 구조체가 너무 무거워진다. 그래서 개발자는 inode 기반 캐시로 방향을 틀었다. 캐시 키는 마운트 네임스페이스 ID, 마운트 ID, inode 번호의 세 필드로 구성된다. inode 번호만으로는 캐시할 수 없는데, inode 번호는 특정 마운트 트리 안에서만 고유하기 때문이다. 하나의 정책이 여러 마운트 트리를 포괄하면 inode가 겹칠 수 있어, 마운트 ID로 어느 마운트 트리를 통해 파일을 봤는지 식별하고, 마운트 네임스페이스 ID로 다른 네임스페이스의 캐시 항목이 잘못 재사용되는 것을 막는다. 캐시 값은 access_index와 캐시 상태 두 부분으로 이뤄지며, 정책은 공간 효율을 위해 비트마스크로 저장하고 access_index는 해당 경로 정책의 비트 위치를 가리킨다.

벤치마크가 보여준 효과

효과는 수치로 뚜렷하게 드러났다. 같은 파일을 20만 번 여는 벤치마크에서 캐시는 커널 사이클을 280억에서 30.3억으로 줄였다. 캐시가 없을 때는 스택 프로파일에서 tail_call_security_check가 89.2%, is_restricted_filepath가 81.9%, path_check_callback이 63.7% 비율로 나타났다. 캐시를 적용한 뒤에는 첫 조회 이후 경로 탐색 비용이 사실상 사라져, is_restricted_filepath와 path_check_callback은 각각 약 0.02% 수준으로 쪼그라들어 플레임그래프에서 거의 보이지 않게 됐다. 측정은 perf의 cycles:k 이벤트로 커널 측 CPU 비용을 잰 결과다. 다만 이 20만 번이라는 조건은 캐시 적중이 극대화되는 반복 접근 시나리오라는 점은 감안할 필요가 있다. 실제 워크로드에서 얻는 이득은 접근 패턴이 얼마나 반복적인지에 좌우되기 때문이다.

정확성을 위해 감수한 트레이드오프

캐시는 성능을 위한 도구지만, 보안 에이전트에서는 정확성이 성능보다 우선한다. 여기서 걸림돌이 되는 것이 하나의 inode를 여러 경로가 공유하는 경우, 대표적으로 하드링크다. 하드링크에서는 서로 다른 두 경로가 같은 inode를 가리키므로, inode 기반 캐시가 잘못된 정책을 적용할 위험이 생긴다. 개발자는 이를 정공법이라기보다 우회책이라고 스스로 인정한다. inode에는 몇 개의 경로가 그 inode를 가리키는지 알려주는 링크 카운트(i_nlink)가 있는데, 이 값을 읽어 1보다 크면 해당 캐시 항목을 쓰지 않고 슬로 패스로 되돌아간다. 캐시 적용 범위를 일부 포기하는 트레이드오프지만, 정확한 캐시가 가장 중요하다는 판단에서 나온 선택이다.

국내 실무자 관점에서 이 사례가 주는 시사점은 두 가지다. 하나는 최적화의 출발점이 직관이 아니라 프로파일링이어야 한다는 점이다. 개발자조차 병목이 정책 집행에 있을 것으로 여겼지만 실제로는 정책 조회에 있었고, 이는 측정 없이는 놓치기 쉬운 지점이다. 다른 하나는 이 캐시가 전적으로 에이전트 내부에서 동작한다는 점이다. 사용자가 정책을 바꾸지 않아도 속도 향상을 그대로 누릴 수 있어, 운영 부담 없이 적용 가능한 개선이라는 점이 실무적으로 매력적이다. 관련 코드는 GitHub의 bomfather/agent 저장소에 공개돼 있어, eBPF와 LSM 훅을 활용한 경로 기반 정책 설계를 고민하는 팀이라면 구현 세부를 직접 살펴볼 만하다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://nathannaveen.dev/posts/dropping-ebpf-cpu-cost-by-90/
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기
처리 중...