개발 프로젝트/GCP 실습 일지

GCP 시작 가이드 #4 — Cloud Run 배포 실전

DataHunter7 2026. 6. 3. 17:05

핵심 요약

  • 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은 두 가지 실행 환경을 제공한다:

항목1st gen (gVisor)2nd gen (microVM)
격리 방식 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 요청 라이프사이클

콜드 스타트 디버깅을 위해 라이프사이클을 정확히 이해해야 한다:

1. 요청 도착
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:

# Stage 1: 빌드
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 앱 기준:

# Stage 1: 의존성 설치
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 파일도 필수다. 대표적인 제외 항목:

.git
.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로 지정한다:

gcloud run deploy my-app \
  --source . \
  --region=asia-northeast3 \
  --set-env-vars="GOOGLE_ENTRYPOINT=uvicorn main:app --host 0.0.0.0 --port 8080"
 
 

Buildpacks 권장 사용처: 프로토타입, 사이드 프로젝트, 표준 웹 프레임워크. 운영 환경은 Dockerfile이 더 안전하다(이미지 내용을 완전히 통제할 수 있음).


3. 배포 명령과 핵심 옵션

3.1 표준 배포 명령

서울 리전, 운영 환경 기준 권장 옵션 풀세트:

gcloud run deploy my-api \
  --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 파일로 관리한다:

apiVersion: serving.knative.dev/v1
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
 
 

적용 명령:

gcloud run services replace service.yaml --region=asia-northeast3
 
 

3.3 Terraform으로 IaC 관리

운영 환경은 Terraform 권장:

resource "google_cloud_run_v2_service" "api" {
  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.type="cloud_run_revision"
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를 일시적으로 부스트한다:

gcloud run deploy my-api \
  --source . \
  --region=asia-northeast3 \
  --cpu-boost
 
 

추가 비용 없이 콜드 스타트 시간 30~50% 단축 효과. 거의 항상 켜두는 게 이득이다.

기법 4: Min-instances로 회피

최소 1개 인스턴스를 항상 활성화:

gcloud run services update my-api \
  --region=asia-northeast3 \
  --min-instances=1

 

 

콜드 스타트 0이지만, idle 인스턴스도 과금된다. 4.3절에서 비용 분석.

기법 5: 2nd gen 회피 (Python/Node.js의 경우)

CPU/IO 집약적이지 않은 일반 웹 API는 1st gen이 콜드 스타트 면에서 유리할 수 있다:

gcloud run deploy my-api \
  --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 사용량 확인:

gcloud monitoring metrics list \
  --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를 거치지 않고 안전하게 연결된다.

배포 명령에 추가 옵션:

gcloud run deploy my-api \
  --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 경로로 연결:

import sqlalchemy

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 부여하면 별도 인증 없이 클라이언트 사용 가능:

from google.cloud import storage

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에 저장하고 환경변수로 주입:

echo -n "my-db-password" | gcloud secrets create db-password --data-file=-

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, 권장):

gcloud run deploy my-api \
  --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% 라우팅하지 않고 보류:

gcloud run deploy my-api \
  --source . \
  --region=asia-northeast3 \
  --no-traffic \
  --tag=canary
 
 

이렇게 하면 새 Revision은 canary-my-api-xxx.run.app 같은 별도 URL로 접근 가능하지만, 메인 트래픽은 받지 않는다.

7.2 카나리 배포 패턴

신규 Revision에 10% 트래픽 할당:

gcloud run services update-traffic my-api \
  --region=asia-northeast3 \
  --to-revisions=my-api-00042-abc=10,my-api-00041-xyz=90
 
 

5분간 메트릭 모니터링 후 점진 증가:

gcloud run services update-traffic my-api \
  --region=asia-northeast3 \
  --to-revisions=my-api-00042-abc=50,my-api-00041-xyz=50
 
 

문제 발견 시 즉시 롤백:

gcloud run services update-traffic my-api \
  --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 생성과 실행

gcloud run jobs create migrate-db \
  --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. 마무리 및 다음 글

핵심 정리

  1. Cloud Run은 컨테이너 기반 fully managed serverless로, Services와 Jobs 두 리소스 타입을 가진다.
  2. 콜드 스타트 최적화의 우선순위: 측정 → 이미지 크기 → 앱 부팅 시간 → cpu-boost → min-instances. min-instances는 마지막 수단이다.
  3. concurrency 기본값 80은 일반 웹 API에 적절하지만, 메모리/CPU 사용량을 측정해서 조정이 필요하다.
  4. 운영 환경은 Dockerfile + Terraform IaC + 카나리 배포 + Secret Manager 통합이 표준 패턴이다.
  5. 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 / GCP

태그: gcp google-cloud cloud-run serverless containers cold-start dockerfile terraform cloud-architecture devops