핵심 요약
- Cloud Run은 컨테이너 기반 fully managed serverless다. Knative 호환이며, 요청 처리 시간에만 과금되는 request-based 모델을 기본으로 한다.
- 콜드 스타트의 본질은 컨테이너 부팅 + 앱 초기화 + 첫 요청 처리 세 단계다. min-instances로 회피하거나, 부팅 최적화로 단축한다.
- min-instances는 항상 켜두는 인스턴스로 콜드 스타트 0이지만 idle 시간도 과금된다. SLA 요구사항과 비용의 트레이드오프 결정이 필요하다.
- 동시성(concurrency) 설정은 메모리 사용량과 직결된다. 기본값 80은 stateful 워크로드엔 위험하며, 워크로드별 측정 후 조정이 원칙이다.
- 본 글은 컨테이너 빌드 전략, Buildpacks vs Dockerfile, Cloud Run + Cloud SQL + GCS 통합 운영 패턴을 다룬다.
사전 지식: 시리즈 #1~#3, Docker 기본 명령, HTTP 라이프사이클, 컨테이너 이미지 레이어 개념
작성 시점: 2026년 5월 기준 (Cloud Run 2nd gen execution environment GA, Cloud Run jobs GA)
1. Cloud Run 아키텍처 이해
1.1 두 가지 리소스 타입
Cloud Run에는 두 가지 리소스가 있다:
| Cloud Run Services | HTTP 요청 처리 | HTTPS / gRPC / Eventarc | 요청당 최대 60분 |
| Cloud Run Jobs | 배치 작업 | gcloud / API / Scheduler | 작업당 최대 24시간 |
본 글은 주로 Services를 다루며, Jobs는 8절에서 짧게 다룬다.
1.2 Execution Environment - 1st vs 2nd gen
Cloud Run은 두 가지 실행 환경을 제공한다:
| 격리 방식 | gVisor 샌드박스 | Linux KVM microVM |
| 콜드 스타트 | 빠름 (1~3초) | 느림 (2~5초) |
| 시스템 콜 호환성 | 제한적 (eBPF, FUSE 제한) | Linux 완전 호환 |
| CPU 성능 | 낮음 (특히 디스크 I/O) | 호스트와 유사 |
| 사용 권장 | 일반 웹 API | CPU/IO 집약적, 네이티브 바이너리 |
기본값은 2nd gen이며, 1st gen은 명시적으로 --execution-environment=gen1로 지정한다. 단순 웹 API는 1st gen이 콜드 스타트 면에서 유리하다.
1.3 요청 라이프사이클
콜드 스타트 디버깅을 위해 라이프사이클을 정확히 이해해야 한다:
2. Cloud Run이 활성 인스턴스 확인
├─ 있음 → 5번으로
└─ 없음 → 3번으로 (콜드 스타트)
3. 컨테이너 부팅 (이미지 pull + 컨테이너 시작)
4. 앱 초기화 (PORT 환경변수 listen 대기)
5. 요청 라우팅
6. 응답 반환
7. idle 타이머 시작 (기본 15분 후 인스턴스 종료)
콜드 스타트 = 1번부터 5번까지 = 이미지 pull 시간 + 앱 부팅 시간 + 첫 요청 처리 시간.
각 단계의 일반적인 비중(2nd gen 기준):
- 컨테이너 부팅: 1~2초
- 앱 초기화: 0.5~10초 (앱별 편차 큼)
- 첫 요청 처리: 0.1~3초
앱 초기화가 가장 큰 변수다. JVM 앱은 5~10초도 흔하다.
2. 컨테이너 이미지 빌드 전략
2.1 세 가지 빌드 옵션
Cloud Run에 배포할 이미지를 만드는 방법은 3가지다:
| Dockerfile | gcloud run deploy --source | 완전 제어, 표준 | Dockerfile 작성 필요 |
| Buildpacks | gcloud run deploy --source (Dockerfile 없을 때) | 자동 감지, 빠른 시작 | 커스터마이징 어려움 |
| Cloud Build | gcloud builds submit | CI/CD 통합, 캐시 활용 | 설정 복잡 |
운영 환경 권장 순서: Dockerfile → Cloud Build → Buildpacks (역순으로 신중함).
2.2 권장 Dockerfile 패턴 - 멀티스테이지
Python FastAPI 앱 기준 권장 Dockerfile:
FROM python:3.12-slim AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Stage 2: 런타임
FROM python:3.12-slim
WORKDIR /app
# 비-root 사용자 생성 (보안)
RUN groupadd -r app && useradd -r -g app app
# 빌드 단계의 패키지만 복사
COPY --from=builder /root/.local /home/app/.local
COPY --chown=app:app . .
ENV PATH=/home/app/.local/bin:$PATH \
PYTHONUNBUFFERED=1 \
PORT=8080
USER app
EXPOSE 8080
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
주요 포인트:
- 멀티스테이지로 최종 이미지 크기 절반 이상 감소
- python:3.12-slim (Alpine은 musl libc 이슈로 권장하지 않음)
- 비-root 사용자 실행 (Cloud Run 2nd gen에서 권장)
- PYTHONUNBUFFERED=1로 로그 즉시 출력
- PORT=8080 환경변수 (Cloud Run의 표준 PORT)
2.3 Node.js Dockerfile 예시
Express 앱 기준:
FROM node:20-slim AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force
# Stage 2: 런타임
FROM node:20-slim
WORKDIR /app
RUN groupadd -r app && useradd -r -g app app
COPY --from=deps /app/node_modules ./node_modules
COPY --chown=app:app . .
ENV NODE_ENV=production \
PORT=8080
USER app
EXPOSE 8080
CMD ["node", "server.js"]
2.4 빌드 캐시 활용
Cloud Build는 이전 빌드의 레이어 캐시를 자동 활용하지만, base image와 의존성 설치 단계를 먼저 두는 것이 핵심이다. 코드 변경이 잦은 레이어를 뒤에 배치하면 의존성 재설치를 피할 수 있다.
.dockerignore 파일도 필수다. 대표적인 제외 항목:
.github
node_modules
__pycache__
*.pyc
.env
.venv
.DS_Store
*.md
tests/
.pytest_cache/
이게 누락되면 컨텍스트 크기가 수십~수백 MB로 부풀어 빌드 시간이 길어지고, 잠재적으로 비밀 정보가 이미지에 포함될 수 있다.
2.5 Buildpacks - Dockerfile 없이
Dockerfile을 작성하지 않으면 Google Cloud Buildpacks가 자동 감지로 빌드한다. 지원 언어와 감지 기준:
- Python: requirements.txt 또는 pyproject.toml 존재
- Node.js: package.json 존재
- Go: go.mod 존재
- Java: pom.xml 또는 build.gradle 존재
- Ruby: Gemfile 존재
Buildpacks 사용 시 진입점은 환경변수 GOOGLE_ENTRYPOINT로 지정한다:
--source . \
--region=asia-northeast3 \
--set-env-vars="GOOGLE_ENTRYPOINT=uvicorn main:app --host 0.0.0.0 --port 8080"
Buildpacks 권장 사용처: 프로토타입, 사이드 프로젝트, 표준 웹 프레임워크. 운영 환경은 Dockerfile이 더 안전하다(이미지 내용을 완전히 통제할 수 있음).
3. 배포 명령과 핵심 옵션
3.1 표준 배포 명령
서울 리전, 운영 환경 기준 권장 옵션 풀세트:
--source . \
--region=asia-northeast3 \
--platform=managed \
--service-account=my-api-sa@my-project.iahttp://m.gserviceaccount.com \
--memory=512Mi \
--cpu=1 \
--concurrency=80 \
--min-instances=0 \
--max-instances=10 \
--timeout=60s \
--no-allow-unauthenticated \
--execution-environment=gen2 \
--labels=env=prod,team=platform
각 옵션의 의미와 결정 기준은 4절부터 상세 설명한다.
3.2 YAML로 선언적 배포
명령 옵션이 많아지면 YAML 파일로 관리한다:
kind: Service
metadata:
name: my-api
labels:
env: prod
team: platform
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/minScale: "1"
autoscaling.knative.dev/maxScale: "10"
run.googleapis.com/execution-environment: gen2
run.googleapis.com/cpu-throttling: "false"
spec:
serviceAccountName: my-api-sa@my-project.iahttp://m.gserviceaccount.com
containerConcurrency: 80
timeoutSeconds: 60
containers:
- image: asia-northeast3-docker.pkg.dev/my-project/my-repo/my-api:v1.2.3
resources:
limits:
cpu: "1"
memory: 512Mi
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: db-credentials
key: host
ports:
- containerPort: 8080
적용 명령:
3.3 Terraform으로 IaC 관리
운영 환경은 Terraform 권장:
name = "my-api"
location = "asia-northeast3"
template {
service_account = google_service_account.api.email
scaling {
min_instance_count = 1
max_instance_count = 10
}
containers {
image = "asia-northeast3-docker.pkg.dev/${var.project_id}/my-repo/my-api:${var.image_tag}"
resources {
limits = {
cpu = "1"
memory = "512Mi"
}
cpu_idle = false
}
ports {
container_port = 8080
}
env {
name = "DB_HOST"
value_source {
secret_key_ref {
secret = google_secret_manager_secret.db_host.secret_id
version = "latest"
}
}
}
}
timeout = "60s"
max_instance_request_concurrency = 80
}
traffic {
type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
percent = 100
}
}
4. 콜드 스타트 최적화
4.1 측정 우선
최적화 전에 측정한다. Cloud Logging에서 콜드 스타트 식별 쿼리:
resource.labels.service_name="my-api"
labels."run.googleapis.com/startupProbeMs" > 0
또는 메트릭 익스플로러에서 run.googleapis.com/container/startup_latencies 메트릭의 p50, p95, p99를 확인한다.
4.2 최적화 기법
기법 1: 이미지 크기 줄이기
- 멀티스테이지 빌드
- distroless 또는 slim 이미지 사용
- 불필요한 패키지 제거
- 효과: 이미지 pull 시간 200ms~1초 감소
기법 2: 앱 부팅 시간 단축
- Lazy import: 사용되지 않는 모듈은 함수 안에서 import
- Connection pool 초기화는 첫 요청에 위임 (eager init 지양)
- Heavy framework 회피 (Django → Flask/FastAPI 등)
- 효과: 부팅 시간 1~5초 감소
기법 3: Startup CPU Boost
부팅 시 CPU를 일시적으로 부스트한다:
--source . \
--region=asia-northeast3 \
--cpu-boost
추가 비용 없이 콜드 스타트 시간 30~50% 단축 효과. 거의 항상 켜두는 게 이득이다.
기법 4: Min-instances로 회피
최소 1개 인스턴스를 항상 활성화:
--region=asia-northeast3 \
--min-instances=1
콜드 스타트 0이지만, idle 인스턴스도 과금된다. 4.3절에서 비용 분석.
기법 5: 2nd gen 회피 (Python/Node.js의 경우)
CPU/IO 집약적이지 않은 일반 웹 API는 1st gen이 콜드 스타트 면에서 유리할 수 있다:
--source . \
--region=asia-northeast3 \
--execution-environment=gen1
4.3 Min-instances 비용 분석
min-instances=1 운영 시 비용 계산 (asia-northeast3, 2026년 5월 기준):
CPU/메모리 always-on 인스턴스 (1 vCPU, 512Mi) 가격:
- vCPU: $0.000018/vCPU-sec
- Memory: $0.000002/GiB-sec
월 24/7 운영 시:
- CPU: 1 × 0.000018 × 60 × 60 × 24 × 30 = $46.66
- Memory: 0.5 × 0.000002 × 60 × 60 × 24 × 30 = $2.59
- 합계: 약 $49/월
CPU Allocation을 "CPU is only allocated during request processing"으로 두면 요청 시에만 과금되지만, min-instances 자체가 의미 없어진다. 항상 활성 인스턴스가 필요하면 CPU always allocated가 필수다.
의사결정: SLA 요구사항이 p95 200ms 이하라면 min-instances=1이 정답. p95 3초 이내면 cpu-boost + 이미지 최적화로 충분한 경우가 많다.
5. Concurrency와 메모리 튜닝
5.1 Concurrency의 의미
Cloud Run의 concurrency는 단일 인스턴스가 동시에 처리하는 요청 수다. 기본값 80은 일반적인 웹 API에 적절하지만, 다음 워크로드는 조정이 필요하다:
- CPU 집약적: ML 추론, 이미지 처리 등은 concurrency=1로 둬야 안정적
- 메모리 누수 가능 stateful 코드: 80개 동시 처리 시 OOM 위험
- 외부 DB 연결: connection pool 크기와 concurrency 합산해서 DB 부하 계산 필요
5.2 메모리와 concurrency의 관계
각 요청이 평균 50MB 메모리를 사용한다면:
- concurrency=80 → 최대 4GB 메모리 필요
- concurrency=10 → 최대 500MB로 충분
메모리 한도(--memory)는 concurrency × 요청당 메모리 + 앱 베이스 메모리로 계산해야 OOM을 방지한다.
5.3 측정 도구
실제 메모리/CPU 사용량 확인:
--filter="metric.type:run.googleapis.com/container"
핵심 메트릭:
- run.googleapis.com/container/memory/utilizations: 메모리 사용률
- run.googleapis.com/container/cpu/utilizations: CPU 사용률
- run.googleapis.com/container/instance_count: 인스턴스 수
p99가 70% 이하로 유지되도록 메모리를 잡는 것이 안전 마진의 기준이다.
5.4 동시 요청 수 가시화 - concurrent 메트릭
run.googleapis.com/container/request_count 와 run.googleapis.com/container/instance_count 의 비율로 인스턴스당 평균 concurrency를 계산할 수 있다. 이 값이 설정값에 자주 도달하면 max-instances 증가 또는 concurrency 조정이 필요하다.
6. Cloud Run + 주변 서비스 통합
6.1 Cloud SQL 연결
Cloud Run에서 Cloud SQL 접속은 Unix socket 방식이 표준이다. Public IP나 외부 endpoint를 거치지 않고 안전하게 연결된다.
배포 명령에 추가 옵션:
--source . \
--region=asia-northeast3 \
--add-cloudsql-instances=my-project:asia-northeast3:my-instance \
--set-env-vars="DB_SOCKET_PATH=/cloudsql/my-project:asia-northeast3:my-instance"
앱 코드에서는 socket 경로로 연결:
engine = sqlalchemy.create_engine(
sqlalchemy.engine.url.URL.create(
drivername="postgresql+pg8000",
username="db-user",
password=db_password,
database="my-db",
query={"unix_sock": "/cloudsql/my-project:asia-northeast3:my-instance/.s.PGSQL.5432"}
)
)
SA에 roles/cloudsql.client 권한 부여 필수.
6.2 GCS 접근
#3편의 ADC 패턴을 그대로 활용한다. Cloud Run 서비스 SA에 roles/storage.objectAdmin 부여하면 별도 인증 없이 클라이언트 사용 가능:
client = storage.Client()
bucket = client.bucket("my-app-uploads-prod")
blob = bucket.blob("user-uploads/img.jpg")
blob.upload_from_filename("/tmp/img.jpg")
6.3 Secret Manager
DB 비밀번호, API 키 등 민감 정보는 Secret Manager에 저장하고 환경변수로 주입:
gcloud run services update my-api \
--region=asia-northeast3 \
--update-secrets=DB_PASSWORD=db-password:latest
SA에 roles/secretmanager.secretAccessor 권한 필요. 코드에서는 그냥 os.environ["DB_PASSWORD"]로 접근.
6.4 VPC 연결
Cloud Run에서 VPC 내 리소스(GCE VM, Cloud SQL Private IP, Memorystore 등)에 접근하려면 VPC Connector 또는 Direct VPC egress가 필요하다.
Direct VPC egress (2024년 GA, 권장):
--source . \
--region=asia-northeast3 \
--network=my-vpc \
--subnet=my-subnet \
--vpc-egress=all-traffic
VPC Connector 대비 장점: 별도 Connector 인스턴스 비용 없음, 레이턴시 낮음, 처리량 높음.
7. 트래픽 분할과 점진적 배포
7.1 Revision 모델
Cloud Run은 배포할 때마다 새 Revision을 생성한다. 트래픽을 여러 Revision에 분할할 수 있다.
배포 후 즉시 100% 라우팅하지 않고 보류:
--source . \
--region=asia-northeast3 \
--no-traffic \
--tag=canary
이렇게 하면 새 Revision은 canary-my-api-xxx.run.app 같은 별도 URL로 접근 가능하지만, 메인 트래픽은 받지 않는다.
7.2 카나리 배포 패턴
신규 Revision에 10% 트래픽 할당:
--region=asia-northeast3 \
--to-revisions=my-api-00042-abc=10,my-api-00041-xyz=90
5분간 메트릭 모니터링 후 점진 증가:
--region=asia-northeast3 \
--to-revisions=my-api-00042-abc=50,my-api-00041-xyz=50
문제 발견 시 즉시 롤백:
--region=asia-northeast3 \
--to-revisions=my-api-00041-xyz=100
7.3 자동 카나리 - Cloud Deploy
수동 트래픽 분할 대신 Cloud Deploy로 자동화할 수 있다. progressive delivery, 자동 롤백, 승인 게이트 등을 선언적으로 관리한다. 별도 글에서 상세 다룬다.
8. Cloud Run Jobs - 배치 워크로드
8.1 Services와의 차이
Jobs는 HTTP 요청이 아닌 명시적 실행 기반이다. 사용 케이스:
- DB 마이그레이션
- 배치 ETL
- 정기 데이터 처리
- 일회성 스크립트
8.2 Job 생성과 실행
--image=asia-northeast3-docker.pkg.dev/my-project/my-repo/migrate:v1 \
--region=asia-northeast3 \
--task-timeout=600s \
--max-retries=3 \
--memory=512Mi
gcloud run jobs execute migrate-db --region=asia-northeast3 --wait
Cloud Scheduler와 연동하면 cron 형식의 정기 실행도 가능하다.
9. 흔한 함정과 디버깅
함정 1: PORT 환경변수 무시
Cloud Run은 PORT 환경변수(기본 8080)에서 listen해야 한다. 코드에 하드코딩된 포트는 작동하지 않는다. 컨테이너가 PORT에서 시작 응답을 못 하면 4분 후 타임아웃으로 배포 실패한다.
함정 2: 0.0.0.0 vs localhost
localhost(127.0.0.1)에서 listen하면 외부 요청 라우팅 실패. 반드시 0.0.0.0에서 listen.
함정 3: 백그라운드 작업 중단
Cloud Run은 요청 응답 후 CPU 할당을 회수한다. async/background 작업이 실행 중이어도 응답이 끝나면 멈춘다. 백그라운드 처리가 필요하면 Pub/Sub 또는 Cloud Tasks로 분리.
--no-cpu-throttling 옵션으로 always-on CPU도 가능하지만 비용이 증가한다.
함정 4: 컨테이너가 한 번에 최대 80개 요청 처리 = 인스턴스당 80개
Cloud Run의 concurrency 한도는 인스턴스당이다. 트래픽 1만 RPS면 max-instances * concurrency가 그 이상이어야 한다. 한도 초과 시 503 에러.
함정 5: stdout/stderr 외의 로그는 보이지 않음
파일에 로그 쓰면 휘발된다. 모든 로그는 stdout/stderr로 보내야 Cloud Logging에 자동 수집된다. Python의 경우 PYTHONUNBUFFERED=1 필수.
함정 6: 메모리 부족 (OOM)
OOM 시 컨테이너는 SIGKILL로 종료된다. Cloud Logging에 명시적 메시지 없이 인스턴스가 사라지면 OOM 의심. run.googleapis.com/container/memory/utilizations 메트릭의 p99 확인.
10. 마무리 및 다음 글
핵심 정리
- Cloud Run은 컨테이너 기반 fully managed serverless로, Services와 Jobs 두 리소스 타입을 가진다.
- 콜드 스타트 최적화의 우선순위: 측정 → 이미지 크기 → 앱 부팅 시간 → cpu-boost → min-instances. min-instances는 마지막 수단이다.
- concurrency 기본값 80은 일반 웹 API에 적절하지만, 메모리/CPU 사용량을 측정해서 조정이 필요하다.
- 운영 환경은 Dockerfile + Terraform IaC + 카나리 배포 + Secret Manager 통합이 표준 패턴이다.
- Cloud SQL은 Unix socket, GCS는 ADC, 민감정보는 Secret Manager, VPC 접근은 Direct VPC egress가 각 영역의 권장 방식이다.
다음 글(#5 예정): 비용 최적화 패턴 — Committed Use Discounts, Sustained Use Discounts, Spot VM, Cloud Run min-instances 최적화, FinOps 대시보드 구축.
참고 자료
- Cloud Run 공식 문서
- Cloud Run Pricing
- Container Runtime Contract
- General Development Tips
- Cloud Run with Cloud SQL
- Google Cloud Buildpacks
- Knative Service Spec
카테고리: Cloud / GCP
태그: gcp google-cloud cloud-run serverless containers cold-start dockerfile terraform cloud-architecture devops
'개발 프로젝트 > GCP 실습 일지' 카테고리의 다른 글
| GCP 시작 가이드 #5 — 비용 최적화 패턴 (0) | 2026.06.05 |
|---|---|
| GCP 시작 가이드 #3 — Cloud Storage 깊이 있게 (0) | 2026.05.30 |
| GCP 시작 가이드 #2 — IAM과 Service Account 깊이 있게 (0) | 2026.05.27 |
| GCP 시작 가이드 #1 — 계정 구조, 빌링, Free Tier의 함정 (0) | 2026.05.22 |