
Uvicorn 서버 10회차 : 실전 트러블슈팅 & 점검 루틴(운영자 관점)
요약
운영에서 장애가 무서운 이유는 기술이 어려워서가 아니라, 급한 상황에서 “어디부터 봐야 하는지”가 흔들리기 때문입니다. 이번 회차는 Uvicorn(FastAPI/ASGI) 서버 운영 중 자주 터지는 문제를 정리하고, 장애가 발생했을 때의 점검 순서를 루틴으로 고정하는 데 목적이 있습니다.
핵심은 복잡한 원인을 한 번에 맞히는 것이 아니라, 증상을 빠르게 분류하고(네트워크/프로세스/프록시/리소스), 중간 확인 지점을 통해 범위를 좁혀가는 것입니다. 이를 위해 포트 충돌, import 실패, 프록시 502를 일부러 만들어보고, 같은 루틴으로 복구하는 실습을 진행합니다.
목차
| 0 | 학습 목표 |
| 1 | 핵심 개념 |
| 2 | 장애 대응 루틴(어디부터 볼지) |
| 3 | 디버깅 전략(중간 결과 확인) |
| 4 | 실습: 장애 시나리오 3개 재현→복구 |
| 5 | 산출물: Uvicorn 운영 점검 체크리스트(내 런북 v1) |
| 6 | 블로그 최적화 정보 |
학습 목표
이번 회차의 목표는 “장애가 났을 때 첫 5분을 안정적으로 보내는 방법”을 만드는 것입니다. 장애 상황에서 어디부터 확인할지 루틴을 확립하고, 포트 충돌·방화벽·프록시·워커/메모리·타임아웃이라는 대표 원인들을 증상별로 연결할 수 있도록 합니다. 마지막에는 운영자가 바로 복사해 쓰는 형태로 “Uvicorn 운영 점검 체크리스트(런북 v1)”를 완성합니다.
핵심 개념
- 포트 충돌: 이미 다른 프로세스가 같은 포트를 듣고 있어 Uvicorn이 바인딩에 실패하는 상황입니다.
- 방화벽/보안그룹: 서버 내부는 열려 있지만 외부에서 접근이 차단되어 “서버가 죽은 것처럼 보이는” 상황입니다.
- 프록시 설정(Nginx): 외부 요청은 Nginx가 받고 Uvicorn으로 전달하는데, upstream 연결 실패나 타임아웃으로 502/504가 발생할 수 있습니다.
- 워커/메모리: 워커 수 과다, 메모리 부족(OOM), 스왑 과다로 응답 지연·프로세스 종료가 발생할 수 있습니다.
- 타임아웃: 프록시 타임아웃, 클라이언트 타임아웃, 애플리케이션 처리 지연이 엮여 장애처럼 보입니다.
상세 설명
2. 장애 대응 루틴(운영자 관점): “증상 분류 → 범위 축소”
장애가 발생했을 때 제일 먼저 할 일은 원인을 추측하는 것이 아니라, 범위를 빠르게 줄이는 것입니다. 아래 루틴은 Uvicorn 단독 실행이든, Nginx 뒤에 붙은 구성(가장 흔한 운영 구성)이든 공통으로 적용됩니다.
2-1. 1분 트리아지: 지금 문제는 “어디 구간”인가?
가장 먼저 구간을 나눕니다. ① 애플리케이션(코드/임포트) ② Uvicorn 프로세스(실행/바인딩) ③ 서버 네트워크(포트/방화벽) ④ 프록시(Nginx upstream) ⑤ 리소스(메모리/CPU/워커)입니다. 구간을 나누면, “요청이 어디까지 도달하는지”를 중간 확인으로 판단할 수 있습니다.
2-2. 5분 루틴: 아래 순서로만 확인해도 대부분 해결됩니다
운영에서 가장 강력한 전략은 “항상 같은 순서로 보는 것”입니다. 아래는 실전에서 가장 효율적인 순서입니다.
| 순서 | 무엇을 확인? | 의미(범위 축소) |
|---|---|---|
| 1 | 프로세스가 살아 있나? | 죽어 있으면 네트워크/프록시보다 먼저 실행 실패(임포트, 설정, OOM)부터 봅니다. |
| 2 | 포트를 듣고 있나? | 살아 있는데 포트가 안 열려 있으면 바인딩 실패, 잘못된 host/port, 권한 문제를 의심합니다. |
| 3 | 서버 내부에서 응답이 오나? | 로컬 curl이 되면 앱/프로세스는 정상이고, 외부/방화벽/프록시 쪽으로 범위를 좁힙니다. |
| 4 | Nginx가 upstream에 붙나? | 502는 대부분 “Nginx → Uvicorn” 연결 구간의 문제입니다(포트/주소/리스닝/타임아웃). |
| 5 | 리소스/타임아웃이 병목인가? | 느려졌다면 워커/메모리/타임아웃/DB 지연 등 성능 요인을 점검합니다. |
3. 디버깅 전략(중간 결과 확인): “한 단계씩 통과시키기”
초보자에게 가장 쉬운 디버깅은 “중간 확인 지점”을 만들고, 그 지점까지는 정상인지 확인하는 것입니다. Uvicorn 운영에서는 중간 확인 지점이 명확합니다. (1) systemd/프로세스 상태 (2) 포트 리스닝 (3) 로컬 curl (4) Nginx 로그 (5) 외부 curl입니다. 한 단계씩 통과시키면 원인이 거의 자동으로 좁혀집니다.
3-1. 운영에서 자주 쓰는 확인 명령(기본 세트)
# 1) 서비스/프로세스 상태(운영에서 제일 먼저)
sudo systemctl status uvicorn
ps aux | grep -E "uvicorn|gunicorn" | grep -v grep
# 2) 포트 리스닝 확인(주소/포트/프로세스까지 같이)
sudo ss -lntp | grep ":8000"
# 또는
sudo lsof -i :8000
# 3) 서버 내부에서 앱이 응답하는지(프록시 제외)
curl -i [http://127.0.0.1:8000/health](http://127.0.0.1:8000/health)
# 4) 로그(최근 200줄만 빠르게)
sudo journalctl -u uvicorn -n 200 --no-pager
sudo tail -n 200 /var/log/nginx/error.log
이 네 덩어리를 1분 안에 실행할 수 있으면, 장애 대응 속도가 체감될 정도로 빨라집니다. 이제 실습으로 실제 장애를 만들고 같은 방식으로 복구해보겠습니다.
4. 실습: 장애 시나리오 3개를 일부러 만들어 복구
실습은 “일부러 실패를 만든 뒤, 루틴대로 복구”하는 방식입니다. 각 시나리오마다 목표는 같습니다. 증상을 확인하고, 중간 확인 지점을 통해 원인 구간을 좁히고, 정확한 복구 조치를 적용합니다. 아래 예시는 Linux 기준이며, 포트는 8000, 엔드포인트는 /health를 가정합니다.
실습 준비: 최소 구성(예시 FastAPI 앱)
# app.py
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health():
return {"status": "ok"}
# 실행(로컬 확인)
uvicorn app:app --host 127.0.0.1 --port 8000
curl -i http://127.0.0.1:8000/health
시나리오 A: 포트 점유(포트 충돌)로 Uvicorn 실행 실패
의도적으로 장애 만들기는 간단합니다. 8000 포트를 다른 프로세스가 먼저 사용하도록 만든 뒤, Uvicorn을 같은 포트로 띄우면 됩니다.
# 1) 일부러 8000 포트를 점유(예: 임시 서버)
python -m http.server 8000
# 2) 다른 터미널에서 Uvicorn 실행(실패해야 정상)
uvicorn app:app --host 127.0.0.1 --port 8000
대표 증상은 “Address already in use” 또는 바인딩 실패 메시지입니다. 이제 루틴대로 복구합니다.
# 1) 포트 누가 쓰는지 확인
sudo ss -lntp | grep ":8000"
# 또는
sudo lsof -i :8000
# 2) 점유한 프로세스 종료(실습용)
# 출력에서 PID를 확인한 뒤 종료
kill -TERM
# 안 되면(실습 환경) 강제 종료
kill -KILL
# 3) 다시 실행 후 로컬 확인
uvicorn app:app --host 127.0.0.1 --port 8000
curl -i [http://127.0.0.1:8000/health](http://127.0.0.1:8000/health)
운영 팁은 “왜 점유됐는지”까지 남기는 것입니다. 배포 스크립트가 중복 실행됐는지, 이전 프로세스가 종료되지 않았는지, systemd가 재시작 루프에 빠졌는지까지 확인하면 재발 방지가 됩니다.
시나리오 B: import 실패(모듈/경로 문제)로 프로세스가 바로 종료
Uvicorn은 app:app처럼 “모듈:객체”를 임포트해 실행합니다. 이때 모듈 이름이 틀리거나, 작업 디렉터리가 달라 모듈을 찾지 못하면 프로세스가 시작하자마자 종료됩니다. 운영에서는 “서비스가 살아있지 않다”로 보이기 때문에 1번 단계(프로세스/로그)에서 바로 잡아야 합니다.
의도적으로 장애 만들기는 app.py 파일명을 바꾸거나, 존재하지 않는 모듈을 지정하면 됩니다.
# 1) 일부러 잘못된 모듈로 실행
uvicorn wrong_module:app --host 127.0.0.1 --port 8000
복구 루틴은 “로그로 임포트 에러 확인 → 파이썬에서 직접 import 테스트 → 실행 명령 수정” 순서가 가장 빠릅니다.
# 1) 에러 로그 확인(운영이면 systemd/journalctl)
sudo journalctl -u uvicorn -n 200 --no-pager
# 2) 현재 디렉터리/가상환경에서 import가 되는지 직접 테스트
python -c "import app; print('import ok')"
# 3) 올바른 모듈:객체로 실행
uvicorn app:app --host 127.0.0.1 --port 8000
추가로 자주 막히는 포인트는 작업 디렉터리입니다. systemd로 실행하면 WorkingDirectory가 다를 수 있어 “로컬에서는 되는데 서비스로는 안 됨”이 발생합니다. 이때는 systemd 서비스 파일에 WorkingDirectory를 정확히 주거나, uvicorn 옵션으로 앱 디렉터리를 지정하는 방식이 도움이 됩니다.
# 예: 앱 디렉터리를 명시(환경에 따라 사용 가능)
uvicorn app:app --app-dir /srv/myapp --host 127.0.0.1 --port 8000
시나리오 C: 프록시 502(Bad Gateway)로 외부 요청 실패
운영에서 가장 흔한 형태는 “Uvicorn은 살아 있는데, 외부에서는 502가 뜬다”입니다. 이 경우 대부분 원인은 Nginx가 upstream(Uvicorn)과 연결을 못하거나, 연결은 되지만 타임아웃/응답 크기/헤더 문제로 실패하는 것입니다. 초보자에게는 502가 복잡해 보이지만, 루틴대로 보면 오히려 가장 빨리 해결되는 축에 속합니다.
4-3-1. 의도적으로 502 만들기(업스트림 포트 불일치)
예를 들어 Nginx는 8000으로 보내는데, Uvicorn이 8001에서 떠 있다면 502가 납니다. 실습에서는 Uvicorn을 8001로 실행한 뒤, Nginx는 8000으로 보내는 구성으로 일부러 mismatch를 만듭니다.
# 1) Uvicorn을 다른 포트로 실행
uvicorn app:app --host 127.0.0.1 --port 8001
# 2) (가정) Nginx는 8000 upstream으로 설정되어 있어 502가 발생하는 상태
4-3-2. Nginx 설정 예시(정상 예시)와 확인 포인트
# /etc/nginx/sites-available/myapp.conf (예시)
server {
listen 80;
server_name example.com;
location / {
proxy_pass [http://127.0.0.1:8000](http://127.0.0.1:8000);
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60;
}
}
4-3-3. 502 복구 루틴(가장 빠른 순서)
502는 “프록시가 업스트림에 붙는지”만 확인하면 대부분 끝납니다. 아래 순서로 진행하면 초보자도 안정적으로 복구할 수 있습니다.
# 1) 서버 내부에서 Uvicorn이 실제로 응답하는지 확인(프록시 제외)
curl -i http://127.0.0.1:8000/health
# 2) 안 되면: Uvicorn 포트/프로세스부터 다시 확인
sudo ss -lntp | grep ":8000"
sudo journalctl -u uvicorn -n 200 --no-pager
# 3) 되는데도 502면: Nginx 에러 로그로 upstream 원인 확인
sudo tail -n 200 /var/log/nginx/error.log
# 4) Nginx 설정 검사 후 리로드
sudo nginx -t
sudo systemctl reload nginx
실습에서 mismatch를 만들었다면 해결은 간단합니다. Nginx의 proxy_pass 포트를 Uvicorn 실제 포트(8001)로 맞추거나, Uvicorn을 Nginx가 기대하는 포트(8000)로 다시 띄우면 됩니다. 운영에서는 “포트가 왜 달라졌는지(배포 설정/환경변수/서비스 파일)”까지 확인해야 재발이 줄어듭니다.
보너스: 502/504가 “가끔만” 뜬다면 타임아웃·리소스를 의심합니다
항상 502가 아니라, 트래픽이 늘 때만 502/504가 뜬다면 단순 연결 실패보다 타임아웃이나 리소스 병목일 가능성이 큽니다. 이때는 proxy_read_timeout, 앱 처리시간, DB 지연, 워커 수, 메모리 사용량을 함께 확인해야 합니다. 특히 워커를 과도하게 늘리면 메모리 사용량이 급증해 OOM으로 프로세스가 죽고, 그 순간 502가 튀는 패턴이 자주 발생합니다.
5. 산출물: Uvicorn 운영 점검 체크리스트(내 런북 v1)
아래 표는 운영자가 장애 시 그대로 따라할 수 있는 런북 형태로 정리한 체크리스트입니다. 핵심은 “항상 같은 순서로” 점검하고, 중간 확인 결과에 따라 다음 액션을 고정하는 것입니다.
| 구간 | 점검(명령/확인) | 정상 기준 | 이상 시 조치 |
|---|---|---|---|
| 프로세스 | systemctl status uvicorn, journalctl -u uvicorn -n 200 | active(running), 에러 스택 없음 | 임포트/환경변수/WorkingDirectory 확인 후 재시작, 에러 원인부터 제거 |
| 포트 | ss -lntp | grep :8000, lsof -i :8000 | LISTEN이 uvicorn(또는 gunicorn)으로 잡힘 | 포트 점유 프로세스 종료 또는 포트 변경, 서비스 재기동 |
| 로컬 응답 | curl -i http://127.0.0.1:8000/health | 200 OK(또는 정상 응답 코드) | 앱 라우팅/임포트/설정 문제 또는 서비스 미기동, 로그로 역추적 |
| 프록시 | tail -n 200 /var/log/nginx/error.log, nginx -t | upstream 연결 에러 없음, 설정 테스트 OK | proxy_pass 주소/포트/host 바인딩 확인, Nginx reload |
| 방화벽 | 외부 접근만 실패 시 보안그룹/방화벽 규칙 확인 | 필요 포트(80/443) 인바운드 허용 | 보안그룹/방화벽 규칙 수정 후 외부 curl 재검증 |
| 리소스 | free -m, top/htop, dmesg(또는 journal OOM 로그) | 메모리 여유, OOM 흔적 없음 | 워커 수 조정, 메모리 증설/제한, 병목(DB/외부 API) 점검 |
| 타임아웃 | Nginx proxy_read_timeout, 앱 처리시간, 외부 API 지연 | 응답이 타임아웃 전에 완료 | 타임아웃 상향(필요 시), 느린 구간 프로파일링/캐시/비동기 처리 |
런북 v1 미니 스크립트(장애 첫 60초용)
# 1) 서비스/로그
sudo systemctl status uvicorn
sudo journalctl -u uvicorn -n 80 --no-pager
# 2) 포트/로컬 헬스체크
sudo ss -lntp | grep ":8000"
curl -i [http://127.0.0.1:8000/health](http://127.0.0.1:8000/health)
# 3) 프록시(502 의심 시)
sudo nginx -t
sudo tail -n 80 /var/log/nginx/error.log
추가로 생각해볼 점
운영 루틴이 잡히면 다음 단계는 “재발 방지”입니다. 장애를 복구하는 것과, 같은 장애가 다시 안 나게 만드는 것은 별개입니다. 포트 충돌이 자주 난다면 배포/재시작 스크립트가 중복 실행되지 않게 잠금(lock)이나 프로세스 관리(systemd, supervisor)를 강화하는 편이 좋고, 502가 간헐적으로 난다면 타임아웃을 무작정 늘리기 전에 병목 구간을 먼저 찾아야 합니다. 런북 v1을 실제 운영에서 쓰면서, 여러분 환경에 맞게 체크 항목을 추가해 “내 런북 v2”로 발전시키는 것이 가장 좋은 학습 루틴입니다.
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.