제로에서 프로덕션 데모 연습까지의 궁극적인 Cloud Run 가이드

1. 소개

이 Codelab에서는 Cloud Run을 시작하는 기본사항을 안내합니다. Cloud Run에서 호스팅되는 AI 에이전트용 VPC 액세스, Secret Manager, ADK를 비롯한 추가 기능을 사용하는 방법을 알아봅니다.

학습할 내용

  • nginx 이미지 배포
  • 소스 코드에서 배포
  • 배포 롤백
  • 배포 미리보기
  • 개발자 지식 MCP 서버 도구 사용
  • Cloud Run에서 Secret Manager 사용
  • VPC 내의 내부 Cloud Run 서비스에 연결
  • Cloud Run에 ADK 에이전트 배포

필요한 항목

  • 웹브라우저(예: Chrome)
  • 결제가 사용 설정된 Google Cloud 프로젝트

Google Cloud 프로젝트 만들기

  1. Google Cloud 콘솔의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 선택하거나 만듭니다.
  2. Cloud 프로젝트에 결제가 사용 설정되어 있는지 확인합니다. 프로젝트에 결제가 사용 설정되어 있는지 확인하는 방법을 알아보세요.

Cloud Shell 시작

Cloud Shell은 Google Cloud에서 실행되는 명령줄 환경으로, 필요한 도구가 미리 로드되어 제공됩니다.

  1. Google Cloud 콘솔 상단에서 Cloud Shell 활성화를 클릭합니다.
  2. Cloud Shell에 연결되면 인증을 확인합니다.
    gcloud auth list
    
  3. 프로젝트가 구성되었는지 확인합니다.
    gcloud config get project
    
  4. 프로젝트가 예상대로 설정되지 않은 경우 설정합니다.
    export PROJECT_ID=<YOUR_PROJECT_ID>
    gcloud config set project $PROJECT_ID
    

환경 변수 설정

이 Codelab에서는 다음 환경 변수를 사용합니다.

먼저 리전을 설정합니다.

export REGION=<YOUR_REGION>

그런 다음 PROJECT_ID와 REGION을 확인합니다.

echo "PROJECT_ID: $PROJECT_ID | REGION: $REGION"

2. 이미지에서 배포

이 섹션에서는 Docker Hub에서 직접 표준 nginx 이미지를 배포합니다. 공개적으로 액세스할 수 있도록 구성하고 컨테이너 포트를 80으로 설정합니다.

  1. nginx 서비스를 배포합니다.
   gcloud run deploy nginx-service \
     --image=nginx \
     --allow-unauthenticated \
     --port=80 \
     --region=$REGION
  1. 배포가 완료되면 명령어 출력에 서비스 URL이 표시됩니다. 브라우저에서 해당 URL을 열면 'Welcome to nginx!' 페이지가 표시됩니다.

3. 소스에서 배포

mkdir color-app && cd $_

다음 콘텐츠로 requirements.txt이라는 파일을 만듭니다.

Flask>=2.0.0
gunicorn>=20.0.0

다음 콘텐츠로 main.py이라는 파일을 만듭니다.

import os
from flask import Flask, render_template_string

app = Flask(__name__)

TEMPLATE = """
<!doctype html>
<html lang="en">
<head>
    <title>Cloud Run Traffic Revisions</title>
    <style>
        body {
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 50vh;
            background-color: darkseagreen;
            font-family: sans-serif;
        }
        .content {
            background-color: rgba(255, 255, 255, 0.8); /* Semi-transparent white background */
            padding: 2em;
            border-radius: 8px;
            text-align: center;
            box-shadow: 0 4px 8px rgba(0,0,0,0.1);
        }
    </style>
</head>
<body>
    <div class="content">
	  <!-- ROLLBACK DEMO: change this text to "gray" -->
        <p>background color: <strong>darkseagreen</strong></p>
    </div>
</body>
</html>
"""

@app.route('/')
def main():
    
    return render_template_string(TEMPLATE)

