Cloud Run을 사용하여 동적 콘텐츠 제공 및 마이크로서비스 호스팅

Cloud Run을 Firebase 호스팅과 페어링하면 동적 콘텐츠를 생성하여 제공하거나 REST API를 마이크로서비스로 빌드할 수 있습니다.

Cloud Run을 사용해 컨테이너 이미지로 패키징된 애플리케이션을 배포합니다. 그런 다음 Firebase 호스팅을 사용해 HTTPS 요청을 전달하면 컨테이너화된 앱을 트리거할 수 있습니다.

  • Cloud Run은 여러 언어(Go, Node.js, Python, 자바 등)를 지원하므로 원하는 프로그래밍 언어와 프레임워크를 자유롭게 사용할 수 있습니다.
  • Cloud Run은 수신된 요청을 처리하기 위해 컨테이너 이미지를 자동 및 수평 확장한 다음 수요가 감소하면 축소합니다.
  • 요청 처리 도중 소비한 CPU, 메모리, 네트워킹에 대해서만 비용을 지불하면 됩니다.

Firebase 호스팅과 통합된 Cloud Run의 사용 사례 및 샘플은 Google의 서버리스 개요를 참조하세요.


이 가이드에서는 다음을 수행하는 방법을 설명합니다.

  1. 간단한 Hello World 애플리케이션 작성
  2. 앱을 컨테이너화하여 Container Registry에 업로드
  3. 컨테이너 이미지를 Cloud Run에 배포
  4. 컨테이너화된 앱으로 호스팅 요청 전달

제공하는 동적 콘텐츠의 성능을 높이기 위해 필요에 따라 캐시 설정을 조정할 수 있습니다.

시작하기 전에

Cloud Run을 사용하기 전에 Cloud Billing 계정 설정, Cloud Run API 사용 설정, gcloud 명령줄 도구 설치 등 몇 가지 초기 작업을 먼저 완료해야 합니다.

프로젝트에 대한 결제 설정

Cloud Run은 무료 사용 할당량을 제공하지만 Firebase 프로젝트와 연결된 Cloud Billing 계정이 있어야 Cloud Run을 사용해 볼 수 있습니다.

API 사용 설정 및 SDK 설치

  1. 다음 단계에 따라 Google API 콘솔에서 Cloud Run API를 사용 설정합니다.

    1. Google API 콘솔에서 Cloud Run API 페이지를 엽니다.

    2. 메시지가 나타나면 Firebase 프로젝트를 선택합니다.

    3. Cloud Run API 페이지에서 사용 설정을 클릭합니다.

  2. Cloud SDK를 설치하고 초기화합니다.

  3. gcloud 도구가 올바른 프로젝트에 구성되었는지 확인합니다.

    gcloud config list

1단계: 샘플 애플리케이션 작성

Cloud Run은 다음 샘플에 나와 있는 언어 외에도 많은 다른 언어를 지원합니다.

Go

  1. helloworld-go라는 새 디렉터리를 만든 후 이 위치로 디렉터리를 변경합니다.

    mkdir helloworld-go
    cd helloworld-go
  2. helloworld.go라는 새 파일을 만든 후 다음 코드를 추가합니다.

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/http"
    	"os"
    )
    
    func handler(w http.ResponseWriter, r *http.Request) {
    	log.Print("helloworld: received a request")
    	target := os.Getenv("TARGET")
    	if target == "" {
    		target = "World"
    	}
    	fmt.Fprintf(w, "Hello %s!\n", target)
    }
    
    func main() {
    	log.Print("helloworld: starting server...")
    
    	http.HandleFunc("/", handler)
    
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    	}
    
    	log.Printf("helloworld: listening on port %s", port)
    	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
    }
    

    이 코드는 PORT 환경 변수로 정의한 포트를 리슨하는 기본 웹 서버를 생성합니다.

앱이 완성되었으며 컨테이너화되어 Container Registry에 업로드될 수 있습니다.

