본문 바로가기
IT/Linux

[Linux] 리눅스 시스템 장애, 프로세스 비정상 종료 원인 분석 및 해결 사례 연구

by 수누다 2026. 7. 1.

[Linux] 리눅스 시스템 장애, 프로세스 비정상 종료 원인 분석 및 해결 사례 연구

운영 중인 서버에서 갑자기 애플리케이션이 내려가고, 재시작은 되는데 원인이 안 보일 때가 있죠. 저도 홈랩과 실무에서 이런 일을 여러 번 겪었습니다. 특히 리눅스 프로세스 비정상 종료는 겉으로 보기엔 단순한 장애처럼 보여도, 실제로는 메모리 부족, 시그널(signal, 운영체제가 프로세스에 보내는 제어 신호), 파일 디스크립터(file descriptor, 열린 파일 핸들), 권한 문제, 디스크 I/O 지연처럼 여러 원인이 겹쳐 있는 경우가 많거든요. 이번 글에서는 제가 직접 겪었던 실제 시스템 장애 사례를 바탕으로, 로그 분석원인 분석을 어떻게 진행했는지, 그리고 어떤 순서로 복구했는지 차근차근 정리해보겠습니다.

리눅스 프로세스 비정상 종료 분석 흐름을 보여주는 서버 운영 다이어그램

장애 발생부터 로그 수집, 원인 분석, 복구까지의 전체 흐름을 한눈에 보는 개요 이미지입니다.

1. 왜 리눅스 프로세스 비정상 종료가 까다로운가

처음 장애를 보면 보통 이렇게 생각합니다. "프로세스가 죽었네? 다시 올리면 되겠지." 근데 여기서 끝내면 같은 문제가 또 터지더라고요. 쉽게 말해 리눅스 프로세스 비정상 종료는 결과일 뿐이고, 진짜 봐야 하는 건 그 직전의 시스템 상태입니다.

  • OOM Killer(Out Of Memory Killer, 메모리 부족 시 커널이 프로세스를 강제 종료하는 기능)가 개입했는지
  • SIGKILL, SIGSEGV, SIGABRT 같은 종료 신호가 있었는지
  • systemd가 재시작 정책으로 계속 덮어쓰고 있지는 않은지
  • 애플리케이션 로그와 커널 로그의 시간이 정확히 맞는지
  • 디스크 공간이나 inode(아이노드, 파일 메타데이터 구조)가 바닥난 건 아닌지

여기서 중요한 포인트! 애플리케이션만 보면 절반만 보는 겁니다. 커널 로그, 서비스 매니저, 리소스 한계값까지 같이 봐야 퍼즐이 맞습니다.

2. 개념부터 정리: 프로세스는 왜 비정상 종료될까요?

저도 처음엔 헷갈렸는데, 원인을 큰 범주로 나누면 훨씬 수월합니다.

2-1. 대표적인 종료 원인

원인 설명 대표 징후
메모리 부족 커널이 OOM Killer를 실행 dmesg에 kill process 기록
세그멘테이션 폴트 잘못된 메모리 접근 Segmentation fault, core dumped
강제 종료 시그널 운영자, 스크립트, 오케스트레이터가 종료 exit code 137, signal 9
리소스 제한 초과 ulimit, nofile, nproc 초과 Too many open files
스토리지 문제 디스크 full, inode 고갈, I/O 지연 write 실패, journal 오류
권한/환경 문제 파일 권한, 환경 변수, 라이브러리 누락 permission denied, missing library

2-2. 종료 코드(exit code)도 힌트입니다

운영하다 보면 종료 코드 하나가 실마리가 되기도 합니다. 예를 들어 137은 보통 SIGKILL과 연결해서 보게 되고, 139는 Segmentation fault 가능성을 먼저 의심하게 되죠. 물론 이 숫자만 보고 단정하면 안 됩니다. 저는 항상 "종료 코드 확인 → journald 확인 → 커널 로그 확인" 순서로 갔습니다.

3. 사례 연구: 새벽에 반복된 시스템 장애, 처음엔 앱 버그인 줄 알았습니다

이번 사례 연구는 제가 홈랩에서 돌리던 API 서비스에서 실제로 겪은 패턴을 바탕으로 재구성한 내용입니다. 구조는 단순했습니다. Nginx(엔진엑스, 웹 서버) 뒤에 Python 기반 API가 있었고, systemd로 서비스 관리 중이었죠. 증상은 이랬습니다.

  1. 특정 시간대에 응답 지연이 먼저 발생했습니다.
  2. 이후 워커 프로세스가 하나씩 사라졌습니다.
  3. systemd가 자동 재시작했지만 잠시 뒤 다시 종료됐습니다.
  4. 모니터링에서는 CPU보다 메모리 사용량이 비정상적으로 치솟았습니다.

처음엔 코드 메모리 누수(memory leak, 메모리를 반환하지 못하는 현상)인 줄 알았거든요. 근데 로그를 차근차근 맞춰보니, 원인이 하나가 아니었습니다. 삽질 좀 했습니다 ㅎㅎ

