Cloud Run 함수 시작하기

1. 소개

개요

Cloud Run FunctionsCloud RunEventarc로 구동되는 Google Cloud의 Functions as a Service 제품으로, 성능 및 확장성에 대한 고급 제어, 함수 런타임에 대한 추가 제어, 90개 이상의 이벤트 소스의 트리거를 사용할 수 있습니다.

이 Codelab에서는 HTTP 호출에 응답하고 Pub/Sub 메시지 및 Cloud 감사 로그에 의해 트리거되는 Cloud Run 함수를 만드는 방법을 안내합니다.

이 Codelab에서는 --base-image 플래그를 사용하여 기본 이미지를 지정하여 함수 배포에 자동 기본 이미지 업데이트도 사용합니다. Cloud Run에서 기본 이미지에 대한 자동 업데이트를 사용하면 Google이 기본 이미지의 운영체제 및 언어 런타임 구성요소에 보안 패치를 자동으로 적용할 수 있습니다. 기본 이미지가 업데이트되도록 서비스를 다시 빌드하거나 다시 배포할 필요가 없습니다. 자세한 내용은 자동 기본 이미지 업데이트를 참고하세요.

자동 기본 이미지 업데이트를 사용하지 않으려면 이 Codelab에 표시된 예에서 --base-image 플래그를 삭제하면 됩니다.

학습할 내용

  • Cloud Run 함수 및 자동 기본 이미지 업데이트 사용 방법 개요
  • HTTP 호출에 응답하는 함수를 작성하는 방법
  • Pub/Sub 메시지에 응답하는 함수를 작성하는 방법
  • Cloud Storage 이벤트에 응답하는 함수를 작성하는 방법
  • 두 버전 간에 트래픽을 분할하는 방법
  • 최소 인스턴스로 콜드 스타트를 없애는 방법

2. 설정 및 요구사항

루트 폴더 만들기

모든 예제의 루트 폴더를 만듭니다.

mkdir crf-codelab
cd crf-codelab

환경 변수 설정

이 Codelab 전체에서 사용할 환경 변수를 설정합니다.

gcloud config set project <YOUR-PROJECT-ID>
REGION=<YOUR_REGION>

PROJECT_ID=$(gcloud config get-value project)

API 사용 설정

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

gcloud services enable \
  artifactregistry.googleapis.com \
  cloudbuild.googleapis.com \
  eventarc.googleapis.com \
  run.googleapis.com \
  logging.googleapis.com \
  pubsub.googleapis.com

3. HTTP 함수

첫 번째 함수에서는 HTTP 요청에 응답하는 인증된 Node.js 함수를 만들어 보겠습니다. 또한 함수가 HTTP 요청에 응답하는 데 더 많은 시간을 할애할 수 있는 방법을 보여주기 위해 10분 제한 시간을 사용해 보겠습니다.

만들기

앱 폴더를 만들고 해당 폴더로 이동합니다.

mkdir hello-http
cd hello-http

HTTP 요청에 응답하는 index.js 파일을 만듭니다.

const functions = require('@google-cloud/functions-framework');

functions.http('helloWorld', (req, res) => {
  res.status(200).send('HTTP with Node.js in Cloud Run functions!');
});

package.json 파일을 만들어 종속 항목을 지정합니다.

{
  "name": "nodejs-run-functions-codelab",
  "version": "0.0.1",
  "main": "index.js",
  "dependencies": {
    "@google-cloud/functions-framework": "^2.0.0"
  }
}

배포

함수를 배포합니다.

gcloud run deploy nodejs-run-function \
      --source . \
      --function helloWorld \
      --base-image nodejs22 \
      --region $REGION \
      --timeout 600 \
      --no-allow-unauthenticated

이 명령어는 빌드팩을 사용하여 함수 소스 코드를 프로덕션에 즉시 사용 가능한 컨테이너 이미지로 변환합니다.

다음 사항을 참고하세요.

  • --source 플래그는 Cloud Run에 함수를 실행 가능한 컨테이너 기반 서비스로 빌드하도록 지시하는 데 사용됩니다.
  • --function 플래그 (신규)는 호출하려는 함수 서명이 되도록 새 서비스의 진입점을 설정하는 데 사용됩니다.
  • --base-image 플래그 (신규)는 nodejs22, python312, go123, java21, dotnet8, ruby33, php83과 같은 함수의 기본 이미지 환경을 지정합니다. 기본 이미지 및 각 이미지에 포함된 패키지에 대한 자세한 내용은 런타임 기본 이미지를 참고하세요.
  • (선택사항) --timeout 플래그를 사용하면 함수가 HTTP 요청에 응답하는 데 더 긴 제한 시간을 사용할 수 있습니다. 이 예에서는 10분 응답 시간을 보여주기 위해 600초가 사용됩니다.
  • (선택사항) 함수가 공개적으로 호출되지 않도록 하는 --no-allow-unauthenticated

