13년차의 서버실

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

[태그:] 네트워크 디버깅

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

  • [HomeLabs] UDM Pro 문제 해결: WAN 불안정과 메모리 누수 디버깅

    [HomeLabs] UDM Pro 문제 해결: WAN 불안정과 메모리 누수 디버깅

    UDM Pro 문제 해결: WAN 불안정과 메모리 누수 디버깅

    홈랩을 오래 굴리다 보면 제일 사람을 지치게 하는 순간이 있습니다. 분명 인터넷은 들어오는데 체감이 끊기는 것 같고, 화상회의는 순간적으로 튀고, 게임 핑은 갑자기 치솟고, 로그를 보면 또 멀쩡해 보이는 상황이요. 저도 UniFi Dream Machine Pro(UDM Pro)를 메인 라우터로 쓰면서 이런 일을 꽤 겪었습니다. 특히 UDM Pro 문제 해결이 필요한 순간은 보통 한 번에 오지 않더라고요. WAN 불안정과 리소스 누적이 겹치면서, 겉으로는 회선 문제처럼 보이는데 실제로는 장비 내부 상태가 원인인 경우가 있었습니다.

    처음엔 ISP 회선부터 의심했습니다. 저도 늘 그랬거든요. 근데 며칠 단위로 패턴을 기록해 보니까, 회선 자체보다는 UDM Pro 내부 메모리 사용량이 점점 올라가고 특정 시점부터 UI 반응과 WAN 품질이 같이 나빠지는 흐름이 보였습니다. 여기서 중요한 포인트는 WAN 불안정과 메모리 누수를 따로 봐서는 안 된다는 거예요. 오늘 글은 제가 실제로 홈랩 라우터를 디버깅할 때 쓰는 순서대로 정리해보겠습니다.

    UDM Pro 문제 해결을 위한 홈랩 네트워크 전체 구성도

    UDM Pro를 중심으로 WAN, 스위치, 서버, 클라이언트가 연결된 홈랩 구조를 한눈에 보여주는 개요 이미지입니다.

    1. 왜 UDM Pro 문제 해결이 까다로운가

    쉽게 말해 라우터 문제는 증상이 거짓말을 많이 합니다. 인터넷이 순간적으로 느려지면 다들 WAN부터 의심하지만, 실제로는 NAT(Network Address Translation, 주소 변환), IDS/IPS(Intrusion Detection/Prevention System, 침입 탐지/차단), 로그 적재, 컨트롤러 프로세스 같은 내부 요소가 먼저 흔들리는 경우가 있습니다.

    특히 UniFi UDM Pro는 라우터, 컨트롤러, 보안 게이트웨이 역할이 한 장비에 묶여 있거든요. 이 구조는 편합니다. 정말 편해요. 근데 한쪽 리소스가 밀리면 다른 기능에도 영향을 줄 수 있어요. 제가 직접 해보니 이런 식으로 연결되더라고요.

    • 메모리 사용량이 계속 올라감
    • 관리 UI 반응 속도가 둔해짐
    • 세션 처리나 DPI(Deep Packet Inspection, 패킷 심층 분석) 쪽이 무거워짐
    • WAN 지연시간과 패킷 손실이 간헐적으로 튐
    • 사용자는 그냥 인터넷이 끊긴다고 느끼게 됨

    그래서 네트워크 디버깅은 증상보다 흐름을 봐야 합니다. 한 번의 속도 측정으로 끝내면 안 되고, 시간 축으로 기록해야 하죠.

    2. WAN 불안정과 메모리 누수를 어떻게 구분할까

    저도 처음엔 헷갈렸는데, 기준을 세워두면 생각보다 빨리 갈립니다. WAN 쪽 문제인지, 장비 내부 문제인지 대략 아래처럼 분류할 수 있어요.

    증상 WAN 회선 이슈 가능성 장비 내부 리소스 이슈 가능성
    특정 시간대만 느림 높음 중간
    재부팅 직후 멀쩡함 낮음 높음
    관리 UI도 같이 느려짐 낮음 매우 높음
    외부 핑은 튀는데 내부 통신은 정상 높음 중간
    며칠 지나면 반복적으로 악화 중간 높음

    재부팅 후 한동안 멀쩡하다가 다시 나빠진다면, 저는 거의 무조건 메모리와 프로세스 상태부터 봅니다. 반대로 장비는 멀쩡한데 ISP 게이트웨이까지 핑이 튄다면 회선 쪽일 확률이 높더라고요.

    3. 먼저 확인할 최소 체크리스트

    본격적으로 SSH 붙기 전에, 저는 아래 순서부터 확인합니다. 괜히 깊이 들어가기 전에 큰 원인을 먼저 쳐내는 거죠.

    1. 인터넷 회선 장애 공지가 있는지 확인합니다.
    2. 광모뎀 또는 상위 모뎀의 링크 상태가 변한 적 있는지 봅니다.
    3. UDM Pro의 WAN 포트 협상 속도와 케이블 상태를 확인합니다.
    4. 최근 설정 변경 사항, 특히 IDS/IPS, Smart Queue, Traffic Identification 관련 변경이 있었는지 봅니다.
    5. 문제가 생기는 시간대가 백업, 카메라 업로드, 대용량 동기화 시간과 겹치는지 체크합니다.

    여기서 중요한 포인트! 설정 변경 이력을 꼭 보셔야 합니다. 홈랩은 특히 제가 직접 건드린 게 원인인 경우가 많았거든요. 삽질 좀 했습니다 ㅎㅎ

    4. 실전 1단계: UDM Pro에서 기본 상태 확인

    이제 SSH로 들어가서 최소한의 상태를 봅니다. 특정 버전 의존적인 명령보다는 범용 Linux/BusyBox 계열 명령 위주로 접근하는 게 안전하더라고요.

    ssh admin@udm-pro-ip
    uptime
    free -m
    top
    df -h
    dmesg | tail -n 50
    ping -c 20 1.1.1.1
    ping -c 20 8.8.8.8

    제가 실제로 써보니까 여기서 바로 감이 오는 경우가 많았습니다.

    • uptime: 장비가 얼마나 오래 켜져 있었는지 확인해요.
    • free -m: 메모리 사용량과 여유 메모리를 봅니다.
    • top: 어떤 프로세스가 CPU/메모리를 계속 먹는지 봐요.
    • dmesg: NIC(Network Interface Card, 네트워크 인터페이스) 오류나 커널 메시지를 확인합니다.
    • ping: 외부 목적지까지 손실과 지연 변동이 있는지 빠르게 확인해요.

    만약 이 시점에 관리 UI도 느리고, 메모리 여유가 비정상적으로 줄어 있다면 회선보다 내부 상태를 더 의심해볼 만합니다.

    UniFi UDM Pro의 WAN 불안정과 리소스 점검 장면

    SSH 터미널에서 uptime, free, top, ping 결과를 확인하며 UDM Pro 문제의 원인을 좁혀가는 장면을 보여주는 이미지입니다.

    5. 실전 2단계: 외부 모니터링으로 WAN 불안정을 잡아내기

    라우터 안에서만 보면 놓치는 게 있습니다. 그래서 저는 항상 별도 리눅스 장비나 NAS, 미니 PC 하나에서 외부 모니터링을 같이 돌립니다. 이게 진짜 중요합니다. 라우터가 힘들어하는 순간에도 바깥에서 본 기록이 남거든요.

    아래처럼 간단한 bash 스크립트를 하나 돌려두면 packet loss와 latency 변화를 시간대별로 남길 수 있습니다.

    #!/usr/bin/env bash
    TARGETS=("1.1.1.1" "8.8.8.8")
    LOGFILE="/var/log/wan-check.log"
    
    while true; do
      TS=$(date "+%Y-%m-%d %H:%M:%S")
      for target in "${TARGETS[@]}"; do
        RESULT=$(ping -c 5 -W 2 "$target" | tail -n 2 | tr '\n' ' ')
        echo "$TS target=$target $RESULT" >> "$LOGFILE"
      done
      sleep 60
    done

    이 로그를 보면 재미있는 패턴이 보여요. 문제가 생긴 시간에 모든 대상이 동시에 튀면 WAN 또는 라우터 공통 구간 문제일 가능성이 높고, 특정 대상만 흔들리면 외부 경로 문제일 수도 있습니다.

    좀 더 정리하면 이런 기준으로 보면 됩니다.

    1. 모든 외부 대상 핑이 동시에 튄다: UDM Pro 또는 회선 공통 구간 의심
    2. 관리 UI가 동시에 느려진다: 내부 리소스 문제 가능성 상승
    3. 재부팅 후 그래프가 초기화되듯 좋아진다: 메모리 누적 문제 가능성 상승
    4. 특정 시간에만 튄다: 스케줄 작업이나 트래픽 폭증 의심

    6. 실전 3단계: 메모리 누수처럼 보일 때 제가 확인한 포인트

    이 부분은 조심해서 봐야 해요. 엄밀히 말하면 모든 메모리 증가가 곧바로 leak(누수)인 건 아니거든요. Linux 계열 시스템은 캐시를 적극적으로 쓰기 때문에, 숫자만 보고 결론 내리면 안 됩니다. 저도 처음엔 숫자 보고 깜짝 놀랐는데, 실제로는 캐시(cache)와 프로세스 실제 점유 메모리(resident memory)를 같이 봐야 하더라고요.

    제가 주로 보는 패턴은 이렇습니다.

    • 특정 프로세스의 메모리 점유가 시간에 따라 계속 증가하는가
    • 관리 기능이 느려지는 시점과 메모리 증가 시점이 겹치는가
    • 재부팅 또는 관련 기능 비활성화 후 증상이 사라지는가
    • DPI, IDS/IPS, 트래픽 통계 같은 부가 기능과 상관관계가 있는가

    여기서 너무 공격적으로 설정을 다 꺼버리면 원인 파악이 안 돼요. 저는 보통 한 번에 하나씩만 바꿉니다. 예를 들면 이런 순서죠.

    1. 트래픽이 많은 시간대를 기록합니다.
    2. 그 시간대 직전과 직후의 메모리 상태를 비교합니다.
    3. 부가 기능을 하나만 조정합니다.
    4. 24시간에서 72시간 정도 추세를 다시 봅니다.

    이 방식이 느려 보여도 제일 덜 헤맵니다. 한꺼번에 다 바꾸면 드디어 됐다 싶다가도 뭐가 원인이었는지 모르게 끝나거든요.

    기능별 점검 포인트

    항목 왜 확인하나 제가 보는 신호
    IDS/IPS 패킷 분석 부하 증가 가능성 CPU 상승, 지연 증가
    DPI/트래픽 통계 세션 및 분석 정보 누적 장기 사용 시 UI 반응 저하
    Smart Queue 대역폭 제어에 따른 처리 부담 고부하 시간대 지연 증가
    로그 적재 문제 시점 추적용 이상 메시지 반복 여부

    7. ⚠️ 제가 실제로 겪었던 흔한 함정

    이 섹션은 꼭 넣고 싶었어요. 문서만 보면 안 보이는 부분이 있거든요.

    첫 번째 함정은 케이블과 링크 협상입니다. WAN 불안정이라고 해서 무조건 소프트웨어 문제는 아닙니다. 애매하게 손상된 케이블이나 포트 접점 문제는 정말 사람 미치게 합니다. 핑이 항상 나쁜 게 아니라 간헐적으로만 튀니까요. 저는 예전에 라우터 설정만 계속 들여다보다가, 결국 상위 모뎀과 UDM Pro 사이 케이블 교체로 증상이 크게 줄어든 적도 있었습니다.

    두 번째는 재부팅 효과를 과대평가하는 것입니다. 재부팅 후 괜찮아졌다고 해서 원인이 사라진 건 아니에요. 단지 증상이 초기화된 걸 수 있거든요. 특히 UDM Pro 문제 해결에서 이 패턴이 자주 보입니다.

    세 번째는 로그 없이 기억에 의존하는 것입니다. 사람 기억은 생각보다 부정확합니다. 저는 이제 무조건 시간 기록부터 남깁니다. 끊긴 시간, 어떤 서비스가 영향받았는지, 그때 내부 UI가 느렸는지까지 같이 적어두면 나중에 원인 분리가 쉬워져요.

    네 번째는 기능을 너무 많이 켜두는 것입니다. 홈랩 라우터는 만능처럼 보여도 결국 자원은 유한합니다. 기능을 켜는 건 쉽지만, 디버깅은 어려워집니다.

    UDM Pro 문제 해결을 위한 WAN 지연과 메모리 사용량 대시보드

    메모리 사용량 증가와 외부 핑 지연 상승이 같은 시점에 나타나는 대시보드 형태의 결과 시각화 이미지입니다.

    8. 검증: 문제를 해결했다고 판단하는 기준

    해결은 느낌으로 하면 안 됩니다. 이 부분은 인프라 쪽에서 특히 중요하죠. 저는 아래 기준을 만족해야 해결로 봐요.

    1. 24시간 이상 외부 대상 핑 손실이 안정적일 것
    2. 관리 UI 반응 속도가 이전보다 일관될 것
    3. 메모리 사용량 추세가 비정상적으로 계속 상승하지 않을 것
    4. 문제 시간대에도 WAN 지연이 급격히 튀지 않을 것
    5. 사용자 체감 이슈가 재현되지 않을 것

    가능하면 before/after를 직접 남겨보세요. 예를 들어 설정 조정 전후로 다음 항목을 비교하면 좋습니다.

    date
    uptime
    free -m
    ping -c 20 1.1.1.1
    ping -c 20 8.8.8.8
    dmesg | tail -n 20

    그리고 간단한 점검표도 추천드립니다.

    검증 항목 변경 전 변경 후
    UI 반응 속도 느림/보통/빠름 느림/보통/빠름
    외부 핑 안정성 불안정/보통/안정 불안정/보통/안정
    메모리 증가 추세 가파름/완만/안정 가파름/완만/안정
    재현 여부 있음 없음

    이렇게 남겨두면 다음에 비슷한 문제 생겨도 훨씬 빨라져요. 저는 이 기록 덕분에 두 번째부터는 훨씬 덜 헤맸습니다.

    9. 정리: UniFi UDM Pro 디버깅은 순서가 전부입니다

    정리하면, UniFi UDM Pro에서 보이는 WAN 불안정은 회선 문제처럼 보여도 장비 내부 리소스 이슈와 연결되는 경우가 꽤 있어요. 특히 장시간 운영 후 느려지고, 재부팅 후 잠시 좋아지고, 관리 화면까지 버벅인다면 메모리와 프로세스 상태를 꼭 같이 보셔야 합니다. 제가 직접 해보니 핵심은 복잡한 기술보다도 순서 있게 분리해서 보는 습관이더라고요.

    • 회선과 내부 리소스를 분리해서 확인하기
    • 외부 모니터링 로그 남기기
    • 기능은 한 번에 하나씩만 조정하기
    • 재부팅 전후를 반드시 기록하기

    혹시 이런 경험 있으신가요? 멀쩡하던 홈랩 라우터가 며칠 지나면 이상하게 답답해지는 그 느낌이요. 저도 처음엔 이게 뭔가 싶었는데, 결국 기록과 비교가 답이었어요. 다음 글에서는 UniFi 환경에서 VLAN(Virtual LAN, 가상 랜) 분리와 모니터링 기준을 어떻게 잡는지 이어서 다뤄볼 예정입니다. 이전 글에서 다뤘던 홈랩 기본 세그먼트 설계 내용과 함께 보시면 더 이해가 잘 되실 거예요.

    UDM Pro 문제 해결 단계와 체크리스트 요약 인포그래픽

    회선 점검, SSH 확인, 외부 모니터링, 검증 단계까지 한 장으로 정리한 요약 인포그래픽 이미지입니다.

    자주 묻는 질문

    Q1. 메모리 사용량이 높으면 무조건 메모리 누수인가요?

    아닙니다. 캐시 사용 때문일 수 있습니다. 중요한 건 시간에 따라 특정 프로세스가 계속 증가하는지, 그리고 그 증가가 실제 장애 증상과 연결되는지예요.

    Q2. WAN 불안정이면 바로 ISP에 문의해야 하나요?

    바로 문의해도 되지만, 가능하면 먼저 외부 핑 기록과 라우터 내부 상태를 같이 확보해두세요. 문의 품질이 달라집니다.

    Q3. 홈랩 라우터에서 기능을 많이 켜면 무조건 안 좋은가요?

    무조건은 아닙니다. 다만 IDS/IPS, DPI, 큐잉 같은 기능은 자원 사용량과 체감 성능 사이 균형을 봐야 합니다. 목적 없는 활성화는 디버깅 난이도만 올릴 수 있어요.

  • [Linux] 리눅스 서버 네트워크 연결 장애: IP 설정부터 방화벽까지 디버깅 5단계

    [Linux] 리눅스 서버 네트워크 연결 장애: IP 설정부터 방화벽까지 디버깅 5단계

    [리눅스] 리눅스 서버 네트워크 연결 장애: IP 설정부터 방화벽까지 디버깅 5단계

    리눅스 네트워크 장애는 평소엔 조용하다가 꼭 바쁠 때 터지더라고요. SSH는 안 붙고, 서비스 Health Check(헬스 체크, 상태 확인)는 빨갛게 뜨고, 팀 메신저에는 왜 서버가 안 되냐는 메시지가 쌓입니다. 저도 홈랩이랑 운영 서버를 만지면서 비슷한 상황을 정말 많이 겪었는데, 처음엔 케이블 문제인가 싶었는데, 실제로는 리눅스 IP 설정 하나가 꼬였던 적도 있었고, 반대로 IP는 멀쩡한데 iptables(아이피테이블스, 리눅스 패킷 필터) 규칙 때문에 통신이 막힌 적도 많았어요.

    그래서 오늘은 제가 실제로 쓰는 기준으로 리눅스 네트워크 장애를 좁혀 가는 5단계 디버깅 흐름을 정리해보겠습니다. 무작정 재부팅부터 하는 게 아니라, 아래에서 위로 차근차근 확인하는 방식이죠. 특히 Ubuntu 계열에서 자주 쓰는 netplan(넷플랜, 네트워크 설정 추상화 도구), systemd-networkd(시스템디 네트워크 관리 데몬), 그리고 방화벽 쪽의 nftables(엔에프테이블스, 최신 패킷 필터 프레임워크)까지 같이 보겠습니다.

    리눅스 네트워크 장애 점검 순서를 보여주는 전체 흐름도

    리눅스 서버에서 링크 상태, IP 설정, 라우팅, DNS, 방화벽 순서로 점검하는 전체 디버깅 흐름을 보여주는 개요 이미지입니다.

    리눅스 네트워크를 층으로 나눠 봐야 하는 이유

    쉽게 말해 네트워크 장애는 한 덩어리가 아니라 여러 층으로 나뉘어 있거든요. 물리 링크가 살아 있는지, 인터페이스가 Up(업, 활성화) 상태인지, IP가 붙었는지, 기본 게이트웨이(Default Gateway, 기본 경로)가 맞는지, 이름 해석 DNS가 되는지, 마지막으로 방화벽이 막고 있지는 않은지요. 여기서 중요한 포인트는 위 증상만 보고 아래 원인을 단정하면 삽질이 길어진다는 점입니다.

    제가 직접 해보니 제일 효율적인 방법은 이렇더라고요. 1단계에서 링크와 인터페이스를 확인하고, 2단계에서 리눅스 IP 설정을 보고, 3단계에서 라우팅과 DNS를 확인한 뒤, 4단계에서 netplan과 systemd-networkd를 보고, 마지막 5단계에서 iptables 또는 nftables를 점검하는 겁니다. 이 순서대로 보면 원인 범위가 빠르게 줄어들어요.

    리눅스 네트워크 장애 디버깅 5단계

    1단계. 링크 상태와 인터페이스 확인 (제일 많이 놓치는 부분)

    제일 먼저 보는 건 케이블, 가상 NIC, 스위치 포트, 인터페이스 상태입니다. 이 단계에서 해결되는 경우가 생각보다 많더라고요. 특히 VM(가상머신)에서는 가상 스위치 설정이, 베어메탈에서는 포트 비활성화가 원인일 때가 꽤 있거든요.

    ip link show
    ip -br link
    ethtool eth0
    

    여기서 보는 포인트는 간단해요.

    1. 인터페이스 상태가 UP인지 확인하세요.
    2. LOWER_UP 표시가 있는지 봐야 합니다. 이게 없으면 링크 자체가 안 잡힌 경우가 많거든요.
    3. ethtool로 Speed(속도), Duplex(이중화 모드), Link detected 여부를 봅니다.

    예를 들어 인터페이스가 내려가 있으면 이렇게 올릴 수 있어요.

    sudo ip link set eth0 up
    

    별거 아닌 것 같지만, 저도 예전에 테스트하다가 인터페이스를 내려놓고 그대로 잊어버려서 한참 헤맨 적이 있습니다. 진짜 허무했어요 ㅎㅎ

    2단계. 리눅스 IP 설정 확인

    다음은 IP 주소, 서브넷 마스크(Subnet Mask, 네트워크 범위 정보), DHCP(디에이치씨피, 자동 IP 할당) 여부를 확인하는 거예요. 여기서 리눅스 IP 설정이 의도와 다르게 잡혀 있으면 외부 통신이 바로 꼬입니다.

    ip addr show
    ip -br addr
    hostname -I
    

    출력에서 확인할 것은 아래입니다.

    • 예상한 인터페이스에 IP가 붙었는지
    • 대역이 맞는지 예: 192.168.10.0/24
    • 중복 IP 가능성은 없는지
    • DHCP로 받아야 하는 서버인데 주소가 비어 있지는 않은지

    Ubuntu 서버에서 netplan을 쓴다면 설정 파일은 보통 이렇게 생겨요.

    network:
      version: 2
      renderer: networkd
      ethernets:
        eth0:
          dhcp4: false
          addresses:
            - 192.168.10.20/24
          routes:
            - to: default
              via: 192.168.10.1
          nameservers:
            addresses:
              - 1.1.1.1
              - 8.8.8.8
    

    설정 반영은 아래처럼 하면 됩니다.

    sudo netplan generate
    sudo netplan apply
    

    처음엔 이게 뭔가 싶었는데, YAML(야믈, 들여쓰기 기반 설정 형식) 들여쓰기 하나만 틀려도 적용이 안 되더라고요. 그래서 저는 반영 전에 꼭 눈으로 한 번 더 봅니다.

    리눅스 IP 설정과 netplan 점검 장면

    netplan YAML 설정과 ip 명령 결과를 나란히 확인하는 실전 점검 장면을 담은 이미지입니다.

    3단계. 라우팅과 DNS 확인

    IP가 붙어 있어도 목적지로 가는 길이 없으면 통신이 안 되거든요. 그래서 라우팅 테이블(Route Table, 경로 정보)과 DNS를 따로 봐야 합니다. 이 구간에서 많이들 헷갈리세요. 핑은 IP로 되는데 도메인은 안 된다면 DNS 쪽일 가능성이 높고, 같은 대역은 되는데 외부가 안 된다면 기본 게이트웨이 문제일 가능성이 커요.

    ip route show
    ip route get 8.8.8.8
    ping -c 4 192.168.10.1
    ping -c 4 8.8.8.8
    getent hosts example.com
    resolvectl status
    

    제가 주로 이렇게 해석하더라고요.

    증상 가능성 높은 원인 우선 확인할 것
    게이트웨이 핑 실패 L2 링크, VLAN, 잘못된 IP 케이블, 스위치, 인터페이스 상태
    외부 IP 핑 실패 기본 라우트 누락, 업스트림 차단 ip route, 게이트웨이
    도메인만 실패 DNS 설정 오류 nameserver, resolvectl
    특정 포트만 실패 방화벽 또는 서비스 미기동 ss, iptables, nftables

    여기서 중요한 포인트! DNS 문제를 네트워크 전체 장애로 오해하는 경우가 정말 많아요. 실제로 써보니까 IP 통신과 이름 해석을 분리해서 보는 습관이 장애 시간을 많이 줄여줍니다.

    4단계. netplan, systemd-networkd 로그 확인

    설정 파일이 맞아 보여도 실제 적용 과정에서 실패할 수 있거든요. 특히 cloud-init(클라우드이닛, 초기 인스턴스 설정 도구)와 netplan이 같이 엮인 환경에서는 예상과 다르게 덮어써지는 경우도 있어요.

    networkctl status eth0
    systemctl status systemd-networkd
    journalctl -u systemd-networkd --no-pager
    sudo netplan try
    

    netplan try는 꽤 유용하더라고요. 원격 서버에서 네트워크 설정 바꿀 때 잘못 적용하면 SSH가 끊길 수 있거든요. 이 명령은 일정 시간 안에 확인하지 않으면 롤백되는 방식이라 조금 더 안전합니다.

    저도 홈랩에서 라우트를 바꾸다가 접속이 끊겨서 콘솔로 다시 들어간 적이 몇 번 있어요. 그 뒤로는 원격 작업에서는 무조건 보수적으로 갑니다. 가능하면 콘솔 접근 경로를 하나 확보하고 작업하시길 권장합니다.

    리눅스 네트워크 장애 원인을 systemd-networkd 로그로 분석하는 모습

    systemd-networkd 상태 출력과 journal 로그를 보며 적용 실패 원인을 찾는 디버깅 장면입니다.

    5단계. iptables, nftables, 서비스 포트 확인

    마지막 단계는 방화벽과 리스닝 포트(Listening Port, 대기 중인 서비스 포트)예요. 여기까지 왔는데도 통신이 안 되면, 사실 방화벽일 때가 꽤 많습니다. 특히 오래된 서버는 iptables를, 최근 배포판은 nftables를 쓰는 경우가 많아서 둘 다 확인해야 헷갈리지 않아요.

    sudo iptables -L -n -v
    sudo iptables -S
    sudo nft list ruleset
    ss -tulpn
    

    체크 포인트는 이렇습니다.

    • INPUT 체인 기본 정책이 DROP인지
    • SSH, HTTP, HTTPS 등 필요한 포트 허용 규칙이 있는지
    • 서비스가 실제로 해당 포트에서 listen 중인지
    • iptables와 nftables가 혼재되어 정책을 헷갈리게 만들고 있지는 않은지

    예를 들어 SSH 22/tcp가 막혀 있으면 서버는 살아 있어도 접속이 안 돼요. 반대로 방화벽은 열려 있는데 서비스가 안 떠 있으면 역시 안 되죠. 그래서 저는 항상 패킷 필터와 서비스 포트를 같이 봅니다.

    ⚠️ 실제로 자주 겪는 트러블슈팅 포인트

    여기서는 제가 삽질 좀 했던 사례를 중심으로 적어보겠습니다. 혹시 이런 경험 있으신가요? 겉으로는 리눅스 네트워크 장애처럼 보이는데, 실제 원인은 아주 사소한 설정 하나인 경우 말입니다.

    1. YAML 들여쓰기 오류
      netplan은 형식이 엄격해요. 공백 수가 틀리면 적용이 실패하거나 의도와 다르게 해석됩니다.
    2. 인터페이스 이름 혼동
      예전엔 eth0로 익숙했는데, 환경에 따라 ens18, enp1s0처럼 다르게 보여요. 설정 파일과 실제 NIC 이름이 다르면 당연히 안 붙습니다.
    3. 기본 라우트 누락
      같은 대역 통신만 되고 외부가 안 되는 전형적인 증상이죠.
    4. DNS 서버 미설정
      핑은 되는데 apt 업데이트나 도메인 접근이 안 됩니다.
    5. 방화벽 정책 잔존
      이전 작업에서 넣어둔 DROP 규칙이 그대로 남아 있는 경우가 있더라고요.

    이런 문제를 줄이려면 변경 전후 비교가 중요합니다. 저는 보통 현재 상태를 먼저 저장해 둬요.

    ip addr show
    ip route show
    resolvectl status
    sudo iptables -S
    sudo nft list ruleset
    

    그리고 변경 후에는 꼭 다시 비교합니다. 이 습관이 쌓이면 리눅스 네트워크 디버깅 속도가 확실히 빨라집니다.

    검증 방법: 어디까지 확인해야 정말 해결된 걸까요?

    설정만 반영됐다고 끝이 아닙니다. 리눅스 네트워크 장애는 재현이 사라진 것처럼 보여도 실제 서비스 경로가 여전히 막혀 있을 수 있거든요. 그래서 저는 최소한 아래 검증은 꼭 합니다.

    1. 게이트웨이 핑 확인
    2. 외부 IP 핑 확인
    3. 도메인 이름 해석 확인
    4. 필요 포트 접속 확인
    5. 서비스 로그와 클라이언트 관점 확인
    ping -c 2 192.168.10.1
    ping -c 2 8.8.8.8
    getent hosts example.com
    curl -I http://example.com
    nc -zv 127.0.0.1 22
    

    여기까지 다 통과하면 거의 끝입니다. 드디어 됐다! 싶은 순간이 오죠. 다만 운영 환경이라면 여기서 한 번 더, 다른 서버나 사용자 위치에서 역방향 확인도 해보시는 걸 권장합니다. 서버 안에서만 되는 경우가 있거든요.

    리눅스 네트워크 장애 복구 후 검증 화면

    ping, curl, 포트 체크 결과가 정상으로 돌아온 모습을 한눈에 보여주는 검증 이미지입니다.

    정리 표: 단계별 점검 포인트 한 번에 보기

    단계 확인 명령 핵심 질문
    1. 링크 ip link, ethtool 인터페이스와 물리 링크가 살아 있나?
    2. IP ip addr 리눅스 IP 설정이 맞게 붙었나?
    3. 라우팅/DNS ip route, getent, resolvectl 길이 있나? 이름 해석이 되나?
    4. 설정 적용 netplan, networkctl, journalctl 설정이 실제로 반영됐나?
    5. 방화벽/포트 iptables, nft, ss 패킷과 서비스 포트가 열려 있나?

    이 표만 머릿속에 넣어두셔도 현장에서 꽤 도움이 됩니다. 특히 초반에 당황해서 이것저것 동시에 건드리기 시작하면 원인 추적이 더 어려워져요. 순서대로, 하나씩, 확인한 사실만 쌓아가는 게 제일 빠릅니다.

    리눅스 네트워크 장애 5단계 디버깅 요약 인포그래픽

    링크, IP, 라우팅, 설정 적용, 방화벽 점검 순서를 한 장으로 요약한 체크리스트 이미지입니다.

    마무리: 리눅스 네트워크 장애는 감으로 풀기보다 순서로 푸는 게 낫습니다

    오늘 정리한 흐름의 핵심은 단순합니다. 리눅스 네트워크 장애가 생기면 링크, IP, 라우팅, DNS, 설정 적용, 방화벽 순서로 보자는 거예요. 저도 처음엔 여기저기 막 건드렸는데, 실제로 써보니까 장애 대응에서 제일 중요한 건 화려한 명령어보다 점검 순서였습니다.

    특히 netplan과 systemd-networkd를 쓰는 환경에서는 설정 파일과 적용 로그를 같이 봐야 하고, 구형 환경이나 혼재된 환경에서는 iptables와 nftables를 둘 다 체크해야 합니다. 이 부분만 익숙해져도 리눅스 네트워크 디버깅이 훨씬 덜 막막해집니다.

    다음 글에서는 tcpdump(티씨피덤프, 패킷 캡처 도구)로 패킷 흐름을 직접 보면서 원인을 좁히는 방법도 다뤄보겠습니다. 이전 글에서 다뤘던 홈랩 VLAN 구성 글이 있다면 같이 보셔도 흐름 이해에 도움이 됩니다. 현장에서 바로 써먹을 수 있는 기준으로 계속 정리해보겠습니다.

    자주 묻는 질문

    Q1. ping은 되는데 웹 접속만 안 되면 어디부터 봐야 하나요?

    A. 보통은 서비스 포트와 방화벽을 먼저 봅니다. ss -tulpn으로 리스닝 여부를 확인하고, 그다음 iptables 또는 nftables 규칙을 점검해보세요.

    Q2. netplan apply 전에 더 안전한 방법이 있나요?

    A. 원격 서버라면 netplan try를 먼저 권장합니다. 잘못 적용돼도 자동 롤백되는 흐름이라 실수 비용이 줄어듭니다.

    Q3. DNS 문제와 라우팅 문제는 어떻게 구분하나요?

    A. IP로는 되는데 도메인만 안 되면 DNS 문제일 가능성이 커요. 반대로 외부 IP 자체가 안 되면 라우팅이나 게이트웨이를 먼저 보시면 됩니다.

  • [k8s] GKE WireGuard 네트워크 장애: 디버깅 및 해결 사례 연구

    [k8s] GKE WireGuard 네트워크 장애: 디버깅 및 해결 사례 연구

    [k8s] GKE WireGuard 네트워크 장애: 디버깅 및 해결 사례 연구

    안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 제가 최근 GKE (Google Kubernetes Engine) 환경에서 WireGuard를 쓰다가 겪었던 네트워크 장애 경험과, 그걸 해결하기 위해 삽질했던 과정을 솔직하게 공유해볼게요. 😅 아마 인프라 엔지니어 분들이라면 누구나 한 번쯤은 복잡한 네트워크 문제로 밤샘 디버깅을 해본 경험이 있으실 거더라고요. 특히 쿠버네티스(Kubernetes) 같은 동적인 환경에서는 예측 불가능한 변수가 많아 정말 골치 아플 때가 많습니다. 저도 이번에 제대로 당했는데, 결국 해결하고 나니 또 하나 배우는 게 있네요. 💡

    이번 글에서는 GKE에서 WireGuard를 운영하면서 발생했던 특정 네트워크 통신 문제를 어떻게 발견하고, 어떤 도구들을 사용해서 원인을 분석했으며, 최종적으로 어떤 방법으로 해결했는지 자세히 이야기해 드릴게요. 혹시 비슷한 문제로 고민하고 계신 분들에게 작은 도움이 되었으면 좋겠습니다.

    GKE 클러스터와 WireGuard VPN 터널이 연결된 전체 아키텍처의 개념도입니다.

    GKE와 WireGuard, 왜 함께 쓰려 했을까요?

    먼저, 왜 제가 GKE 클러스터에 WireGuard를 도입하려고 했는지부터 말씀드려야겠네요. 사실 GKE 자체적으로도 훌륭한 네트워크 기능들을 제공합니다. VPC Native 클러스터는 Private IP를 사용하고, Cloud VPN이나 Interconnect를 통해 온프레미스(On-Premise) 환경과도 쉽게 연결할 수 있거든요. 그럼에도 불구하고 제가 WireGuard를 선택한 이유는 크게 두 가지였습니다.

    • 성능과 간결함 (Performance & Simplicity): WireGuard는 다른 VPN 솔루션들에 비해 오버헤드(Overhead)가 정말 적고, 설정도 간결하더라고요. 홈랩에서 다양한 테스트를 해봤는데 성능이 정말 만족스러웠거든요. 복잡한 IPsec 설정에 비하면 정말 ‘혁명’ 같았습니다.
    • 특정 서비스의 보안 강화 및 멀티 클러스터 네트워킹: 특정 마이크로서비스(Microservice) 간의 통신을 더욱 안전하게 암호화하고, 나아가 다른 클라우드 환경이나 온프레미스에 있는 또 다른 쿠버네티스 클러스터와 안전하게 연결하고 싶었습니다. GKE의 기본 네트워킹을 보완하는 개념으로 접근한 거죠.

    쉽게 말해, GKE의 기본 네트워크 인프라는 유지하되, 그 위에 특정 트래픽에 대해서만 WireGuard 터널(Tunnel)을 만들어서 보안과 유연성을 동시에 잡으려는 시도였습니다. 🚀

    GKE에 WireGuard 실전 구현 (그리고 문제의 시작)

    GKE에 WireGuard를 배포하는 방법은 여러 가지가 있겠지만, 저는 각 노드(Node)에 WireGuard 인터페이스를 생성하고 설정을 유지하기 위해 DaemonSet을 활용했습니다. DaemonSet은 모든 노드에 파드(Pod)를 하나씩 배포해주니까, WireGuard를 인프라 레벨에서 관리하기에 딱 맞겠다 싶었거든요.

    간단한 흐름은 이렇습니다.

    1. WireGuard 커널 모듈이 설치된 베이스 이미지 준비 (또는 initContainer 활용).
    2. 각 노드의 파드에서 WireGuard 인터페이스(wg0) 생성 및 설정.
    3. 다른 피어(Peer)들과의 연결 설정 (PublicKey, Endpoint, AllowedIPs).
    4. 필요한 라우팅 테이블(Routing Table) 및 iptables 규칙 추가.

    초기 배포는 나름 순조로웠습니다. DaemonSet으로 WireGuard 파드를 띄우고, 각 노드에 wg0 인터페이스가 잘 생성되는 것을 확인했거든요. kubectl exec -it [wireguard-pod] -- wg show 명령어로 상태를 확인해보니 피어들과의 핸드셰이크(Handshake)도 정상적으로 이루어지는 것처럼 보였어요. 🎉

    apiVersion: apps/v1
    kind: DaemonSet
    metadata:
      name: wireguard-node
      namespace: kube-system
    spec:
      selector:
        matchLabels:
          app: wireguard-node
      template:
        metadata:
          labels:
            app: wireguard-node
        spec:
          hostNetwork: true # 노드 네트워크 직접 사용
          hostPID: true # 프로세스 네임스페이스 공유
          containers:
          - name: wireguard
            image: <your-wireguard-image>
            securityContext:
              privileged: true # 특권 모드 활성화
              capabilities:
                add:
                - NET_ADMIN
                - SYS_MODULE
            volumeMounts:
            - name: lib-modules
              mountPath: /lib/modules
              readOnly: true
            - name: wg-config
              mountPath: /etc/wireguard
            command: ["sh", "-c"]
            args: 
              - |-
                # WireGuard 커널 모듈 로드
                modprobe wireguard
                # WireGuard 인터페이스 및 라우팅 설정 스크립트 실행
                /usr/local/bin/setup_wireguard.sh
          volumes:
          - name: lib-modules
            hostPath:
              path: /lib/modules
          - name: wg-config
            configMap:
              name: wireguard-config
              items:
              - key: wg0.conf
                path: wg0.conf
    

    이렇게 설정하고, ConfigMap에 각 노드의 WireGuard 설정 파일(wg0.conf)을 넣어서 배포했습니다. 초기에는 내부 서비스 간 통신이나 외부 WireGuard 피어와의 통신도 잘 되는 듯했어요. 그런데….

    GKE DaemonSet을 이용한 WireGuard 설정 YAML과 WireGuard `wg0.conf` 파일 예시

    WireGuard 설정을 위한 DaemonSet YAML과 wg0.conf 파일의 구성 예시입니다.

    ⚠️ 삽질의 시작: 네트워크 장애 발생 및 쿠버네티스 트러블슈팅

    문제는 GKE 노드가 재시작되거나, 특정 네트워크 이벤트를 겪은 후에 발생했습니다. 갑자기 WireGuard 터널을 통해 통신해야 하는 파드들이 서로 연결되지 않는 현상이 나타나기 시작한 겁니다. 처음엔 ‘이게 뭔가?’ 싶었죠. 🤯

    증상

    • WireGuard 터널을 사용하는 파드(Pod) 간의 통신 불능.
    • wg show 명령으로는 터널이 up 상태이고 피어들도 연결된 것처럼 보임.
    • 하지만 ping이나 curl 같은 기본적인 네트워크 명령어조차 실패.

    디버깅 과정

    저는 다음과 같은 단계로 디버깅을 시작했습니다.

    1. wg show 확인: 가장 먼저 WireGuard 자체의 상태를 확인했습니다. 아까 말씀드렸듯이, 터널은 정상적으로 보였거든요. latest handshake 시간도 최근으로 업데이트되고 있었고요.
    2. ip a, ip route 확인: WireGuard 인터페이스(wg0)가 정상적으로 IP를 가지고 있는지, 그리고 WireGuard 서브넷(Subnet)으로 가는 라우팅 테이블이 제대로 설정되어 있는지 확인했습니다. 여기서 이상한 점을 발견했습니다. 특정 노드에서는 WireGuard 서브넷으로 가는 라우팅 규칙이 사라져 있거나, GKE의 자체 네트워킹과 충돌하는 듯한 규칙이 혼재되어 있었습니다.
    3. iptables -L -n -v 확인: 라우팅 문제가 아니라 iptables 규칙 문제일 수도 있겠다 싶어서 확인해봤거든요. GKE는 자체적으로 복잡한 iptables 규칙들을 관리하는데, 제가 추가한 WireGuard 관련 규칙들이 제대로 적용되지 않거나, GKE의 규칙에 의해 덮어씌워지는 경우가 있었습니다. 특히 FORWARD 체인에서 문제가 발생할 가능성이 높다고 판단했어요.
    4. tcpdump 활용: 실제 패킷(Packet)이 어디까지 도달하는지 확인하기 위해 tcpdump를 사용했습니다. WireGuard 인터페이스(wg0)와 물리 인터페이스(eth0 등)에서 동시에 패킷을 캡처해보니, 파드에서 WireGuard 서브넷으로 나가는 패킷은 wg0으로 들어오지만, 실제 암호화되어 외부로 나가는 트래픽이 없거나, 혹은 돌아오는 응답이 중간에 사라지는 것을 확인했습니다.
    5. GKE 노드 재시작 테스트: 가장 결정적인 단서는 GKE 노드를 재시작할 때마다 문제가 발생한다는 것이었어요. 노드가 재시작되면 WireGuard DaemonSet 파드도 다시 시작되지만, 그 과정에서 네트워크 설정의 영속성(Persistence)이 깨지는 것이 분명했거든요.

    원인 분석: GKE의 자체 네트워킹과 WireGuard의 미묘한 충돌

    결론적으로 원인은 GKE의 자체 네트워킹 시스템과 WireGuard의 라우팅 및 iptables 설정이 충돌하거나, 혹은 부팅 시점의 Race Condition 때문이라는 것을 알게 되었습니다. GKE는 각 노드에 파드 IP를 할당하고, 복잡한 라우팅과 iptables 규칙을 관리합니다. 여기에 WireGuard가 별도의 터널 인터페이스를 만들고 라우팅 규칙을 추가하면서, 특정 상황에서 GKE의 기존 규칙이 WireGuard 규칙을 덮어쓰거나, WireGuard 규칙이 GKE 트래픽을 예상치 못한 곳으로 보내버리는 문제가 발생했던 거였거든요.

    특히 노드 재시작 시, GKE의 네트워킹이 먼저 설정을 완료하고 나서 WireGuard DaemonSet이 동작하면서, WireGuard가 추가하는 규칙들이 제대로 자리 잡지 못하는 경우가 있었습니다. 😩

    ✅ 해결책: 네트워크 설정의 영속성 확보

    이 문제를 해결하기 위해 여러 방법을 시도해봤는데, 가장 효과적이었던 방법은 네트워크 설정의 영속성을 강화하고, WireGuard 설정 스크립트가 GKE의 네트워킹 초기화 이후에 안정적으로 실행되도록 하는 것이었어요.

    1. PostUp/PostDown 스크립트 활용 및 영속성 강화

    WireGuard는 wg0.conf 파일 내에 PostUp 및 PostDown 명령어를 정의할 수 있습니다. 저는 여기에 필요한 라우팅 규칙과 iptables 규칙을 명시적으로 추가하고, wg-quick up wg0 명령어를 통해 이 스크립트들이 실행되도록 했습니다. DaemonSet의 command 섹션에서 이 부분을 좀 더 견고하게 만들었거든요.

    # /usr/local/bin/setup_wireguard.sh 내용 예시
    
    #!/bin/bash
    
    # WireGuard 커널 모듈 로드
    modprobe wireguard
    
    # GKE CNI 초기화를 위해 잠시 대기 (노드마다 다를 수 있음)
    sleep 10
    
    # /etc/wireguard/wg0.conf 파일에 PostUp/PostDown 설정 포함
    # 예시: PostUp = ip route add 10.10.0.0/16 dev wg0; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT
    
    # WireGuard 인터페이스 활성화
    wg-quick up wg0
    
    # 설정이 유실되지 않도록 주기적으로 모니터링
    while true; do
      sleep 300
      # 필요시 wg0 상태 체크 및 재설정 로직
    done
    

    또한, WireGuard DaemonSet 파드가 시작될 때마다 이 setup_wireguard.sh 스크립트가 실행되도록 하고, 스크립트 내부에서 GKE 네트워킹이 완전히 준비될 때까지 일정 시간 대기하는 로직을 추가했습니다.

    2. AllowedIPs 정확한 설정

    wg0.conf 파일의 각 피어(Peer)에 대한 AllowedIPs 설정을 더욱 정확하게 지정했어요. 예를 들어, 특정 피어의 WireGuard IP만 허용하는 것이 아니라, 해당 피어 뒤에 있는 서브넷 전체를 AllowedIPs에 포함시켜 라우팅이 명확하게 이루어지도록 했습니다. 이건 WireGuard가 패킷을 어디로 포워딩할지 결정하는 정말 중요한 요소거든요.

    [Peer]
    PublicKey = <peer-public-key>
    Endpoint = <peer-endpoint>:51820
    AllowedIPs = 10.10.0.0/16, 192.168.10.0/24 # 정확한 서브넷 명시
    PersistentKeepalive = 25
    

    이 두 가지 방법을 적용하고 GKE 노드를 재부팅해보니, 드디어 문제가 해결되었습니다! 🎉 재부팅 후에도 WireGuard 터널을 통한 통신이 정상적으로 이루어졌고, 파드 간 연결도 원활했습니다. 정말이지 며칠 밤낮으로 씨름했던 문제가 해결되니 얼마나 기뻤는지 모릅니다.

    WireGuard 터널의 정상 작동을 확인하는 `wg show` 명령어 출력 및 네트워크 트래픽 모니터링 그래프

    WireGuard 터널의 정상 작동을 확인하는 wg show 명령어 출력과 정상적인 네트워크 트래픽 그래프입니다.

    마무리하며: GKE와 커스텀 네트워크 솔루션 통합 시 교훈

    이번 GKE WireGuard 네트워크 장애 해결 사례를 통해 제가 얻은 교훈은 다음과 같습니다.

    1. 관리형 서비스와 커스텀 솔루션의 경계 이해: GKE 같은 관리형 쿠버네티스 서비스는 자체적인 네트워크 관리 로직을 가지고 있습니다. 여기에 WireGuard 같은 커스텀 네트워크 솔루션을 통합할 때는 GKE의 기본 네트워킹 동작 방식을 정확히 이해하고, 충돌이 발생하지 않도록 주의해야 합니다. 특히 라우팅 테이블과 iptables 규칙은 가장 민감한 부분이거든요.
    2. 네트워크 설정의 영속성 확보: 쿠버네티스 노드는 언제든 재시작될 수 있습니다. 이때 수동으로 설정했던 네트워크 규칙들이 유실되지 않도록 DaemonSet의 command나 initContainer를 통해 영속적인 스크립트 실행 환경을 구축하는 것이 정말 중요합니다.
    3. 꼼꼼한 네트워크 디버깅 도구 활용: ip route, iptables, tcpdump, wg show 등 기본적인 네트워크 디버깅 도구들은 아무리 강조해도 지나치지 않습니다. 눈에 보이는 문제가 아니라, 실제 패킷의 흐름을 추적하는 것이 문제 해결의 핵심이거든요.
    4. 문서화 및 철저한 테스트: 복잡한 네트워크 설정을 적용할 때는 반드시 상세한 문서화를 해두고, 노드 재시작 등 다양한 시나리오에서 충분한 테스트를 거쳐야 합니다. 저도 이번에 테스트 시나리오를 좀 더 촘촘히 짰어야 했는데, 초기 검증이 미흡했던 점이 아쉬웠어요.

    GKE 운영 환경에서 커스텀 네트워크 솔루션을 도입하는 것은 분명 도전적인 일이지만, 잘만 활용하면 서비스의 유연성과 보안을 크게 향상시킬 수 있습니다. 이번 삽질 경험이 여러분의 GKE 운영에 작은 도움이 되기를 바라며, 다음번에는 또 다른 홈랩 삽질기를 들고 찾아오겠습니다! 😊

    GKE WireGuard 통합 시 고려해야 할 중요 체크리스트 인포그래픽

    GKE와 WireGuard를 통합할 때 고려해야 할 핵심 사항들을 요약한 체크리스트 인포그래픽입니다.