- Published on
Cloud Run 503·콜드스타트 7분 지연 해결 가이드
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
Cloud Run은 설정을 크게 건드리지 않아도 안정적으로 돌아가는 편이지만, 특정 조건이 겹치면 첫 요청이 수 분(심하면 7분) 동안 지연되거나, 그 과정에서 503이 튀는 케이스가 실제로 발생합니다. 특히 트래픽이 뜸한 서비스, 이미지가 큰 서비스, 외부 의존성이 많은 서비스에서 잘 드러납니다.
이 글은 “왜 7분까지 늘어났는지”를 플랫폼 레벨(프로비저닝) + 컨테이너 레벨(부팅) + 앱 레벨(초기화) + 네트워크/의존성 레벨로 쪼개서 확인하고, 재발 방지까지 묶어 정리합니다.
증상 정의: 503과 7분 지연은 같은 문제일까
Cloud Run에서 503은 여러 의미를 가집니다.
- 인스턴스가 아직 준비되지 않음: 새 인스턴스를 띄우는 중인데 요청이 먼저 들어옴
- 컨테이너가 기동했지만 포트를 열지 못함: 앱이
PORT로 리슨하기 전에 타임아웃 - 요청 처리 중 앱이 죽거나 OOM: 컨테이너 재시작 루프
- 업스트림(외부 API/DB) 대기: 앱이 초기화에서 외부 호출을 기다리다가 응답 지연, 그 사이 플랫폼 타임아웃으로 503
“7분” 같은 비정상적으로 긴 지연은 보통 아래 조합에서 나옵니다.
- 이미지 Pull 및 시작이 느림(대형 이미지, 콜드 노드) +
- 앱 초기화가 외부 의존성에 강하게 묶임(예: DB 마이그레이션, 시크릿/메타데이터 호출) +
- Cloud Run 요청/기동 타임아웃 및 동시성 설정이 부적절
먼저 확인할 것: 로그와 지표로 구간 나누기
1) Cloud Logging에서 503의 출처 확인
Cloud Run 서비스 로그에서 다음을 우선 찾습니다.
- 요청 로그의
status=503가 Cloud Run 자체에서 반환인지 - 컨테이너 로그에 앱 에러/패닉/OOM이 있는지
Container called exit(1)류의 메시지
필터 예시(콘솔의 로그 탐색기에서):
- 리소스: Cloud Run Revision
- 조건:
httpRequest.status=503
그리고 같은 시각대에 컨테이너 표준 출력/에러 로그를 같이 봅니다. 503이 찍히는데 앱 로그가 전혀 없다면, 앱까지 요청이 도달하지 못했을 확률이 큽니다(프로비저닝/기동 지연).
2) Cloud Monitoring에서 Cold start 지표 확인
Cloud Run은 인스턴스 수, 요청 지연, CPU/메모리 사용량을 통해 “어디서 막히는지” 감이 옵니다.
- 새 리비전 배포 직후 인스턴스 증가가 느린가
- 인스턴스는 올라왔는데 요청 지연만 긴가
- CPU가 0에 가깝게 유지되는데 지연만 긴가(대기 I/O 가능성)
원인 1: 이미지가 너무 크거나 Pull이 느림
콜드스타트가 길어지는 가장 흔한 원인 중 하나가 대형 이미지입니다. 특히 다음이 겹치면 Pull 시간이 길어집니다.
- 베이스 이미지가 무겁다(예: 풀 OS 이미지)
- 멀티스테이지 빌드 없이 런타임에 빌드 툴체인까지 포함
- 레이어 캐시가 깨져 매번 대량 레이어가 바뀜
해결: 멀티스테이지 + 슬림 베이스 + 캐시 안정화
Node.js 예시(멀티스테이지):
# syntax=docker/dockerfile:1
FROM node:20-bookworm AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-bookworm-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY /app/package*.json ./
RUN npm ci --omit=dev
COPY /app/dist ./dist
EXPOSE 8080
CMD ["node", "dist/server.js"]
- 런타임 이미지는
-slim계열로 축소 npm ci레이어가 불필요하게 깨지지 않도록COPY순서 고정
빌드 캐시가 자주 깨져 이미지가 비대해지는 문제는 Docker 빌드 최적화와도 연결됩니다. 빌드 캐시 관점은 이 글도 함께 보면 도움이 됩니다.
원인 2: 앱이 PORT 리슨 전에 “무거운 초기화”를 함
Cloud Run은 컨테이너가 지정된 포트(기본 8080, 환경변수 PORT)를 리슨해야 준비 완료로 봅니다. 그런데 앱이 다음을 “서버 리슨 이전”에 수행하면, 그 시간만큼 콜드스타트가 늘어납니다.
- DB 연결 풀 생성 및 연결 테스트(타임아웃이 길면 수 분)
- 외부 API 헬스체크
- 마이그레이션 실행
- 대용량 모델/파일 다운로드
해결: 리슨을 먼저, 초기화는 비동기로
Express 예시(서버 리슨 먼저):
import express from "express";
const app = express();
app.get("/healthz", (req, res) => res.status(200).send("ok"));
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log("listening on", port);
// 무거운 초기화는 리슨 이후 비동기로
warmup().catch((e) => console.error("warmup failed", e));
});
async function warmup() {
// 예: DB 연결, 캐시 프라임 등
}
중요 포인트는 “첫 요청을 받기 전에 반드시 끝내야 하는 작업”과 “백그라운드로 돌려도 되는 작업”을 분리하는 것입니다. 첫 요청이 반드시 의존해야 한다면, 그 의존성의 타임아웃을 짧게 잡고 실패 시 빠르게 실패시키는 편이 7분 지연보다 낫습니다.
원인 3: 외부 의존성(특히 DB) 연결이 느리거나 막힘
7분 지연은 DB 연결 타임아웃(기본이 길게 잡힌 라이브러리)에서 자주 나옵니다.
- Cloud SQL로 가는데 커넥터 설정이 잘못됨
- VPC 커넥터 경유로 라우팅되는데 방화벽/NAT/라우트 문제
- DNS가 느리거나 간헐 실패
해결: 연결 타임아웃을 명시하고, 실패를 빨리 감지
예: PostgreSQL 클라이언트에서 타임아웃을 명시합니다.
import pg from "pg";
const pool = new pg.Pool({
connectionString: process.env.DATABASE_URL,
statement_timeout: 5000,
query_timeout: 5000,
connectionTimeoutMillis: 5000
});
또한 Cloud Run에서 VPC 커넥터를 쓴다면, egress 설정(모든 트래픽 vs 프라이빗만)과 함께 DNS/라우팅을 점검해야 합니다. 쿠버네티스 사례지만 “DNS는 살아있는데 간헐 실패” 류의 패턴 분석은 진단 방식이 유사합니다.
원인 4: CPU 할당 정책 때문에 스타트업이 느려짐
Cloud Run은 요청 처리 중에만 CPU를 주는 모드가 기본입니다. 이 경우 “요청이 오기 전” 백그라운드 초기화가 느리거나 사실상 진행이 안 될 수 있습니다.
- 첫 요청이 들어와야 CPU가 생김
- 그런데 앱은 첫 요청을 처리하기 위해 초기화를 해야 함
- 초기화가 CPU를 많이 쓰면 첫 요청 지연이 커짐
해결: Startup CPU boost 또는 항상 CPU 할당 고려
- Startup CPU boost를 켜서 콜드스타트 구간 CPU를 더 받게 함
- 또는 CPU를 항상 할당(비용 증가)하여 백그라운드 초기화 가능
이 설정은 “트래픽이 뜸한데 첫 요청이 항상 느리다” 유형에서 체감이 큽니다.
원인 5: 최소 인스턴스가 0이라 완전 콜드가 발생
트래픽이 없을 때 인스턴스가 0으로 내려가면, 다음 요청은 무조건 콜드스타트입니다. 서비스가 B2B, 배치성, 관리자 페이지처럼 “가끔 호출”되는 구조면 콜드스타트를 피하기 어렵습니다.
해결: min instances로 워밍 유지
운영에서 흔한 선택지는 다음입니다.
min instances=1로 최소 1개는 항상 유지- 트래픽이 많아지는 시간대만 스케줄링 워밍(Cloud Scheduler로 주기 호출)
비용과 지연 사이의 트레이드오프를 명확히 정하고 결정하는 게 좋습니다.
원인 6: 동시성(concurrency)과 타임아웃이 부적절
동시성이 너무 높을 때
동시성이 높으면 인스턴스 수를 덜 늘려도 되지만, 콜드스타트 직후 한 인스턴스에 요청이 몰려 앱 초기화/캐시 미스/커넥션 풀 경쟁이 심해질 수 있습니다.
- 예: 동시성 80에서 첫 인스턴스에 대량 요청 유입
- 초기화가 완료되기 전 요청들이 대기하다가 타임아웃
해결은 보통 다음 중 하나입니다.
- 동시성을 낮춰 인스턴스가 더 빨리 늘어나게 유도
- 앱 내부에서 준비 완료 전 요청을 빠르게 503으로 반환하고 재시도 유도(권장도는 케이스별)
요청 타임아웃이 너무 길 때
요청 타임아웃이 길면 “사용자는 계속 기다리고, 결국 실패”가 됩니다. 차라리 짧게 실패시키고 재시도하는 편이 사용자 경험이 나을 수 있습니다.
실전 체크리스트: 7분 지연을 30초 이하로 줄이는 순서
아래는 실제 트러블슈팅에서 효과가 큰 순서입니다.
- 로그로 구간 분리: 503이 플랫폼인지 앱인지부터
- 이미지 크기 축소: 멀티스테이지, 슬림 베이스, 불필요 파일 제거
- 서버 리슨을 먼저: 무거운 초기화는 리슨 이후 비동기
- 외부 의존성 타임아웃 단축: DB/DNS/API 연결 타임아웃을 초 단위로
- Startup CPU boost 적용: 콜드스타트 구간 가속
- min instances 적용: 최소 1개 워밍 유지(핵심 서비스면 강력 추천)
- 동시성 재조정: 초기 폭주를 줄이거나 스케일아웃 유도
배포/설정 예시: gcloud로 핵심 옵션 적용
아래는 자주 쓰는 옵션을 한 번에 반영하는 형태의 예시입니다(서비스/환경에 맞게 조정).
gcloud run deploy my-service \
--image=asia-northeast3-docker.pkg.dev/myproj/myrepo/myimg:sha-123 \
--region=asia-northeast3 \
--port=8080 \
--min-instances=1 \
--concurrency=20 \
--cpu=1 \
--memory=512Mi \
--timeout=30
주의: Startup CPU boost, CPU always allocated 같은 옵션은 콘솔/리전/런타임 세대에 따라 노출 방식이 다를 수 있어, 콘솔에서 토글을 확인한 뒤 동일 설정을 CLI로 맞추는 흐름이 안전합니다.
“503이 배포 직후만 난다”면: 리비전 전환과 캐시/드리프트도 점검
배포 직후에만 503이 난다면 다음도 함께 봐야 합니다.
- 새 리비전이 뜨는 동안 트래픽 전환 비율
- 환경변수/시크릿/권한이 리비전마다 달라지는 드리프트
- IaC 적용 충돌로 설정이 의도와 다르게 반영
특히 Terraform로 Cloud Run과 관련 리소스를 관리할 때는 apply 충돌이나 드리프트로 인해 “분명 설정했는데 적용이 안 된 상태”가 생길 수 있습니다.
결론: 7분 콜드스타트는 “한 가지”가 아니라 “연쇄 지연”이다
Cloud Run에서 7분 지연과 503은 대개 단일 원인이 아니라,
- 큰 이미지 Pull
PORT리슨 전 초기화- 외부 의존성 타임아웃
- CPU 정책 및 min instances
가 연쇄적으로 겹치며 만들어집니다.
가장 재현성과 효과가 큰 처방은 다음 조합입니다.
- 이미지 다이어트(멀티스테이지) +
- 리슨 먼저(초기화 분리) +
- 의존성 타임아웃 단축 +
- Startup CPU boost 또는 min instances 1
이 4가지만 제대로 적용해도 “첫 요청 7분” 같은 극단적 케이스는 대부분 10초에서 30초 수준으로 내려오고, 운영 체감상 503도 크게 줄어듭니다.