테스트

다음 명령어를 사용하여 함수를 테스트합니다.

# get the Service URL
SERVICE_URL="$(gcloud run services describe nodejs-run-function --region $REGION --format 'value(status.url)')"

# invoke the service
curl -H "Authorization: bearer $(gcloud auth print-identity-token)" -X GET $SERVICE_URL

HTTP with Node.js in Cloud Run functions! 메시지가 응답으로 표시됩니다.

4. Pub/Sub 함수

두 번째 함수의 경우 특정 주제에 게시된 Pub/Sub 메시지에 의해 트리거되는 Python 함수를 만들어 보겠습니다.

Pub/Sub 인증 토큰 설정

2021년 4월 8일 이전에 Pub/Sub 서비스 계정을 사용 설정한 경우 Pub/Sub 서비스 계정에 iam.serviceAccountTokenCreator 역할을 부여합니다.

PROJECT_NUMBER=$(gcloud projects list --filter="project_id:$PROJECT_ID" --format='value(project_number)')

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member  serviceAccount:service-$PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com \
  --role roles/iam.serviceAccountTokenCreator

만들기

샘플에 사용할 Pub/Sub 주제를 만듭니다.

TOPIC=cloud-run-functions-pubsub-topic
gcloud pubsub topics create $TOPIC

앱 폴더를 만들고 해당 폴더로 이동합니다.

mkdir ../hello-pubsub
cd ../hello-pubsub

CloudEvent ID가 포함된 메시지를 로깅하는 main.py 파일을 만듭니다.

import functions_framework

@functions_framework.cloud_event
def hello_pubsub(cloud_event):
   print('Pub/Sub with Python in Cloud Run functions! Id: ' + cloud_event['id'])

다음 콘텐츠로 requirements.txt 파일을 만들어 종속 항목을 지정합니다.

functions-framework==3.*

배포

함수를 배포합니다.

gcloud run deploy python-pubsub-function \
       --source . \
       --function hello_pubsub \
       --base-image python313 \
       --region $REGION \
       --no-allow-unauthenticated

서비스 계정 ID에 사용할 프로젝트 번호를 가져옵니다.

PROJECT_NUMBER=$(gcloud projects list --filter="project_id:$PROJECT_ID" --format='value(project_number)')

트리거 만들기

gcloud eventarc triggers create python-pubsub-function-trigger  \
    --location=$REGION \
    --destination-run-service=python-pubsub-function  \
    --destination-run-region=$REGION \
    --event-filters="type=google.cloud.pubsub.topic.v1.messagePublished" \
    --transport-topic=projects/$PROJECT_ID/topics/$TOPIC \
    --service-account=$PROJECT_NUMBER-compute@developer.gserviceaccount.com

테스트

주제에 메시지를 전송하여 함수를 테스트합니다.

gcloud pubsub topics publish $TOPIC --message="Hello World"

로그에 수신된 CloudEvent가 표시되어야 합니다.

gcloud run services logs read python-pubsub-function --region $REGION --limit=10

5. Cloud Storage 함수

다음 함수에서는 Cloud Storage 버킷의 이벤트에 응답하는 Node.js 함수를 만들어 보겠습니다.

설정

Cloud Storage 함수를 사용하려면 Cloud Storage 서비스 계정에 pubsub.publisher IAM 역할을 부여하세요.

SERVICE_ACCOUNT=$(gsutil kms serviceaccount -p $PROJECT_NUMBER)

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$SERVICE_ACCOUNT \
  --role roles/pubsub.publisher

만들기

앱 폴더를 만들고 해당 폴더로 이동합니다.

mkdir ../hello-storage
cd ../hello-storage

Cloud Storage 이벤트에 응답하는 index.js 파일을 만듭니다.

const functions = require('@google-cloud/functions-framework');

functions.cloudEvent('helloStorage', (cloudevent) => {
  console.log('Cloud Storage event with Node.js in Cloud Run functions!');
  console.log(cloudevent);
});

package.json 파일을 만들어 종속 항목을 지정합니다.

{
  "name": "nodejs-crf-cloud-storage",
  "version": "0.0.1",
  "main": "index.js",
  "dependencies": {
    "@google-cloud/functions-framework": "^2.0.0"
  }
}