Node.js

  1. helloworld-nodejs라는 새 디렉터리를 만든 후 이 위치로 디렉터리를 변경합니다.

    mkdir helloworld-nodejs
    cd helloworld-nodejs
  2. 다음 콘텐츠로 package.json이라는 파일을 만듭니다.

    {
      "name": "knative-serving-helloworld",
      "version": "1.0.0",
      "description": "Simple hello world sample in Node",
      "main": "index.js",
      "scripts": {
        "start": "node index.js"
      },
      "author": "",
      "license": "Apache-2.0",
      "dependencies": {
        "express": "^4.18.2"
      }
    }
    
  3. index.js라는 새 파일을 만든 후 다음 코드를 추가합니다.

    const express = require('express');
    const app = express();
    
    app.get('/', (req, res) => {
      console.log('Hello world received a request.');
    
      const target = process.env.TARGET || 'World';
      res.send(`Hello ${target}!\n`);
    });
    
    const port = process.env.PORT || 8080;
    app.listen(port, () => {
      console.log('Hello world listening on port', port);
    });
    

    이 코드는 PORT 환경 변수로 정의한 포트를 리슨하는 기본 웹 서버를 생성합니다.

앱이 완성되었으며 컨테이너화되어 Container Registry에 업로드될 수 있습니다.

Python

  1. helloworld-python라는 새 디렉터리를 만든 후 이 위치로 디렉터리를 변경합니다.

    mkdir helloworld-python
    cd helloworld-python
  2. app.py라는 새 파일을 만든 후 다음 코드를 추가합니다.

    import os
    
    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/')
    def hello_world():
        target = os.environ.get('TARGET', 'World')
        return 'Hello {}!\n'.format(target)
    
    if __name__ == "__main__":
        app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080)))
    

    이 코드는 PORT 환경 변수로 정의한 포트를 리슨하는 기본 웹 서버를 생성합니다.

앱이 완성되었으며 컨테이너화되어 Container Registry에 업로드될 수 있습니다.

Java

  1. 자바 SE 8 이상 JDKCURL을 설치합니다.

    다음 단계에서 새 웹 프로젝트를 만들려면 이 작업만 수행하면 됩니다. 나중에 설명하게 될 Dockerfile은 컨테이너에 모든 종속 항목을 로드합니다.

  2. 콘솔에서 cURL과 unzip 명령어를 차례로 사용하여 새로운 빈 웹 프로젝트를 만듭니다.

    curl https://start.spring.io/starter.zip \
        -d dependencies=web \
        -d name=helloworld \
        -d artifactId=helloworld \
        -o helloworld.zip
    unzip helloworld.zip

    이렇게 하면 SpringBoot 프로젝트가 만들어집니다.

  3. / 매핑을 처리하기 위해 @RestController를 추가하여 src/main/java/com/example/helloworld/HelloworldApplication.java에서 SpringBootApplication 클래스를 업데이트하고 @Value 필드도 추가하여 TARGET 환경 변수를 제공합니다.

    package com.example.helloworld;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @SpringBootApplication
    public class HelloworldApplication {
    
      @Value("${TARGET:World}")
      String target;
    
      @RestController
      class HelloworldController {
        @GetMapping("/")
        String hello() {
          return "Hello " + target + "!";
        }
      }
    
      public static void main(String[] args) {
        SpringApplication.run(HelloworldApplication.class, args);
      }
    }
    

    이 코드는 PORT 환경 변수로 정의한 포트를 리슨하는 기본 웹 서버를 생성합니다.

앱이 완성되었으며 컨테이너화되어 Container Registry에 업로드될 수 있습니다.

