~서버가 로그 없이 죽었다 : 쿠버네티스를 디버거로 쓴 사연~ 스톡잇! 개발기 #9

2025. 12. 26. 21:21·모의투자
서버가 로그 없이 죽었다 : 쿠버네티스를 디버거로 쓴 사연

지난 글에서 FastAPI로 AI 서버를 구축했지만 서버가 요청을 받자마자 로그도 없이 비정상 종료되는 현상을 겪었습니다.

이번 글에서는 로그도 없이 비정상 종료되는 상황을 어떻게 해결했는지 이야기해보겠습니다.

왜 도커가 아닌 쿠버네티스인가?

FastAPI로 AI 서버 개발을 마친 후, 배포 단계에서 저는 Docker 컨테이너가 아닌 Kubernetes(K8s) 환경을 선택했습니다.

프로젝트 규모에 비해 과한 선택(Over-engineering)이 아니냐는 의문이 들 수 있지만, 여기에는 AI 서비스의 특성을 고려한 명확한 기술적 이유가 있었습니다.

첫째, 이기종 서버의 자원 격리 (Resource Isolation)

Stockit은 Spring Boot(메인)와 Python(AI) 두 개의 서버로 구성된 MSA를 지향하는 서비스 분리 구조(Modular Architecture)입니다.

  • 메인 서버 (Spring Boot): I/O Bound (DB 입출력, 웹소켓 통신)
  • AI 서버 (Python): CPU/Memory Bound (K-Means 클러스터링 연산) 

성격이 완전히 다른 두 서버를 한 인스턴스에 섞어두면 자원 경합이 발생합니다.

K8s를 통해 두 서버를 독립된 Pod로 격리하고, AI 서버에만 필요한 컴퓨팅 자원을 할당하는 것이 효율적이라 판단했습니다.


둘째, 부하 패턴에 따른 독립적 확장 (Independent Scaling)

주식 시장이 열리는 오전 9시에는 메인 서버의 트래픽이 폭증하지만, AI 서버는 한가합니다.  반대로 장 마감 후에는 AI 서버가 분석을 위해 바쁘게 돌아갑니다.

K8s의 HPA(Horizontal Pod Autoscaler)를 사용하면 각 서버의 부하 패턴에 맞춰 독립적으로 스케일 인&아웃이 가능합니다. 이를 통해 트래픽이 몰리는 시간에도 서비스에 문제가 생기지 않도록 만들고 평상시에는 최소한의 자원만을 사용하고 싶었습니다.

도커만 사용할 경우 이런 상황에서 수동으로 서버 인스턴스를 추가/제거 해야하기때문에 대응이 느리다고 생각했습니다.


셋째, 운영 복잡성 관리

Stockit은 단순한 모놀리식 서비스가 아닙니다. Spring Boot, Python AI, PostgreSQL(Stateful), Redis 등 다양한 컴포넌트가 얽혀 있습니다. 각기 다른 언어와 설정을 가진 컴포넌트들을 K8s는 선언적(Declarative) 방식으로 일관되게 관리할 수 있게 해줍니다.

kubectl apply -f

명령어 하나로 모든 컴포넌트를 배포하고 관리할 수 있어 운영 효율성이 크게 향상됩니다.


마지막, 안정성 (Stability)

가장 중요한 이유였습니다. 모의 투자 플랫폼은 24시간 안정적으로 운영되어야하는데 메인 서버가 오류로 인해 다운된다면 사용자는 저희 앱을 신뢰할 수 없게 됩니다.

그래서 K8s의 Deployment는 Pod의 상태를 지속적으로 모니터링 하고 서버가 죽었다면 자동으로 재시작(Self-healing)하고 준비 검사(Readiness Probe)를 통해 사용자에게 오류가 노출되는 것을 방지합니다

도커만 사용할 경우엔 컨테이너가 죽으면 수동으로 다시 실행하거나 복구 스크립트를 따로 작성해야합니다

이러한 이유로 저는 K8s 클러스터 위에 AI 서버를 배포하기로 결정했습니다.


문제 : 로그도 없는 curl (52) 오류