배포

먼저 Cloud Storage 버킷을 만들거나 기존 버킷을 사용합니다.

export BUCKET_NAME="gcf-storage-$PROJECT_ID"
​​export BUCKET="gs://gcf-storage-$PROJECT_ID"
gsutil mb -l $REGION $BUCKET

함수를 배포합니다.

gcloud run deploy nodejs-crf-cloud-storage \
 --source . \
 --base-image nodejs22 \
 --function helloStorage \
 --region $REGION \
 --no-allow-unauthenticated

함수가 배포되면 Cloud Console의 Cloud Run 섹션에서 확인할 수 있습니다.

이제 Eventarc 트리거를 만듭니다.

BUCKET_REGION=$REGION

gcloud eventarc triggers create nodejs-crf-cloud-storage-trigger \
  --location=$BUCKET_REGION \
  --destination-run-service=nodejs-crf-cloud-storage \
  --destination-run-region=$REGION \
  --event-filters="type=google.cloud.storage.object.v1.finalized" \
  --event-filters="bucket=$BUCKET_NAME" \
  --service-account=$PROJECT_NUMBER-compute@developer.gserviceaccount.com

테스트

버킷에 파일을 업로드하여 함수를 테스트합니다.

echo "Hello World" > random.txt
gsutil cp random.txt $BUCKET/random.txt

로그에 수신된 CloudEvent가 표시되어야 합니다.

gcloud run services logs read nodejs-crf-cloud-storage --region $REGION --limit=10

6. Cloud 감사 로그

다음 함수에서는 Compute Engine VM 인스턴스가 생성될 때 Cloud 감사 로그 이벤트를 수신하는 Node.js 함수를 만들어 보겠습니다. 이에 따라 새로 생성된 VM에 VM 생성자를 지정하는 라벨이 추가됩니다.

새로 생성된 Compute Engine VM 확인

VM이 생성되면 Compute Engine은 감사 로그 2개를 내보냅니다.

첫 번째는 VM 생성 시작 시에 발생합니다. 두 번째는 VM이 생성된 후에 발생합니다.

감사 로그에서 작업 필드는 first: truelast: true 값을 포함하여 서로 다릅니다. 두 번째 감사 로그에는 인스턴스에 라벨을 지정하는 데 필요한 모든 정보가 포함되어 있으므로 last: true 플래그를 사용하여 Cloud Run 함수에서 이를 감지합니다.

설정

Cloud 감사 로그 기능을 사용하려면 Eventarc의 감사 로그를 사용 설정해야 합니다. eventarc.eventReceiver 역할이 있는 서비스 계정도 사용해야 합니다.

  1. Compute Engine API에 대해 Cloud 감사 로그 관리자 읽기, 데이터 읽기, 데이터 쓰기 로그 유형을 사용 설정합니다.
  2. 기본 Compute Engine 서비스 계정에 eventarc.eventReceiver IAM 역할을 부여합니다.
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com \
  --role roles/eventarc.eventReceiver

함수 만들기

이 Codelab에서는 node.js를 사용하지만 https://github.com/GoogleCloudPlatform/eventarc-samples에서 다른 예시를 확인할 수 있습니다.

package.json 파일 만들기

{
  "dependencies": {
    "googleapis": "^84.0.0"
  }
}

node.js 파일 만들기

// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const { google } = require("googleapis");
var compute = google.compute("v1");

exports.labelVmCreation = async (cloudevent) => {
  const data = cloudevent.body;

  // in case an event has >1 audit log
  // make sure we respond to the last event
  if (!data.operation || !data.operation.last) {
    console.log("Operation is not last, skipping event");
    return;
  }

  // projects/dogfood-gcf-saraford/zones/us-central1-a/instances/instance-1
  var resourceName = data.protoPayload.resourceName;
  var resourceParts = resourceName.split("/");
  var project = resourceParts[1];
  var zone = resourceParts[3];
  var instanceName = resourceParts[5];
  var username = data.protoPayload.authenticationInfo.principalEmail.split("@")[0];

  console.log(`Setting label username: ${username} to instance ${instanceName} for zone ${zone}`);

  var authClient = await google.auth.getClient({
    scopes: ["https://www.googleapis.com/auth/cloud-platform"]
  });

  // per docs: When updating or adding labels in the API,
  // you need to provide the latest labels fingerprint with your request,
  // to prevent any conflicts with other requests.
  var labelFingerprint = await getInstanceLabelFingerprint(authClient, project, zone, instanceName);

  var responseStatus = await setVmLabel(
    authClient,
    labelFingerprint,
    username,
    project,
    zone,
    instanceName
  );

  // log results of setting VM label
  console.log(JSON.stringify(responseStatus, null, 2));
};

