Cloud Run 작업 시작하기

1. 소개

96d07289bb51daa7.png

개요

Cloud Run 서비스는 HTTP 요청을 무기한으로 리슨하는 컨테이너에 적합하지만 Cloud Run 작업은 완료될 때까지 실행되고 요청을 처리하지 않는 컨테이너에 더 적합할 수 있습니다. 예를 들어 데이터베이스의 레코드 처리, Cloud Storage 버킷의 파일 목록 처리 또는 Pi 계산과 같은 장기 실행 작업이 Cloud Run 작업으로 구현된 경우 잘 작동합니다.

작업은 요청을 처리하거나 포트에서 리슨할 수 없습니다. 즉, Cloud Run 서비스와 달리 웹 서버를 번들로 제공해서는 안 됩니다. 대신 작업 컨테이너가 완료되면 작업을 종료해야 합니다.

Cloud Run 작업에서는 여러 태스크를 지정하여 컨테이너의 여러 복사본을 동시에 실행할 수 있습니다. 각 태스크는 실행 중인 컨테이너의 복사본 하나를 나타냅니다. 각 태스크가 데이터의 하위 집합을 독립적으로 처리할 수 있는 경우 여러 태스크를 사용하는 것이 유용합니다. 예를 들어 Cloud SQL에서 레코드 10,000개 또는 Cloud Storage에서 파일 10,000개를 레코드 또는 파일 1,000개를 각각 동시에 처리하는 태스크 10개를 통해 더 빠르게 처리할 수 있습니다.

작업 워크플로

다음 두 단계만 거치면 Cloud Run 작업을 간편하게 사용할 수 있습니다.

  1. 작업을 만듭니다. 여기에는 컨테이너 이미지, 리전, 환경 변수 등 작업을 실행하는 데 필요한 모든 구성이 캡슐화됩니다.
  2. 작업을 실행합니다. 그러면 작업이 새로 실행됩니다. 필요한 경우 Cloud Scheduler를 사용하여 일정에 따라 작업이 실행되도록 설정합니다.

미리보기 제한사항

미리보기 중에는 Cloud Run 작업에 다음과 같은 제약 조건이 있습니다.

  • 리전별로 프로젝트당 최대 동시 실행 수는 50회(동일하거나 다른 작업)입니다.
  • Cloud Console의 Cloud Run 작업 페이지에서 기존 작업을 확인하고, 실행을 시작하고, 실행 상태를 모니터링할 수 있습니다. Cloud Console에서는 현재 새 작업 만들기가 지원되지 않으므로 gcloud를 사용하여 새 작업을 만듭니다.
  • 프로덕션 워크로드에 Cloud Run 작업을 사용하지 마세요. 안정성이나 성능이 보장되지 않습니다. Cloud Run 작업은 GA 이전 알림 없이 이전 버전과 호환되지 않는 방식으로 변경될 수 있습니다.

이 Codelab에서는 먼저 Node.js 애플리케이션을 탐색하여 웹페이지의 스크린샷을 찍고 Cloud Storage에 저장합니다. 그런 다음 애플리케이션의 컨테이너 이미지를 빌드하고, Cloud Run에서 작업으로 실행하고, 더 많은 웹페이지를 처리하도록 작업을 업데이트한 뒤, Cloud Scheduler로 일정에 따라 작업을 실행합니다.

실습 내용

  • 앱을 사용하여 웹페이지의 스크린샷을 찍는 방법
  • 애플리케이션의 컨테이너 이미지를 빌드하는 방법
  • 애플리케이션의 Cloud Run 작업을 만드는 방법
  • Cloud Run 작업으로 애플리케이션을 실행하는 방법
  • 작업을 업데이트하는 방법
  • Cloud Scheduler로 작업을 예약하는 방법

2 설정 및 요구사항