2단계: 앱을 컨테이너화하여 Container Registry에 업로드

  1. 소스 파일과 동일한 디렉터리에 Dockerfile이라는 새 파일을 만들어 샘플 앱을 컨테이너화합니다. 다음 콘텐츠를 파일에 복사합니다.

    Go

    # Use the official Golang image to create a build artifact.
    # This is based on Debian and sets the GOPATH to /go.
    FROM golang:latest as builder
    
    ARG TARGETOS
    ARG TARGETARCH
    
    # Create and change to the app directory.
    WORKDIR /app
    
    # Retrieve application dependencies using go modules.
    # Allows container builds to reuse downloaded dependencies.
    COPY go.* ./
    RUN go mod download
    
    # Copy local code to the container image.
    COPY . ./
    
    # Build the binary.
    # -mod=readonly ensures immutable go.mod and go.sum in container builds.
    RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -mod=readonly -v -o server
    
    # Use the official Alpine image for a lean production container.
    # https://hub.docker.com/_/alpine
    # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
    FROM alpine:3
    RUN apk add --no-cache ca-certificates
    
    # Copy the binary to the production image from the builder stage.
    COPY --from=builder /app/server /server
    
    # Run the web service on container startup.
    CMD ["/server"]
    

    Node.js

    # Use the official lightweight Node.js 12 image.
    # https://hub.docker.com/_/node
    FROM node:12-slim
    
    # Create and change to the app directory.
    WORKDIR /usr/src/app
    
    # Copy application dependency manifests to the container image.
    # A wildcard is used to ensure both package.json AND package-lock.json are copied.
    # Copying this separately prevents re-running npm install on every code change.
    COPY package*.json ./
    
    # Install production dependencies.
    RUN npm install --only=production
    
    # Copy local code to the container image.
    COPY . ./
    
    # Run the web service on container startup.
    CMD [ "npm", "start" ]
    

    Python

    # Use the official lightweight Python image.
    # https://hub.docker.com/_/python
    FROM python:3.7-slim
    
    # Allow statements and log messages to immediately appear in the Knative logs
    ENV PYTHONUNBUFFERED True
    
    # Copy local code to the container image.
    ENV APP_HOME /app
    WORKDIR $APP_HOME
    COPY . ./
    
    # Install production dependencies.
    RUN pip install Flask gunicorn
    
    # Run the web service on container startup. Here we use the gunicorn
    # webserver, with one worker process and 8 threads.
    # For environments with multiple CPU cores, increase the number of workers
    # to be equal to the cores available.
    CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 app:app
    

    자바

    # Use the official maven/Java 8 image to create a build artifact: https://hub.docker.com/_/maven
    FROM maven:3.5-jdk-8-alpine as builder
    
    # Copy local code to the container image.
    WORKDIR /app
    COPY pom.xml .
    COPY src ./src
    
    # Build a release artifact.
    RUN mvn package -DskipTests
    
    # Use the Official OpenJDK image for a lean production stage of our multi-stage build.
    # https://hub.docker.com/_/openjdk
    # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
    FROM openjdk:8-jre-alpine
    
    # Copy the jar to the production image from the builder stage.
    COPY --from=builder /app/target/helloworld-*.jar /helloworld.jar
    
    # Run the web service on container startup.
    CMD ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/helloworld.jar"]
    

  2. Dockerfile이 포함되어 있는 디렉터리에서 다음 명령어를 실행하여 Cloud Build로 컨테이너 이미지를 빌드합니다.

    gcloud builds submit --tag gcr.io/PROJECT_ID/helloworld

    성공하면 다음 이미지 이름이 포함된 성공 메시지가 표시됩니다.
    (gcr.io/PROJECT_ID/helloworld)

이제 컨테이너 이미지가 Container Registry에 저장되고 원하는 경우 다시 사용할 수 있습니다.

Cloud Build 대신 로컬에 설치된 Docker 버전을 사용하여 로컬에서 컨테이너를 빌드할 수 있습니다.

3단계: Cloud Run에 컨테이너 이미지 배포

  1. 다음 명령어를 사용하여 배포합니다.

    gcloud run deploy --image gcr.io/PROJECT_ID/helloworld

  2. 메시지가 표시되면 다음 단계를 수행합니다.