async function getInstanceLabelFingerprint(authClient, project, zone, instanceName) {
  var request = {
    project: project,
    zone: zone,
    instance: instanceName,
    auth: authClient
  };

  var response = await compute.instances.get(request);
  var labelFingerprint = response.data.labelFingerprint;
  return labelFingerprint;
}

async function setVmLabel(authClient, labelFingerprint, username, project, zone, instanceName) {
  var request = {
    project: project,
    zone: zone,
    instance: instanceName,

    resource: {
      labels: { "creator": username },
      labelFingerprint: labelFingerprint
    },

    auth: authClient
  };

  var response = await compute.instances.setLabels(request);
  return response.statusText;
}

배포

함수를 배포합니다.

gcloud run deploy gce-vm-labeler \
  --source . \
  --function labelVmCreation \
  --region $REGION \
  --no-allow-unauthenticated

이제 트리거를 만듭니다. 함수가 --trigger-event-filters 플래그를 사용하여 Compute Engine 삽입에 대한 감사 로그를 필터링하는 방식을 확인하세요.

gcloud eventarc triggers create gce-vm-labeler-trigger \
  --location=$REGION \
  --destination-run-service=gce-vm-labeler \
  --destination-run-region=$REGION \
  --event-filters="type=google.cloud.audit.log.v1.written,serviceName=compute.googleapis.com,methodName=v1.compute.instances.insert" \
  --service-account=$ROJECT_NUMBER-compute@developer.gserviceaccount.com

테스트

환경 변수를 설정합니다.

# if you're using europe-west1 as your region
ZONE=europe-west1-d
VM_NAME=codelab-crf-auditlog

다음 명령어를 실행하여 VM을 만듭니다.

gcloud compute instances create $VM_NAME --zone=$ZONE --machine-type=e2-medium --image-family=debian-11  --image-project=debian-cloud

VM 생성이 완료되면 Cloud Console의 기본 정보 섹션에서 또는 다음 명령어를 사용하여 추가된 creator 라벨을 확인할 수 있습니다.

gcloud compute instances describe $VM_NAME --zone=$ZONE

다음 예와 같이 출력에 라벨이 표시됩니다.

...
labelFingerprint: ULU6pAy2C7s=
labels:
  creator: atameldev
...

삭제

VM 인스턴스를 삭제해야 합니다. 이 실습에서는 다시 사용되지 않습니다.

gcloud compute instances delete $VM_NAME --zone=$ZONE

7. 트래픽 분할

Cloud Run 함수는 함수의 여러 버전을 지원하여 여러 버전 간에 트래픽을 분할하고 함수를 이전 버전으로 롤백할 수 있습니다.

이 단계에서는 함수의 두 버전을 배포한 다음 두 버전 간에 트래픽을 50대 50으로 분할합니다.

만들기

앱 폴더를 만들고 해당 폴더로 이동합니다.

mkdir ../traffic-splitting
cd ../traffic-splitting

색상 환경 변수를 읽고 해당 배경색으로 Hello World을 사용하여 응답하는 Python 함수로 main.py 파일을 만듭니다.

import os

color = os.environ.get('COLOR')

def hello_world(request):
    return f'<body style="background-color:{color}"><h1>Hello World!</h1></body>'

다음 콘텐츠로 requirements.txt 파일을 만들어 종속 항목을 지정합니다.

functions-framework==3.*

배포

주황색 배경으로 함수의 첫 번째 버전을 배포합니다.

COLOR=orange
gcloud run deploy hello-world-colors \
 --source . \
 --base-image python313 \
 --function hello_world \
 --region $REGION \
 --allow-unauthenticated \
 --update-env-vars COLOR=$COLOR

이 시점에서 브라우저에서 HTTP 트리거 (위 배포 명령어의 URI 출력)를 확인하여 함수를 테스트하면 주황색 배경의 Hello World가 표시됩니다.

36ca0c5f39cc89cf.png

노란색 배경으로 두 번째 버전을 배포합니다.

COLOR=yellow
gcloud run deploy hello-world-colors \
 --source . \
 --base-image python313 \
 --function hello_world \
 --region $REGION \
 --allow-unauthenticated \
 --update-env-vars COLOR=$COLOR