로컬 테스트를 마치고 Docker 이미지를 빌드한 후 터미널에서 curl 명령어를 날려 AI 서버에 분석 요청을 보냈습니다.

curl -X POST http://localhost:8000/portfolio/analyze ...

하지만 돌아온 건 에러 메시지였습니다.

curl: (52) Empty reply from server

Empty reply. 즉, 서버에 연결은 됐는데 응답이 오기도 전에 서버가 연결을 끊어버렸다는 뜻입니다. 서버가 요청을 받자마자 비정상 종료(Crash)된 것입니다.

더 큰 문제는 로그가 없다는 점이었습니다.

로컬 Docker 환경에서 컨테이너는 프로세스가 종료되면 즉시 멈춰버립니다(Exited).

docker run --rm 옵션이라도 썼다면 컨테이너 자체가 사라져 버리죠. docker logs를 확인해 봐도 아무런 에러 기록이 남아있지 않았습니다.

서버가 유언(Error Log)을 남길 틈도 없이 '즉사'해버린 것입니다.

말 그대로 원인을 알 수 없는 '블랙박스' 상태가 되어버렸습니다. 처음에는 맥북과 리눅스 서버 간의 아키텍처 차이로 인한 메모리 충돌(Segmentation Fault) 등을 의심하며 막막해했습니다.


가설 : 쿠버네티스를 '진단 도구'로 활용하다

이때 문득 스쳐 지나간 생각이 있었습니다. "로그를 볼 수 없다면, 로그를 볼 수 있는 환경으로 데려가자."

저는 앞서 구축해 둔 Kubernetes 클러스터를 운영 환경이 아닌 강력한 디버깅 도구로 활용하기로 했습니다.

  1. K8s는 Pod가 죽으면 "내 목표는 항상 떠 있게 유지하는 건데!"라고 생각하며 즉시 재시작을 시도합니다.
  2. 계속 죽고 계속 살리는 무한 반복 상태인 CrashLoopBackOff가 됩니다.
  3. 그리고 결정적으로, 죽기 직전의 로그(Previous Log)를 보관해 줍니다.

저는 AI 서버를 K8s에 배포했습니다. 예상대로 Pod는 시작되자마자 죽었고, CrashLoopBackOff 상태가 되었습니다.

저는  로그를 확인했습니다.

# Pod 상태 확인
kubectl get pods -l app=stock-analyze-server
# 결과:
# NAME                                  READY   STATUS             RESTARTS   AGE
# stock-analyze-deployment-xxxxx        0/1     CrashLoopBackOff   5          2m

# ⭐ 이전 Pod의 로그 확인 (핵심 명령어!)
kubectl logs stock-analyze-deployment-xxxxx --previous

 

해결 : Dockerfile 수정과 서비스 정상화

FileNotFoundError: [Errno 2] No such file or directory: 'ai_models/kmeans_model.pkl'

메모리 문제도, 환경 호환성 문제도 아니었습니다. 단순히 파일이 없어서 죽은 것이었습니다.

초기 프로젝트 구조 (문제 발생 시점)

AI/
├── app/                    # FastAPI 소스 코드
│   ├── main.py
│   └── ...
├── ai_models/              # ⚠️ 모델 파일 (app 폴더 밖!)
│   ├── kmeans_model.pkl
│   └── scaler.pkl
└── Dockerfile

원인은 Dockerfile의 COPY 명령어 실수였습니다.

FastAPI 코드가 있는 app 폴더만 복사하고, 정작 중요한 학습된 모델 파일들이 있는 ai_models 폴더를 복사하지 않았던 것입니다. 서버 시작 시 모델 로드 코드가 실행되면서 파일을 찾지 못해 즉시 종료되었던 것이죠.

원인을 알았으니 해결은 간단했습니다. Dockerfile 명령어를 수정하여 프로젝트의 모든 폴더(모델 포함)를 복사하도록 변경했습니다.

(참고: 문제 해결 후 프로젝트 구조를 개선하여 `ai_models`를 `app/` 폴더 안으로 이동시켰습니다.)

COPY . /app/

이미지를 다시 빌드하고 배포하자, CrashLoopBackOff 상태였던 Pod가 거짓말처럼 Running (1/1) 상태로 변했습니다.

