시스템 모니터링과 디버깅
strace·proc·dmesg·journalctl·코어 덤프·perf로 시스템 콜과 자원 사용을 추적해 장애 원인을 좁힙니다.
프로그램이 이상하게 동작한다고 할 때, 로그만으로 원인을 찾지 못한다면 OS 수준의 디버깅 도구가 필요합니다.
프로세스가 어떤 시스템 콜을 호출하는지, 커널이 무슨 메시지를 출력하는지, 메모리와 CPU를 어떻게 사용하는지를 추적하면 대부분의 문제를 진단할 수 있습니다.
strace: 시스템 콜 추적
strace는 추적 대상이 커널에 요청하는 시스템 콜과 시그널을 보여줍니다. 권한과 필터의 영향을 받으며 새 자식·스레드까지 추적하려면 -f 같은 옵션을 사용합니다.
"프로그램이 내부에서 무엇을 하고 있는지"를 들여다보는 도구입니다.
# 프로그램의 모든 시스템 콜 추적
strace ./myprogram
# 실행 중인 프로세스에 붙이기
strace -p 1234
# 파일 관련 시스템 콜만 추적
strace -e trace=file ./myprogram
# 네트워크 관련 시스템 콜만 추적
strace -e trace=network ./myprogram
# 시스템 콜별 커널 CPU 시간 통계 (기본값)
strace -c ./myprogram
# 타임스탬프와 함께 출력
strace -t ./myprogram
# 멀티스레드 프로그램 추적 (모든 스레드)
strace -f ./myprogram프로그램이 특정 파일을 찾지 못해 실패한다면, strace -e trace=file로 어떤 경로를 탐색하는지 확인할 수 있습니다.
strace 출력 읽기
# open("/etc/config.json", O_RDONLY) = -1 ENOENT (No such file or directory)
# → 파일이 존재하지 않음. 경로 확인 필요
# connect(3, {sa_family=AF_INET, sin_port=htons(3306), sin_addr=inet_addr("10.0.0.5")}, 16) = -1 ETIMEDOUT
# → 해당 주소·포트 연결 타임아웃. 네트워크·방화벽·서버 상태 등을 추가 확인
# write(1, "Hello\n", 6) = 6
# → stdout에 6바이트 성공적으로 기록
# mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f...
# → 익명 메모리 매핑 (malloc 구현에서도 사용할 수 있음)strace -c는 기본적으로 시스템 콜의 커널 CPU 시간·호출 수·오류 수를 요약합니다. 다음은 출력 형식을 설명하기 위한 예시이며 이 교재에서 실행한 측정 결과가 아닙니다.
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- --------
65.21 0.123456 12 10238 read
20.15 0.038123 8 4762 write
8.33 0.015789 157 100 10 open
...위 65%는 집계한 시스템 콜 시간 중 read의 비중입니다. 애플리케이션 전체 실행 시간이나 I/O 대기 비율이 아니므로 이것만으로 I/O 병목을 확정하지 않습니다. 경과 시간 요약이 필요하면 지원하는 버전에서 -w를 함께 사용하고 추적 오버헤드도 고려합니다.
/proc 파일 시스템
/proc는 커널이 제공하는 가상 파일 시스템입니다.
실제 파일이 아니라 커널이 실시간으로 생성하는 정보입니다.
# CPU 정보 (노출된 논리 CPU 수, 모델)
cat /proc/cpuinfo | grep "model name" | head -1
cat /proc/cpuinfo | grep processor | wc -l # 노출된 논리 CPU 항목 수
# 메모리 정보
cat /proc/meminfo | head -10
# 시스템 부하 (1분, 5분, 15분 평균)
cat /proc/loadavg
# 0.75 0.62 0.58 2/456 12345
# → 실행 가능 태스크와 중단 불가 대기의 평균 수. CPU 사용률과 다름
# 커널 버전
cat /proc/version
# 부팅 후 경과 시간
cat /proc/uptime프로세스별 /proc 정보
PID=1234
# 프로세스 상태 요약
cat /proc/$PID/status
# Name, State, Pid, PPid, Threads, VmSize, VmRSS 등
# 메모리 맵 (코드, 힙, 스택, 라이브러리 주소)
cat /proc/$PID/maps | head -20
# 7f1234560000-7f1234580000 r-xp ... /lib/libc.so.6
# → r-xp = 읽기/실행 가능, 쓰기 불가
# 열린 파일 디스크립터 목록
ls -la /proc/$PID/fd/
# 0 → stdin, 1 → stdout, 2 → stderr
# 3 → socket:[12345] (네트워크 연결)
# 4 → /var/log/app.log (로그 파일)
# 실행 명령어
cat /proc/$PID/cmdline | tr '\0' ' '
# 환경 변수
cat /proc/$PID/environ | tr '\0' '\n'
# I/O 통계
cat /proc/$PID/io
# read_bytes, write_bytes → 실제 디스크 I/O/proc/[pid]/maps는 프로세스의 메모리 레이아웃을 보여줍니다.
코드 세그먼트, 힙, 스택, 공유 라이브러리의 가상 주소 범위와 권한을 확인할 수 있습니다.
import os
def process_info(pid):
"""특정 프로세스의 상태를 /proc에서 읽기"""
proc_dir = f"/proc/{pid}"
if not os.path.exists(proc_dir):
print(f"PID {pid} 프로세스가 존재하지 않습니다.")
return
# 상태 정보 파싱
with open(f"{proc_dir}/status") as f:
status = {}
for line in f:
parts = line.strip().split(":\t")
if len(parts) == 2:
status[parts[0]] = parts[1].strip()
print(f"이름: {status.get('Name', 'N/A')}")
print(f"상태: {status.get('State', 'N/A')}")
print(f"스레드 수: {status.get('Threads', 'N/A')}")
print(f"가상 메모리: {status.get('VmSize', 'N/A')}")
print(f"물리 메모리: {status.get('VmRSS', 'N/A')}")
# fd 수 확인
fd_dir = f"{proc_dir}/fd"
try:
fd_count = len(os.listdir(fd_dir))
print(f"열린 fd 수: {fd_count}")
except PermissionError:
print("fd 확인 권한 없음 (소유자·접근 정책 확인)")
process_info(1)이 코드는 /proc의 여러 파일을 순서대로 읽으므로 하나의 원자적 스냅샷이 아닙니다. 확인 도중 프로세스가 종료되거나 PID가 재사용될 수 있으며, 예제는 이러한 경합과 모든 읽기 오류를 처리하지 않습니다.
dmesg와 시스템 로그
dmesg는 커널이 출력하는 메시지를 보여줍니다.
하드웨어 오류, 드라이버 문제, OOM Killer 동작 등 커널 수준의 이벤트가 기록됩니다.
# 커널 메시지 (에러와 경고만)
dmesg --level=err,warn | tail -20
# OOM Killer가 동작했는지 확인
dmesg | grep -i "oom"
# → "Out of memory: Kill process 1234 (java) score 950 or sacrifice child"
# → 종료 대상 선정에는 점수뿐 아니라 OOM 범위와 제외 조건 등이 적용됨
# 디스크 에러 확인
dmesg | grep -iE "error|fail|I/O"
# 실시간 커널 메시지 모니터링
dmesg -w
# 사람이 읽기 쉬운 타임스탬프
dmesg -T | tail -20journalctl (systemd 로그)
# 최근 부팅 이후 로그
journalctl -b
# 특정 서비스의 로그
journalctl -u nginx.service --since "2 hours ago"
# 실시간 로그 추적
journalctl -f
# 에러 이상 심각도만
journalctl -p err
# 특정 시간 범위
journalctl --since "2024-01-15 10:00" --until "2024-01-15 11:00"
# 디스크 사용량 확인 및 정리
journalctl --disk-usage
journalctl --vacuum-size=500M # 보관된 저널을 정리. 활성 파일은 제외전통적 로그 파일
# 시스템 로그
/var/log/syslog # Ubuntu/Debian
/var/log/messages # CentOS/RHEL
# 인증 로그 (SSH 로그인 시도 등)
/var/log/auth.log # Ubuntu
/var/log/secure # CentOS
# 커널 로그
/var/log/kern.log
# 실패한 로그인 시도 확인 (보안)
grep "Failed password" /var/log/auth.log | tail -20
# 특정 IP의 SSH 로그인 시도 횟수
grep "Failed password" /var/log/auth.log | awk '{print $11}' | sort | uniq -c | sort -rn | head -10같은 시간대의 애플리케이션 로그, 서비스 재시작, 커널 이벤트를 대조합니다. 위 awk의 11번째 필드는 특정 SSH 로그 형식만 전제하므로 invalid user 등 메시지 형태가 달라지면 원본 줄에서 IP 위치를 다시 확인해야 합니다.
코어 덤프 분석
프로세스가 세그먼테이션 폴트로 비정상 종료되면 코어 덤프(Core Dump)가 생성될 수 있습니다.
코어 덤프는 프로세스가 죽는 순간의 메모리 스냅샷입니다.
# 코어 덤프 활성화
ulimit -c unlimited
# 코어 덤프 저장 경로 설정
echo "/var/crash/core.%e.%p.%t" > /proc/sys/kernel/core_pattern
# %e = 실행 파일 이름
# %p = PID
# %t = 타임스탬프# 코어 덤프 분석 (gdb 사용)
gdb ./myprogram /var/crash/core.myprogram.1234.1705312800
# gdb 프롬프트에서:
(gdb) bt # 콜 스택 (backtrace) 확인
#0 0x00007f... in strlen () from /lib/libc.so.6
#1 0x00401234 in process_request (req=0x0) at server.c:142
#2 0x004015ab in handle_client (fd=5) at server.c:89
#3 0x00401800 in main () at server.c:42
(gdb) frame 1 # 문제 프레임으로 이동
(gdb) list # 소스 코드 확인
(gdb) print req # 변수 값 확인 → 0x0 (NULL!)
(gdb) info locals # 모든 로컬 변수 출력위 예시의 req=0x0은 NULL 사용을 의심할 단서입니다. 실제로 실패한 명령, strlen에 전달한 값, 소스와 디버그 정보까지 대조해 원인을 확인합니다. 코어 덤프는 크기 제한·저장 경로 권한·core_pattern 처리 방식에 따라 생성되지 않을 수도 있습니다.
운영 환경에서는 코어 덤프 크기를 제한하거나 별도의 디렉토리에 저장하도록 설정합니다.
코어 덤프에 민감한 데이터(비밀번호, 토큰)가 포함될 수 있으므로 접근 권한 관리에도 주의합니다.
perf: 성능 프로파일링
perf는 리눅스 커널의 성능 분석 도구입니다.
선택한 하드웨어·소프트웨어 이벤트를 세거나 샘플링해 비용이 집중되는 구간을 찾습니다.
# CPU 프로파일링 (30초간 샘플링)
perf record -g -p 1234 -- sleep 30
# 결과 확인
perf report
# 시스템 전체 통계 (5초간)
perf stat -a sleep 5
# → cache-misses, context-switches, page-faults 등
# 특정 이벤트 카운트
perf stat -e cache-misses,cache-references ./myprogram# perf report 출력 예시
45.23% myapp myapp [.] hash_lookup
22.11% myapp myapp [.] parse_json
15.67% myapp libc.so.6 [.] malloc
8.44% myapp myapp [.] validate_input예시의 hash_lookup은 조사할 후보입니다. 어떤 이벤트를 기록했고 자체 비용인지 하위 호출을 포함한 비용인지 확인한 뒤 최적화를 검토합니다.
# 플레임 그래프 생성 (시각화)
perf record -g -p 1234 -- sleep 30
perf script | stackcollapse-perf.pl | flamegraph.pl > flamegraph.svg위 변환에는 별도로 설치한 FlameGraph의 스크립트가 필요합니다. 플레임 그래프는 수집한 스택을 합쳐 보여주며, 가로축은 시간 순서가 아닙니다.
같은 퍼센트라도 도구와 기록 방식에 따라 뜻이 다릅니다.
| 출력 | 집계하는 대상 | 여기서 바로 알 수 없는 것 |
|---|---|---|
| strace -c | 추적한 시스템 콜의 커널 CPU 시간 비중 | 전체 실행 시간, 대기 시간, I/O 병목 확정 |
| perf report | 선택한 이벤트의 샘플 비용 분포 | 모든 이벤트에 공통인 CPU·벽시계 시간 비율 |
| CPU 플레임 그래프 | 샘플에 나타난 스택의 누적 폭 | 실행 순서나 각 호출의 개별 소요 시간 |
- strace -c
- 집계하는 대상: 추적한 시스템 콜의 커널 CPU 시간 비중여기서 바로 알 수 없는 것: 전체 실행 시간, 대기 시간, I/O 병목 확정
- perf report
- 집계하는 대상: 선택한 이벤트의 샘플 비용 분포여기서 바로 알 수 없는 것: 모든 이벤트에 공통인 CPU·벽시계 시간 비율
- CPU 플레임 그래프
- 집계하는 대상: 샘플에 나타난 스택의 누적 폭여기서 바로 알 수 없는 것: 실행 순서나 각 호출의 개별 소요 시간
프로세스·기간·이벤트를 맞춰 비교합니다. /proc, 로그와 샘플은 서로 보완하는 증거이며 한 지표만으로 병목을 확정하지 않습니다.
다음 절에서는 커널 파라미터 튜닝과 OS 관련 기술 면접 주제를 정리하겠습니다.