
Cloud RunからFilestoreにアクセスしてファイルの書き込みと読み出しを試してみた
はじめに
こんにちは。
コンサルティング部の渡邉です。
Cloud Run はサーバーレスでコンテナを実行できる便利なサービスですが、コンテナはステートレスであるため、ファイルの永続化や複数インスタンス間でのデータ共有が課題になることがあります。この課題を解決する方法の1つとして、Cloud Run の NFS ボリュームマウント機能 を使って Filestore に接続する方法があります。
ちょうど、Cloud RunからFilestoreに接続したいケースがあったので、実際にCloud RunサービスからFilestoreに接続してファイルの読み書きを行うまでを手を動かして検証したいと思います。
Filestore とは
Filestore は、Google Cloudが提供するフルマネージドなNFS(Network File System)ファイルサーバーです。複数のクライアントから同時にファイルシステムへアクセスでき、POSIX準拠のファイル操作がサポートされています。
主な接続先として、Compute Engine VM、GKEクラスタ、Cloud Runサービスなどがあります。
サービスティア
Filestoreは用途に応じて複数のサービスティアを提供しています。
| サービスティア | 容量 | 可用性 | 主なユースケース |
|---|---|---|---|
| Zonal | 1 TiB〜100 TiB | ゾーン | HPC、メディアレンダリング、データ分析 |
| Regional | 100 GiB〜100 TiB | リージョン | SAP、Oracle などミッションクリティカルなワークロード |
| Multishares for GKE (Enterprise) | 1 TiB〜10 TiB | リージョン | 小規模シェアが必要な GKE ワークロード |
| Basic HDD(レガシー) | 1 TiB〜63.9 TiB | ゾーン | 基本的なファイル共有 |
| Basic SSD(レガシー) | 2.5 TiB〜63.9 TiB | ゾーン | 高パフォーマンスなファイル共有 |
Cloud Run からFilestoreに接続する仕組み
Cloud Run からFilestoreに接続するには、以下の要素が必要です。
- Filestore インスタンス: VPC ネットワーク内に作成されたNFSファイルサーバー
- Direct VPC egress: Cloud RunサービスをVPCネットワークに接続する機能
- NFS ボリュームマウント: Cloud Runのコンテナ内にFilestoreのファイル共有をマウントする機能
これらを組み合わせることで、Cloud Run のコンテナからローカルファイルシステムと同じようにファイルを読み書きできます。
全体のアーキテクチャは以下のとおりです。
Cloud Run から Filestore への接続には Direct VPC egress の利用が推奨されています。サーバレス VPC コネクタも利用可能ですが、パフォーマンスの観点から Direct VPC egress が推奨されています。
接続フローの詳細
Cloud Runからリクエストを受けてからFilestore上のファイルにアクセスするまでのフローを、シーケンス図で示します。
Cloud RunでNFSボリュームマウントを使用すると、コンテナ起動時にNFSマウントが行われます。そのため、マウントが完了しないとコンテナが開始されず、Filestoreへの接続に問題がある場合はサービス自体が起動に失敗します。
接続時の注意点
Cloud Run でNFSボリュームマウントを使用する際には、いくつかの制約がありますので認識したうえで利用する必要があります。
| 項目 | 内容 |
|---|---|
| NFS ロック | サポートされていない(自動的に no-lock モードでマウント) |
| マウントのタイムアウト | 全マウントの合計で30秒のタイムアウトが存在する |
| マウント不可パス | /dev、/proc、/sys およびそのサブディレクトリはマウントできない |
| 書き込み権限 | デフォルトでは root ユーザー(uid 0)のみ書き込み可能 |
| コールドスタートへの影響 | NFS マウント処理により、コンテナの起動時間がわずかに増加する |
実際に試してみる
ここからは、VPC ネットワークとサブネットを新規作成し、Filestoreインスタンスを構築して、Cloud RunサービスからNFSボリュームとしてマウントするまでの手順を実際に試していきます。
全体のフローは以下のとおりです。
前提条件
- Google Cloud プロジェクトが作成済みであること
gcloudCLI がインストール・認証済みであること- 以下の IAM ロールが付与されていること:
- Cloud Run Developer(
roles/run.developer) - Cloud Filestore Editor(
roles/file.editor) - Service Account User(
roles/iam.serviceAccountUser) - Compute Network Admin(
roles/compute.networkAdmin)
- Cloud Run Developer(
環境変数の設定
以降の手順で使用する環境変数を設定します。PROJECT_ID はご自身の Google Cloud プロジェクト ID に置き換えてください。
export PROJECT_ID="YOUR_PROJECT_ID"
export REGION="asia-northeast1"
export ZONE="asia-northeast1-a"
export NETWORK_NAME="filestore-network"
export SUBNET_NAME="filestore-subnet"
export SUBNET_RANGE="10.0.0.0/24"
export FILESTORE_NAME="nfs-server"
export SHARE_NAME="vol1"
export SERVICE_NAME="filestore-demo"
1. API の有効化
必要なAPIを有効化します。
Cloud Runのソースデプロイを使用するため、Artifact Registry APIとCloud Build APIも有効化しています。
gcloud services enable file.googleapis.com \
compute.googleapis.com \
run.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.googleapis.com \
--project=${PROJECT_ID}
Operation "operations/acf.p2-YOUR_PROJECT_NUMBER-5e26ee30-4db6-484f-a827-a52b1381e0fe" finished successfully.
Cloud Run のソースデプロイでは、Cloud BuildがデフォルトのCompute Engineサービスアカウントを使用してコンテナイメージをビルドします。このサービスアカウントに Cloud StorageとArtifact Registryへの書き込み権限を付与します。
PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} \
--format='value(projectNumber)')
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
--role="roles/storage.objectAdmin"
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
--member="serviceAccount:${PROJECT_NUMBER}-compute@developer.gserviceaccount.com" \
--role="roles/artifactregistry.writer"
2. VPC ネットワークとサブネットの作成
Cloud RunとFilestoreを接続するための専用VPCネットワークとサブネットを新規作成します。
カスタムモードのVPCネットワークを構築することで、サブネットのIP範囲を明示的に指定することができます。オートモードの場合はリージョンごとにサブネットが自動作成されますが、IP範囲の管理やFilestoreへのアクセス制御を細かく行いたい場合はカスタムモードが適しています。
# カスタムモードで VPC ネットワークを作成
gcloud compute networks create ${NETWORK_NAME} \
--subnet-mode=custom \
--project=${PROJECT_ID}
Created [https://www.googleapis.com/compute/v1/projects/YOUR_PROJECT_ID/global/networks/filestore-network].
NAME: filestore-network
SUBNET_MODE: CUSTOM
BGP_ROUTING_MODE: REGIONAL
IPV4_RANGE:
GATEWAY_IPV4:
INTERNAL_IPV6_RANGE:
Instances on this network will not be reachable until firewall rules
are created. As an example, you can allow all internal traffic between
instances as well as SSH, RDP, and ICMP by running:
$ gcloud compute firewall-rules create <FIREWALL_NAME> --network filestore-network --allow tcp,udp,icmp --source-ranges <IP_RANGE>
$ gcloud compute firewall-rules create <FIREWALL_NAME> --network filestore-network --allow tcp:22,tcp:3389,icmp
# サブネットを作成
gcloud compute networks subnets create ${SUBNET_NAME} \
--network=${NETWORK_NAME} \
--range=${SUBNET_RANGE} \
--region=${REGION} \
--project=${PROJECT_ID}
Created [https://www.googleapis.com/compute/v1/projects/YOUR_PROJECT_ID/regions/asia-northeast1/subnetworks/filestore-subnet].
NAME: filestore-subnet
REGION: asia-northeast1
NETWORK: filestore-network
RANGE: 10.0.0.0/24
STACK_TYPE: IPV4_ONLY
IPV6_ACCESS_TYPE:
INTERNAL_IPV6_PREFIX:
EXTERNAL_IPV6_PREFIX:
3. Filestore インスタンスの作成
作成したVPCネットワーク上にFilestoreインスタンスを作成します。今回は検証目的のため、Basic HDDティアを使用します。
Filestoreではnfs-export-optionsを指定することで、接続元のIPアドレスに基づいたアクセス制御を設定できます。今回はステップ2で作成したサブネット(10.0.0.0/24)からのみ読み書きを許可するように設定します。
まず、アクセス制御の設定を含むJSONファイルを作成します。
cat > nfs-export-options.json << EOF
{
"--file-share": {
"capacity": "1024",
"name": "${SHARE_NAME}",
"nfs-export-options": [
{
"access-mode": "READ_WRITE",
"ip-ranges": ["${SUBNET_RANGE}"],
"squash-mode": "NO_ROOT_SQUASH"
}
]
}
}
EOF
アクセス制御ファイルのnfs-export-optionsオプションに関する説明は以下の通りです。
| オプション | 説明 | 今回の設定値 |
|---|---|---|
ip-ranges |
アクセスを許可するIPアドレスまたはCIDR範囲 | 10.0.0.0/24(Cloud Run サブネット) |
access-mode |
READ_WRITE または READ_ONLY |
READ_WRITE |
squash-mode |
NO_ROOT_SQUASH(root 許可)またはROOT_SQUASH(root を匿名ユーザーにマッピング) |
NO_ROOT_SQUASH |
--flags-fileでJSONファイルを指定してFilestoreインスタンスを作成します。
今回は検証のためNO_ROOT_SQUASHを使用していますが、本番環境ではROOT_SQUASHの利用を推奨します。ROOT_SQUASHを有効にすると、クライアントのrootユーザーからのアクセスがanon_uid/anon_gidで指定した匿名ユーザーにマッピングされ、root権限での意図しないファイル操作を防止できます。
gcloud filestore instances create ${FILESTORE_NAME} \
--zone=${ZONE} \
--tier=BASIC_HDD \
--network=name="${NETWORK_NAME}" \
--flags-file=nfs-export-options.json \
--project=${PROJECT_ID}
Waiting for [operation-1787005386693-659459d491ac6-3d01d785-082adc46] to finish...done.
4. Filestore インスタンスの IP アドレスを確認
Cloud RunでNFSマウントを設定する際に、FilestoreインスタンスのIPアドレスが必要なので確認していきます。
gcloud filestore instances describe ${FILESTORE_NAME} \
--zone=${ZONE} \
--project=${PROJECT_ID}
createTime: '2026-08-17T22:23:18.308203565Z'
fileShares:
- capacityGb: '1024'
name: vol1
nfsExportOptions:
- accessMode: READ_WRITE
ipRanges:
- 10.0.0.0/24
squashMode: NO_ROOT_SQUASH
name: projects/YOUR_PROJECT_ID/locations/asia-northeast1-a/instances/nfs-server
networks:
- connectMode: DIRECT_PEERING
ipAddresses:
- 10.169.154.82
modes:
- MODE_IPV4
network: filestore-network
reservedIpRange: 10.169.154.80/29
performanceLimits:
maxIops: '600'
maxReadIops: '600'
maxReadThroughputBps: '104857600'
maxWriteIops: '1000'
maxWriteThroughputBps: '104857600'
protocol: NFS_V3
satisfiesPzi: true
satisfiesPzs: false
state: READY
tier: BASIC_HDD
出力のipAddressesフィールドに表示されるIPアドレスを環境変数に設定します。
export FILESTORE_IP=$(gcloud filestore instances describe ${FILESTORE_NAME} \
--zone=${ZONE} \
--project=${PROJECT_ID} \
--format='value(networks[0].ipAddresses[0])')
echo ${FILESTORE_IP}
10.169.154.82
5. ファイアウォールルールの作成
カスタムVPCネットワークでは、デフォルトですべての egress(外向き通信)が許可されています。ここでは、セキュリティ強化のため、まずNFS関連ポート以外の内部向けegressを拒否するルールを作成し、次にFilestoreへのNFS通信のみを明示的に許可するルールを作成します。
# 内部向け egress をデフォルトで拒否するルール(優先度 1000)
gcloud compute firewall-rules create deny-internal-egress \
--network=${NETWORK_NAME} \
--direction=EGRESS \
--action=DENY \
--rules=all \
--destination-ranges=10.0.0.0/8 \
--priority=1000 \
--project=${PROJECT_ID}
Creating firewall...working..Created [https://www.googleapis.com/compute/v1/projects/YOUR_PROJECT_ID/global/firewalls/deny-internal-egress].
Creating firewall...done.
NAME: deny-internal-egress
NETWORK: filestore-network
DIRECTION: EGRESS
PRIORITY: 1000
ALLOW:
DENY: all
DISABLED: False
# Filestore への NFS 通信のみを許可するルール(優先度 900)
gcloud compute firewall-rules create allow-nfs-to-filestore \
--network=${NETWORK_NAME} \
--direction=EGRESS \
--action=ALLOW \
--rules=tcp:111,tcp:2046,tcp:2049,tcp:2050,tcp:4045 \
--destination-ranges=${FILESTORE_IP}/32 \
--priority=900 \
--project=${PROJECT_ID}
Creating firewall...working..Created [https://www.googleapis.com/compute/v1/projects/YOUR_PROJECT_ID/global/firewalls/allow-nfs-to-filestore].
Creating firewall...done.
NAME: allow-nfs-to-filestore
NETWORK: filestore-network
DIRECTION: EGRESS
PRIORITY: 900
ALLOW: tcp:111,tcp:2046,tcp:2049,tcp:2050,tcp:4045
DENY:
DISABLED: False
NFS通信に必要なポートは以下のとおりです。
| ポート | プロトコル | 用途 |
|---|---|---|
| 111 | TCP | portmapper |
| 2046 | TCP | NFS |
| 2049 | TCP | NFS |
| 2050 | TCP | NFS |
| 4045 | TCP | NFS lock manager |
--destination-rangesをFilestoreのIPアドレス(/32)に限定することで、Cloud Runサブネットからの内部通信をFilestoreのみに制限しています。--priorityの値が小さいルールが優先されるため、allowルール(900)が denyルール(1000)より優先されます。
6. アプリケーションの作成
Cloud Runにデプロイする、Filestoreに接続してファイルの読み書きを行うアプリケーションを作成します。
まず、作業用ディレクトリを作成します。
mkdir filestore-app
cd filestore-app
main.py
NFSマウントしたディレクトリに対してファイルの書き込み・読み込み・一覧表示を行うFlaskアプリケーションです。
import os
import datetime
from flask import Flask, jsonify
app = Flask(__name__)
NFS_MOUNT_PATH = "/mnt/nfs"
@app.route("/")
def index():
nfs_exists = os.path.isdir(NFS_MOUNT_PATH)
return jsonify({
"status": "ok",
"nfs_mounted": nfs_exists,
"endpoints": ["/write", "/read", "/files"]
})
@app.route("/write")
def write_file():
file_path = os.path.join(NFS_MOUNT_PATH, "test.txt")
timestamp = datetime.datetime.now().isoformat()
with open(file_path, "w") as f:
f.write(f"Hello from Cloud Run! Written at {timestamp}")
return jsonify({"message": "File written", "path": file_path, "timestamp": timestamp})
@app.route("/read")
def read_file():
file_path = os.path.join(NFS_MOUNT_PATH, "test.txt")
if not os.path.exists(file_path):
return jsonify({"error": "File not found. Call /write first."}), 404
with open(file_path, "r") as f:
content = f.read()
return jsonify({"content": content, "path": file_path})
@app.route("/files")
def list_files():
if not os.path.isdir(NFS_MOUNT_PATH):
return jsonify({"error": "NFS mount not found"}), 500
files = os.listdir(NFS_MOUNT_PATH)
return jsonify({"files": files, "count": len(files)})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))
requirements.txt
flask==3.1.*
gunicorn==23.*
Dockerfile
Dockerfile上では、USER root を指定しています。Filestoreのファイル共有はデフォルトで rootユーザー(uid 0)のみに書き込み権限があるためです。本番環境では、セキュリティの観点からファイル共有のパーミッションを変更し、非rootユーザーで実行することを推奨します。
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY main.py .
USER root
EXPOSE 8080
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "main:app"]
7. Cloud Run サービスのデプロイ
Direct VPC egressとNFSボリュームマウントを設定して、ソースコードから直接 Cloud Runサービスをデプロイします。
--source=. を指定すると、ソースコードがCloud Buildでビルドされ、Artifact Registryにコンテナイメージが保存された後、Cloud Runにデプロイされます。
ステップ 6 で作成した filestore-app ディレクトリ内で以下のコマンドを実行します。
gcloud run deploy ${SERVICE_NAME} \
--source=. \
--network=${NETWORK_NAME} \
--subnet=${SUBNET_NAME} \
--vpc-egress=private-ranges-only \
--add-volume=mount-path=/mnt/nfs,type=nfs,location=${FILESTORE_IP}:/${SHARE_NAME},readonly=false \
--region=${REGION} \
--allow-unauthenticated \
--project=${PROJECT_ID}
Building using Dockerfile and deploying container to Cloud Run service [filestore-demo] in project [YOUR_PROJECT_ID] region [asia-northeast1]
Building and deploying new service...
Validating configuration...done
Uploading sources...done
Building Container... Logs are available at [ https://console.cloud.google.com/cloud-build/builds;region=asia-northeast1/3c27a672-ea40-442f-aa08-fcac209239ec?project=YOUR_PROJECT_NUMBER ]....done
Setting IAM Policy...warning
Creating Revision...done
Routing traffic...done
Completed with warnings:
Setting IAM policy failed, try "gcloud beta run services add-iam-policy-binding --region=asia-northeast1 --member=allUsers --role=roles/run.invoker filestore-demo"
Service [filestore-demo] revision [filestore-demo-00001-hjr] has been deployed and is serving 100 percent of traffic.
Service URL: https://filestore-demo-YOUR_PROJECT_NUMBER.asia-northeast1.run.app
デプロイ後、サービスの状態を確認します。
gcloud run services describe ${SERVICE_NAME} \
--region=${REGION} \
--project=${PROJECT_ID}
✔ Service filestore-demo in region asia-northeast1
URL: https://filestore-demo-YOUR_PROJECT_NUMBER.asia-northeast1.run.app
Ingress: all
Traffic:
100% LATEST (currently filestore-demo-00001-hjr)
Scaling: Auto (Min: 0, Max: 10)
Threat Detection: Enabled
Last updated on 2026-08-17T22:38:21.946935Z by xxxxxxxx@example.com:
Revision filestore-demo-00001-hjr
Container None
Image: asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/cloud-run-source-deploy/filestore-demo@sha256:971078c0ca48fc66da5263a5503de28eca8fc20fa5c4544b2ed4420237e7d05d
Port: 8080
Memory: 512Mi
CPU: 1000m
Volume Mounts:
/mnt/nfs
name: nfs-der-rut
type: nfs
location: 10.169.154.82:/vol1
Startup Probe:
TCP every 240s
Port: 8080
Initial delay: 0s
Timeout: 240s
Failure threshold: 1
Type: Default
Service account: YOUR_PROJECT_NUMBER-compute@developer.gserviceaccount.com
Concurrency: 80
Timeout: 300s
VPC access:
Network: filestore-network
Subnet: filestore-subnet
Egress: private-ranges-only
Volumes:
nfs-der-rut
type: nfs
location: 10.169.154.82:/vol1
出力にVPCネットワーク・サブネットの情報と、NFSボリュームマウントの設定が表示されることを確認します。
8. 動作確認
デプロイしたCloud RunサービスのURLにアクセスして、Filestoreへのファイルの読み書きが正しく動作することを確認します。
# サービスの URL を取得
SERVICE_URL=$(gcloud run services describe ${SERVICE_NAME} \
--region=${REGION} \
--format='value(status.url)' \
--project=${PROJECT_ID})
# NFS マウントの状態を確認
curl -s ${SERVICE_URL}/ | jq .
{
"endpoints": [
"/write",
"/read",
"/files"
],
"nfs_mounted": true,
"status": "ok"
}
# ファイルの書き込み
curl -s ${SERVICE_URL}/write | jq .
{
"message": "File written",
"path": "/mnt/nfs/test.txt",
"timestamp": "2026-08-17T22:48:20.688232"
}
# ファイルの読み込み
curl -s ${SERVICE_URL}/read | jq .
{
"content": "Hello from Cloud Run! Written at 2026-08-17T22:48:20.688232",
"path": "/mnt/nfs/test.txt"
}
# ファイル一覧の確認
curl -s ${SERVICE_URL}/files | jq .
{
"count": 2,
"files": [
"test.txt",
"lost+found"
]
}
各エンドポイントの結果から、以下のことが確認できました。
| 確認項目 | 結果 | わかること |
|---|---|---|
/(ルート) |
nfs_mounted: true |
Cloud Run コンテナ内の /mnt/nfs に NFS ボリュームが正常にマウントされている |
/write |
message: "File written" |
Filestore のファイル共有に対してファイルの書き込みが成功している。nfs-export-options で設定した READ_WRITE アクセスモードと NO_ROOT_SQUASH が正しく機能している |
/read |
/write で書き込んだ内容がそのまま読み取れた |
NFS 経由でのデータの永続化と読み込みが正常に動作している |
/files |
test.txt と lost+found の2ファイル |
test.txt は /write で作成したファイル。lost+found は Filestore のファイル共有が作成された際に自動生成されるディレクトリで、ファイルシステムとして正しく認識されていることを示す |
この結果から、Cloud Run から Direct VPC egress 経由で Filestore に接続し、NFS ボリュームマウントを通じてファイルの読み書きが問題なく行えることが確認できました。
まとめ
本記事では、VPC ネットワークとサブネットを新規作成し、Filestoreインスタンスを構築して、Cloud RunサービスからNFSボリュームマウントでファイルの読み書きを行う方法をご紹介しました。
NFS ボリュームマウントを使うことで、Cloud Runのコンテナからローカルファイルシステムと同じ操作でファイルの読み書きが可能になります。既存のアプリケーションがファイルシステムに依存している場合でも、コードの変更を最小限に抑えて Cloud Runに移行できる点が大きなメリットです。
接続にあたっては Direct VPC egress によるネットワーク接続が必須となります。サーバレスVPC Connectorsも利用可能ですが、パフォーマンスの観点から Direct VPC egress が公式に推奨されています。なお、NFSマウントのタイムアウトは全マウント合計で30秒であるなど制約事項も多く存在しているので、事前に把握しておく必要があります。
この記事が誰かの助けになれば幸いです。
以上、コンサルティング部の渡邉でした!