if __name__ == '__main__':
    port = int(os.environ.get('PORT', 8080))
    app.run(debug=True, host='0.0.0.0', port=port)

이제 다음 명령어를 실행해 보세요.

gcloud run deploy \
 --allow-unauthenticated
 --region $REGION

4. 롤백 및 미리보기 링크

이 섹션에서는 버그를 도입하고 수정 방법을 조사하는 동안 이전 버전으로 롤백하는 방법을 알아봅니다.

  1. 먼저 현재 트래픽을 제공하는 버전의 이름을 기록합니다. 이 버전에는 버그가 포함되어 있지 않습니다.
GOOD_REVISION=$(gcloud run revisions list --service color-app \
  --region $REGION --format 'value(REVISION)')
  1. color-app main.py 파일에서 'ROLLBACK DEMO'를 검색하고 다음으로 줄을 업데이트합니다.
<p>background color: <strong>gray</strong></p>
  1. 이제 gcloud run deploy를 다시 실행합니다. 이전 구성이 사용된 방식을 확인합니다.

이제 버그를 배포했으므로 소스로 돌아가 변경하거나 git revert를 실행한 다음 빌드하고 새 빌드를 트리거하는 등의 작업을 할 수 있습니다. 하지만 이 과정에서 오류가 발생할 수 있습니다.

더 안전한 방법은 롤백하는 것입니다.

  1. 이전 버전으로 롤백하려면 다음을 실행하세요.
gcloud run services update-traffic color-app \
  --to-revisions=$GOOD_REVISION=100 \
  --region=$REGION

이제 트래픽을 수신하지 않는 새 버전을 배포할 수 있습니다.

  1. 이제 텍스트를 다시 darkseagreen로 변경하여 버그를 수정합니다.
<p>background color: <strong>darkseagreen</strong></p>
  1. 배포하여 수정사항을 확인합니다. 트래픽의 100% 가 GOOD_REVISION에 고정되어 있으므로 트래픽이 수신되지 않습니다.
gcloud run deploy color-app --no-traffic --tag bugfix --region $REGION
  1. 배포를 확인합니다.

URL이 약간 다른 것을 확인할 수 있습니다. 이 배포를 방문하면 버그 수정이 표시됩니다.

  1. 최신 버전으로 트래픽을 다시 전송합니다.

이제 트래픽을 최신 버전으로 다시 설정합니다.

gcloud run services update-traffic color-app \
  --to-latest \
  --region=$REGION

버전 태그를 삭제합니다.

gcloud run services update-traffic color-app \
  --remove-tags=bugfix \
  --region=$REGION

롤백에 관한 자세한 내용은 문서를 참고하세요.

5. 개발자 지식 MCP 서버

개발자 지식 MCP 서버를 사용하면 AI 기반 개발 도구가 Google의 공식 개발자 문서를 검색하고 Firebase, Google Cloud, Android, 지도 등 Google 제품의 정보를 가져올 수 있습니다. AI 애플리케이션을 Google의 공식 문서 라이브러리에 직접 연결하면 최신 정보와 공신력 있는 맥락을 기반으로 코드를 작성하고 안내를 받을 수 있습니다.

AI 에이전트가 개발자 지식 MCP 서버에 액세스할 수 있도록 설치 안내를 따르세요.

설치 후 모델의 학습 날짜가 지난 후에 제공되었을 수 있는 문서의 최신 기능에 관해 AI 에이전트에게 질문할 수 있습니다.

예를 들어 Cloud Run 출시 노트를 보면 2026년 2월 24일에 'Cloud Run 서비스 상태 (미리보기)를 사용하여 외부 트래픽에 대한 자동 장애 조치 및 장애 복구가 포함된 고가용성 멀티 리전 Cloud Run 서비스 배포'에 관한 항목이 표시됩니다.

이제 AI 에이전트에게 '다중 리전 자동 장애 조치용 새로운 Cloud Run 기능에 대해 자세히 알려 줘'라고 요청할 수 있습니다.