4. 실전 분석 1단계: 로그 분석으로 타임라인부터 맞췄습니다

장애 분석에서 제가 제일 먼저 하는 건 "시간축 맞추기"입니다. 여러 로그를 뒤섞어 보면 정신없는데, 같은 시각 기준으로 정렬하면 보이는 게 많아집니다.

date
uptime
systemctl status myapi.service
journalctl -u myapi.service --since "2026-07-01 00:00:00" --until "2026-07-01 03:00:00"
journalctl -k --since "2026-07-01 00:00:00" --until "2026-07-01 03:00:00"

여기서 제가 확인한 포인트는 세 가지였습니다.

  • 서비스 종료 시각과 커널 로그 시각이 맞는지
  • 재시작 직전의 에러 메시지가 있는지
  • 같은 시간대에 다른 시스템 이벤트가 있었는지

실제로 써보니까 journalctl -u만 보면 부족한 경우가 많더라고요. journalctl -k로 커널 메시지를 같이 봐야 합니다.

journalctl -u myapi.service -n 50
journalctl -k -n 100 | egrep -i "killed process|oom|segfault|out of memory"
dmesg -T | egrep -i "killed process|oom|segfault"
리눅스 프로세스 비정상 종료 원인 분석을 위한 로그 분석 장면

서비스 로그와 커널 로그를 나란히 비교하면서 장애 시점을 맞춰보는 분석 화면을 표현한 이미지입니다.

5. 실전 분석 2단계: OOM, 파일 디스크립터, 디스크 상태를 함께 봤습니다

로그를 보니 일단 OOM 메시지가 보였습니다. 그런데 거기서 끝이 아니더라고요. 왜 메모리가 찼는지, 다른 자원도 같이 무너졌는지를 확인해야 재발을 막을 수 있거든요.

5-1. 메모리 상태 확인

free -h
vmstat 1 5
cat /proc/meminfo | egrep "MemAvailable|SwapTotal|SwapFree"
ps aux --sort=-%mem | head -20

여기서 특정 워커 프로세스가 메모리를 비정상적으로 많이 먹는 걸 확인했습니다. 근데 또 하나, 스왑(swap, 메모리 부족 시 디스크를 임시 메모리처럼 쓰는 공간)이 사실상 여유가 없더라고요. 메모리 압박이 생기면 커널이 버티지 못하고 강제 종료로 넘어가더라고요.

5-2. 파일 디스크립터와 프로세스 제한 확인

ulimit -n
cat /proc/$(pgrep -f myapi | head -1)/limits
lsof -p $(pgrep -f myapi | head -1) | wc -l
ss -tanp | head -20

혹시 이런 경험 있으신가요? 메모리만 보고 있었는데 실제론 소켓(socket, 네트워크 연결 끝점)이 쌓여서 Too many open files가 먼저 터지는 경우요. 저도 예전에 이걸 놓쳐서 원인 분석을 반나절 더 했었습니다.

5-3. 디스크와 inode 상태 확인

df -h
df -i
iostat -xz 1 3

로그 적재 서버에서는 디스크 공간보다 inode 고갈이 더 자주 문제였습니다. 파일이 너무 잘게 쪼개져 쌓이면 공간이 남아도 쓰기를 못 하거든요. 이 경우 프로세스가 로그 기록 실패 후 연쇄적으로 비정상 동작하는 경우도 있습니다.

6. 원인 분석 결과: 단일 원인이 아니라 복합 장애였습니다

분석 결과를 정리하면 이랬습니다.

  1. 배치 작업이 시작되면서 API 워커 메모리 사용량이 급증했습니다.
  2. 동시에 외부 요청이 몰리며 연결 수가 늘어났습니다.
  3. 애플리케이션의 로그 파일 회전(log rotation, 로그 파일 순환 관리)이 늦어져 쓰기 부하가 커졌습니다.
  4. 결국 커널이 OOM Killer를 실행해 워커 프로세스를 종료했습니다.

즉, 표면적으로는 리눅스 프로세스 비정상 종료였지만, 실제 뿌리는 메모리 압박 + 연결 누적 + 로그 처리 지연의 조합이었습니다. 처음엔 앱 버그 하나만 의심했는데, 시스템 전반을 봐야 답이 나오더라고요.

6-1. 제가 적용한 systemd 보완 설정

[Unit]
Description=My API Service
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/srv/myapi
ExecStart=/srv/myapi/venv/bin/gunicorn app:app --workers 2 --bind 0.0.0.0:8000
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
TimeoutStopSec=30
KillSignal=SIGTERM

[Install]
WantedBy=multi-user.target

KillSignalLimitNOFILE 설정은 생각보다 중요합니다. 종료 시그널을 정리해두면 강제 종료 전에 정리 작업을 할 여지가 생기고, 파일 디스크립터 한계를 현실적으로 맞춰두면 예기치 않은 장애를 줄일 수 있습니다.

리눅스 프로세스 비정상 종료 대응을 위한 systemd 설정 구성 이미지

서비스 재시작 정책, 파일 디스크립터 제한, 종료 시그널 처리 지점을 정리한 구성 이미지입니다.