자습형 환경 설정

  1. Google Cloud Console에 로그인하여 새 프로젝트를 만들거나 기존 프로젝트를 재사용합니다. 아직 Gmail이나 Google Workspace 계정이 없는 경우 계정을 만들어야 합니다.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • 프로젝트 이름은 이 프로젝트 참가자의 표시 이름입니다. 이는 Google API에서 사용하지 않는 문자열이며 언제든지 업데이트할 수 있습니다.
  • 프로젝트 ID는 모든 Google Cloud 프로젝트에서 고유해야 하며, 변경할 수 없습니다(설정된 후에는 변경할 수 없음). Cloud Console은 고유한 문자열을 자동으로 생성합니다. 일반적으로 신경 쓰지 않아도 됩니다. 대부분의 Codelab에서는 프로젝트 ID를 참조해야 하며(일반적으로 PROJECT_ID로 식별됨), 마음에 들지 않는 경우 임의로 다시 생성하거나 직접 지정해서 사용할 수 있는지 확인하세요. 프로젝트가 생성되면 프로젝트 ID가 '고정'됩니다.
  • 세 번째 값은 일부 API에서 사용하는 프로젝트 번호입니다. 이 세 가지 값에 대한 자세한 내용은 문서를 참조하세요.
  1. 다음으로 Cloud 리소스/API를 사용하려면 Cloud Console에서 결제를 사용 설정해야 합니다. 이 Codelab 실행에는 많은 비용이 들지 않습니다. 이 튜토리얼을 마친 후 비용이 결제되지 않도록 리소스를 종료하려면 Codelab의 끝에 있는 '삭제' 안내를 따르세요. Google Cloud 새 사용자에게는 미화 $300 상당의 무료 체험판 프로그램에 참여할 수 있는 자격이 부여됩니다.

Cloud Shell 시작

Google Cloud를 노트북에서 원격으로 실행할 수 있지만, 이 Codelab에서는 Cloud에서 실행되는 명령줄 환경인 Google Cloud Shell을 사용합니다.

Google Cloud Console의 오른쪽 상단 툴바에 있는 Cloud Shell 아이콘을 클릭합니다.

55efc1aaa7a4d3ad.png

환경을 프로비저닝하고 연결하는 데 몇 분 정도 소요됩니다. 완료되면 다음과 같이 표시됩니다.

7ffe5cbb04455448.png

가상 머신에는 필요한 개발 도구가 모두 들어있습니다. 영구적인 5GB 홈 디렉토리를 제공하고 Google Cloud에서 실행되므로 네트워크 성능과 인증이 크게 개선됩니다. 이 실습의 모든 작업은 브라우저만으로 수행할 수 있습니다.

gcloud 설정

Cloud Shell에서 Cloud Run 작업을 배포할 프로젝트 ID와 리전을 설정합니다. 이러한 변수를 PROJECT_IDREGION 변수로 저장합니다. Cloud Run 위치 중 하나에서 리전을 선택할 수 있습니다.

PROJECT_ID=[YOUR-PROJECT-ID]
REGION=[YOUR-REGION]
gcloud config set core/project $PROJECT_ID
gcloud config set run/region $REGION

API 사용 설정

필요한 모든 서비스를 사용 설정합니다.

gcloud services enable \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  run.googleapis.com

3. 코드 가져오기

먼저 Node.js 애플리케이션을 탐색하여 웹페이지의 스크린샷을 찍고 Cloud Storage에 저장해 보겠습니다. 나중에 애플리케이션의 컨테이너 이미지를 빌드하고 Cloud Run에서 작업으로 실행합니다.

Cloud Shell에서 다음 명령어를 실행하여 저장소의 애플리케이션 코드를 클론합니다.

git clone https://github.com/GoogleCloudPlatform/jobs-demos.git

애플리케이션이 포함된 디렉터리로 이동합니다.

cd jobs-demos/screenshot

다음과 같은 파일 레이아웃이 표시됩니다.

screenshot
 |
 ├── Dockerfile
 ├── README.md
 ├── screenshot.js
 ├── package.json

