eBPF 기반 latency 원인 분석 + 로컬 카오스 엔지니어링 통합 플랫폼
본 프로젝트는 두 개의 독립적이면서도 유기적으로 연결되는 도구로 구성됩니다.
| 프로젝트 | 한 줄 설명 | 역할 |
|---|---|---|
| MicroTrace | eBPF 기반 실시간 latency 원인 분석기 | 장애의 원인을 좁힌다 |
| NetSim Lab | Docker 컨테이너 대상 네트워크 장애 주입 도구 | 장애를 재현한다 |
두 도구를 결합하면 "장애 주입 → latency spike 감지 → 원인 분석" 이라는 완전한 피드백 루프가 완성됩니다.
┌─────────────────────────────────────────────────┐
│ 통합 운영 시나리오 │
│ │
│ NetSim Lab MicroTrace │
│ (장애 주입) → (실시간 관측) │
│ │ │ │
│ └────── 피드백 ──────┘ │
│ 루프 완성 │
└─────────────────────────────────────────────────┘
스타트업 3~10인 백엔드 팀. MSA 전환 초기 단계로, Datadog/WhaTap 비용이 부담되어 무료 대안을 모색 중. 간헐적 API 타임아웃이 발생하지만 기존 APM의 1분 평균 데이터로는 원인을 찾지 못하고 있는 상황. 로컬 개발 환경에서 네트워크 예외 상황을 재현할 도구도 마땅치 않아, retry/timeout 로직의 신뢰성을 검증하지 못하고 있음.
전체 지도는 docs/README.md 참고 (문서는 역할별로 분리됨).
| 문서 | 내용 |
|---|---|
| docs/README.md | 문서 지도 (역할 구분 + 빠른 진입) |
| overview.md | MicroTrace 개요 (AI용 축약) |
| microtrace.md | MicroTrace 상세 기획서 (사람용 전문) |
| netsim.md | NetSim Lab 상세 기획서 |
| integration.md | 통합/독립 운영 시나리오 |
| todo.md | 다음 작업 및 완료 이력 |
| interview-qa.md | Q&A |
| microtrace.code.md | 코드맵 (기능→파일/심볼) |
| progress.md | 구현 진행 기록 |
| 계층 | MicroTrace | NetSim Lab |
|---|---|---|
| 커널/시스템 | C, eBPF (libbpf), sock_ops, kprobe, uprobe | tc/netem, nsenter, netlink |
| 백엔드 | Go (Goroutine, Channel, WebSocket) | Go (Docker API, REST API) |
| 프론트엔드 | React Web (TypeScript) | Web UI (React) |
| 인프라 | Docker, cgroup v2 | Docker API, Network Namespace |
디버깅할 때 필요한 테스트 컨테이너, collector, React 개발 서버를 한 번에 실행합니다.
make devEC2처럼 Docker API로 IP→서비스명을 자동 조회할 수 없는 환경은 StaticResolver 설정 파일을 지정해 실행합니다.
make dev-static HOSTS=collector/hosts.example.yaml또는 같은 의미로 아래처럼 짧게 실행할 수 있습니다.
make dev HOSTS=collector/hosts.example.yaml실행 후 접속:
| 대상 | 주소 |
|---|---|
| React 대시보드 | http://localhost:5173 또는 런처가 출력한 WSL IP 주소 |
| Collector 테스트 페이지 | http://localhost:9090 또는 런처가 출력한 WSL IP 주소 |
| WebSocket | 프론트가 접속한 host 기준으로 자동 연결 |
종료는 실행 중인 터미널에서 Ctrl+C를 누르면 됩니다. 기본적으로 테스트 컨테이너도 함께 정리됩니다. 컨테이너를 유지하고 싶으면 아래처럼 실행합니다.
KEEP_CONTAINERS=1 make dev테스트 컨테이너만 수동으로 정리하려면:
make dev-down프론트엔드 방향: React Web (TypeScript) 확정. Wails(데스크톱 앱)는 로컬 단일 머신에서만 동작하므로, EC2 멀티 호스트·K8s 환경에서 팀원이 브라우저로 접속할 수 없음. React Web은 URL 하나로 모든 환경에서 공유 가능.
사용자가 자신의 마이크로서비스에 MicroTrace를 올리면, 먼저 서비스 간 latency spike와 리소스 이상 징후를 감지하고, 이를 토폴로지와 상세 그래프에서 함께 보여줍니다. 특정 노드나 엣지를 클릭하면 RTT뿐 아니라 CPU/IO 같은 원인 후보를 확인하는 상세 분석 화면으로 진입합니다.
토폴로지 화면 (Overview)
┌─────────────────────────────────┐
│ │
│ [service-a] ──🔴──▶ [service-b] │ ← 빨간색 = 레이턴시 높음
│ │ │
│ └────────▶ [postgres] │ ← 화살표 두께 = 트래픽 양
│ │
└─────────────────────────────────┘
클릭하면 ▼
상세 화면 (service-a → service-b)
┌─────────────────────────────────┐
│ RTT 시계열 그래프 │
│ 재전송 횟수 │
│ P50 / P95 / P99 레이턴시 │
└─────────────────────────────────┘
| Phase | 내용 |
|---|---|
| Phase 1 | collector에 Docker API 연동 → IP를 컨테이너 이름으로 매핑 → 서비스 간 latency 시각화 기반 마련 |
| Phase 2 | latency spike 감지 + CPU/IO/Memory 리소스 수집 (고정 1초 주기, resource_agent 별도 바이너리) |
| Phase 3 | React 토폴로지/상세 화면 → RTT, CPU, IO, 재전송을 함께 보여주는 drill-down UI |
Docker Compose와 Kubernetes는 서비스 이름 체계와 네트워크 구조가 근본적으로 다르므로, 별개의 구현체로 설계합니다. 확장성을 위해 ServiceResolver 인터페이스를 공통으로 두고, 환경에 따라 구현체를 교체하는 방식을 사용합니다.
// collector/resolver/resolver.go
type ServiceResolver interface {
Resolve(ip string) string // IP → 서비스 이름
}
// 지금 구현: Docker API로 IP → 컨테이너 이름 조회
type DockerResolver struct { ... }
// 나중에 추가: k8s API로 Pod/Service 조회
type KubernetesResolver struct { ... }| 모드 | 대상 환경 | 이름 매핑 방식 |
|---|---|---|
| Docker Compose 모드 | 단일 호스트, 로컬 개발 환경 | Docker API (컨테이너 이름) |
| Kubernetes 모드 | 다중 노드, 프로덕션 환경 | k8s API (Pod/Service 이름) |
현재 수집 중인 기본 지표(RTT, 재전송) 외에 아래 지표들을 단계적으로 추가합니다.
Jitter (RTT 변동폭)
레이턴시 스파이크를 탐지하려면 "RTT가 높다"가 아니라 "RTT가 갑자기 튀었다"를 알아야 합니다. 현재 srtt_us는 커널이 EWMA로 평활화한 값이라 500ms 스파이크가 발생해도 부드럽게 반영됩니다. 실제 스파이크를 잡으려면 mdev_us(RTT Mean Deviation)가 필요합니다.
e->jitter_us = skops->mdev_us >> 3; // mdev도 고정소수점연결 종료 상태 (RST / FIN / Timeout)
연결이 어떻게 끝났는지 파악하면 장애 원인을 분류할 수 있습니다.
| 상태 | 의미 |
|---|---|
| 정상 FIN | 클라이언트/서버가 정상 종료 |
| RST 수신 | 상대방이 연결을 강제 끊음 (서버 에러, 방화벽 차단) |
| Timeout | 응답 없음 (서비스 다운, 네트워크 단절) |
BPF_SOCK_OPS_STATE_CB를 추가하면 "이 서비스 연결이 RST로 끊기는 비율이 15%" 같은 진단이 가능합니다.
Congestion Window (cwnd)
네트워크가 느린 원인이 실제 네트워크 지연인지, TCP 혼잡 제어가 속도를 줄인 것인지 구분하는 데 사용합니다.
- cwnd가 급감 → 패킷 유실 → TCP가 속도를 줄인 것
- cwnd는 정상인데 RTT만 높음 → 순수 네트워크 지연
바이트 카운터 (Throughput)
연결별로 얼마나 데이터를 주고받았는지 파악해 "이 연결이 중요한 연결인지" 판단하는 데 사용합니다. 연결 종료 시점(STATE_CB)에 한 번만 기록하므로 오버헤드가 거의 없습니다.
skops->bytes_sent
skops->bytes_received| 지표 | 접근 방법 | 이유 |
|---|---|---|
| SYN Retransmission | BPF_SOCK_OPS_TCP_CONNECT_CB 시점 추적 |
연결 자체가 안 되는 경우에만 유의미 |
| Receive Window | skops->rcv_wnd |
cwnd보다 원인 분석 기여도 낮음 |
| Delivery Rate | skops->rate_delivered |
Phase 3 상세 분석 시 추가 |
// tcp_trace_common.h
#define EVENT_TYPE_CONNECT 1
#define EVENT_TYPE_RTT 2
#define EVENT_TYPE_RETRANSMIT 3
#define EVENT_TYPE_CLOSE 4
struct event {
__u8 type;
__u32 pid; // local_port (소켓 식별용)
__u32 daddr;
__u16 dport;
char comm[16];
// 기존
__u64 latency_us; // srtt_us >> 3
// Phase 2 추가
__u64 timestamp_ns; // bpf_ktime_get_ns()
__u64 jitter_us; // mdev_us >> 3
__u32 snd_cwnd; // congestion window
__u32 total_retrans; // 누적 재전송 횟수
// CLOSE 이벤트용
__u32 old_state; // 이전 TCP 상태
__u64 bytes_sent;
__u64 bytes_received;
};