- Published on
GCP Cloud Run 503/timeout 원인 7가지 진단법
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Cloud Run을 운영하다 보면 가장 곤란한 장애 유형이 503 과 각종 timeout 입니다. 공통점은 “서버가 응답을 못 했다”는 결과만 남기고, 원인은 콜드 스타트부터 업스트림(외부 API/DB) 지연, 동시성 설정, 메모리 부족까지 너무 넓게 퍼져 있다는 점입니다.
이 글은 Cloud Run에서 503 또는 timeout 이 보일 때, 무엇부터 확인하면 원인을 가장 빨리 좁힐 수 있는지를 7가지로 정리합니다. 각 항목은 “증상 패턴 → 확인 위치(로그/메트릭/설정) → 해결 방향”으로 구성했습니다.
참고: 스트리밍/SSE 기반 트래픽은 프록시 버퍼링/idle timeout 이슈로
499/502가 섞여 보이기도 합니다. 이 경우는 LLM SSE 스트리밍 499 502 급증과 응답 끊김을 잡는 프록시 튜닝 체크리스트도 함께 보세요.
1) 콜드 스타트(인스턴스 0에서 기동)로 인한 초기 지연
전형적인 증상
- 트래픽이 뜸한 서비스에서 첫 요청만 느리거나 timeout
- 배포 직후 또는 스케일 아웃 직후에만
503가 증가
어디서 확인하나
- Cloud Logging에서 해당 요청의 총 지연 시간과 애플리케이션 로그의 서버 부팅 시점을 비교
- Cloud Monitoring에서 인스턴스 수가 0에서 1로 증가하는 구간과 오류율 상관관계 확인
해결 방향
min-instances를 1 이상으로 설정해 워밍 유지(비용 증가)- 컨테이너 부팅 시간을 줄이기
- 불필요한 초기화(대형 모델 로딩, 마이그레이션, 원격 설정 전체 fetch)를 요청 경로에서 분리
- 의존성 다운로드/컴파일 같은 작업이 런타임에 발생하지 않도록 이미지 빌드 단계로 이동
체크용 예시(콜드 스타트 최소화 설정)
gcloud run services update SERVICE_NAME \
--min-instances=1 \
--cpu=1 \
--memory=512Mi \
--region=REGION
2) 요청 처리 시간이 Cloud Run/프록시 타임아웃을 초과
Cloud Run은 “요청이 너무 오래 걸리면” 플랫폼 레벨에서 연결을 끊을 수 있습니다. 여기에는 애플리케이션 타임아웃뿐 아니라, 클라이언트·로드밸런서·프록시의 타임아웃도 포함됩니다.
전형적인 증상
- 특정 엔드포인트에서만 timeout
- 로그에 애플리케이션 에러는 없는데, 클라이언트는 timeout
- 긴 작업(리포트 생성, 대용량 업로드/다운로드, 외부 API 호출 연쇄)에서 재현
어디서 확인하나
- Cloud Logging에서
requestLatency계열 필드(요청 지연) 확인 - 애플리케이션 APM/로그에서 “응답을 쓰기 직전까지 진행됐는지” 확인
해결 방향
- 긴 작업을 HTTP 요청-응답 모델에서 분리
- Cloud Tasks로 비동기화
- Pub/Sub 이벤트로 처리
- 작업 결과는 폴링 또는 Webhook으로 전달
- 외부 API 호출에 타임아웃/재시도/서킷브레이커를 명시
Node.js 예시(외부 호출 타임아웃 강제)
import fetch from 'node-fetch';
export async function callUpstream(url) {
const controller = new AbortController();
const t = setTimeout(() => controller.abort(), 3000);
try {
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) throw new Error(`upstream status=${res.status}`);
return await res.text();
} finally {
clearTimeout(t);
}
}
3) 동시성(concurrency) 과부하로 인한 큐잉/응답 지연
Cloud Run은 인스턴스 하나가 여러 요청을 동시에 처리할 수 있습니다. 이 값이 너무 높으면 애플리케이션이 CPU/메모리/DB 커넥션을 공유하면서 병목이 생기고, 결과적으로 지연이 누적되어 timeout 또는 503 로 이어질 수 있습니다.
전형적인 증상
- CPU 사용률이 높거나, GC가 잦아지며 지연이 급증
- DB 커넥션 부족, 스레드풀 고갈, 큐 대기 증가
- 트래픽 피크에서만
503가 상승
어디서 확인하나
- Cloud Monitoring에서 CPU 사용률, 메모리 사용률, 요청 지연 p95/p99
- DB 메트릭(커넥션 수, 대기 시간)과 동시간대 비교
해결 방향
concurrency를 낮춰 인스턴스당 동시 처리량을 줄이고 스케일 아웃 유도- 애플리케이션 워커/스레드풀/DB 풀 크기와
concurrency를 함께 설계
설정 예시
gcloud run services update SERVICE_NAME \
--concurrency=20 \
--max-instances=200 \
--region=REGION
4) 메모리 부족(OOM) 또는 GC 폭주로 인한 프로세스 재시작
Cloud Run에서 메모리가 부족하면 컨테이너가 종료되거나, OOM 직전까지 가면서 GC가 폭주해 응답이 급격히 느려질 수 있습니다. 이때 클라이언트는 503 또는 timeout으로 관측합니다.
전형적인 증상
- 특정 시점 이후 갑자기 지연이 커지고, 곧바로 인스턴스가 교체됨
- 대용량 요청(파일 처리, 이미지 변환, 대규모 JSON 파싱)에서 재현
어디서 확인하나
- Cloud Logging에서 컨테이너 종료/재시작 흔적 확인
- Cloud Monitoring에서 메모리 사용률이 한계치에 붙는지 확인
해결 방향
- 메모리 상향 또는 작업 방식 변경(스트리밍 처리, 청크 처리)
- 언어 런타임별 메모리 제한/힙 설정 점검
관련해서 “OOM이 왜 났는지”를 더 깊게 추적하는 방법은 리눅스 관점에서도 도움이 됩니다. 리눅스 OOM Kill 원인 추적 - dmesg·cgroup·journalctl 글의 접근 방식을 참고하면, 메모리 압박 상황을 구조적으로 해석하는 데 유리합니다.
5) 헬스/리스닝 포트/응답 헤더 문제로 인한 503
Cloud Run은 컨테이너가 지정된 포트에서 정상적으로 리스닝해야 합니다. 또한 애플리케이션이 예외로 인해 응답을 제대로 만들지 못하거나, 프록시가 해석 불가능한 응답을 받으면 503 이 발생할 수 있습니다.
전형적인 증상
- 배포 직후부터 거의 모든 요청이
503 - 로컬에서는 되는데 Cloud Run에서만 실패
어디서 확인하나
- Cloud Logging의 런타임 로그(컨테이너가 바인딩한 포트, 서버 시작 로그)
- Cloud Run 서비스 설정의
PORT환경변수/컨테이너 포트
해결 방향
- 반드시
0.0.0.0로 바인딩하고,PORT를 사용 - 프레임워크 기본 포트(예:
3000)를 하드코딩하지 않기
Python Flask 예시
import os
from flask import Flask
app = Flask(__name__)
@app.get("/")
def hello():
return "ok"
if __name__ == "__main__":
port = int(os.environ.get("PORT", "8080"))
app.run(host="0.0.0.0", port=port)
6) VPC 커넥터/NAT/DNS 등 네트워크 병목으로 업스트림 timeout
Cloud Run이 사설망(VPC) 자원(Cloud SQL, Memorystore, 내부 API) 또는 외부 인터넷으로 나가는 경로에서 병목이 생기면, 애플리케이션은 “그냥 느려졌다”로 보이지만 결과는 timeout/503 로 나타납니다.
전형적인 증상
- 외부 API 호출이 특정 리전/시간대에만 느려짐
- Cloud SQL 연결이 간헐적으로 실패 또는 연결 지연
- DNS resolve가 간헐적으로 오래 걸림
어디서 확인하나
- Cloud Logging에서 외부 호출 지연(호출 전후 타임스탬프 로깅)
- Cloud Monitoring에서 Serverless VPC Access 커넥터 메트릭(처리량, 드롭)
- Cloud NAT 사용 시 NAT 포트 고갈 의심(동시 연결 급증)
해결 방향
- 커넥터 스케일/대역폭 설정 재검토
- 외부 호출에 커넥션 풀/keep-alive 적용, 불필요한 새 연결 남발 방지
- DNS/HTTP 클라이언트 타임아웃을 명시하고, 재시도는 지수 백오프로 제한
7) 의존 서비스(DB/캐시/외부 API) 장애 전파와 재시도 폭주
Cloud Run 자체는 멀쩡한데, 뒤쪽 의존 서비스가 느려지면 애플리케이션 스레드/워커가 대기 상태로 쌓입니다. 여기에 “무제한 재시도”가 붙으면, 정상 트래픽까지 밀려 503 과 timeout이 폭발합니다.
전형적인 증상
- 특정 외부 API 장애가 시작점인데, Cloud Run 오류율이 뒤늦게 폭증
- 로그에 동일한 업스트림 에러가 반복
- 재시도 때문에 요청당 처리 시간이 점점 늘어남
어디서 확인하나
- 애플리케이션 로그에서 업스트림별 실패율, 재시도 횟수
- Cloud Monitoring에서 p95/p99 지연이 계단식으로 증가하는지 확인
해결 방향
- 서킷브레이커(일정 실패율 이상이면 빠르게 실패)
- 재시도 상한(횟수/총 시간)과 지터 적용
- 폴백 응답(캐시, 제한된 기능 모드) 고려
MSA 환경에서 장애 전파와 보상 트랜잭션까지 얽히면 원인 추적이 더 어려워집니다. 이때는 MSA 사가 패턴 보상 트랜잭션 실패 디버깅처럼 “어디서 실패가 시작됐고 어떻게 전파됐는지”를 이벤트 흐름으로 분해하는 방식이 도움이 됩니다.
진단을 빠르게 만드는 로그 포맷(권장)
원인 7가지를 공통적으로 빠르게 좁히려면, 최소한 아래 5가지는 모든 요청 로그에 남기는 편이 좋습니다.
traceId또는 요청 상관관계 ID- 엔드포인트/메서드
- 총 처리 시간(ms)
- 업스트림 호출별 소요 시간(ms)
- 결과 코드(성공/실패, 실패 사유)
Express 미들웨어 예시
import crypto from 'crypto';
export function requestLogger(req, res, next) {
const rid = req.header('x-request-id') || crypto.randomUUID();
const start = Date.now();
res.setHeader('x-request-id', rid);
res.on('finish', () => {
const ms = Date.now() - start;
console.log(JSON.stringify({
rid,
method: req.method,
path: req.originalUrl,
status: res.statusCode,
latency_ms: ms
}));
});
next();
}
마무리: 503/timeout 을 “원인별로 분리”하면 빨라진다
Cloud Run의 503/timeout 은 한 가지 해결책으로 끝나는 문제가 아니라, 콜드 스타트/타임아웃/동시성/메모리/네트워크/의존 서비스로 분해해서 보는 순간 해결 속도가 빨라집니다.
운영 중이라면 다음 순서로 점검하는 것을 권합니다.
- 배포 직후/트래픽 공백 후에만 발생하는가(콜드 스타트)
- 특정 엔드포인트만 긴가(요청 타임아웃)
- 피크에서만 터지는가(동시성/스케일)
- 재시작 흔적이 있는가(OOM/런타임)
- VPC/외부 호출이 느린가(네트워크)
- 업스트림 장애가 전파됐는가(재시도/서킷브레이커)
이 6단계만 습관화해도, “대충 Cloud Run이 불안정하다”에서 “어떤 병목이 어떤 설정/코드로 이어졌는지”까지 훨씬 빠르게 도달할 수 있습니다.