다음은 각 파일의 간략한 설명입니다.

  • screenshot.js에는 애플리케이션의 Node.js 코드가 포함됩니다.
  • package.json은 라이브러리 종속 항목을 정의합니다.
  • Dockerfile은 컨테이너 이미지를 정의합니다.

4. 코드 살펴보기

코드를 탐색하려면 Cloud Shell 창 상단의 Open Editor 버튼을 클릭하여 내장된 텍스트 편집기를 사용합니다.

f78880c00c0af1ef.png

다음은 각 파일의 간략한 설명입니다.

screenshot.js

screenshot.js는 먼저 Puppeteer 및 Cloud Storage를 종속 항목으로 추가합니다. Puppeteer는 웹페이지의 스크린샷을 찍는 데 사용하는 Node.js 라이브러리입니다.

const puppeteer = require('puppeteer');
const {Storage} = require('@google-cloud/storage');

Puppeteer를 초기화하는 initBrowser 함수와 지정된 URL의 스크린샷을 찍는 takeScreenshot 함수가 있습니다.

async function initBrowser() {
  console.log('Initializing browser');
  return await puppeteer.launch();
}

async function takeScreenshot(browser, url) {
  const page = await browser.newPage();

  console.log(`Navigating to ${url}`);
  await page.goto(url);

  console.log(`Taking a screenshot of ${url}`);
  return await page.screenshot({
    fullPage: true
  });
}

그 다음에는 Cloud Storage 버킷을 가져오거나 만드는 함수와 웹페이지의 스크린샷을 버킷에 업로드하는 함수가 있습니다.

async function createStorageBucketIfMissing(storage, bucketName) {
  console.log(`Checking for Cloud Storage bucket '${bucketName}' and creating if not found`);
  const bucket = storage.bucket(bucketName);
  const [exists] = await bucket.exists();
  if (exists) {
    // Bucket exists, nothing to do here
    return bucket;
  }

  // Create bucket
  const [createdBucket] = await storage.createBucket(bucketName);
  console.log(`Created Cloud Storage bucket '${createdBucket.name}'`);
  return createdBucket;
}

async function uploadImage(bucket, taskIndex, imageBuffer) {
  // Create filename using the current time and task index
  const date = new Date();
  date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
  const filename = `${date.toISOString()}-task${taskIndex}.png`;

  console.log(`Uploading screenshot as '${filename}'`)
  await bucket.file(filename).save(imageBuffer);
}

마지막으로 main 함수는 진입점입니다.

async function main(urls) {
  console.log(`Passed in urls: ${urls}`);

  const taskIndex = process.env.CLOUD_RUN_TASK_INDEX || 0;
  const url = urls[taskIndex];
  if (!url) {
    throw new Error(`No url found for task ${taskIndex}. Ensure at least ${parseInt(taskIndex, 10) + 1} url(s) have been specified as command args.`);
  }
  const bucketName = process.env.BUCKET_NAME;
  if (!bucketName) {
    throw new Error('No bucket name specified. Set the BUCKET_NAME env var to specify which Cloud Storage bucket the screenshot will be uploaded to.');
  }

  const browser = await initBrowser();
  const imageBuffer = await takeScreenshot(browser, url).catch(async err => {
    // Make sure to close the browser if we hit an error.
    await browser.close();
    throw err;
  });
  await browser.close();

  console.log('Initializing Cloud Storage client')
  const storage = new Storage();
  const bucket = await createStorageBucketIfMissing(storage, bucketName);
  await uploadImage(bucket, taskIndex, imageBuffer);

  console.log('Upload complete!');
}

main(process.argv.slice(2)).catch(err => {
  console.error(JSON.stringify({severity: 'ERROR', message: err.message}));
  process.exit(1);
});