최상의 성능을 얻으려면 다음 리전을 사용하여 Cloud Run 서비스를 호스팅과 같은 위치에 배치합니다.

  • us-west1
  • us-central1
  • us-east1
  • europe-west1
  • asia-east1

호스팅에서 Cloud Run으로 재작성하는 기능은 다음 리전에서 지원됩니다.

  • asia-east1
  • asia-east2
  • asia-northeast1
  • asia-northeast2
  • asia-northeast3
  • asia-south1
  • asia-south2
  • asia-southeast1
  • asia-southeast2
  • australia-southeast1
  • australia-southeast2
  • europe-central2
  • europe-north1
  • europe-southwest1
  • europe-west1
  • europe-west12
  • europe-west2
  • europe-west3
  • europe-west4
  • europe-west6
  • europe-west8
  • europe-west9
  • me-central1
  • me-west1
  • northamerica-northeast1
  • northamerica-northeast2
  • southamerica-east1
  • southamerica-west1
  • us-central1
  • us-east1
  • us-east4
  • us-east5
  • us-south1
  • us-west1
  • us-west2
  • us-west3
  • us-west4
  • us-west1
  • us-central1
  • us-east1
  • europe-west1
  • asia-east1
  1. 배포가 완료될 때까지 잠시 기다립니다. 성공하면 명령줄에 서비스 URL이 표시됩니다. 예를 들면 다음과 같습니다. https://helloworld-RANDOM_HASH-us-central1.a.run.app

  2. 웹브라우저에서 서비스 URL을 열고 배포한 컨테이너로 이동합니다.

다음 단계에서는 Firebase가 호스팅하는 사이트에 동적 콘텐츠를 생성할 수 있도록 Firebase 호스팅 URL에서 이 컨테이너화된 앱에 액세스하는 방법을 설명합니다.

4단계: 컨테이너화된 앱으로 호스팅 요청 전달

재작성 규칙을 사용하면 특정 패턴과 일치하는 요청을 단일 대상으로 전달할 수 있습니다.

다음 예시에서는 호스팅 사이트의 /helloworld 페이지에서 보내는 모든 요청을 전달하여 helloworld 컨테이너 인스턴스의 시작 및 실행을 트리거하는 방법을 보여줍니다.

  1. 다음 사항을 확인하세요.

    CLI 설치 및 호스팅 초기화에 관한 자세한 내용은 호스팅 시작 가이드를 참조하세요.

  2. firebase.json 파일을 엽니다.

  3. hosting 섹션 아래에 다음 rewrite 구성을 추가합니다.

    "hosting": {
      // ...
    
      // Add the "rewrites" attribute within "hosting"
      "rewrites": [ {
        "source": "/helloworld",
        "run": {
          "serviceId": "helloworld",  // "service name" (from when you deployed the container image)
          "region": "us-central1",    // optional (if omitted, default is us-central1)
          "pinTag": true              // optional (see note below)
        }
      } ]
    }
    
  4. 프로젝트 디렉터리의 루트에서 다음 명령어를 실행하여 호스팅 구성을 사이트에 배포합니다.

    firebase deploy --only hosting

이제 다음 URL을 통해 컨테이너에 연결할 수 있습니다.

  • Firebase 하위 도메인:
    PROJECT_ID.web.app/PROJECT_ID.firebaseapp.com/

  • 연결된 모든 커스텀 도메인:
    CUSTOM_DOMAIN/

재작성 규칙에 대한 자세한 내용은 호스팅 구성 페이지를 참조하세요. 또한 다양한 호스팅 구성에 관한 응답 우선순위에 대해서도 알아볼 수 있습니다.

로컬에서 테스트

개발 단계에서는 컨테이너 이미지를 로컬에서 실행하고 테스트할 수 있습니다. 자세한 안내는 Cloud Run 문서를 참조하세요.

다음 단계