📚FOS Study
홈카테고리
홈카테고리

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • agents 페이지로 이동
    • langgraph 페이지로 이동
    • BMAD Method — AI 에이전트로 애자일 개발하는 방법론
    • Claude Code의 Skill 시스템 - 개발자를 위한 AI 자동화의 새로운 차원
    • Claude Code를 11일 동안 쓴 결과 — 데이터로 본 나의 사용 패턴
    • Claude Code 멀티 에이전트 — Teams
    • 하네스 엔지니어링 — 오래 실행되는 AI 에이전트를 위한 설계
    • 멀티모달 LLM (Multimodal Large Language Model)
    • AI 에이전트와 함께 MVP 만들기 — dooray-cli 사례
  • architecture 페이지로 이동
    • 캐시 설계 전략 총정리
    • 디자인 패턴
    • 분산 트랜잭션
  • css 페이지로 이동
    • FlexBox 페이지로 이동
  • database 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • redis 페이지로 이동
    • 김영한의-실전-데이터베이스-설계 페이지로 이동
    • 커넥션 풀 크기는 얼마나 조정해야할까?
    • 인덱스 - DB 성능 최적화의 핵심
    • 역정규화 (Denormalization)
    • 데이터 베이스 정규화
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • k8s-in-action 페이지로 이동
    • monitoring 페이지로 이동
    • Envoy Proxy
    • Graceful Shutdown
  • go 페이지로 이동
    • Go 언어 기본 학습
  • http 페이지로 이동
    • HTTP Connection Pool
  • interview 페이지로 이동
    • 210812 페이지로 이동
    • 뱅크샐러드 AI Native Server Engineer
    • CJ 올리브영 커머스플랫폼유닛 Back-End 개발 지원 자료
    • 마이리얼트립 - Platform Solutions실 회원주문개발 Product Engineer
    • NHN 서비스개발센터 AI서비스개발팀
    • nhn gameenvil console backend 직무 인터뷰 준비
    • 면접을 대비해봅시다
    • 토스증권 Server Developer (Platform) 지원 자료
    • 토스증권 Server Developer (Product) 지원 자료
    • 토스뱅크 Server Developer (Product) 지원 자료
    • Tossplace Node.js Developer
    • 토스플레이스 Node.js 백엔드 컬처핏
  • java 페이지로 이동
    • jdbc 페이지로 이동
    • opentelemetry 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • 더_자바_코드를_조작하는_다양한_방법 페이지로 이동
    • Java의 로깅 환경
    • MDC (Mapped Diagnostic Context)
    • OpenTelemetry 란 무엇인가?
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • Data_Structures_and_Algorithms 페이지로 이동
    • Heap 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node.js
    • npm vs pnpm 선택기준은 무엇인가요?
    • `setImmediate()`
  • kafka 페이지로 이동
    • Kafka 기본
    • Kafka를 사용하여 **데이터 정합성**은 어떻게 유지해야 할까?
    • 메시지 전송 신뢰성
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • tmux — Terminal Multiplexer
  • network 페이지로 이동
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • react 페이지로 이동
    • JSX 페이지로 이동
    • VirtualDOM 페이지로 이동
    • v16 페이지로 이동
  • resume 페이지로 이동
    • CJ 올리브영 지원 문항
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
📚FOS Study

개발 학습 기록을 정리하는 블로그입니다.

바로가기

  • 홈
  • 카테고리

소셜

  • GitHub
  • Source Repository

© 2025 FOS Study. Built with Next.js & Tailwind CSS

목록으로 돌아가기
📁task/ ai-service-team

OCR 서버 배포·스케일인 시 503 에러 수정 — Graceful Shutdown 미적용

약 3분
2026년 4월 2일
GitHub에서 보기

OCR 서버 배포·스케일인 시 503 에러 수정 — Graceful Shutdown 미적용

진행 기간: 2026.04

Graceful shutdown 개념은 devops/graceful-shutdown.md 참고


배경

General OCR 서비스를 배포(롤링 업데이트)하거나 오토스케일러가 스케일인할 때마다 짧은 시간 동안 503 에러가 클러스터 단위로 발생했다. 에러 로그를 보면 패턴이 일정했다.

