教程:将 Filestore 与 Cloud Run 搭配使用


本教程说明如何将 Filestore 作为网络文件系统装载到 Cloud Run 服务上,以便在多个容器和服务之间共享数据。本教程使用 Cloud Run 第二代执行环境

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

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

设计概览

Filestore 实例托管在 Virtual Private Cloud (VPC) 网络中。VPC 网络内的资源使用专用 IP 地址范围与 Google API 和服务进行通信;因此,客户端必须与 Filestore 实例位于同一网络中,才能访问存储在该实例中的文件。Cloud Run 服务需要无服务器 VPC 访问通道连接器才能连接到 VPC 网络来与 Filestore 进行通信。详细了解无服务器 VPC 访问通道

文件系统架构

该图显示了通过无服务器 VPC 访问通道连接器连接到 Filestore 实例的 Cloud Run 服务。为了获得最佳性能,Filestore 实例和连接器与 Cloud Run 服务位于同一 VPC 网络中,是“默认”设置且位于同一区域/可用区。

限制

  • 本教程未介绍如何选择文件系统,也不介绍生产就绪要求。详细了解 Filestore 和可用的服务层级

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

目标

  • 在默认 VPC 网络上创建 Filestore 实例来充当文件共享。

  • 在同一默认 VPC 网络上创建无服务器 VPC 访问通道连接器以连接到 Cloud Run 服务。

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

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

费用

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

准备工作

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  5. Make sure that billing is enabled for your Google Cloud project.

  6. IAM 权限仅会控制对 Filestore 操作(例如创建 Filestore 实例)的访问权限。如需控制对文件共享操作(例如读取或执行)的访问权限,请使用 POSIX 文件权限。详细了解访问权限控制
  7. Enable the Cloud Run, Filestore, Serverless VPC Access, Artifact Registry, and Cloud Build APIs.

    Enable the APIs

  8. 安装并初始化 gcloud CLI
  9. 更新 Google Cloud CLI:gcloud components update

所需的角色

如需获得完成本教程所需的权限,请让您的管理员为您授予项目的以下 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 区域

  3. 为 Filestore 配置 gcloud:

    gcloud config set filestore/zone ZONE

    ZONE 替换为您选择的受支持的 Filestore 可用区

检索代码示例

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

  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 进程,并安装 nfs-common,它提供了最小的 NFS 客户端功能。请阅读使用系统软件包教程,详细了解如何在 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 -y && apt-get install -y \
    tini \
    nfs-common \
	libtool \
    && apt-get clean

# Set fallback mount directory
ENV MNT_DIR /mnt/nfs/filestore

# 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/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/run.sh"]

Python

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

# Install system dependencies
RUN apt-get update -y && apt-get install -y \
    tini \
    nfs-common \
    && apt-get clean

# Set fallback mount directory
ENV MNT_DIR /mnt/nfs/filestore

# 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/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/run.sh"]

Java


# Use the official maven 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 filesystem dependencies
RUN apt-get update -y && apt-get install -y \
    tini \
    nfs-kernel-server \
    nfs-common \
    && apt-get clean

# Set fallback mount directory
ENV MNT_DIR /mnt/nfs/filestore

# 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 run.sh ./run.sh
RUN chmod +x ./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 ["/run.sh"]

在启动脚本中定义进程

启动脚本会创建一个目录作为装载点,可通过此目录中访问 Filestore 实例。接下来,该脚本使用 mount 命令通过指定实例的 IP 地址和文件共享名称将 Filestore 实例挂接到服务的装载点,然后启动应用服务器。mount 命令具有内置的重试功能;因此无需进一步的 bash 脚本。最后,使用 wait 命令侦听任何后台进程退出,然后退出脚本。

Node.js

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

# Create mount directory for service.
mkdir -p $MNT_DIR

