教程:将 Cloud Storage FUSE 与 Cloud Run 搭配使用


本教程说明如何将 Cloud Storage 作为网络文件系统装载到 Cloud Run 服务。同样的方法可用于 Cloud Run 作业。

本教程使用开源 FUSE 适配器在多个容器和服务之间共享数据。

若要装载文件系统,您的服务需要使用 Cloud Run 第二代执行环境。Cloud Run 作业会自动使用第二代环境。

通过第二代执行环境,可以将网络文件系统装载到容器内的目录中。装载文件系统后,您可以在主机系统和实例之间共享资源,也可以在实例被垃圾回收后保留资源。

若要将网络文件系统与 Cloud Run 搭配使用,需要具备高级 Docker 知识,因为您的容器必须运行多个进程,包括文件系统装载和应用进程。本教程介绍了必要的概念,还提供一个工作示例:但是,在根据您自己的应用调整本教程时,请务必了解您可能进行的任何更改所带来的影响。

设计概览

文件系统架构

该图显示了通过 gcsfuse FUSE 适配器连接到 Cloud Storage 存储桶的 Cloud Run 服务。Cloud Run 服务和 Cloud Storage 存储桶位于同一地区,这可免去网络费用并获得最佳性能。

限制

  • 本教程不介绍如何选择文件系统,也不讲解生产就绪性。请查看 POSIX 文件系统的主要区别以及 Cloud Storage FUSE 的其他语义

  • 本教程未介绍如何使用文件系统,也不讨论文件访问模式。

目标

  • 创建 Cloud Storage 存储桶以充当文件共享。

  • 使用系统软件包和 init 进程构建 Dockerfile,以管理装载和应用进程。

  • 部署到 Cloud Run 并验证对服务中文件系统的访问权限。

费用

本教程使用 Google Cloud 的以下收费组件:

准备工作

  1. 登录您的 Google Cloud 账号。如果您是 Google Cloud 新手,请创建一个账号来评估我们的产品在实际场景中的表现。新客户还可获享 $300 赠金,用于运行、测试和部署工作负载。
  2. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  3. 确保您的 Google Cloud 项目已启用结算功能

  4. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  5. 确保您的 Google Cloud 项目已启用结算功能

  6. 启用 Cloud Run, Cloud Storage, Artifact Registry, and Cloud Build API。

    启用 API

  7. 安装并初始化 gcloud CLI

所需的角色

如需获得使用最小一组角色完成本教程所需的权限,请让您的管理员为您授予项目的以下 IAM 角色:

如需详细了解如何授予角色,请参阅管理访问权限

您也可以通过自定义角色或其他预定义角色来获取所需的权限。

设置 gcloud 默认值

要配置您的 Cloud Run 服务的 gcloud 默认值,请执行以下操作:

  1. 设置默认项目:

    gcloud config set project PROJECT_ID

    PROJECT_ID 替换为您在本教程中创建的项目的名称。

  2. 为您选择的区域配置 gcloud:

    gcloud config set run/region REGION

    REGION 替换为您选择的受支持的 Cloud Run 区域

Cloud Run 位置

Cloud Run 是区域级的,这意味着运行 Cloud Run 服务的基础架构位于特定区域,并且由 Google 代管,以便在该区域内的所有可用区以冗余方式提供。

选择用于运行 Cloud Run 服务的区域时,主要考虑该区域能否满足您的延迟时间、可用性或耐用性要求。通常,您可以选择距离用户最近的区域,但除此之外,您还应该考虑 Cloud Run 服务使用的其他 Google Cloud 产品的位置。跨多个位置使用 Google Cloud 产品可能会影响服务的延迟时间和费用。

Cloud Run 可在以下区域使用:

基于层级 1 价格

基于层级 2 价格

  • asia-east2(香港)
  • asia-northeast3(韩国首尔)
  • asia-southeast1(新加坡)
  • asia-southeast2 (雅加达)
  • asia-south1(印度孟买)
  • asia-south2(印度德里)
  • australia-southeast1(悉尼)
  • australia-southeast2(墨尔本)
  • europe-central2(波兰,华沙)
  • europe-west10(柏林)
  • europe-west12(都灵)
  • europe-west2(英国伦敦) 叶形图标 二氧化碳排放量低
  • europe-west3(德国法兰克福) 叶形图标 二氧化碳排放量低
  • europe-west6(瑞士苏黎世) 叶形图标 二氧化碳排放量低
  • me-central1(多哈)
  • me-central2(达曼)
  • northamerica-northeast1(蒙特利尔) 叶形图标 二氧化碳排放量低
  • northamerica-northeast2(多伦多) 叶形图标 二氧化碳排放量低
  • southamerica-east1(巴西圣保罗) 叶形图标 二氧化碳排放量低
  • southamerica-west1(智利圣地亚哥) 叶形图标 二氧化碳排放量低
  • us-west2(洛杉矶)
  • us-west3(盐湖城)
  • us-west4(拉斯维加斯)

