教程:将 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 的以下收费组件:

权限

OwnerEditor 角色可以满足本教程所需的权限。最小一组角色是:

IAM 权限只能控制对 Filestore 操作(例如创建 Filestore 实例)的访问权限。如需控制对文件共享操作(例如读取或执行)的访问权限,请使用 POSIX 文件权限。详细了解访问权限控制

准备工作

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

    转到“项目选择器”

  3. 确保您的 Cloud 项目已启用结算功能。 了解如何确认您的项目是否已启用结算功能

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

    转到“项目选择器”

  5. 确保您的 Cloud 项目已启用结算功能。 了解如何确认您的项目是否已启用结算功能

  6. 启用 Cloud Run, Filestore, Serverless VPC Access, Artifact Registry, and Cloud Build API。

    启用 API

  7. 安装并初始化 Cloud SDK。

设置 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. 将示例应用代码库克隆到本地机器:

    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 示例代码的目录:

    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 的交互方式

Python

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

# 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 命令侦听任何后台进程退出,然后退出脚本。

Python

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

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

Java

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

使用文件

Python

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

Java

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

发布服务

  1. 创建 Filestore 实例:

    gcloud filestore instances create INSTANCE_ID \
        --tier=STANDARD \
        --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)。您可以在 Cloud Console 中查看当前预留的 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 beta 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)

标准层级:1024GiB * $0.20/月 = $204.80
高级层级/大规模 SSD:1024GiB * $0.30/月 = $307.20
企业:1024GiB * $0.60/月 = $614.40
无服务器 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 + 内存 + 请求 + 网络
总计 $204.80 + $12.22 = $217.02/月 + Cloud Run 费用

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

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

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

清理

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

删除项目

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

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

  1. 在 Cloud Console 中,转到管理资源页面。

    转到“管理资源”

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

删除教程资源

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

    gcloud run services delete SERVICE-NAME

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

    您还可以从 Google Cloud Console 中删除 Cloud Run 服务。

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

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

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

后续步骤