13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] strace

  • [Linux] strace 활용: 리눅스 애플리케이션 시스템 콜 추적 및 디버깅 심층 분석

    [Linux] strace 활용: 리눅스 애플리케이션 시스템 콜 추적 및 디버깅 심층 분석

    strace 활용: 리눅스 애플리케이션 시스템 콜 추적 및 디버깅 심층 분석

    서버에서 애플리케이션이 멈췄을 때, strace가 필요한 순간

    strace는 애플리케이션이 리눅스 커널에 보낸 요청과 그 결과를 그대로 보여주는 추적 도구입니다. 파일을 열었는지, 소켓 연결을 시도했는지, 권한 때문에 거절됐는지, 어떤 호출에서 기다리고 있는지를 애플리케이션 로그보다 한 단계 아래에서 확인합니다.

    제가 strace를 꺼내는 순간은 대체로 비슷합니다. 프로세스는 살아 있고 CPU도 튀지 않는데 응답이 없거나, 로그에는 failed 한 줄만 남았거나, 컨테이너 안에서는 파일이 있다고 믿었는데 실제 프로세스는 다른 경로를 보고 있을 때입니다. 이런 문제는 프레임워크 로그만 붙잡고 있으면 오래 돌아갑니다. 커널 입장에서 보면 대개 ENOENT, EACCES, ECONNREFUSED, ETIMEDOUT, futex 대기 같은 단서로 쪼개집니다.

    다만 strace는 만능 관찰기가 아닙니다. 시스템 콜 경계는 잘 보여주지만, 애플리케이션 내부 변수나 비즈니스 로직의 분기까지 알려주지는 않습니다. 그래서 저는 장애 대응 때 순서를 이렇게 잡습니다. 먼저 애플리케이션 로그와 메트릭으로 증상을 좁히고, 파일·권한·네트워크·프로세스 대기처럼 운영체제 경계가 의심될 때 strace를 붙입니다. 이 순서를 지키면 출력의 바다에서 헤매는 시간이 확 줄어듭니다.

    strace가 애플리케이션과 리눅스 커널 사이의 시스템 콜을 추적하는 개요

    strace는 사용자 공간(User Space)의 애플리케이션과 커널(Kernel) 사이에서 오가는 시스템 콜 흐름을 추적합니다. 그래서 “내 코드가 뭘 하려고 했는가”보다 “커널에 실제로 어떤 요청이 도착했는가”를 확인하는 데 강합니다.

    strace 개념: 시스템 콜을 로그처럼 읽는 법

    리눅스 애플리케이션은 파일, 네트워크, 프로세스, 시간, 메모리 같은 자원을 직접 만지지 않습니다. openat(), read(), write(), connect(), statx(), clone(), execve(), futex() 같은 시스템 콜을 통해 커널에 요청합니다. strace는 그 요청의 인자와 반환값을 보여줍니다.

    출력 한 줄은 보통 아래처럼 읽습니다.

    openat(AT_FDCWD, "/etc/myapp/config.yml", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

    왼쪽은 호출 이름과 인자, 오른쪽은 반환값입니다. = -1은 실패, ENOENT는 파일이 없다는 뜻입니다. 이 한 줄만으로도 “설정 로딩 실패”가 코드 문제인지, 배포 경로 문제인지, 마운트 문제인지 조사 방향이 달라집니다.

    증상 먼저 볼 시스템 콜 해석 기준 다음 액션
    설정 파일을 못 읽음 openat, newfstatat, access ENOENT면 경로·마운트, EACCES면 권한·보안 정책 pwdx PID, systemd WorkingDirectory, 컨테이너 볼륨 확인
    외부 API 연결 실패 socket, connect, getsockopt ECONNREFUSED는 대상 포트 거부, ETIMEDOUT은 경로·방화벽 가능성 ss -tnp, 라우팅, 보안 그룹, 프록시 설정 확인
    프로세스가 멈춘 듯 보임 read, poll, epoll_wait, futex I/O 대기인지 이벤트 대기인지 락 대기인지 분리 top -H, 스레드 덤프, FD 상태 같이 확인
    자식 프로세스에서만 실패 clone, fork, execve 부모만 추적하면 핵심 흐름이 안 보일 수 있음 -f 또는 -ff로 PID별 로그 분리
    라이브러리 로딩 실패 openat, mmap, execve .so 탐색 경로가 예상과 다른지 확인 LD_LIBRARY_PATH, ldconfig -p, 컨테이너 이미지 확인

    실전 구현: 기본 명령어보다 필터링이 먼저입니다

    설치는 간단합니다. 운영 서버에 새 패키지를 설치해야 한다면 변경 절차를 따라야 하지만, 대부분의 배포판에서는 표준 패키지로 제공합니다.

    # Debian/Ubuntu 계열
    sudo apt update
    sudo apt install -y strace
    
    # RHEL/CentOS/Fedora 계열
    sudo dnf install -y strace
    
    # 설치 확인
    strace -V

    가장 단순한 실행은 명령 앞에 strace를 붙이는 방식입니다.

    strace ls /tmp

    하지만 실무에서는 이렇게 전체를 보는 일이 많지 않습니다. 출력이 너무 많고, 동적 라이브러리 로딩이나 로케일 파일 접근처럼 지금 문제와 무관한 줄이 섞입니다. 처음부터 범위를 좁히는 편이 낫습니다.

    # 파일 관련 시스템 콜만 추적
    strace -e trace=file ls /etc/nginx
    
    # 네트워크 관련 시스템 콜만 추적
    strace -e trace=network curl -I https://example.com
    
    # 프로세스 실행 흐름 확인
    strace -e trace=process bash -lc 'echo hello'
    
    # 시간 정보와 각 호출 소요 시간 표시
    strace -tt -T -e trace=file ls /etc
    
    # 실패한 시스템 콜만 보고 싶을 때
    strace -e trace=file -e status=failed ls /does-not-exist

    -e trace=file은 파일 관련 호출 그룹만 표시합니다. -e trace=network는 소켓과 연결 흐름을 좁혀 보여줍니다. -tt는 시각을 마이크로초 단위까지 자세히 표시하고, -T는 각 시스템 콜에 걸린 시간을 꺾쇠괄호로 붙입니다. -e status=failed는 실패한 호출만 추려서 볼 때 유용합니다. strace 버전이나 배포판에 따라 지원 옵션이 다를 수 있으니, 현장 서버에서는 strace -h로 한 번 확인하는 습관이 좋습니다.

    strace 명령으로 파일 관련 시스템 콜을 필터링하는 Linux 디버깅 화면

    운영 환경에서는 전체 추적보다 trace=file, trace=network, status=failed처럼 질문을 좁히는 방식이 훨씬 빠릅니다.

    이미 실행 중인 프로세스에 붙어서 Linux 디버깅하기

    실제 장애에서는 새 명령을 실행하는 것보다 이미 떠 있는 프로세스를 봐야 할 때가 많습니다. 이때는 -p로 PID에 붙습니다.

    # PID 확인
    pgrep -af 'nginx|gunicorn|java|node'
    
    # 실행 중인 프로세스에 연결
    sudo strace -p 12345
    
    # 자식 프로세스까지 따라가며 파일에 저장
    sudo strace -f -tt -T -s 256 -o /tmp/app.strace.log -p 12345
    
    # PID별로 로그 파일을 나누고 싶을 때
    sudo strace -ff -tt -T -s 256 -o /tmp/app.strace -p 12345

    -f는 fork, clone, vfork로 생기는 자식 프로세스까지 추적합니다. 웹 서버, 워커, 큐 컨슈머, CGI 계열처럼 실행 흐름이 자식 프로세스로 넘어가는 구조에서는 거의 필수입니다. -ff는 PID별로 로그를 분리합니다. 한 파일에 모든 프로세스 로그가 섞이면 시간순으로 따라가기는 쉽지만, 특정 워커만 분석할 때는 분리 로그가 더 편합니다.

    -s 256은 문자열 출력 길이를 늘립니다. 기본 출력 길이로는 긴 파일 경로나 HTTP 헤더 일부가 잘려서 원인을 놓칠 수 있습니다. 분석용이면 -s 256 또는 -s 1024 정도로 늘리고, 민감정보가 섞일 수 있는 환경에서는 저장 위치와 공유 범위를 조심해야 합니다. strace 로그에는 파일 경로, 환경 변수 일부, 소켓 주소, 토큰처럼 보안상 민감한 값이 드러날 수 있습니다.

    운영 서버에 붙일 때는 짧게, 좁게, 파일로 남기는 쪽을 권합니다. strace는 ptrace 기반으로 대상 프로세스를 관찰하므로 오버헤드가 생길 수 있습니다. 특히 초당 시스템 콜이 많은 프로세스에 전체 추적을 오래 걸면 지연이 커질 수 있습니다. 수치를 단정할 수는 없지만, 장애 중인 서비스에 무심코 전체 추적을 오래 붙이는 건 피하는 편이 안전합니다.

    재현 가능한 시나리오 1: 설정 파일을 못 찾는 애플리케이션 분석

    먼저 가장 흔한 파일 경로 문제를 작게 재현해보겠습니다. 일부러 없는 설정 파일을 열고, strace에서 실제 접근 경로와 에러 코드를 확인합니다.

    # app.py
    from pathlib import Path
    
    config_path = Path("/etc/myapp/config.yml")
    print(config_path.read_text())
    python3 app.py
    
    # 파일 관련 시스템 콜만 추적
    strace -e trace=file -s 256 python3 app.py

    출력에서 이런 줄을 찾습니다.

    openat(AT_FDCWD, "/etc/myapp/config.yml", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
    • openat: 파일을 열려고 했습니다.
    • "/etc/myapp/config.yml": 애플리케이션이 실제로 접근한 경로입니다.
    • O_RDONLY: 읽기 전용으로 열려고 했습니다.
    • -1 ENOENT: 호출이 실패했고, 커널은 파일이 없다고 답했습니다.

    여기서 중요한 건 “설정 파일이 없다”가 아니라 “해당 프로세스의 파일 시스템 네임스페이스에서 그 경로가 없다”입니다. 호스트에는 파일이 있어도 컨테이너 안에는 없을 수 있고, systemd 서비스의 WorkingDirectory가 달라 상대 경로가 다르게 해석될 수 있습니다. Kubernetes라면 ConfigMap/Secret 마운트 경로와 컨테이너 이미지를 같이 봐야 합니다.

    EACCES라면 방향이 바뀝니다. 파일 존재 여부보다 소유자, 그룹, 모드, 디렉터리 실행 권한, SELinux/AppArmor 정책을 봐야 합니다. 디렉터리 중간 경로에 실행 권한이 없어도 파일 접근은 실패합니다.

    # 파일과 상위 디렉터리 권한을 함께 확인
    namei -l /etc/myapp/config.yml
    
    # systemd 서비스의 작업 디렉터리와 실행 사용자 확인
    systemctl cat myapp.service
    systemctl show myapp.service -p User -p Group -p WorkingDirectory

    재현 가능한 시나리오 2: 연결 거부와 타임아웃을 구분하기

    네트워크 장애에서 strace가 빛나는 지점은 connect()의 반환값입니다. “안 붙는다”는 말은 너무 넓습니다. 대상이 즉시 거부하는지, 네트워크 경로에서 시간이 빠지는지, DNS 이전 단계인지에 따라 담당 영역이 달라집니다.

    # 로컬에서 열려 있지 않은 포트에 연결 시도
    strace -tt -T -e trace=network curl -v --connect-timeout 3 http://127.0.0.1:9/
    
    # DNS 해석까지 포함해 파일/네트워크 흐름을 함께 확인
    strace -tt -T -e trace=file,network -s 256 curl -v --connect-timeout 3 https://example.com/

    ECONNREFUSED는 대상 호스트까지 도달했지만 해당 포트가 거부했다는 쪽에 가깝습니다. 서비스가 안 떠 있거나, 다른 포트에 떠 있거나, 로컬 방화벽이 즉시 거부하는 식입니다. 반대로 ETIMEDOUT은 응답이 돌아오지 않는 흐름이라 라우팅, 보안 그룹, 방화벽 드롭, 네트워크 ACL을 의심합니다. 둘을 구분하지 않고 “네트워크 문제”라고 뭉개면 담당자도, 조사 순서도 흐려집니다.

    strace 단서 가능성이 큰 원인 바로 이어서 볼 명령
    connect(...) = -1 ECONNREFUSED 대상 포트에 리스닝 서비스 없음, 즉시 거부 정책 ss -ltnp, 대상 서비스 상태, 포트 설정
    connect(...) = -1 ETIMEDOUT 패킷 드롭, 라우팅 문제, 보안 그룹/방화벽 ip route, 방화벽 정책, 클라우드 네트워크 ACL
    openat(... resolv.conf ...) 이후 지연 DNS 설정 또는 네임서버 응답 문제 resolvectl status, dig, /etc/resolv.conf
    EACCES 또는 EPERM 보안 정책, 권한, 샌드박스 제한 SELinux/AppArmor, 컨테이너 capability, seccomp 프로파일

    strace 옵션 비교: 장애 유형별 조합을 외우는 편이 낫습니다

    옵션을 백과사전처럼 외울 필요는 없습니다. 장애 유형별로 손에 익는 조합을 만들어두면 됩니다. 저는 아래 표를 기준으로 시작하고, 필요할 때만 넓힙니다.

    목적 추천 명령 장점 주의할 점
    실행 중 서비스가 멈춘 위치 확인 sudo strace -p PID 즉시 현재 대기 호출 확인 짧게 붙이고 필요하면 필터 추가
    파일·권한 문제 추적 sudo strace -f -e trace=file -s 256 -o /tmp/file.log -p PID 경로, 권한, 라이브러리 탐색 확인 민감한 파일 경로가 로그에 남을 수 있음
    네트워크 연결 실패 분석 strace -tt -T -e trace=network curl -v URL 연결 거부와 타임아웃 구분 DNS까지 보려면 trace=file,network가 더 유용할 수 있음
    자식 프로세스 포함 추적 sudo strace -ff -tt -T -s 256 -o /tmp/app.strace -p PID 워커별 로그 분리 로그 파일이 여러 개 생기므로 정리 필요
    호출 빈도 요약 strace -c COMMAND 어떤 시스템 콜이 많은지 빠르게 파악 개별 실패 경로는 보이지 않음
    반환값 중심 필터링 strace -e status=failed -e trace=file COMMAND 실패 호출만 빠르게 확인 성공했지만 느린 호출은 놓칠 수 있음
    strace 옵션별 Linux 디버깅 선택 흐름 요약

    옵션 선택의 핵심은 “무엇이 궁금한가”입니다. 파일이 궁금하면 trace=file, 네트워크면 trace=network, 자식 프로세스가 의심되면 -f, 흐름 공유가 필요하면 -o부터 붙이면 됩니다.

    주의사항과 트러블슈팅: 실제 현장에서 자주 밟는 함정

    권한 문제: Operation not permitted

    다른 사용자의 프로세스에 붙을 때 Operation not permitted가 나올 수 있습니다. 우선 root 권한으로 실행합니다.

    sudo strace -p 12345

    그래도 막힌다면 ptrace 제한이나 컨테이너 보안 정책을 봐야 합니다. Ubuntu 계열에서는 /proc/sys/kernel/yama/ptrace_scope가 관련될 수 있습니다.

    cat /proc/sys/kernel/yama/ptrace_scope

    이 값을 낮추면 붙을 수 있는 범위가 넓어질 수 있지만, 보안 정책을 약하게 만드는 결정입니다. 운영 서버에서 임의로 바꾸기보다 승인된 디버그 절차, 동일 사용자 실행, 재현 환경, 디버그 컨테이너를 먼저 검토하는 편이 맞습니다.

    로그가 너무 많아서 못 읽겠는 문제

    전체 추적을 파일로 남기면 몇 초 만에도 읽기 어려운 양이 될 수 있습니다. 먼저 실패 호출만 보거나, 파일과 네트워크처럼 관심 범위를 좁힙니다.

    # 파일 문제만 본다
    sudo strace -f -e trace=file -e status=failed -s 256 -o /tmp/file.failed.log -p 12345
    
    # 네트워크 문제만 본다
    sudo strace -f -tt -T -e trace=network -s 256 -o /tmp/network.log -p 12345
    
    # 요약 통계만 본다
    strace -c curl -I https://example.com

    futex가 많이 보이면 무조건 문제일까?

    futex는 멀티스레드 애플리케이션에서 흔합니다. 많이 보인다는 사실만으로 장애라고 판단하면 안 됩니다. 중요한 건 맥락입니다. 요청 처리가 멈춘 상태에서 특정 스레드가 계속 futex 대기에 머물고, CPU 사용률은 낮고, 처리량이 떨어졌다면 락 경합이나 데드락 가능성을 봅니다. 이때 strace만으로 결론을 내리지 말고 스레드 단위 관찰을 같이 해야 합니다.

    # 스레드별 CPU/상태 확인
    top -H -p 12345
    
    # 프로세스의 스레드 목록 확인
    ps -L -p 12345 -o pid,tid,stat,comm
    
    # Java라면 스레드 덤프와 함께 비교
    jstack 12345 > /tmp/jstack.12345.txt

    컨테이너에서는 strace가 안 붙는 경우

    컨테이너 안에서 strace를 쓰려면 패키지가 없거나, ptrace 권한이 막혀 있거나, seccomp 프로파일 때문에 제한될 수 있습니다. 운영 정책이 허용한다면 디버그 컨테이너나 임시 권한 부여를 사용합니다.

    # Docker에서 재현 환경을 만들 때의 예시
    # 운영에 그대로 적용하기 전에 보안 정책을 반드시 확인하세요.
    docker run --rm -it --cap-add SYS_PTRACE --security-opt seccomp=unconfined ubuntu:latest bash

    Kubernetes에서는 노드 접근, ephemeral container, 보안 컨텍스트, 배포 조직의 운영 기준을 같이 봐야 합니다. 여기서 중요한 판단은 “운영 파드에 도구를 설치할지”가 아니라 “동일 증상을 낮은 위험으로 관찰할 방법이 있는지”입니다.

    검증과 결과 해석: 에러 코드, 반복, 대기 시간을 분리해서 봅니다

    strace 로그를 받을 때 저는 세 갈래로 읽습니다. 첫째, 실패 코드를 봅니다. 둘째, 같은 호출이 반복되는지 봅니다. 셋째, -T 기준으로 특정 호출이 오래 걸리는지 봅니다.

    1. 실패 코드: ENOENT, EACCES, EPERM, ECONNREFUSED, ETIMEDOUT, EROFS 같은 반환값을 우선 확인합니다.
    2. 반복 패턴: 같은 경로, 같은 포트, 같은 FD에 대한 호출이 짧은 간격으로 반복되는지 봅니다.
    3. 대기 지점: -T 출력에서 connect, read, poll, epoll_wait, futex 뒤에 시간이 길게 붙는지 봅니다.

    파일 문제는 openat()의 경로와 반환값이 거의 출발점입니다. ENOENT면 경로, 마운트, 작업 디렉터리, 배포 산출물을 봅니다. EACCES면 권한, 상위 디렉터리 실행 권한, SELinux/AppArmor, 컨테이너 사용자 UID를 봅니다. EROFS가 보이면 읽기 전용 파일 시스템이나 컨테이너 마운트 옵션 쪽입니다.

    네트워크 문제는 connect() 반환값으로 먼저 나눕니다. ECONNREFUSED는 상대가 거부한 상황에 가깝고, ETIMEDOUT은 응답이 돌아오지 않는 상황에 가깝습니다. DNS 문제는 connect() 이전의 /etc/resolv.conf, /etc/hosts, NSS 관련 파일 접근 흐름에서 힌트가 나올 수 있습니다.

    # strace 로그에서 자주 보는 실패만 빠르게 훑기
    grep -E 'ENOENT|EACCES|EPERM|ECONNREFUSED|ETIMEDOUT|EROFS' /tmp/app.strace.log | head -100
    
    # 특정 설정 파일 접근 여부 확인
    grep '/etc/myapp/config.yml' /tmp/app.strace.log
    
    # 오래 걸린 호출 후보를 눈으로 보기 쉽게 추리기
    grep -E '<[0-9]+\.[0-9]+>' /tmp/app.strace.log | head -50
    strace 로그에서 시스템 콜 오류를 분류해 분석하는 화면

    해석은 감이 아니라 분류입니다. 에러 코드로 범주를 나누고, 반복 패턴으로 재현성을 보고, 대기 시간으로 병목 후보를 좁히면 strace 로그가 훨씬 덜 거칠게 느껴집니다.

    언제 strace를 쓰고, 언제 다른 도구를 먼저 써야 할까

    strace는 강력하지만 모든 문제의 첫 번째 도구는 아닙니다. 시스템 콜 경계의 증거가 필요할 때 가장 좋고, 애플리케이션 내부 상태나 장기 성능 분석이 필요할 때는 다른 도구가 더 맞습니다.

    n

    상황 추천 도구 이유
    파일 경로·권한·라이브러리 탐색이 의심됨 strace 실제 접근 경로와 커널 반환값을 바로 확인 가능
    어떤 포트로 연결하는지, 거부인지 타임아웃인지 확인 strace + ss 호출 결과와 소켓 상태를 함께 확인
    열린 파일과 소켓 목록이 궁금함 lsof, ss 추적보다 현재 상태 스냅샷이 빠름
    CPU 병목이나 함수별 비용 분석 perf, 언어별 profiler strace는 사용자 공간 함수 비용을 설명하지 못함
    메모리 누수·GC·힙 상태 분석 런타임별 도구 시스템 콜 로그만으로는 힙 구조를 알 수 없음
    락 경합·데드락 의심 strace + 스레드 덤프 futex 대기만으로는 원인 스레드를 특정하기 어려움

    제가 쓰는 기준은 단순합니다. “커널에 무엇을 요청했는지”가 질문이면 strace가 맞습니다. “코드 내부에서 왜 그 요청을 했는지”가 질문이면 로그, 디버거, 프로파일러, 스레드 덤프가 필요합니다.

    자주 묻는 질문

    strace를 운영 서버에서 써도 괜찮나요?

    가능은 하지만 짧게 쓰는 쪽을 권합니다. -e trace=...로 범위를 줄이고, -o로 파일에 저장하고, 필요한 순간에만 붙이세요. 초당 시스템 콜이 많은 프로세스에 전체 추적을 오래 거는 방식은 피하는 편이 안전합니다.

    애플리케이션 로그와 strace 중 무엇을 먼저 봐야 하나요?

    대부분은 애플리케이션 로그가 먼저입니다. 로그에서 파일, 권한, 네트워크, 외부 프로세스 실행, 대기 상태가 의심될 때 strace로 내려가면 좋습니다. 처음부터 strace를 보면 단서보다 소음이 많을 수 있습니다.

    컨테이너에서도 쓸 수 있나요?

    쓸 수 있습니다. 다만 컨테이너 이미지에 strace가 없을 수 있고, SYS_PTRACE capability, seccomp, AppArmor, Kubernetes 보안 정책에 막힐 수 있습니다. 운영 파드에 직접 설치하기보다 디버그 컨테이너나 재현 환경을 먼저 고려하세요.

    strace 로그에 민감정보가 남나요?

    남을 수 있습니다. 파일 경로, 실행 인자, 소켓 주소, 일부 문자열 버퍼가 출력될 수 있습니다. -s 값을 크게 잡을수록 더 많은 문자열이 보입니다. 공유 전에는 토큰, 인증 헤더, 고객 데이터가 섞였는지 확인해야 합니다.

    마무리: 장애 유형별로 이렇게 꺼내면 됩니다

    strace는 “리눅스에서 애플리케이션이 실제로 무엇을 요청했는가”를 확인하는 도구입니다. 로그가 애매할 때, 커널의 반환값을 보면 문제가 갑자기 작아지는 순간이 있습니다.

    파일 경로나 권한이 의심되면 strace -e trace=file -s 256로 시작하세요. 네트워크 연결 문제가 의심되면 strace -tt -T -e trace=network로 connect() 반환값을 보세요. 실행 중인 서비스가 멈춘 듯 보이면 sudo strace -f -tt -T -s 256 -o /tmp/app.strace.log -p PID 조합이 출발점으로 좋습니다. 자식 프로세스가 많으면 -ff로 PID별 로그를 분리하세요.

    반대로 CPU 병목, 메모리 누수, 애플리케이션 내부 락 원인까지 strace 하나로 끝내려 하면 돌아갑니다. 그때는 perf, lsof, ss, 스레드 덤프, 언어별 프로파일러와 함께 봐야 합니다. 실무에서 중요한 건 도구 이름이 아니라 관찰 순서입니다. strace는 그 순서에서 “운영체제는 뭐라고 답했나”를 확인하는 가장 직접적인 렌즈입니다.

    strace는 로그가 말해주지 않는 커널 레벨의 단서를 보여주는 실무형 디버깅 도구입니다. 짧게 붙이고, 질문을 좁히고, 반환값으로 다음 조사를 결정하세요.

  • [Linux] strace로 리눅스 애플리케이션 장애 진단: 실제 디버깅 사례 분석

    [Linux] strace로 리눅스 애플리케이션 장애 진단: 실제 디버깅 사례 분석

    [리눅스] strace로 리눅스 애플리케이션 장애 진단

    리눅스 서버를 오래 보다 보면, 로그는 멀쩡한데 애플리케이션만 묘하게 멈춰 있는 순간이 꼭 옵니다. 저도 실제 운영 환경과 홈랩에서 이런 상황을 여러 번 겪었고요. 그럴 때 꽤 자주 꺼내는 도구가 바로 strace 리눅스 디버깅입니다. 시스템 콜 추적(System Call Trace, 커널에 요청하는 함수 흐름 추적)을 보면, 애플리케이션이 지금 무엇을 하다가 막혔는지가 생각보다 적나라하게 드러나거든요.

    처음엔 저도 이게 뭔가 싶었어요. 출력은 엄청 많고, 괄호 투성이고, 에러처럼 보이는 줄도 많아서 겁부터 났거든요. 근데 몇 번 삽질하고 나니까 패턴이 보이더라고요. 특히 시스템 콜 추적은 로그가 없는 장애, 시작은 되는데 응답이 없는 문제, 파일 권한 꼬임, DNS 지연, 소켓 연결 실패 같은 상황에서 정말 강력하더라고요.

    이번 글에서는 제가 실제로 자주 쓰는 방식으로 리눅스 장애 해결에 strace를 어떻게 적용하는지, 그리고 애플리케이션 문제 진단을 할 때 어떤 흐름으로 보면 되는지 사례 중심으로 정리해보겠습니다.

    strace 리눅스 디버깅 개요를 보여주는 시스템 콜 추적 다이어그램

    애플리케이션, 커널, 파일 시스템, 네트워크 호출 흐름을 한눈에 보여주는 strace 리눅스 디버깅 개요 이미지입니다.

    1. 왜 strace가 장애 분석에서 강력한가

    쉽게 말해 strace는 애플리케이션이 커널에게 어떤 부탁을 하는지 보여주는 도구예요. 예를 들면 파일을 여는 <code>openat(), 네트워크에 연결하는 connect(), 데이터를 읽는 read(), 잠깐 멈추는 poll() 같은 호출이 보이거든요. 로그는 개발자가 남겨야 보이지만, 시스템 콜은 애플리케이션이 실제로 살아 움직이려면 거의 피할 수가 없거든요.

    여기서 중요한 포인트! 로그가 없다고 해서 단서가 없는 건 아닙니다. 오히려 strace를 보면 로그를 못 남길 정도로 초반에 죽는 문제도 잡히는 경우가 많아요. 제가 직접 해보니 특히 아래 같은 상황에서 체감이 컸습니다.

    • 프로세스는 살아 있는데 응답이 없음
    • 시작 직후 바로 종료됨
    • 특정 파일이나 소켓 접근에서 실패함
    • DNS 조회나 외부 API 연결에서 지연됨
    • 권한(Permission) 문제인데 로그가 부실함

    2. strace 핵심 개념 쉽게 이해하기

    저도 처음엔 옵션이 너무 많아서 헷갈렸는데, 실무에서 자주 보는 개념은 몇 개 안 돼요.

    2-1. attach와 exec의 차이

    • attach: 이미 실행 중인 프로세스에 붙어서 봅니다. -p PID를 씁니다.
    • exec: 프로그램 실행부터 추적합니다. 서비스가 시작하면서 죽는 문제에 좋아요.

    2-2. 자주 보는 시스템 콜

    • openat(): 파일 열기
    • read() / write(): 데이터 읽기/쓰기
    • connect(): TCP/UDP/Unix Socket 연결
    • stat() 계열: 파일 존재 여부, 메타데이터 확인
    • futex(): 스레드 동기화 대기
    • poll() / select() / epoll_wait(): 이벤트 대기

    2-3. 어떤 도구와 비교하면 좋을까

    도구 주요 용도 강점 주의점
    strace 시스템 콜 추적 파일, 네트워크, 권한 문제를 빠르게 확인 출력이 많아 필터링이 필요
    ltrace 라이브러리 호출 추적 유저 공간 함수 흐름 확인 환경에 따라 활용 범위가 제한적
    journalctl 서비스 로그 확인 systemd 서비스 분석에 편함 로그가 없으면 한계가 명확

    제 경험상 순서는 보통 이래요. 로그 먼저 보고, 안 나오면 ss, lsof, strace로 들어갑니다. 즉, strace는 마지막 수단이라기보다 애매할 때 바로 꺼내는 실전 도구에 가까워요.

    3. strace 기본 사용법: 이 정도만 알아도 바로 써먹습니다

    실전에서 가장 많이 쓰는 명령어부터 보겠습니다.

    1. 실행 중인 프로세스에 붙기
    2. 새로 실행되는 프로그램 추적하기
    3. 출력을 파일로 저장하기
    4. 파일/네트워크 관련 시스템 콜만 필터링하기
    # 실행 중인 PID에 붙기
    strace -p 1234
    
    # 자식 프로세스까지 함께 추적
    strace -f -p 1234
    
    # 실행부터 추적
    strace -f ./app
    
    # 타임스탬프와 소요 시간 보기
    strace -tt -T -f -p 1234
    
    # 결과를 파일로 저장
    strace -f -tt -T -o /tmp/strace.log -p 1234
    
    # 파일/네트워크 관련 호출만 보기
    strace -f -e trace=file,network -p 1234
    

    여기서 -f는 정말 자주 써요. 요즘 애플리케이션은 멀티프로세스나 워커(worker, 작업 프로세스) 구조가 많아서 부모만 보면 핵심이 안 보일 때가 많거든요.

    또 하나 팁을 드리면, 처음부터 모든 걸 보려고 하지 마세요. 출력이 너무 많아요 ㅎㅎ 저는 보통 file, network, process 정도부터 좁혀서 봐요.

    strace 리눅스 디버깅 명령 옵션과 추적 흐름을 설명하는 이미지

    PID attach, 자식 프로세스 추적, 파일 출력, trace 필터링 같은 핵심 옵션을 설명하는 터미널 구성 이미지입니다.

    4. 실제 디버깅 사례 분석: 로그는 멀쩡한데 서비스가 시작되지 않던 문제

    이제 실전 얘기를 해보겠습니다. 예전에 제가 홈랩에서 돌리던 간단한 Python 웹 애플리케이션이 있었는데, systemd로 서비스는 올라온 것처럼 보이는데 정작 포트에서 응답이 없었어요. 로그도 거의 안 찍히더라고요. 처음엔 애플리케이션 버그인 줄 알았는데, strace로 보니까 전혀 다른 문제였습니다.

    4-1. 증상 확인

    systemctl status myapp
    ss -lntp | grep 8000
    journalctl -u myapp -n 50 --no-pager
    

    결과를 보면 서비스 프로세스는 잠깐 떴다가 다시 죽는 식이었어요. 포트 바인딩(bind, 포트 점유)도 안 됐고요. journalctl에는 결정적인 힌트가 없었습니다. 여기서 strace를 붙였어요.

    4-2. 실행부터 추적

    strace -f -tt -T -o /tmp/myapp.strace /usr/bin/python3 /opt/myapp/app.py
    

    로그 파일이 만들어졌으면, 그다음엔 실패 패턴부터 찾아요. 제가 자주 쓰는 방법은 ENOENT, EACCES, ECONNREFUSED 같은 에러 코드를 먼저 찾는 거예요.

    grep -E 'ENOENT|EACCES|ECONNREFUSED|ETIMEDOUT' /tmp/myapp.strace
    

    그때 보였던 핵심 패턴은 이런 형태였습니다.

    openat(AT_FDCWD, "/opt/myapp/config/settings.yml", O_RDONLY) = -1 ENOENT (No such file or directory)
    write(2, "failed to load config\n", 22) = 22
    exit_group(1) = ?
    

    처음엔 진짜 허무했어요. 코드 문제인 줄 알았는데, 실제로는 설정 파일 경로 오타였던 거더라고요. 애플리케이션 로그 레벨이 낮아서 stderr 출력이 서비스 로그에 제대로 안 남던 상황이었고요. 애플리케이션 문제 진단이라고 생각했는데, 실상은 배포 경로 불일치였습니다.

    이런 경험 있으신가요? 분명히 코드 바꾼 건 없는데 배포 후에만 죽는 경우요. 이런 건 strace가 정말 빨리 답을 주더라고요.

    4-3. 문제 수정

    ls -l /opt/myapp/config/
    mkdir -p /opt/myapp/config
    cp /opt/myapp/deploy/settings.yml /opt/myapp/config/settings.yml
    systemctl restart myapp
    

    설정 파일을 맞는 위치에 두고 다시 실행하니 바로 정상 동작했어요. 드디어 됐다! 이런 케이스는 코드 디버거보다 strace가 훨씬 빠르더라고요.

    5. 네트워크 지연 분석에도 strace가 꽤 유용합니다

    strace는 파일만 보는 도구가 아니에요. 네트워크 지연도 흐름을 잡는 데 도움 돼요. 예를 들면 외부 API 호출 전 단계에서 connect()가 오래 걸리는지, DNS 조회 과정에서 대기하는지, Unix Socket 연결이 실패하는지 확인할 수 있습니다.

    strace -f -tt -T -e trace=network -p 1234
    

    예를 들어 출력이 아래처럼 보인다면, 연결 자체에서 문제가 나는 거예요.

    connect(7, {sa_family=AF_UNIX, sun_path="/run/myapp/backend.sock"}, 25) = -1 ENOENT (No such file or directory)
    connect(7, {sa_family=AF_INET, sin_port=htons(443), sin_addr=...}, 16) = -1 ECONNREFUSED (Connection refused)
    

    반대로 poll()이나 epoll_wait()에서 오래 머무는 건, 꼭 장애라고 단정하면 안 돼요. 정상적으로 요청을 기다리는 상태일 수도 있거든요. 그래서 저는 항상 이 호출이 비정상적인 막힘인지, 원래 대기 상태인지를 같이 봅니다.

    strace 리눅스 디버깅으로 파일 누락과 소켓 오류를 분석하는 예시 이미지

    ENOENT, EACCES, ECONNREFUSED 같은 대표 장애 패턴을 strace 출력으로 해석하는 예시 이미지입니다.

    6. ⚠️ 주의사항과 트러블슈팅: strace가 만능은 아닙니다

    여기서 많이들 헷갈리는 포인트가 있어요. 저도 처음엔 그랬고요.

    6-1. futex()가 보인다고 무조건 데드락은 아닙니다

    멀티스레드 프로그램을 보면 futex()가 엄청 자주 나와요. 이걸 보고 바로 데드락(deadlock, 상호 대기)이라고 판단하면 위험해요. 정상적인 스레드 대기일 수도 있거든요. 이럴 땐 CPU 사용률, 스레드 덤프, 애플리케이션 구조를 같이 봐야 합니다.

    6-2. 성능 오버헤드가 있습니다

    strace는 추적 도구라서 당연히 부하가 있어요. 운영 서버에 장시간 전체 추적은 조심해야 합니다. 특히 트래픽이 높은 프로세스는 출력량도 많고, 체감 성능에 영향이 생길 수 있습니다.

    • 짧게 붙어서 패턴만 확인하기
    • -e trace=...로 범위 줄이기
    • -o로 파일 저장 후 오프라인 분석하기

    6-3. 권한 문제로 attach가 안 될 수 있습니다

    strace: attach: ptrace(PTRACE_SEIZE, 1234): Operation not permitted
    

    이 경우엔 보통 권한이나 보안 설정을 확인해야 해요. 컨테이너나 보안 정책이 걸린 환경에서는 ptrace 자체가 제한될 수 있거든요. 저도 컨테이너 환경에서 왜 안 붙나 한참 봤던 적이 있어요. 삽질 좀 했습니다 ㅎㅎ

    6-4. 너무 많은 출력은 오히려 독입니다

    무작정 전체 로그를 보면 눈이 먼저 지쳐요. 그래서 저는 아래 순서로 봅니다.

    1. 에러 코드부터 검색합니다
    2. 마지막 수십 줄 흐름을 봅니다
    3. 반복되는 패턴이 있는지 확인합니다
    4. 파일, 네트워크, 프로세스 관련 호출로 좁힙니다

    7. 검증: 수정 후 무엇을 확인해야 하나

    장애 원인을 찾았다고 끝은 아니에요. 수정 후에는 반드시 재현 경로와 정상 동작을 같이 확인해야 합니다. 제가 보통 체크하는 항목은 아래와 같습니다.

    1. 서비스 프로세스가 바로 종료되지 않는지 확인
    2. 포트가 정상적으로 리슨(listen, 대기) 상태인지 확인
    3. 에러가 발생하던 요청이 정상 응답하는지 확인
    4. strace에서 보이던 실패 패턴이 사라졌는지 확인
    systemctl restart myapp
    systemctl status myapp --no-pager
    ss -lntp | grep 8000
    curl -I http://127.0.0.1:8000/
    

    필요하면 다시 짧게 추적해서 차이를 봐요.

    strace -f -tt -T -e trace=file,network -o /tmp/after_fix.strace -p 1234
    

    수정 전에는 ENOENT가 보였는데, 수정 후에는 해당 파일을 정상적으로 openat() 하는 흐름이 보이면 꽤 확신을 가질 수 있어요. 이런 식으로 리눅스 장애 해결은 추정이 아니라 근거로 밀어붙여야 나중에 덜 흔들립니다.

    strace 리눅스 디버깅 결과를 수정 전후로 비교하는 검증 이미지

    에러 코드가 사라지고 포트 리슨 및 HTTP 응답이 복구된 결과를 비교하는 검증 이미지입니다.

    8. 자주 묻는 질문 정리

    Q1. strace 리눅스 디버깅은 언제 가장 먼저 써야 할까요?

    로그가 비어 있거나, 시작 직후 종료되거나, 파일/권한/소켓 문제로 의심될 때 바로 써볼 만해요. 특히 시스템 콜 추적이 필요한 상황은 애플리케이션 외부 의존성이 꼬였을 때예요.

    Q2. 운영 서버에서도 써도 될까요?

    짧고 제한적으로는 가능해요. 다만 전체 추적을 오래 거는 건 조심하셔야 합니다. 필터링이 정말 중요합니다.

    Q3. strace만으로 모든 문제가 해결되나요?

    아니에요. CPU 바운드 문제, 메모리 누수, 애플리케이션 내부 로직 버그는 다른 도구와 함께 봐야 합니다. strace는 커널 경계에서 보이는 행동을 잘 보여주는 도구니까요.

    9. 마무리: strace는 로그가 말해주지 않는 걸 보여줍니다

    정리해보면, strace는 화려한 도구는 아니에요. 근데 장애 순간에 진짜 실용적이더라고요. 제가 직접 써보니 특히 파일 경로 오류, 권한 문제, 소켓 연결 실패, 시작 직후 종료 같은 문제에서는 체감상 가장 빠르게 본질에 닿는 도구였어요.

    혹시 지금도 프로세스는 떠 있는데 왜 안 되는지 감이 안 오신다면, 너무 복잡하게 가지 말고 짧게 strace 한 번 붙여보세요. 의외로 답은 이미 시스템 콜에 나와 있는 경우가 많아요. 이거 진짜 편하더라고요.

    다음 글에서는 lsof로 열린 파일과 소켓 추적하는 방법, 그리고 이전 글에서 다뤘던 systemd 서비스 로그 읽는 법과 어떻게 조합하면 좋은지도 이어서 정리해보겠습니다.

    strace 사용 시점, 자주 보는 에러 코드, 검증 절차를 요약한 인포그래픽 이미지입니다.