透過 Cloud Run 提供動態內容和託管微服務

Cloud RunFirebase Hosting 配對,產生並提供動態內容,或將 REST API 建構為微服務。

您可以使用 Cloud Run 部署封裝在容器映像檔中的應用程式。接著,您可以使用 Firebase Hosting 將 HTTPS 要求導向至觸發容器化應用程式。

  • Cloud Run 支援多種語言 (包括 Go、Node.js、Python 和 Java),讓您能靈活運用所選的程式設計語言和架構。
  • Cloud Run 自動及水平擴充您的容器映像檔,以處理收到的要求,然後在需求減少時縮減規模。
  • 您只需要為處理要求期間使用的 CPU、記憶體和網路支付費用。

如需 Cloud RunFirebase Hosting 整合的用途範例和範例,請參閱無伺服器總覽


本指南說明如何:

  1. 編寫簡單的 Hello World 應用程式
  2. 將應用程式容器化並上傳至 Artifact Registry
  3. 將容器映像檔部署至 Cloud Run
  4. Hosting 要求導向容器化應用程式

請注意,如要改善動態內容的放送效能,您可以視需要調整快取設定

事前準備

使用 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 環境變數定義的連接埠。

您的應用程式已經完成,可以開始將應用程式容器化,並上傳至 Artifact 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.21.1"
      }
    }
    
  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 環境變數定義的連接埠。

您的應用程式已經完成,可以開始將應用程式容器化,並上傳至 Artifact 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 環境變數定義的連接埠。

您的應用程式已經完成,可以開始將應用程式容器化,並上傳至 Artifact Registry

Java

  1. 安裝 Java SE 8 以上版本的 JDKCURL

    請注意,我們只需在下一步執行這項操作來建立新的 Web 專案。稍後將討論的 Dockerfile 會將所有依附元件載入容器中。

  2. 在主控台中,使用 cURL 和 unzip 指令建立新的空白 Web 專案:

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

    系統便會建立 SpringBoot 專案。

  3. 更新 src/main/java/com/example/helloworld/HelloworldApplication.java 中的 SpringBootApplication 類別,方法是新增 @RestController 來處理 / 對應,並新增 @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 環境變數定義的連接埠。

您的應用程式已經完成,可以開始將應用程式容器化,並上傳至 Artifact Registry

步驟 2:將應用程式納入容器並上傳至 Artifact 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
    
    # Copy local code to the container image.
    COPY . ./
    
    # Install dependencies and tidy up the go.mod and go.sum files.
    RUN go mod tidy
    
    # 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
    

    Java

    # 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) 的「SUCCESS」(成功) 訊息。

容器映像檔現在儲存在 Artifact Registry 中,日後如有需要,可以重複使用。

請注意,您可以使用本機安裝的 Docker 版本,而非 Cloud Build在本機建構容器

步驟 3:將容器映像檔部署至 Cloud Run

  1. 使用下列指令進行部署:

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

  2. 出現提示時:

  3. 稍候片刻,等待部署作業完成。成功完成後,指令列會顯示服務網址。例如:https://helloworld-RANDOM_HASH-us-central1.a.run.app

  4. 在網路瀏覽器中開啟服務網址,以造訪您所部署的容器。

接下來,我們會逐步說明如何Firebase Hosting 網址存取這個容器化應用程式,讓應用程式為 Firebase 託管的網站產生動態內容。

步驟 4:將代管要求直接傳送至容器化應用程式

您可以使用重寫規則,將符合特定模式的請求導向至單一目的地。

以下範例說明如何將 Hosting 網站上 /helloworld 網頁的所有要求導向,以觸發 helloworld 容器執行個體的啟動和執行作業。

  1. 請確認下列事項:

    如需安裝 CLI 和初始化 Hosting 的詳細操作說明,請參閱 Hosting 入門指南

  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

您現在可以透過下列網址存取容器:

  • 您的 Firebase 子網域:
    PROJECT_ID.web.app/PROJECT_ID.firebaseapp.com/

  • 任何已連結的自訂網域
    CUSTOM_DOMAIN/

請造訪 Hosting 設定頁面,進一步瞭解重寫規則的詳細資料。您也可以瞭解各種 Hosting 設定的回應優先順序

本機測試

開發期間,您可以先在本機執行並測試容器映像檔。如需詳細操作說明,請參閱 Cloud Run 說明文件

後續步驟