6. Secret Manager 사용

Cloud Run에서 보안 비밀을 노출하는 방법에는 3가지가 있습니다.

  1. 환경 변수 (배포 시 가져온 버전으로 잠김)
  2. 파일 볼륨으로 마운트됩니다 (최신 버전으로 지속적으로 업데이트됨).
  3. 코드에서 Secret Manager 클라이언트 라이브러리를 사용합니다.

이 섹션에서는 전용 서비스 계정을 사용하여 보안 비밀을 환경 변수로 노출합니다.

  1. 'my-secret'이라는 새 보안 비밀을 만듭니다.
gcloud secrets create my-secret --replication-policy="automatic"
  1. 보안 비밀 값을 새 버전으로 추가합니다.
echo -n "my precious" | gcloud secrets versions add my-secret --data-file=-
  1. color-app에 전용 서비스 계정을 만듭니다.
gcloud iam service-accounts create color-app-sa \
     --display-name="Color App Service Account"
  1. 전용 서비스 계정에 보안 비밀에 대한 액세스 권한을 부여합니다.
   gcloud secrets add-iam-policy-binding my-secret \
     --member="serviceAccount:color-app-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
     --role="roles/secretmanager.secretAccessor"
  1. 다시 배포합니다. 이제 서비스가 MY_SECRET 환경 변수에 액세스할 수 있습니다.
gcloud run deploy color-app \
     --source . \
     --update-secrets=MY_SECRET=my-secret:latest \
     --service-account=color-app-sa@${PROJECT_ID}.iam.gserviceaccount.com \
     --region=$REGION

7. VPC에 연결

이 섹션에서는 다음 아키텍처를 설정합니다.

  • 공개 인터넷에서 액세스할 수 없는 비공개 백엔드
  • 직접 VPC 이그레스를 통해 백엔드와 통신하는 공개 프런트엔드

이 예시에서는 기본 네트워크와 서브넷을 사용합니다.

기본 요건: VPC가 Cloud Run 서비스로 내부 요청을 라우팅할 수 있도록 서브넷에서 비공개 Google 액세스가 사용 설정되어 있는지 확인합니다.

   gcloud compute networks subnets update default \
     --region=$REGION \
     --enable-private-ip-google-access
  1. 이 섹션의 폴더 만들기
mkdir ../vpc-demo
cd ../vpc-demo
  1. 비공개 백엔드 서비스 만들기
mkdir backend
touch backend/app.js
touch backend/package.json

backend/app.js 파일에 다음을 추가합니다.

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello World from the Private Backend!');
});

const port = process.env.PORT || 8080;
server.listen(port, () => {
  console.log(`Private backend listening on port ${port}`);
});

backend/package.json 파일에 다음을 추가합니다.

{
    "name": "backend",
    "scripts": {
        "start": "node app.js"
    }
}
  1. 내부 전용 인그레스로 비공개 백엔드를 배포합니다.
   gcloud run deploy private-backend \
     --source ./backend \
     --region $REGION \
     --ingress internal \
     --no-allow-unauthenticated
  1. 백엔드 URL을 기록합니다. 이 URL은 나중에 프런트엔드 앱에 제공됩니다.
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projec
tNumber)')

export BACKEND_URL="https://private-backend-${PROJECT_NUMBER}.${REGION}.run.app"
  1. 프런트엔드 앱 만들기
mkdir frontend
touch frontend/app.js
touch frontend/package.json

frontend/app.js 파일에 다음을 추가합니다.

const http = require('http');