echo "Mounting Cloud Filestore."
mount -o nolock $FILESTORE_IP_ADDRESS:/$FILE_SHARE_NAME $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 Cloud Filestore."
mount -o nolock $FILESTORE_IP_ADDRESS:/$FILE_SHARE_NAME $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 Cloud Filestore."
mount -o nolock $FILESTORE_IP_ADDRESS:/$FILE_SHARE_NAME $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. 创建 Filestore 实例:

    gcloud filestore instances create INSTANCE_ID \
        --tier=basic-hdd \
        --file-share=name=FILE_SHARE_NAME,capacity=1TiB \
        --network=name="default"

    INSTANCE_ID 替换为 Filestore 实例的名称(即 my-filestore-instance),并将 FILE_SHARE_NAME 替换为从 Filestore 实例提供的目录名称(即 vol1)。请参阅为实例命名为文件共享命名

    客户端(Cloud Run 服务)必须与 Filestore 实例位于同一网络中,才能访问存储在该实例上的文件。此命令会在默认 VPC 网络中创建实例,并分配可用的 IP 地址范围。新项目起初都有一个默认网络,您很可能无需创建单独的网络。

    如需详细了解实例配置,请参阅创建实例

  2. 设置无服务器 VPC 访问通道连接器:

    要连接到 Filestore 实例,您的 Cloud Run 服务需要访问 Filestore 实例已获授权的 VPC 网络。

    每个 VPC 连接器都需要有自己的 /28 子网以放置连接器实例。此 IP 范围不得与 VPC 网络中预留的任何现有 IP 地址重叠。例如,10.8.0.0 (/28) 适用于大多数新项目,您也可以另外指定一个未使用的自定义 IP 范围,例如 10.9.0.0 (/28)。您可以在 Google Cloud 控制台中查看当前预留的 IP 地址范围。

    gcloud compute networks vpc-access connectors create CONNECTOR_NAME \
      --region REGION \
      --range "10.8.0.0/28"

    CONNECTOR_NAME 替换为您的连接器的名称。

    此命令会在默认 VPC 网络中创建一个连接器,该连接器与 Filestore 实例相同,并且具有 e2-micro 机器大小。增加连接器的机器大小可以提高连接器的吞吐量,但也会增加费用。连接器还必须与 Cloud Run 服务位于同一区域。详细了解如何配置无服务器 VPC 访问通道

  3. 使用 Filestore 实例的 IP 地址定义环境变量:

    export FILESTORE_IP_ADDRESS=$(gcloud filestore instances describe INSTANCE_ID --format "value(networks.ipAddresses[0])")
  4. 创建一个服务账号作为服务身份。默认情况下,该账号不具备除项目成员资格之外的任何特权。

    gcloud iam service-accounts create fs-identity

    此服务不需要与 Google Cloud 中的任何其他服务交互;因此,无需向此服务账号分配任何额外权限。

  5. 构建容器映像并部署到 Cloud Run:

    gcloud run deploy filesystem-app --source . \
        --vpc-connector CONNECTOR_NAME \
        --execution-environment gen2 \
        --allow-unauthenticated \
        --service-account fs-identity \
        --update-env-vars FILESTORE_IP_ADDRESS=$FILESTORE_IP_ADDRESS,FILE_SHARE_NAME=FILE_SHARE_NAME

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

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

调试

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

  • 如果连接超时,请确保提供 Filestore 实例的正确 IP 地址。

  • 如果服务器拒绝访问,请检查以确保文件共享名称正确无误。

  • 如果您需要来自装载进程的所有日志,请将 --verbose 标志与装载命令 mount --verbose -o nolock $FILESTORE_IP_ADDRESS:/$FILE_SHARE_NAME $MNT_DIR 结合使用

测试

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

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

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

费用讨论

托管在爱荷华 (us-central1) 且具有 1 TiB Filestore 实例和一个无服务器 VPC 访问通道连接器的示例费用明细。请访问各个价格页面了解最新价格。

产品 每月费用
Filestore(不取决于使用量) 费用 = 预配容量(1024 GiB 或 1 TiB)* 区域层级价格 (us-central1)

基本 HDD 层级:1024GiB * $0.16/月 = $163.84
可用区级 (SSD):1024GiB * $0.25/月 = $256.00
Enterprise(SSD,区域可用性):1024GiB * $0.45/月 = $460.80
无服务器 VPC 访问通道 费用 = 机器大小价格 * 实例数(最小实例数默认为 2)

f1-micro:$3.88 * 2 个实例 = $7.76
e2- micro:$6.11 * 2 个实例 = $12.22
e2-standard-4:$97.83 * 2 个实例 = $195.66
Cloud Run 费用 = CPU + 内存 + 请求 + 网络
总计 $163.84 + $12.22 = $176.06/月 + Cloud Run 费用

本教程使用基本 HDD 层级 Filestore 实例。Filestore 实例的服务层级是其实例类型和存储类型的组合。可以升级实例类型来提高容量和可伸缩性。可以升级存储类型来提升性能。详细了解存储类型建议。区域和容量也会影响 Filestore 价格。例如,爱荷华 (us-central1) 中的基本 HDD 层级 1 TiB 实例的费用是每月每 GiB $0.16,约为每月 $163.84。

无服务器 VPC 访问通道连接器按实例大小和数量以及网络出站流量计费。增加大小和数量可以提高吞吐量或缩短消息的延迟时间。有 3 种大小的机器:f1-micro、e2-micro 和 e2-standard-4。最小实例数为 2,因此最低费用是该机器大小的两倍。

Cloud Run 按内存、CPU、请求数量和网络的资源用量计费,舍入至最接近的 100 毫秒。因此,费用会因服务的设置、请求数量和执行时间而异。此服务每月的费用至少为 $176.06。使用 Google Cloud 价格计算器查看和了解估算值。

清理

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

删除项目

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

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

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

删除教程资源

  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 资源:

后续步骤