upstream connect error or disconnect/reset before headers.
reset reason: connection failure,
transport failure reason: delayed connect error: 111

error 111은 ECONNREFUSED, TCP 레벨에서 연결이 거부됐다는 뜻이다. 응답 헤더에 server: envoy가 있었고, 이건 Envoy 자체는 살아있는데 upstream(포트 50051)에 연결을 못 했다는 의미다.

에러가 30~60초 주기로 묶음 발생하고 자연히 사라지는 패턴 — 배포/스케일인 이벤트와 정확히 일치했다.


원인 분석

서비스 구조는 이렇다.

클라이언트 → Envoy(:5000) → gRPC 서버(:50051)

컨테이너가 종료될 때 실제로 일어나는 일을 추적해봤다.

종료 시퀀스 (수정 전)

  1. preStop hook 실행: Envoy drain_listeners 호출 → sleep 20
  2. preStop 완료 → SIGTERM 전달 → gRPC 서버 즉시 종료 → 포트 50051 닫힘
  3. sleep 20s 동안 Envoy가 아직 살아있어 요청을 upstream으로 라우팅 시도
  4. 50051 연결 거부(ECONNREFUSED) → 503 반환

핵심은 gRPC 서버에 SIGTERM 핸들러가 없었다는 것이다. server_grpc_general_OCR.py의 serve() 함수는 server.wait_for_termination()만 있었고, SIGTERM을 받으면 그냥 죽었다.

# 수정 전
server.start()
server.wait_for_termination()  # SIGTERM 수신 시 즉시 종료

두 번째 문제는 supervisord 설정이었다. [program:grpc-server]에 stopwaitsecs가 없어 기본값 10초가 적용됐다. SIGTERM 핸들러를 추가해도 supervisord가 10초 안에 종료 안 되면 SIGKILL을 날리는 구조였다.


NCS 제약

NHN Cloud Container Service는 terminationGracePeriodSeconds를 30초로 고정한다. API 스펙에도 해당 필드가 없어 변경할 방법이 없다. 따라서 모든 종료 작업은 30초 이내에 끝나야 한다.

기존 preStop sleep 20을 유지하면서 grace period를 늘릴 경우, 20 + grace가 30초를 넘기 때문에 preStop sleep을 15초로 줄이고 grace를 12초로 설정하는 방식으로 예산을 맞췄다.

preStop sleep 15s + gRPC grace 12s + 여유 3s = 30s

수정 내용

server_grpc_general_OCR.py — SIGTERM 핸들러 추가

import signal  # 추가

def serve():
    server = grpc.server(...)
    server.start()

    def handle_sigterm(signum, frame):
        print("SIGTERM received, starting graceful shutdown (grace=12s)...")
        server.stop(grace=12)

    signal.signal(signal.SIGTERM, handle_sigterm)
    server.wait_for_termination()

supervisord.conf — stopwaitsecs 추가

[program:grpc-server]
stopwaitsecs=17    # grace(12s) + 여유(5s)
stopsignal=TERM

Jenkinsfile_deploy_real — preStop sleep 단축

"preStop": ["/bin/sh", "-c", "curl -sf ... drain_listeners || true; sleep 15"]

수정 후 종료 시퀀스

시간이벤트
T+0spreStop 실행: Envoy drain_listeners, sleep 15s 시작
T+15spreStop 완료 → SIGTERM → server.stop(grace=12) 시작
T+15~27sin-flight RPC 처리 완료 대기, 신규 요청 거부
T+27sgRPC 서버 종료, 컨테이너 종료

수정 전에는 T+15s(또는 T+20s)에 gRPC 서버가 즉시 죽고 Envoy가 50051에 연결을 못 해서 503이 났다. 수정 후에는 preStop 완료 시점에 gRPC 서버가 정상적으로 drain을 수행하고 종료된다.

task 카테고리의 다른 글 보기수정 제안하기

댓글

댓글을 불러오는 중...
  • OCR 서버 배포·스케일인 시 503 에러 수정 — Graceful Shutdown 미적용
  • 배경
  • 원인 분석
  • 수정 전
  • NCS 제약
  • 수정 내용
  • 수정 후 종료 시퀀스