如果您已创建 Cloud Run 服务,则可以在 Google Cloud 控制台中的 Cloud Run 信息中心内查看区域。

检索代码示例

如需检索可用的代码示例,请执行以下操作:

  1. 将示例应用代码库克隆到本地机器:

    Node.js

    git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples.git

    或者,您也可以下载该示例的 zip 文件并将其解压缩。

    Python

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    或者,您也可以下载该示例的 zip 文件并将其解压缩。

    Java

    git clone https://github.com/GoogleCloudPlatform/java-docs-samples.git

    或者,您也可以下载该示例的 zip 文件并将其解压缩。

  2. 切换到包含 Cloud Run 示例代码的目录:

    Node.js

    cd nodejs-docs-samples/run/filesystem/

    Python

    cd python-docs-samples/run/filesystem/

    Java

    cd java-docs-samples/run/filesystem/

了解代码

通常,您应该在一个容器中运行单个进程或应用。通过为每个容器运行一个进程,可以更简单地管理多个进程的生命周期:管理重启、在任何进程失败时终止容器,以及 PID 1 责任(例如信号转发和僵尸子进程收割)。但是,如果在 Cloud Run 中使用网络文件系统,您需要使用多进程容器来运行文件系统装载进程和应用。本教程介绍如何在进程失败时终止容器,以及如何管理 PID 1 责任。装载命令具有处理重试的内置功能。

您可以使用进程管理器作为容器的入口点运行和管理多个进程。本教程使用 tini,这是一种 init 替换方法,可以清理僵尸进程并执行信号转发。具体来说,可以通过此 init 进程将关闭时出现的 SIGTERM 信号传播给应用。可以捕获 SIGTERM 信号来正常终止应用。详细了解 Cloud Run 上的容器生命周期

使用 Dockerfile 定义环境配置

此 Cloud Run 服务需要一个或多个其他系统软件包,但这些软件包不会默认提供。RUN 指令将安装 tini 作为我们的 init 进程,还将安装 gcsfuseFUSE 适配器)。请阅读使用系统软件包教程,详细了解如何在 Cloud Run 服务中使用系统软件包。

下一组说明会创建一个工作目录、复制源代码并安装应用依赖项。

ENTRYPOINT 会指定放在 CMD 指令前面的 init 进程二进制文件,在本例中是启动脚本。这将启动单个 tini 进程,然后通过代理将所有接收到的信号传输到根目录在该子进程的某个会话。

CMD 指令会设置运行映像时要执行的命令,也就是启动脚本。它还为 ENTRYPOINT 提供了默认参数。了解 CMD 和 ENTRYPOINT 的交互方式

Node.js


# Use the official Node.js image.
# https://hub.docker.com/_/node
FROM node:20-slim

# Install system dependencies
RUN apt-get update && apt-get install -y \
    curl \
    gnupg \
    lsb-release \
    tini && \
    gcsFuseRepo=gcsfuse-`lsb_release -c -s` && \
    echo "deb https://packages.cloud.google.com/apt $gcsFuseRepo main" | \
    tee /etc/apt/sources.list.d/gcsfuse.list && \
    curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \
    apt-key add - && \
    apt-get update && \
    apt-get install -y gcsfuse && \
    apt-get clean

# Set fallback mount directory
ENV MNT_DIR /mnt/gcs

# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY package*.json ./

# Install production dependencies.
RUN npm install --only=production

# Copy local code to the container image.
COPY . ./

# Ensure the script is executable
RUN chmod +x /app/gcsfuse_run.sh

# Use tini to manage zombie processes and signal forwarding
# https://github.com/krallin/tini
ENTRYPOINT ["/usr/bin/tini", "--"]

# Pass the wrapper script as arguments to tini
CMD ["/app/gcsfuse_run.sh"]

Python