const server = http.createServer(async (req, res) => {
  const backendUrl = process.env.BACKEND_URL;
  
  if (!backendUrl) {
    res.writeHead(500, { 'Content-Type': 'text/plain' });
    return res.end('Error: BACKEND_URL environment variable is missing.');
  }

  try {
    // Fetch the OIDC token from the Metadata server
    const tokenResponse = await fetch(`http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=${backendUrl}`, {
      headers: { 'Metadata-Flavor': 'Google' }
    });

    if (!tokenResponse.ok) {
      throw new Error(`Failed to fetch identity token: ${tokenResponse.statusText}`);
    }
    const token = await tokenResponse.text();

    // Ping the backend with the token
    const response = await fetch(backendUrl, {
      headers: { 'Authorization': `Bearer ${token}` }
    });
    const text = await response.text();

    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end(`Frontend successfully routed through VPC. Backend says: "${text}"`);
  } catch (error) {
    res.writeHead(500, { 'Content-Type': 'text/plain' });
    res.end(`Frontend failed to reach the backend. Error: ${error.message}`);
  }
});

const port = process.env.PORT || 8080;
server.listen(port, () => {
  console.log(`Public frontend listening on port ${port}`);
});
  1. frontend/package.json 파일에 다음을 추가합니다.
{
    "name": "backend",
    "scripts": {
        "start": "node app.js"
    }
}
  1. 프런트엔드 서비스의 전용 서비스 계정을 만듭니다.
  gcloud iam service-accounts create frontend-sa \
     --display-name="Frontend Service Account"
  1. Cloud Run 호출자 역할을 부여합니다.
PROJECT_ID=$(gcloud config get project)
  
gcloud projects add-iam-policy-binding $PROJECT_ID \
     --member="serviceAccount:frontend-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
     --role="roles/run.invoker"
  1. 이제 Direct VPC 이그레스를 사용하여 공개 프런트엔드를 배포합니다. 아웃바운드 요청이 VPC로 강제되도록 '–vpc-egress=all-traffic'을 설정합니다.
   gcloud run deploy public-frontend \
     --source ./frontend \
     --region $REGION \
     --allow-unauthenticated \
     --network default \
     --subnet default \
     --vpc-egress all-traffic \
    --service-account=frontend-sa@${PROJECT_ID}.iam.gserviceaccount.com \
     --set-env-vars BACKEND_URL=$BACKEND_URL
  1. 서비스 확인
  • 프런트엔드 테스트: 공개 프런트엔드 URL을 컬합니다. 백엔드와 성공적으로 통신하고 응답을 반환해야 합니다.
     FRONTEND_URL=$(gcloud run services describe public-frontend --region $REGION --format='value(status.url)')
     curl $FRONTEND_URL
  • 백엔드 테스트 (직접): 로컬 머신 (공용 인터넷)에서 백엔드 URL을 직접 컬링해 봅니다. 인그레스가 'internal'로 제한되고 인증이 필요하므로 404 오류와 함께 실패해야 합니다.
  curl $BACKEND_URL

8. ADK 에이전트 배포

이 섹션에서는 Python 빌드팩이 에이전트 개발 키트 (ADK)의 기본 진입점 감지를 지원하는 방법을 알아봅니다.

다음 폴더 구조를 만듭니다.

adk-demo
 - my_agent
   - __init.py__
   - agent.py
 - requirements.txt
  1. 폴더 구조 만들기
mkdir ../adk-demo
cd ../adk-demo
mkdir my_agent
touch my_agent/__init.py__
touch my_agent/agent.py
touch requirements.txt
  1. my_agent/__init.py__ 파일에 다음 내용을 추가합니다.
from . import agent
  1. my_agent/agent.py 파일에 다음 내용을 추가합니다.
from google.adk import Agent

root_agent = Agent(
    name="demo_agent",
    model="gemini-3-flash-preview",
    instruction="You are a helpful assistant for a Cloud Run demo."
)
  1. requirements.txt 파일에 다음 내용을 추가합니다.
google-adk
  1. 에이전트 전용 서비스 계정을 만듭니다.
  gcloud iam service-accounts create agent-sa \
     --display-name="Agent Service Account"
  1. 서비스 계정에 Vertex AI 사용자 역할을 부여합니다.
PROJECT_ID=$(gcloud config get-value project)
  