7. ⚠️ 트러블슈팅: 실제로 자주 놓치는 포인트

여기부터는 제가 많이 당했던 부분들입니다. 진짜 사소해 보여도 장애 때는 이런 게 치명적이더라고요.

  • 애플리케이션 로그만 보고 커널 로그를 안 보는 실수
    OOM이나 segfault는 앱 로그에 안 남는 경우가 많습니다.
  • 재시작 성공을 복구 완료로 착각하는 실수
    systemd가 살려놨을 뿐, 원인은 그대로일 수 있습니다.
  • exit code를 안 보는 실수
    137, 139 같은 숫자가 꽤 큰 힌트가 됩니다.
  • 모니터링 해상도가 너무 낮은 실수
    1분 단위 그래프만 보면 급격한 스파이크를 놓치기도 합니다.

저는 이 문제 이후로 장애 체크리스트를 아예 만들어뒀습니다.

#!/usr/bin/env bash
set -eu

echo "== service status =="
systemctl status myapi.service --no-pager || true

echo "== recent service logs =="
journalctl -u myapi.service -n 50 --no-pager || true

echo "== kernel errors =="
journalctl -k -n 100 --no-pager | egrep -i "oom|killed process|segfault|error" || true

echo "== memory =="
free -h || true

echo "== disk =="
df -h || true
df -i || true

이런 식으로 기본 수집 스크립트를 만들어두면, 새벽 장애 때 멘탈이 덜 흔들립니다. 드디어 됐다! 싶었던 순간이 이 자동화 만든 뒤였어요.

8. 검증과 결과: 재현, 완화, 모니터링까지 묶어야 끝입니다

문제를 고친 뒤에는 반드시 검증해야 합니다. 저는 아래 순서로 확인했습니다.

  1. 동일 부하 조건에서 메모리 사용량이 다시 치솟는지 확인
  2. 서비스 재시작 없이 일정 시간 안정적으로 유지되는지 확인
  3. 로그 회전과 디스크 사용량이 정상 범위인지 확인
  4. 알람 임계치가 현실적으로 설정됐는지 확인
systemctl daemon-reload
systemctl restart myapi.service
systemctl is-active myapi.service
watch -n 2 'ps aux --sort=-%mem | head -10'

결과적으로 워커가 반복 종료되던 현상은 멈췄고, 피크 시간대에도 응답이 훨씬 안정적으로 유지됐습니다. 무엇보다 좋았던 건, 다음번 비슷한 시스템 장애가 와도 어디부터 볼지 기준이 생겼다는 점입니다.

리눅스 프로세스 비정상 종료 해결 후 안정화 결과 시각화

장애 조치 후 메모리 사용량이 안정되고 프로세스 재시작이 줄어든 결과를 보여주는 검증 이미지입니다.

9. 정리와 다음 단계: 리눅스 프로세스 비정상 종료 대응 체크리스트

이번 경험에서 다시 느낀 건 하나입니다. 리눅스 프로세스 비정상 종료는 프로세스 하나의 문제가 아니라, 시스템 전체의 신호를 읽는 문제라는 점이죠. 저도 처음엔 "왜 죽었지?"만 붙잡고 있었는데, 지금은 "죽기 전에 시스템이 어떤 상태였지?"를 먼저 봅니다.

  • 1순위: 종료 시각과 로그 타임라인 정렬
  • 2순위: 커널 로그에서 OOM, segfault, I/O 오류 확인
  • 3순위: 메모리, 파일 디스크립터, 디스크 상태 점검
  • 4순위: 재시작 정책과 서비스 제한값 재검토
  • 5순위: 재현 테스트와 모니터링 임계치 보완

마지막으로, 장애 원인을 찾았더라도 문서화는 꼭 해두세요. 다음 장애 때 팀 전체 속도가 완전히 달라집니다. 다음 글에서는 coredump(core dump, 비정상 종료 시 메모리 상태를 남긴 파일) 분석과 gdb 기반 세그폴트 추적 방법도 다뤄볼 예정입니다. 이전 글에서 다룬 systemd 서비스 운영 팁과 함께 보면 더 흐름이 잘 잡히실 거예요.

자주 묻는 질문

  • Q. 프로세스가 죽었는데 앱 로그가 비어 있습니다.
    A. 커널 로그와 journalctl -k를 먼저 보세요. OOM이나 segfault는 앱 로그에 안 남을 수 있습니다.
  • Q. 재시작되면 괜찮은 것 아닌가요?
    A. 아닙니다. 자동 재시작은 증상 완화일 뿐이고, 원인 분석이 안 되면 반복됩니다.
  • Q. 가장 먼저 볼 명령어는 뭔가요?
    A. 보통 systemctl status, journalctl -u, journalctl -k, dmesg -T 조합이면 출발점으로 충분합니다.
리눅스 프로세스 비정상 종료 대응 체크리스트 인포그래픽

리눅스 프로세스 비정상 종료 대응 절차를 빠르게 복습할 수 있도록 정리한 요약 인포그래픽입니다.