# Use the official lightweight Python image.
# https://hub.docker.com/_/python
FROM python:3.11-buster

# Install system dependencies
RUN set -e; \
    apt-get update -y && apt-get install -y \
    tini \
    lsb-release; \
    gcsFuseRepo=gcsfuse-`lsb_release -c -s`; \
    echo "deb https://packages.cloud.google.com/apt $gcsFuseRepo main" | \
    tee /etc/apt/sources.list.d/gcsfuse.list; \
    curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \
    apt-key add -; \
    apt-get update; \
    apt-get install -y gcsfuse \
    && apt-get clean

# Set fallback mount directory
ENV MNT_DIR /mnt/gcs

# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./

# Install production dependencies.
RUN pip install -r requirements.txt

# Ensure the script is executable
RUN chmod +x /app/gcsfuse_run.sh

# Use tini to manage zombie processes and signal forwarding
# https://github.com/krallin/tini
ENTRYPOINT ["/usr/bin/tini", "--"]

# Pass the startup script as arguments to Tini
CMD ["/app/gcsfuse_run.sh"]

Java

# Use the official maven/Java 11 image to create a build artifact.
# https://hub.docker.com/_/maven
FROM maven:3-eclipse-temurin-17-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 Eclipse Temurin for base image.
# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
FROM eclipse-temurin:18-jdk-focal

# Install system dependencies
RUN set -e; \
    apt-get update -y && apt-get install -y \
    gnupg2 \
    tini \
    lsb-release; \
    gcsFuseRepo=gcsfuse-`lsb_release -c -s`; \
    echo "deb https://packages.cloud.google.com/apt $gcsFuseRepo main" | \
    tee /etc/apt/sources.list.d/gcsfuse.list; \
    curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \
    apt-key add -; \
    apt-get update; \
    apt-get install -y gcsfuse && apt-get clean

# Set fallback mount directory
ENV MNT_DIR /mnt/gcs

# Copy the jar to the production image from the builder stage.
COPY --from=builder /app/target/filesystem-*.jar /filesystem.jar

# Copy the statup script
COPY gcsfuse_run.sh ./gcsfuse_run.sh
RUN chmod +x ./gcsfuse_run.sh

# Use tini to manage zombie processes and signal forwarding
# https://github.com/krallin/tini
ENTRYPOINT ["/usr/bin/tini", "--"]

# Run the web service on container startup.
CMD ["/gcsfuse_run.sh"]

在启动脚本中定义进程

启动脚本会创建装载点目录,将可在该目录中访问 Cloud Storage 存储桶。接下来,该脚本使用 gcsfuse 命令将 Cloud Storage 存储桶挂接到服务的装载点,然后启动应用服务器。gcsfuse 命令具有内置的重试功能;因此无需进一步的 bash 脚本。最后,使用 wait 命令侦听任何后台进程退出,然后退出脚本。

Node.js

#!/usr/bin/env bash
set -eo pipefail

# Create mount directory for service
mkdir -p $MNT_DIR

echo "Mounting GCS Fuse."
gcsfuse --debug_gcs --debug_fuse $BUCKET $MNT_DIR
echo "Mounting completed."

# Start the application
node index.js &

# Exit immediately when one of the background processes terminate.
wait -n

Python

#!/usr/bin/env bash
set -eo pipefail

# Create mount directory for service
mkdir -p $MNT_DIR

echo "Mounting GCS Fuse."
gcsfuse --debug_gcs --debug_fuse $BUCKET $MNT_DIR
echo "Mounting completed."

# 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.
# Timeout is set to 0 to disable the timeouts of the workers to allow Cloud Run to handle instance scaling.
exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app

Java

#!/usr/bin/env bash
set -eo pipefail

# Create mount directory for service
mkdir -p $MNT_DIR

echo "Mounting GCS Fuse."
gcsfuse --debug_gcs --debug_fuse $BUCKET $MNT_DIR
echo "Mounting completed."

# Start the application
java -jar filesystem.jar

# Exit immediately when one of the background processes terminate.
wait -n

使用文件

Node.js

请参阅 index.js,了解如何与文件系统交互。

Python

请参阅 main.py,了解如何与文件系统交互。

Java

请参阅 FilesystemApplication.java,了解如何与文件系统交互。