main 메서드에 관한 다음 사항에 유의하세요.

  • URL이 인수로 전달됩니다.
  • 버킷 이름이 사용자 정의 BUCKET_NAME 환경 변수로 전달됩니다. 버킷 이름은 모든 Google Cloud에서 전역적으로 고유해야 합니다.
  • CLOUD_RUN_TASK_INDEX 환경 변수는 Cloud Run 작업에서 전달합니다. Cloud Run 작업은 애플리케이션의 여러 복사본을 고유한 태스크로 실행할 수 있습니다. CLOUD_RUN_TASK_INDEX는 실행 중인 태스크의 색인을 나타냅니다. 코드가 Cloud Run 작업 외부에서 실행되는 경우 기본값은 0입니다. 애플리케이션이 여러 태스크로 실행되는 경우 각 태스크/컨테이너는 담당하는 URL을 선택하여 스크린샷을 찍고 이미지를 버킷에 저장합니다.

package.json

package.json 파일은 애플리케이션을 정의하고 Cloud Storage 및 Puppeteer의 종속 항목을 지정합니다.

{
  "name": "screenshot",
  "version": "1.0.0",
  "description": "Create a job to capture screenshots",
  "main": "screenshot.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "Google LLC",
  "license": "Apache-2.0",
  "dependencies": {
    "@google-cloud/storage": "^5.18.2",
    "puppeteer": "^13.5.1"
  }
}

Dockerfile

Dockerfile은 필요한 모든 라이브러리와 종속 항목을 가진 애플리케이션의 컨테이너 이미지를 정의합니다.

FROM node:17-alpine

# Installs latest Chromium (92) package.
RUN apk add --no-cache \
      chromium \
      nss \
      freetype \
      harfbuzz \
      ca-certificates \
      ttf-freefont \
      nodejs \
      npm

# Tell Puppeteer to skip installing Chrome. We'll be using the installed package.
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
    PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser

# Add user so we don't need --no-sandbox.
RUN addgroup -S pptruser && adduser -S -g pptruser pptruser \
    && mkdir -p /home/pptruser/Downloads /app \
    && chown -R pptruser:pptruser /home/pptruser \
    && chown -R pptruser:pptruser /app

# Install dependencies
COPY package*.json ./
RUN npm install

# Copy all files
COPY . .

# Run everything after as a non-privileged user.
USER pptruser

ENTRYPOINT ["node", "screenshot.js"]

5. 컨테이너 이미지 빌드 및 게시

Artifact Registry는 Google Cloud의 컨테이너 이미지 스토리지 및 관리 서비스입니다. 자세한 내용은 컨테이너 이미지 작업을 참조하세요. Artifact Registry는 Docker 저장소에 Docker 및 OCI 컨테이너 이미지를 저장할 수 있습니다.

containers라는 새 Artifact Registry 저장소를 만듭니다.

gcloud artifacts repositories create containers --repository-format=docker --location=$REGION

컨테이너 이미지를 빌드하고 게시합니다.

gcloud builds submit -t $REGION-docker.pkg.dev/$PROJECT_ID/containers/screenshot:v1

몇 분 후에 Artifact Registry에 빌드되고 호스팅되는 컨테이너 이미지가 표시됩니다.

62e50ebe805f9a9c.png

6. 작업 만들기

작업을 만들기 전에 작업을 실행하는 데 사용할 서비스 계정을 만들어야 합니다.

gcloud iam service-accounts create screenshot-sa --display-name="Screenshot app service account"

버킷과 객체를 만드는 데 사용할 수 있도록 서비스 계정에 storage.admin 역할을 부여합니다.

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --role roles/storage.admin \
  --member serviceAccount:screenshot-sa@$PROJECT_ID.iam.gserviceaccount.com

이제 작업을 실행하는 데 필요한 구성이 포함된 Cloud Run 작업을 만들 준비가 되었습니다.

gcloud beta run jobs create screenshot \
  --image=$REGION-docker.pkg.dev/$PROJECT_ID/containers/screenshot:v1 \
  --args="https://example.com" \
  --args="https://cloud.google.com" \
  --tasks=2 \
  --task-timeout=5m \
  --set-env-vars=BUCKET_NAME=screenshot-$PROJECT_ID \
  --service-account=screenshot-sa@$PROJECT_ID.iam.gserviceaccount.com