docker build -t choij17/stock-ai-analyze-server:0.1.17 .

# Kubernetes에 배포
kubectl apply -f k8s/deployment.yaml

# Pod 상태 확인
kubectl get pods -l app=stock-analyze-server
# 결과:
# NAME                                  READY   STATUS    RESTARTS   AGE
# stock-analyze-deployment-xxxxx        1/1     Running   0          30s
```

결과 : 정상적으로 분석 성공

 통합 테스트 결과, 학습 데이터에 없던 'SK하이닉스'를 넣어도 정확히 [초대형 우량주]로 분석해냈습니다.

curl -X POST http://localhost:8000/portfolio/analyze...


마치며: 개발과 운영의 경계에서

단순히 파일을 복사해서 해결했지만, 이 경험은 저에게 기술적으로 큰 교훈을 주었습니다.

첫째, AI 모델은 코드만으로 돌아가지 않습니다. 모델 파일(Weights), 실행 환경(Docker), 그리고 리소스가 완벽하게 일치해야 비로소 서비스가 됩니다.

둘째, 인프라 기술(Kubernetes)이 개발자에게 강력한 무기가 될 수 있습니다. 쿠버네티스를 단순히 '운영팀의 도구'로만 알았다면, 저는 로컬에서 원인을 찾느라 며칠 밤을 새웠을지도 모릅니다.

셋째, '로그 없는 죽음'은 최악의 디버깅 상황입니다. K8s의 CrashLoopBackOff와 kubectl logs --previous는 이러한 상황에서 생명줄과 같은 도구였습니다.

이제 서버는 안정적으로 돌아가지만 또 다른 불안함이 엄습했습니다.

"지금은 잘 되지만, 사용자가 몰리면 AI 서버가 멈추지 않을까? 메모리가 부족하진 않을까?"

K8s를 통해 서버를 살려내는 건 성공했지만, 서버가 '왜' 아픈지, '얼마나' 바쁜지는 아직 알 수 없습니다.

다음 글에서는 AI 서비스의 안정성을 실시간으로 감시하고 데이터 기반으로 운영하기 위한 "AI가 멍때리지 않게 감시하기 : PLG 모니터링"를 다루겠습니다.

'모의투자' 카테고리의 다른 글

~업데이트를 자동화하다 : CI/CD 파이프라인 구축기~ 스톡잇! 개발기 #11  (1) 2025.12.27
~AI가 멍때리지 않게 감시하기 : PLG 모니터링~ 스톡잇! 개발기 #10  (1) 2025.12.26
~파이썬 AI, 웹 서버가 되다 : MSA와 FastAPI 아키텍처에 대한 고찰~ 스톡잇! 개발기 #8  (0) 2025.12.26
~세상의 모든 주식을 분석하다 : 조회에서 예측으로~ 스톡잇! 개발기 #7  (0) 2025.12.26
~투자 성향을 캐릭터로 만들다: 페르소나 매칭 설계~ 스톡잇! 개발기 #6  (0) 2025.12.26
'모의투자' 카테고리의 다른 글
  • ~업데이트를 자동화하다 : CI/CD 파이프라인 구축기~ 스톡잇! 개발기 #11
  • ~AI가 멍때리지 않게 감시하기 : PLG 모니터링~ 스톡잇! 개발기 #10
  • ~파이썬 AI, 웹 서버가 되다 : MSA와 FastAPI 아키텍처에 대한 고찰~ 스톡잇! 개발기 #8
  • ~세상의 모든 주식을 분석하다 : 조회에서 예측으로~ 스톡잇! 개발기 #7
MacArthur17
MacArthur17
Mac(Arthur)hine Learning
  • MacArthur17
    Human Learning LAB
    MacArthur17
  • 전체
    오늘
    어제
    • 분류 전체보기 (67)
      • 모의투자 (32)
      • 분산 시스템 (2)
      • 테커 부트캠프 (20)
      • 백준문제 (24)
      • 공부일기 (0)
      • 후기 (3)
      • 팁 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
MacArthur17
~서버가 로그 없이 죽었다 : 쿠버네티스를 디버거로 쓴 사연~ 스톡잇! 개발기 #9
상단으로

티스토리툴바