使用 Cloud Run 提供动态内容和托管微服务

Cloud RunFirebase Hosting 搭配使用,可生成并提供动态内容,或者构建 REST API 作为微服务。

使用 Cloud Run,您可以部署封装在容器映像中的应用。然后,您可以使用 Firebase Hosting 来定向 HTTPS 请求,以触发容器化应用。

  • Cloud Run 支持多种语言(包括 Go、Node.js、Python 和 Java),让您可以灵活选择编程语言和框架。
  • Cloud Run 可以对您的容器映像进行自动横向扩容以处理收到的请求,并在需求减少时缩容。
  • 您只需为在处理请求期间消耗的 CPU、内存和网络资源付费

如需查看与 Firebase Hosting 集成的 Cloud Run 的使用场景和示例,请访问我们的无服务器概览


本指南将介绍如何执行以下操作:

  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))
    }
    

    这些代码会创建一个基本 Web 服务器,监听由 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);
    });
    

    这些代码会创建一个基本 Web 服务器,监听由 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)))
    

    这些代码会创建一个基本 Web 服务器,监听由 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);
      }
    }
    

    这些代码会创建一个基本 Web 服务器,监听由 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 中,可以根据需要重复使用。

请注意,除了使用 Cloud Build,您也可以使用本地安装的 Docker 版本在本地构建容器

第 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 收到的请求定向到您的容器化应用

通过重写规则,您可以将与特定格式匹配的请求定向到单个目标。

以下示例说明如何定向来自您的 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. 在项目的根目录中运行以下命令,将 Hosting 的配置部署到您的网站:

    firebase deploy --only hosting

现在,您可以通过以下网址访问容器:

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

  • 任何关联的自定义网域
    CUSTOM_DOMAIN/

如需详细了解重写规则,请访问 Hosting 配置页面。您还可以了解各种 Hosting 配置的响应的优先级顺序

在本地测试

开发期间,您可以在本地运行和测试容器映像。有关详细说明,请访问 Cloud Run 文档

后续步骤