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

將 Cloud Run 與 Firebase Hosting 搭配使用,即可生成及提供動態內容,或將 REST API 建構為微服務。

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

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

如需 Cloud Run 與 Firebase 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
    1. 建立名為 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.22.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 以上版本的 JDK 和 CURL。

    請注意,我們只需要執行這項操作,即可在下一個步驟中建立新的網頁專案。稍後會說明的 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. 在 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 說明文件。

後續步驟