eBPF 의 컨셉 및 eBPF 프로그램과 이를 활용한 모던 소프트웨어 개발의 이점들에 대한 간략한 이해
eBPF는 다양한 종류의 트래픽에 걸쳐 작동하므로 통합 관찰가능성이라는 기업의 목표를 달성하는 데 큰 도움이 된다. 예를 들어, 데브옵스 엔지니어는 전체 트레이스 요청, 데이터베이스 질의, HTTP 요청, gRPC 스트림 수집은 물론 CPU 사용량이나 전송 바이트 수와 같은 자원 활용 지표(메트릭스) 수집에도 eBPF를 활용할 수 있다. 따라서 해당 기업은 관련 통계를 산출하고 데이터의 개요를 파악해 다양한 기능의 자원 소모 현황을 이해할 수 있다. 또한, eBPF는 암호화된 트래픽을 처리할 수 있다.
#include <linux/sched.h>
#include <linux/fs.h>
struct key_t {
u32 pid;
u64 inode;
};
BPF_HASH(counts, struct key_t, u64, 256);
int trace_read(struct pt_regs *ctx, struct file *file, char __user *buf, size_t count)
{
struct key_t key = {};
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
key.pid = bpf_get_current_pid_tgid();
key.inode = file->f_inode->i_ino;
u64 *val = counts.lookup(&key);
if (val) {
(*val)++;
} else {
u64 zero = 0;
counts.update(&key, &zero);
}
return 0;
}
from bcc import BPF
# define the eBPF program
program = """
#include <linux/sched.h>
#include <linux/fs.h>
struct key_t {
u32 pid;
u64 inode;
};
BPF_HASH(counts, struct key_t, u64, 256);
int trace_read(struct pt_regs *ctx, struct file *file, char __user *buf, size_t count)
{
struct key_t key = {};
struct task_struct *task = (struct task_struct *)bpf_get_current_task();
key.pid = bpf_get_current_pid_tgid();
key.inode = file->f_inode->i_ino;
u64 *val = counts.lookup(&key);
if (val) {
(*val)++;
} else {
u64 zero = 0;
counts.update(&key, &zero);
}
return 0;
}
"""
# create the BPF module
b = BPF(text=program)
# attach the eBPF program to a tracepoint
b.attach_kprobe(event="vfs_read", fn_name="trace_read")
# display the map contents every second
while True:
print("PID INODE COUNT")
counts = b["counts"]
for k, v in counts.items():
print(f"{k.pid} {k.inode} {v.value}")
print("")
counts.clear()
time.sleep(1)
Kprobe
란?: Kernel code 에 동적으로 중단점을 삽입하여 사용자가 정의하는 핸들러 함수가 실행되도록 하는 강력한 도구
Uprobe
란?: User-level 에 동작하는 function 에 삽입하는 것
- 동적으로 dynamic instrumentation 코드를 삽입하여 모니터링
Tracepoint
란?: 소스코드의 특정 위치(커널 이벤트)에 정적으로 마킹하여 디버깅을 위해 데이터를 수집하게 해주는 것. 커널 소스코드에 미리 정의된 매크로를 사용하고,trace-cmd
툴 사용.
USDT
란: User Space Tracing Dynamic Tracing 의 약자로, 유저레벨의 이벤트를 관측하고 profiling 가능하게 해준다. 컴파일된 코드와dtrace
툴 사용
- 디버깅 및 프로파일링 용도
BCC
(BPF Compiler Collection) 와 같은 툴킷을 사용하여 byte code 로 컴파일BCC
는 backend compiler 로 LLVM (low-level virtual machine) 을 사용LLVM
의 frontend compiler 로는 Clang
을 사용eBPF 프로그램이 커널로 로드될 때 Loader 와 Verifier 를 거치게 됨
eBPF 는 리눅스 VM 에서 돌아가는 프로그램이지만 안전하고 효율적인 사용자 정의 함수들을 실행시키기 위해 Heap 영역을 사용하지 않음. (Stack size: 512 bytes per program)
이에 대해 eBPF 는 Maps 이라는 특수한 형태의 메모리 관리 메카니즘을 을 제공. 이는 유저영역 앱과 공유될 수 있다.
유저레벨에서 eBPF 프로그램을 개발하고, 컴파일 및 로딩하게끔 도와주는 프로그램들
# bpftrace -e 'kprobe:vfs_read /pid == 30153/ { @start[tid] = nsecs; }
kretprobe:vfs_read /@start[tid]/ { @ns = hist(nsecs - @start[tid]); delete(@start[tid]); }'
Attaching 2 probes...
^C
@ns:
[256, 512) 10900 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ |
[512, 1k) 18291 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[1k, 2k) 4998 |@@@@@@@@@@@@@@ |
[2k, 4k) 57 | |
[4k, 8k) 117 | |
[8k, 16k) 48 | |
[16k, 32k) 109 | |
[32k, 64k) 3 | |
bpftrace 를 이용하여 생성된 Flame Graph
XDP 는 리눅스 커널에서 아주 고속으로 패킷들을 처리할 수 있는 방법을 제공하는 기술
XDP는 eBPF Hook의 한 종류로 네트워크 스택에서 패킷을 인터셉트하고 처리. 이러한 XDP 기능을 지원하기 위해 네트워크 장치 드라이버 내에는 XDP 관련 코드가 구현이 되어있다.