이렇게 하면 Cloud Run 작업이 실행되지 않은 상태로 생성됩니다.

웹페이지가 인수로 전달되는 것을 확인합니다. 스크린샷을 저장할 버킷 이름이 환경 변수로 전달됩니다.

--tasks 플래그로 실행할 태스크의 수를 지정하여 컨테이너의 여러 사본을 동시에 실행할 수 있습니다. 각 태스크는 실행 중인 컨테이너의 복사본 하나를 나타냅니다. 각 태스크가 데이터의 하위 집합을 독립적으로 처리할 수 있는 경우 여러 태스크를 사용하는 것이 유용합니다. 이를 위해 각 태스크는 CLOUD_RUN_TASK_INDEX 환경 변수에 저장된 색인을 인식합니다. 코드는 데이터의 하위 집합을 처리하는 태스크를 결정하는 역할을 합니다. 이 샘플에서 --tasks=2를 확인하세요. 이렇게 하면 처리하려는 URL 2개에 대해 컨테이너 2개가 실행됩니다.

각 태스크는 최대 1시간 동안 실행될 수 있습니다. 이 예시와 같이 --task-timeout 플래그를 사용하여 이 제한 시간을 줄일 수 있습니다. 작업이 성공적으로 완료되려면 모든 태스크가 성공해야 합니다. 실패한 태스크는 기본적으로 다시 시도되지 않습니다. 태스크가 실패할 경우 다시 시도되도록 구성할 수 있습니다. 태스크가 재시도 횟수를 초과하면 전체 작업이 실패합니다.

기본적으로 작업은 가능한 한 많은 태스크를 동시에 실행합니다. 작업의 태스크 수와 동일하며 최대 100개입니다. 확장성이 제한된 백엔드에 액세스하는 작업의 경우 동시 로드를 더 낮게 설정하는 것이 좋습니다. 제한된 수의 활성 연결을 지원하는 데이터베이스를 예시로 들 수 있습니다. --parallelism 플래그를 사용하여 동시 로드를 줄일 수 있습니다.

7. 작업 실행

작업을 실행하기 전에 작업을 나열하여 작업이 생성되었는지 확인합니다.

gcloud beta run jobs list

✔
JOB: screenshot
REGION: $REGION
LAST RUN AT:
CREATED: 2022-02-22 12:20:50 UTC

다음 명령어를 사용하여 작업을 실행합니다.

gcloud beta run jobs execute screenshot

이렇게 하면 작업이 실행됩니다. 현재 및 이전 실행을 나열할 수 있습니다.

gcloud beta run jobs executions list --job screenshot

...
JOB: screenshot
EXECUTION: screenshot-znkmm
REGION: $REGION
RUNNING: 1
COMPLETE: 1 / 2
CREATED: 2022-02-22 12:40:42 UTC

실행을 설명합니다. 녹색 체크표시와 tasks completed successfully 메시지가 표시됩니다.

gcloud beta run jobs executions describe screenshot-znkmm
✔ Execution screenshot-znkmm in region $REGION
2 tasks completed successfully

Image:           $REGION-docker.pkg.dev/$PROJECT_ID/containers/screenshot at 311b20d9...
Tasks:           2
Args:            https://example.com https://cloud.google.com
Memory:          1Gi
CPU:             1000m
Task Timeout:    3600s
Parallelism:     2
Service account: 11111111-compute@developer.gserviceaccount.com
Env vars:
  BUCKET_NAME    screenshot-$PROJECT_ID

Cloud Console의 Cloud Run 작업 페이지에서 상태를 확인할 수도 있습니다.

e59ed4e532b974b1.png

Cloud Storage 버킷을 확인하면 생성된 스크린샷 파일 2개가 표시됩니다.

f2f86e60b94ba47c.png