gcloud projects add-iam-policy-binding $PROJECT_ID \
     --member="serviceAccount:agent-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
     --role="roles/aiplatform.user"
  1. ADK 에이전트 배포

Gemini API에 액세스할 수 있는 리전에 배포해야 합니다. 이 예시에서는 us-west1입니다.

gcloud run deploy my-adk-agent-demo \
   --source . \
   --region us-west1 \
   --allow-unauthenticated \
   --service-account=agent-sa@${PROJECT_ID}.iam.gserviceaccount.com \
   --set-env-vars="GOOGLE_GENAI_USE_VERTEXAI=TRUE,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global"
  1. 엔드포인트 컬링

에이전트가 프로덕션에 즉시 사용 가능한 API로 제공되는 것을 확인할 수 있습니다.

Cloud Run 서비스 URL을 환경 변수에 기록합니다.

AGENT_URL=$(gcloud run services describe my-adk-agent-demo \
  --region us-west1 \
  --format 'value(status.url)')

에이전트와 세션 만들기

curl -X POST $AGENT_URL/apps/my_agent/users/u_123/sessions/s_123 -H "Content-Type: application/json" -d '{"key1": "value1", "key2": 42}'

Cloud Run이 무엇인지 물어보고 에이전트가 말한 내용만 표시하도록 대답을 필터링합니다.

curl -X POST $AGENT_URL/run \
-H "Content-Type: application/json" \
-d "{
   \"appName\": \"my_agent\",
   \"userId\": \"u_123\",
   \"sessionId\": \"s_123\",
   \"newMessage\": { 
        \"role\": \"user\", 
        \"parts\": [{ \"text\": \"What is Cloud Run?\" 
    }]}
}" | python3 -c "import sys, json; print(json.load(sys.stdin)[-1]['content']['parts'][0]['text'])"

다음과 비슷한 내용이 표시됩니다.

Hello! I am **demo_agent**, and I'm here to help you with your Cloud Run demo. **Cloud Run** is a fully managed compute platform by Google Cloud that allows you to run **containerized applications** in a serverless environment...

9. 삭제

Google Cloud 계정에 지속적으로 요금이 청구되지 않도록 하려면 전체 프로젝트를 삭제하거나 (아래 참고) 이 Codelab에서 만든 개별 리소스를 삭제하면 됩니다.

nginx, color-app, private-backend, public-frontend 서비스를 삭제합니다.

gcloud run services delete nginx-service --region $REGION --quiet
gcloud run services delete color-app --region $REGION --quiet
gcloud run services delete private-backend --region $REGION --quiet
gcloud run services delete public-frontend --region $REGION --quiet

ADK 에이전트를 삭제합니다 (참고: 이 예에서는 us-west1에 배포됨).

gcloud run services delete my-adk-agent-demo --region us-west1 --quiet

Secret Manager에 저장된 보안 비밀을 삭제합니다.

gcloud secrets delete my-secret --quiet

Color App 서비스 계정 삭제

gcloud iam service-accounts delete color-app-sa@${PROJECT_ID}.iam.gserviceaccount.com --quiet

ADK 에이전트 서비스 계정 삭제

gcloud iam service-accounts delete agent-sa@${PROJECT_ID}.iam.gserviceaccount.com --quiet

(선택사항) 프로젝트 삭제

이 Codelab을 위해 새 프로젝트를 만든 경우 전체 프로젝트를 삭제하여 모든 리소스를 한 번에 삭제할 수 있습니다.

# run only if you want to delete the entire project
gcloud projects delete $PROJECT_ID

10. 축하합니다.

Codelab을 완료했습니다. Cloud Run 시작하기의 기본사항을 살펴보았습니다.

학습한 내용

  • nginx 이미지 배포
  • 소스 코드에서 배포
  • 배포 롤백
  • 배포 미리보기
  • 개발자 지식 MCP 서버 도구 사용
  • Cloud Run에서 Secret Manager 사용
  • VPC 내의 내부 Cloud Run 서비스에 연결
  • Cloud Run에 ADK 에이전트 배포