CPU 100% 서버 장애를 겪고 만든 자동 대응 스크립트

// INCIDENT REPORT · 03:00 AM

새벽, CPU 100% 서버가 뻗었다

그리고 프로세스 자동으로 죽였다가 서비스까지 날렸다 😇

그날 밤 무슨 일이 있었냐면

새벽 3시. 자고 있는데 폰이 울렸다. Slack 알림이 아니라 고객사 전화였다.

"서비스 안 되는데요?"

노트북 열고 접속했더니 대충 기억이 안나는데 이런 상태였다.

top
top - 03:14:22 up 47 days, 6:32, 1 user, load average: 24.31, 22.18, 19.44
Tasks: 312 total, 8 running, 304 sleeping
%Cpu(s): 99.8 us, 0.1 sy, 0.0 ni, 0.0 id
MiB Mem : 15,872.0 total, 142.3 free, 14,891.2 used
PID USER %CPU %MEM COMMAND
18842 app 99.4 43.2 python3 worker.py
18901 app 0.3 8.1 gunicorn: worker
891 root 0.1 0.3 sshd

python3 worker.py 가 CPU를 99.4% 먹고 있었다. 배치 작업이 무한루프에 빠진 거였다.

새벽 3시 반쯤 겨우 정리하고 나서 생각했다. "다음엔 자동으로 감지해서 알려줘야겠다."

근데 거기서 실수를 하나 했다.

프로세스 자동으로 죽였다가 서비스 날린 썰

처음 만든 스크립트에 이런 로직을 넣었다.

# ❌ 이렇게 하면 안 됩니다
if cpu_percent >= 95:
    for proc in top_cpu_processes:
        if proc.cpu_percent > 80:
            proc.kill()  # 🔥 이게 문제였다

논리적으로는 맞다. CPU 95% 넘으면 주범 프로세스 죽이면 되잖아. 근데 현실은 달랐다.

💀
gunicorn worker가 같이 죽었다
CPU 높은 프로세스 죽였더니 마침 그 옆에서 실제 API 요청 처리하던 worker도 같이 날아갔다. 장애 해결하려다 장애 심화.
😱
스크립트가 판단을 못 한다
배치 작업인지 API 서버인지 DB인지 스크립트는 모른다. CPU 높다고 무조건 죽이면 진짜 중요한 프로세스도 같이 간다.
⚠️
kill은 사람이 해야 한다
상황 판단은 결국 사람 몫이다. 스크립트는 빠르게 알려주고, 안전한 것만 자동으로 처리하면 충분하다.

그래서 다시 만든 v2 — 철학을 바꿨다

자동 킬을 전부 제거하고 역할을 명확히 나눴다.

# v2 철학
스크립트가 할 일 감지 + 안전한 조치 + 빠른 알림
사람이 할 일 판단 + kill + 근본 원인 해결

스크립트가 자동으로 하는 것 (안전한 것만)

✅ 페이지 캐시 정리
서비스 영향 없이 메모리 일부 회수. drop_caches 사용.
✅ 스왑 재활성화
단편화된 스왑 정리. swapoff -a && swapon -a
⚙️ 서비스 재시작 (선택)
설정 파일에서 명시한 서비스만. 기본값 비활성화.
❌ 프로세스 자동 kill
제거. 판단은 사람이 한다.

Slack 알림에 뭐가 오냐면

새벽에 알림 받았을 때 멍한 상태로 바로 조치할 수 있게 복붙용 명령어까지 알림에 넣었다.

🚨 [CRITICAL] 서버 이상 감지 — production-server-01
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CPU 99.4% 🔴 Memory 88.2% ⚠️
Load 24.31 / 22.18 / 19.44
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔥 CPU 주범 TOP 3
`python3` (PID 18842) — 99.4%
`gunicorn` (PID 18901) — 0.3%
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 즉시 조치 가이드 (복붙용) 예시
top -p 18842
ps aux | grep python3
kill -15 18842 # graceful
kill -9 18842 # 최후 수단

설치 & 사용법

# 1. 설치
pip install psutil requests

# 2. CONFIG 수정
vim auto_response.py
# SLACK_WEBHOOK, SERVER_NAME 입력

# 3. 현재 상태 확인
python3 auto_response.py --status

# 4. Slack 테스트
python3 auto_response.py --test-slack

# 5. crontab 등록 (5분마다)
crontab -e
*/5 * * * * python3 /opt/scripts/auto_response.py

배운 것

자동화는 만능이 아니다. 판단이 필요한 영역은 사람에게 남겨야 한다. 스크립트는 사람이 더 빠르게, 덜 멍한 상태로 판단할 수 있도록 돕는 도구일 뿐이다.

새벽 3시에 알림 받아서 Slack 열었을 때 주범 PID랑 복붙 명령어가 바로 있으면, 머리가 반쯤 잠든 상태에서도 30초 안에 조치할 수 있다. 그게 이 스크립트의 목표다.

// 교훈
자동화는 판단을 대체하지 못한다.
속도를 높여줄 뿐이다.
전체 코드 → endofpython.blogspot.com > Python참고
Tags: #장애대응 #Python #자동화 #DevOps #AWS #삽질기록