최신 버전이므로 함수를 테스트하면 노란색 배경의 Hello World가 표시됩니다.

391286a08ad3cdde.png

트래픽을 50대 50으로 분할

주황색 버전과 노란색 버전 간에 트래픽을 분할하려면 Cloud Run 서비스의 버전 ID를 찾아야 합니다. 버전 ID를 확인하는 명령어는 다음과 같습니다.

gcloud run revisions list --service hello-world-colors \
  --region $REGION --format 'value(REVISION)'

출력은 다음과 유사해야 합니다.

hello-world-colors-00001-man
hello-world-colors-00002-wok

이제 다음과 같이 두 버전 간에 트래픽을 분할합니다 (버전 이름에 따라 X-XXX 업데이트).

gcloud run services update-traffic hello-world-colors \
  --region $REGION \
  --to-revisions hello-world-colors-0000X-XXX=50,hello-world-colors-0000X-XXX=50

테스트

공개 URL을 방문하여 함수를 테스트합니다. 절반의 시간 동안은 주황색 버전이 표시되고 나머지 절반의 시간 동안은 노란색 버전이 표시됩니다.

36ca0c5f39cc89cf.png 391286a08ad3cdde.png

자세한 내용은 롤백, 점진적 출시, 트래픽 마이그레이션을 참고하세요.

8. 최소 인스턴스

Cloud Run Functions에서는 요청 처리를 위해 실행 및 준비 상태로 유지할 최소 함수 인스턴스 수를 지정할 수 있습니다. 이는 콜드 스타트 수를 제한하는 데 유용합니다.

이 단계에서는 초기화가 느린 함수를 배포합니다. 콜드 스타트 문제가 발생합니다. 그런 다음 최소 인스턴스 값이 1로 설정된 함수를 배포하여 콜드 스타트를 없앱니다.

만들기

앱의 폴더를 만들고 해당 폴더로 이동합니다.

mkdir ../min-instances
cd ../min-instances

main.go 파일을 만듭니다. 이 Go 서비스에는 긴 초기화를 시뮬레이션하기 위해 10초 동안 절전 모드로 전환되는 init 함수가 있습니다. 또한 HTTP 호출에 응답하는 HelloWorld 함수도 있습니다.

package p

import (
        "fmt"
        "net/http"
        "time"
)

func init() {
        time.Sleep(10 * time.Second)
}

func HelloWorld(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Slow HTTP Go in Cloud Run functions!")
}

배포

최소 인스턴스 기본값 0으로 함수의 첫 번째 버전을 배포합니다.

gcloud run deploy go-slow-function \
 --source . \
 --base-image go123 \
 --function HelloWorld \
 --region $REGION \
 --no-allow-unauthenticated

다음 명령어를 사용하여 함수를 테스트합니다.

# get the Service URL
SERVICE_URL="$(gcloud run services describe go-slow-function --region $REGION --format 'value(status.url)')"

# invoke the service
curl -H "Authorization: bearer $(gcloud auth print-identity-token)" -X GET $SERVICE_URL

첫 번째 호출 시 10초 지연 (콜드 스타트)이 발생한 후 메시지가 표시됩니다. 후속 호출은 즉시 반환되어야 합니다.

최소 인스턴스 설정

첫 번째 요청에서 콜드 스타트를 없애려면 다음과 같이 --min-instances 플래그를 1로 설정하여 함수를 다시 배포합니다.

gcloud run deploy go-slow-function \
 --source . \
 --base-image go123 \
 --function HelloWorld \
 --region $REGION \
 --no-allow-unauthenticated \
 --min-instances 1

테스트

함수를 다시 테스트합니다.

curl -H "Authorization: bearer $(gcloud auth print-identity-token)" -X GET $SERVICE_URL

첫 번째 요청에서 더 이상 10초 지연이 발생하지 않습니다. 최소 인스턴스 덕분에 (오랜 시간 사용하지 않은 후) 첫 번째 호출의 콜드 스타트 문제가 사라졌습니다.

자세한 내용은 최소 인스턴스 사용을 참고하세요.

9. 축하합니다.

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

학습한 내용

  • Cloud Run 함수 및 자동 기본 이미지 업데이트 사용 방법 개요
  • HTTP 호출에 응답하는 함수를 작성하는 방법
  • Pub/Sub 메시지에 응답하는 함수를 작성하는 방법
  • Cloud Storage 이벤트에 응답하는 함수를 작성하는 방법
  • 두 버전 간에 트래픽을 분할하는 방법
  • 최소 인스턴스로 콜드 스타트를 없애는 방법