실행이 완료되기 전에 중지해야 하는 경우도 있습니다. 다른 매개변수를 사용하여 작업을 실행해야 하거나 코드에 오류가 있고 불필요한 컴퓨팅 시간을 사용하는 것을 원하지 않기 때문일 수 있습니다.

작업 실행을 중지하려면 실행을 삭제해야 합니다.

gcloud beta run jobs executions delete screenshot-znkmm

8. 작업 업데이트

다음 실행 시 컨테이너의 새 버전이 Cloud Run 작업에서 자동으로 선택되지 않습니다. 작업의 코드를 변경하는 경우 컨테이너를 다시 빌드하고 작업을 업데이트해야 합니다. 태그가 지정된 이미지를 사용하면 현재 사용 중인 이미지의 버전을 확인할 수 있습니다.

마찬가지로 일부 구성 변수를 업데이트하려면 작업도 업데이트해야 합니다. 후속 작업 실행 시 새 컨테이너 및 구성 설정이 사용됩니다.

작업을 업데이트하고 --args 플래그에서 앱이 스크린샷을 찍는 페이지를 변경합니다. 또한 페이지 수를 반영하도록 --tasks 플래그를 업데이트합니다.

gcloud beta run jobs update screenshot \
  --args="https://www.pinterest.com" \
  --args="https://www.apartmenttherapy.com" \
  --args="https://www.google.com" \
  --tasks=3

작업을 다시 실행합니다. 이번에는 실행이 완료되기를 기다리는 동안 --wait 플래그를 전달합니다.

gcloud beta run jobs execute screenshot --wait

몇 초 후에 버킷에 스크린샷 3개가 더 표시됩니다.

ce91c96dcfd271bb.png

9. 작업 예약

지금까지 이 Codelab에서는 작업을 수동으로 실행하는 것을 보여줍니다. 실제 시나리오에서는 이벤트에 대한 응답이나 일정에 따라 작업을 실행해야 할 수 있습니다. Cloud Run REST API를 사용하면 됩니다. Cloud Scheduler를 사용하여 일정에 따라 스크린샷 작업을 실행하는 방법을 살펴보겠습니다.

먼저 Cloud Scheduler API가 사용 설정되어 있는지 확인합니다.

gcloud services enable cloudscheduler.googleapis.com

매일 오전 9시에 Cloud Run 작업을 실행하는 Cloud Scheduler 작업을 만듭니다.

PROJECT_NUMBER="$(gcloud projects describe $(gcloud config get-value project) --format='value(projectNumber)')"

gcloud scheduler jobs create http screenshot-scheduled --schedule "0 9 * * *" \
   --http-method=POST \
   --uri=https://$REGION-run.googleapis.com/apis/run.googleapis.com/v1/namespaces/$PROJECT_ID/jobs/screenshot:run \
   --oauth-service-account-email=$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
   --location $REGION

Cloud Scheduler 작업이 만들어지고 Cloud Run 작업을 호출할 준비가 되었는지 확인합니다.

gcloud scheduler jobs list

ID: screenshot-scheduled
LOCATION: $REGION
SCHEDULE (TZ): 0 9 * * * (Etc/UTC)
TARGET_TYPE: HTTP
STATE: ENABLED

테스트하려면 Cloud Scheduler를 수동으로 트리거하세요.

gcloud scheduler jobs run screenshot-scheduled

몇 초 후 Cloud Scheduler 호출로 인해 스크린샷이 3개 더 표시됩니다.

971ea598020cf9ba.png

10. 축하합니다

축하합니다. Codelab을 완료했습니다.

학습한 내용

  • 앱을 사용하여 웹페이지의 스크린샷을 찍는 방법
  • 애플리케이션의 컨테이너 이미지를 빌드하는 방법
  • 애플리케이션의 Cloud Run 작업을 만드는 방법
  • Cloud Run 작업으로 애플리케이션을 실행하는 방법
  • 작업을 업데이트하는 방법
  • Cloud Scheduler로 작업을 예약하는 방법