리눅스 트러블슈팅 — top, strace, lsof로 서버 문제를 진단하는 방법
"서버가 갑자기 느려졌는데, 어디서부터 뭘 봐야 할까?"
서버 장애가 발생하면 당황하기 쉽습니다. 공부하다 보니 결국 진단 도구를 체계적으로 알고 있느냐가 문제 해결 속도를 좌우하더라고요. 기본 도구(top, strace, lsof)부터 최신 eBPF 기반 트레이싱까지 정리해봤습니다.
진단의 흐름 — USE 방법론
Brendan Gregg가 제안한 USE 방법론은 트러블슈팅의 체계적 프레임워크입니다.
- Utilization: 자원이 얼마나 사용되고 있는지
- Saturation: 자원이 포화되어 대기가 발생하는지
- Errors: 에러가 발생하고 있는지
모든 자원(CPU, 메모리, 디스크, 네트워크)에 대해 이 세 가지를 확인하면 대부분의 문제를 좁혀갈 수 있습니다.
top / htop — CPU와 메모리 전체 현황
서버 문제가 생겼을 때 가장 먼저 실행하는 명령입니다.
top 핵심 읽기
$ top
top - 14:23:01 up 45 days, 3:12, 2 users, load average: 8.52, 4.21, 2.10
Tasks: 231 total, 3 running, 228 sleeping, 0 stopped, 0 zombie
%Cpu(s): 85.2 us, 12.3 sy, 0.0 ni, 2.1 id, 0.0 wa, 0.0 hi, 0.4 si
MiB Mem: 16384.0 total, 512.3 free, 14821.7 used, 1050.0 buff/cache
MiB Swap: 4096.0 total, 2048.0 free, 2048.0 used. 982.3 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
1234 app 20 0 8.2g 6.1g 12340 S 340.0 38.2 1234:56 java
5678 root 20 0 512m 128m 8420 S 45.0 0.8 567:89 python
주요 지표 해석
| 항목 | 의미 | 위험 신호 |
|---|---|---|
| load average | 1분/5분/15분 평균 실행 대기 프로세스 수 | CPU 코어 수보다 높으면 포화 |
| us (user) | 유저 공간 CPU 사용률 | 높으면 애플리케이션 로직이 원인 |
| sy (system) | 커널 공간 CPU 사용률 | 높으면 시스템 콜/IO가 원인 |
| wa (iowait) | IO 대기 비율 | 높으면 디스크 병목 |
| RES | 실제 물리 메모리 사용량 | 총 메모리에 근접하면 OOM 위험 |
htop의 장점
$ htop
# CPU 코어별 사용률을 바 그래프로 표시
# 트리 뷰로 부모-자식 프로세스 관계 확인
# 프로세스 필터링, 정렬이 인터랙티브
F5로 트리 뷰 전환 — 어떤 프로세스가 자식을 많이 만들었는지 한눈에 파악F6으로 정렬 기준 변경 — MEM% 순으로 메모리 소비 상위 프로세스 확인
strace — 시스템 콜 추적
프로세스가 커널에 요청하는 모든 시스템 콜을 추적합니다. "이 프로세스가 대체 뭘 하고 있는 거야?"라는 질문에 답을 줍니다.
기본 사용법
# 실행 중인 프로세스에 붙이기
$ strace -p <PID>
read(3, "HTTP/1.1 200 OK\r\n"..., 8192) = 345
write(4, "response data"..., 128) = 128
epoll_wait(5, [{EPOLLIN, {u32=3}}], 128, 5000) = 1
# 특정 시스템 콜만 필터링
$ strace -e trace=open,read,write -p <PID>
# 시스템 콜별 통계 (어디서 시간을 쓰는지)
$ strace -c -p <PID>
% time seconds usecs/call calls syscall
------ ----------- ----------- --------- ---------
65.21 0.043210 43 1005 futex
20.12 0.013330 13 1024 read
10.45 0.006920 7 1024 write
자주 쓰는 옵션
# 타임스탬프 포함 (-t: 초, -tt: 마이크로초)
$ strace -tt -p <PID>
14:23:01.123456 read(3, ...) = 128
# 문자열 출력 길이 늘리기 (기본 32바이트)
$ strace -s 1024 -p <PID>
# 자식 프로세스까지 추적
$ strace -f -p <PID>
프로덕션 주의사항
strace는 ptrace 시스템 콜 기반입니다. 모든 시스템 콜에서 대상 프로세스를 멈추고 정보를 추출하므로:
- 대상 프로세스 성능이 2~10배 느려짐
- 프로덕션에서는 짧은 시간만 사용하거나, 이후 설명할 eBPF 도구를 사용하는 것이 권장됩니다
lsof — 파일과 포트 점유 확인
"List Open Files" — 프로세스가 열고 있는 파일, 소켓, 파이프 등을 보여줍니다. 리눅스에서는 모든 것이 파일이므로 활용 범위가 넓습니다.
포트 점유 확인 (가장 자주 쓰는 용도)
# 8080 포트를 누가 쓰고 있나?
$ lsof -i :8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 1234 app 45u IPv6 12345 0t0 TCP *:8080 (LISTEN)
# 특정 프로세스가 열고 있는 모든 파일
$ lsof -p 1234
# 특정 파일을 누가 열고 있나?
$ lsof /var/log/app.log
실전 활용
# 삭제된 파일인데 디스크 공간을 차지하는 경우 찾기
$ lsof +L1
# 프로세스가 파일을 열어둔 상태에서 파일을 삭제하면
# 디스크 공간이 해제되지 않음 → 프로세스 재시작 필요
# TCP 연결 상태별 확인
$ lsof -i -sTCP:ESTABLISHED
$ lsof -i -sTCP:CLOSE_WAIT # CLOSE_WAIT 누수 확인
netstat / ss — 네트워크 상태
ss (netstat의 현대적 대체)
# 리스닝 중인 TCP 포트와 프로세스
$ ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 128 *:8080 *:* users:(("java",pid=1234,fd=45))
# 연결 상태 통계
$ ss -s
Total: 1523
TCP: 892 (estab 456, closed 234, orphaned 12, timewait 190)
# TIME_WAIT 연결 확인
$ ss -t state time-wait
CLOSE_WAIT 누수 진단
# CLOSE_WAIT 상태가 계속 쌓이면 → 애플리케이션이 소켓을 close하지 않는 버그
$ ss -t state close-wait | wc -l
523 # 500개 이상이면 심각한 누수
dmesg — 커널 로그
커널이 남기는 메시지를 확인합니다. OOM, 하드웨어 에러, 파일시스템 문제 등이 기록됩니다.
# 최근 커널 로그
$ dmesg -T | tail -50
# OOM Killer 동작 확인
$ dmesg -T | grep -i oom
[Mon Mar 28 14:23:01 2026] Out of memory: Killed process 1234 (java)
total-vm:8388608kB, anon-rss:6291456kB
# 디스크 에러 확인
$ dmesg -T | grep -i error
OOM Killer가 동작했다면 어떤 프로세스가 왜 죽었는지, 그 시점의 메모리 상황을 알 수 있습니다.
실전 시나리오별 진단
시나리오 1: CPU 100%
# 1단계: 어떤 프로세스인지 확인
$ top -bn1 | head -20
# → java 프로세스가 CPU 340% (4코어 중 3.4코어 사용)
# 2단계: us가 높은지 sy가 높은지 확인
# us가 높으면 → 애플리케이션 로직 (무한루프, 잘못된 알고리즘)
# sy가 높으면 → 시스템 콜 과다 (과도한 IO, 락 경합)
# 3단계: Java라면 스레드 덤프
$ jstack <PID> > thread_dump.txt
# → RUNNABLE 상태의 스레드가 어떤 메서드를 실행 중인지 확인
# 4단계: sy가 높은 경우 strace로 확인
$ strace -c -p <PID>
# → futex 시스템 콜이 65%면 락 경합 문제
시나리오 2: OOM (Out of Memory)
# 1단계: OOM 발생 확인
$ dmesg -T | grep -i oom
# 2단계: 현재 메모리 상태
$ free -h
total used free shared buff/cache available
Mem: 16Gi 15Gi 200Mi 128Mi 800Mi 500Mi
# 3단계: 프로세스별 메모리 사용 (RSS 기준 상위)
$ ps aux --sort=-%mem | head -10
# 4단계: Java 힙 덤프 (Java 프로세스인 경우)
$ jmap -dump:format=b,file=heap.hprof <PID>
시나리오 3: 포트 충돌
# "Address already in use" 에러 발생 시
$ lsof -i :8080
# → 이미 해당 포트를 사용 중인 프로세스 확인
# 좀비 프로세스가 포트를 잡고 있는 경우
$ ss -tlnp | grep 8080
# → PID 확인 후 kill
시나리오 4: 디스크 풀
# 1단계: 디스크 사용량 확인
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 100G 99G 1G 99% /
# 2단계: 큰 디렉토리 찾기
$ du -sh /* 2>/dev/null | sort -rh | head -10
# 3단계: 삭제된 파일이 공간을 차지하는 경우
$ lsof +L1
# → 프로세스가 열어둔 삭제된 파일 확인, 프로세스 재시작으로 해결
eBPF — 프로덕션 트레이싱의 새로운 표준
strace의 성능 문제를 근본적으로 해결하는 기술입니다.
eBPF란
커널 내부에서 안전하게 실행되는 프로그램을 작성할 수 있는 프레임워크입니다. 커널 코드를 수정하지 않고도 시스템 콜, 네트워크, 파일시스템 등 거의 모든 커널 이벤트를 관찰할 수 있습니다.
strace vs eBPF 비교
| 항목 | strace | eBPF (bpftrace) |
|---|---|---|
| 메커니즘 | ptrace (프로세스 중단) | 커널 내부 JIT 실행 |
| ** 오버헤드** | 2~10배 성능 저하 | 거의 제로 |
| ** 프로덕션 사용** | 단시간만 권장 | 상시 사용 가능 |
| ** 기능 범위** | 시스템 콜만 | 시스템 콜 + 커널 함수 + 유저 프로브 |
bpftrace 예제
# 파일 open 시스템 콜 추적 (strace 대체)
$ bpftrace -e 'tracepoint:syscalls:sys_enter_openat {
printf("%s %s\n", comm, str(args->filename));
}'
java /var/log/app.log
nginx /etc/nginx/nginx.conf
# 프로세스별 시스템 콜 횟수 카운트
$ bpftrace -e 'tracepoint:raw_syscalls:sys_enter {
@[comm] = count();
}'
# Ctrl+C로 종료하면 결과 출력
@[java]: 125432
@[nginx]: 34521
BCC (BPF Compiler Collection)
자주 쓰는 eBPF 도구를 미리 만들어놓은 컬렉션입니다.
# TCP 연결 추적 (실시간)
$ tcpconnect
PID COMM SADDR DADDR DPORT
1234 java 10.0.0.1 10.0.0.2 3306
# 느린 파일 IO 추적
$ biosnoop
TIME(s) COMM PID DISK T SECTOR BYTES LAT(ms)
0.000 java 1234 sda R 123456 4096 15.23
# 함수 실행 시간 측정
$ funclatency /usr/lib/jvm/*/libjvm.so:JVM_GC
CO-RE와 최신 동향
eBPF의 초기 문제는 커널 버전마다 데이터 구조가 달라 호환성이 떨어지는 것이었습니다. CO-RE(Compile Once, Run Everywhere) 워크플로가 이 문제를 해결했습니다.
- BTF(BPF Type Format)를 통해 커널 구조체 정보를 런타임에 조회
- 한 번 컴파일한 eBPF 프로그램이 여러 커널 버전에서 동작
- 2024~2025년 기준 대부분의 주요 배포판에서 BTF 지원
eBPF 기반 프로파일링 도구
| 도구 | 용도 |
|---|---|
| Pixie | 코드 수정 없이 eBPF로 HTTP/gRPC/DB 쿼리 자동 프로파일링 |
| Parca | eBPF 기반 연속 프로파일링 (CPU 사용 핫스팟 분석) |
| Cilium | eBPF 기반 네트워크 관찰성 + 보안 정책 |
진단 도구 선택 가이드
서버가 느리다!
│
├─ CPU 문제? → top/htop → strace -c → perf/bpftrace
│
├─ 메모리 문제? → free -h → ps aux --sort=-%mem → dmesg (OOM 확인)
│
├─ 디스크 문제? → df -h → du -sh → lsof +L1 → iostat
│
├─ 네트워크 문제? → ss -s → ss -t state close-wait → tcpdump/tcpconnect
│
└─ 프로덕션 상시 관찰? → eBPF (bpftrace, BCC, Pixie)
정리
- top/htop: 가장 먼저 실행 — CPU/메모리 전체 현황 파악, us vs sy 비율이 원인 방향을 알려줌
- strace: "이 프로세스가 뭘 하는지" 시스템 콜 레벨로 추적 — 단, ptrace 오버헤드 때문에 프로덕션에서는 주의
- lsof: 포트 충돌, 파일 점유, 삭제된 파일 디스크 점유 확인
- ss/netstat: 네트워크 연결 상태, CLOSE_WAIT 누수 진단
- dmesg: OOM Killer 동작, 하드웨어 에러, 커널 레벨 문제 확인
- eBPF: 프로덕션 트레이싱의 새로운 표준 — 거의 제로 오버헤드, CO-RE로 범용성 확보
- Pixie, Parca 같은 eBPF 기반 도구가 코드 수정 없는 프로파일링을 가능하게 함
공부하다 보니 도구를 아는 것만큼 "어떤 상황에서 어떤 도구를 먼저 쓸지" 순서를 아는 것이 중요하더라고요. USE 방법론(Utilization → Saturation → Errors)을 기본 프레임으로 갖고 있으면 당황하지 않고 체계적으로 접근할 수 있습니다.