发布服务

  1. 创建 Cloud Storage 存储桶或重复使用现有存储桶:

    gsutil mb -l REGION gs://BUCKET_NAME
    

    BUCKET_NAME 替换为 Cloud Storage 存储桶的名称,即 my-fuse-bucket。Cloud Storage 存储桶名称必须是全局唯一的,并受命名要求的约束。

    设置 -l 来指定存储桶的位置。例如 us-central1。为获得最佳性能并避免跨地区网络费用,请确保 Cloud Storage 存储桶与需要访问它的 Cloud Run 服务位于同一地区。

  2. 创建一个服务账号作为服务身份:

    gcloud iam service-accounts create fs-identity
  3. 向服务账号授予访问 Cloud Storage 存储桶的权限:

    gcloud projects add-iam-policy-binding PROJECT_ID \
         --member "serviceAccount:fs-identity@PROJECT_ID.iam.gserviceaccount.com" \
         --role "roles/storage.objectAdmin"
    
  4. 如需从源代码进行部署,请删除额外的 Dockerfile 并重命名教程 Dockerfile:

    rm Dockerfile
    cp gcsfuse.Dockerfile Dockerfile
    
  5. 构建容器映像并部署到 Cloud Run:

    gcloud run deploy filesystem-app --source . \
        --execution-environment gen2 \
        --allow-unauthenticated \
        --service-account fs-identity \
        --update-env-vars BUCKET=BUCKET_NAME
    

    此命令会构建和部署 Cloud Run 服务,并指定第二代执行环境。从源代码部署将根据 Dockerfile 构建映像,并将该映像推送到 Artifact Registry 代码库:cloud-run-source-deploy

    详细了解如何从源代码部署

调试

如果部署失败,请检查 Cloud 日志记录来获取更多详细信息。

如果您需要来自装载进程的所有日志,请将 --foreground 标志与启动脚本 gcsfuse_run.sh 中的装载命令结合使用:

  gcsfuse --foreground --debug_gcs --debug_fuse GCSFUSE_BUCKET MNT_DIRECTORY &
  

  • 为 HTTP 请求/响应调试输出添加 --debug_http
  • 添加 --debug_fuse 以启用与 Fuse 相关的调试输出。
  • 添加 --debug_gcs 以输出 GCS 请求和时间信息。

查看有关问题排查指南的更多提示。

测试

如需试用完整服务,请执行以下操作:

  1. 在浏览器中导航至上述部署步骤提供的网址。
  2. 您应该会在 Cloud Storage 存储桶中看到新创建的文件。
  3. 点击文件以查看内容。

如果您选择继续开发这些服务,请注意,它们已限制了 Identity and Access Management (IAM) 对 Google Cloud 其余服务的访问权限,并需要额外的 IAM 角色才能访问众多其他服务。

费用讨论

Cloud Storage 价格在很大程度上取决于数据存储、按存储类别存储的数据量、存储桶的位置、网络用量,以及从您的存储桶读取的数据量或在不同存储桶之间转移的数据量。详细了解 Cloud Storage FUSE 产生的费用

Cloud Run 按内存、CPU、请求数量和网络的资源用量计费,舍入至最接近的 100 毫秒。因此,费用会因服务的设置、请求数量和执行时间而异。

例如,存储在爱荷华 (us-central1) 的 Standard Storage 存储桶中的 1 TiB 数据每 GiB 每月的费用为 $0.02,约为 1024 GiB * $0.02 = $20.48。此估算值取决于 Cloud Run 服务和 Cloud Storage 存储桶托管在同一地区来降低出站流量费用。

请访问各价格页面了解最新价格,或者在 Google Cloud 价格计算器中了解估算值。

清理

如果您为本教程创建了一个新项目,请删除项目。 如果您使用的是现有项目,希望保留此项目且不保留本教程中添加的任何更改,请删除为教程创建的资源

删除项目

为了避免产生费用,最简单的方法是删除您为本教程创建的项目。

如需删除项目,请执行以下操作:

  1. 在 Google Cloud 控制台中,进入管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

删除教程资源

  1. 删除您在本教程中部署的 Cloud Run 服务:

    gcloud run services delete SERVICE-NAME

    其中,SERVICE-NAME 是您选择的服务名称。

    您还可以从 Google Cloud 控制台中删除 Cloud Run 服务。

  2. 移除您在教程设置过程中添加的 gcloud 默认区域配置:

     gcloud config unset run/region
    
  3. 移除项目配置:

     gcloud config unset project
    
  4. 删除在本教程中创建的其他 Google Cloud 资源:

后续步骤