使用动态挂载文件存储NAS功能,函数实例的每个用户会话可动态挂载到文件存储NAS中专属的子目录,实现多租户环境下持久化数据的安全隔离。
背景介绍
随着 AI Agent 应用的快速发展,越来越多的场景需要在安全沙箱(Sandbox)中执行用户自定义代码,并持久化保存会话期间生成的文件、项目代码、数据集等关键资产。这类应用通常以多租户 SaaS 形式部署,成千上万的用户会话并发运行,每个会话都需独立、隔离的存储空间。
在过去的方案中,开发者常将所有会话挂载到同一个共享的NAS根目录下。然而,这种做法存在严重的数据越权访问风险:一个恶意或异常会话可能通过路径遍历、权限提升等方式访问甚至篡改其他租户的数据,无法满足企业级安全与合规要求。
为应对这一挑战,阿里云函数计算(FC)在支持会话亲和与会话隔离的基础上,进一步将隔离能力延伸至持久化存储层,推出 “动态挂载文件存储NAS” 功能。该功能允许每个 AI Sandbox 会话在启动时,自动、安全地挂载到专属的 NAS 子目录,实现从计算到存储的端到端多租户隔离。
功能说明
核心能力
函数计算通过会话动态挂载 NAS功能,允许您在创建会话(Session)时,按需为该会话挂载专属的 NAS 目录,并指定独立的用户身份(POSIX UID/GID)。其核心特性包括:
动态与按需:存储的挂载与会话生命周期绑定,仅在需要时分配,会话销毁后自动解绑。
强隔离性:通过为每个会话指定独立的 User ID/Group ID,从文件系统层面彻底隔离不同租户的数据访问权限。
高灵活性:可为不同租户挂载到 NAS 文件系统的不同子目录,轻松实现数据分区。
性能优化:结合会话预热,可在实例启动时预先挂载好数据目录,加速模型或数据的加载。
工作原理
该功能将 NAS 挂载操作与 CreateSession API 调用深度集成。以 HeaderField 亲和为例,整体流程如下:

准备阶段:创建并配置带有存储的会话
此阶段的目标是创建一个与特定存储绑定的、隔离的会话环境,以便后续使用。
发起创建请求:后端管理服务调用
CreateSessionAPI,并在请求中指定NAS挂载配置以及用于隔离的用户身份(UID/GID)。准备实例与挂载:函数计算(FC)平台接收请求后,准备一个函数实例,该函数实例会根据配置执行NAS挂载操作,将专属目录挂载到实例的指定路径(例如
/mnt/data)。绑定并返回ID:挂载成功后,平台将此实例与一个新生成的唯一
SessionID进行绑定,并将该ID返回给后端服务。
调用阶段:使用已配置的会话
此阶段使用已准备好的会话环境来执行实际的业务逻辑。
发起调用请求:后端管理服务在调用
InvokeFunctionAPI 时,通过一个特定的HTTP请求头(如x-affinity-header-v1)传入之前获取的SessionID。路由请求:函数计算平台的网关与调度器根据此
SessionID,将请求精确地路由到已绑定的特定函数实例上。执行代码:函数代码在该实例中执行。此时,由于环境和权限都已预先配置好,代码可以直接读写已挂载的NAS目录。
返回结果:函数执行完毕后,将结果逐层返回给最初的调用方。
适用范围
功能邀测:会话生命周期管理功能处于公测阶段,使用前请提交工单申请;
地域限制:不支持在华北5(呼和浩特)地域使用,动态挂载NAS功能仅限美国(硅谷)地域使用,如需支持其他地域,在提交工单时补充说明。
文件存储NAS:您已经创建了文件系统NAS,并规划好了用于租户数据隔离的目录结构;
会话亲和类型:适用于HeaderField和Cookie亲和类型。
操作步骤
以下将以 Python SDK 为例,完整演示如何为一个函数配置并使用动态 NAS 挂载。
步骤一:配置函数(前置设定)
为使函数支持会话动态挂载NAS,需要在创建函数或更新函数配置时,完成三项关键设置:
开启会话亲和:
登录函数计算控制台,点击函数管理>函数列表
在函数列表页,点击需要配置的函数名称,进入函数详情页
在配置页,找到高级配置,点击

打开会话亲和开关,选择HeaderField 亲和并配置Header Name,例如:
x-affinity-header-v1说明不能以
x-fc-前缀开头,以字母开头,非首字符可包含数字、中划线、下划线、字母,长度大于等于5个字符并且不超过40个字符。点击部署完成配置更新。
开启实例隔离:开启配置>高级配置>实例隔离,并选择会话隔离;
允许访问 VPC配置
开启配置>高级配置>网络>允许访问 VPC;
配置方式选择自定义配置;
专有网络选择挂载点所在的 VPC。
步骤二:创建带 NAS 配置的会话
安装依赖
macOS / Linux
# 使用 pip3 安装 pip3 install alibabacloud_fc20230330 alibabacloud_credentials alibabacloud_tea_openapi alibabacloud_tea_util # 如遇权限问题,使用 --user 参数 pip3 install --user alibabacloud_fc20230330 alibabacloud_credentials alibabacloud_tea_openapi alibabacloud_tea_util # macOS Homebrew Python 环境需使用 --break-system-packages pip3 install --break-system-packages alibabacloud_fc20230330 alibabacloud_credentials alibabacloud_tea_openapi alibabacloud_tea_utilWindows
# 使用 pip 安装 pip install alibabacloud_fc20230330 alibabacloud_credentials alibabacloud_tea_openapi alibabacloud_tea_util # 或使用 Python 3 指定 py -3 -m pip install alibabacloud_fc20230330 alibabacloud_credentials alibabacloud_tea_openapi alibabacloud_tea_util编写创建会话的代码
创建python代码文件(如:
creatSession.py),将下列代码复制到文件中并替换核心参数。示例演示了如何为“租户 A”创建一个会话,并将其专属的 NAS 目录 (<YOUR-NAS-SERVER-ADDR>:/tenant-a-data) 挂载到实例的/mnt/data路径,同时指定用户身份为UID=1001和GID=1001。核心方法及核心参数说明:
重要会话动态挂载NAS和在函数配置>高级配置>存储中挂载NAS可以同时配置,但是需要注意:
下文
NASConfig中定义的User ID/Group ID必须与函数挂载配置中使用的用户(User ID)/用户组(Group ID)保持一致同一个挂载路径 (
mount_dir) 不能同时用于会话动态挂载和函数挂载
config.endpoint:<账号ID>:替换为阿里云账号ID;
<Endpoint>:请参考函数计算3.0 服务区域列表,规则为
fcv3.[region_id].aliyuns.com;
CreateSessionInput:session_ttlin_seconds:会话总生命周期(单位:秒);
session_idle_timeout_in_seconds:会话闲置过期时间(单位:秒);
client.create_session_with_options:将<函数名称>替换为创建Session的函数名称;NASMountConfig:NAS 挂载配置mount_dir:实例内的挂载路径,如:/home/testserver_addr:NAS 文件系统地址及租户专属子目录
user_id:为此会话指定独立的 POSIX User IDgroup_id:为此会话指定独立的 POSIX Group ID
# -*- coding: utf-8 -*- from alibabacloud_fc20230330.client import Client as FC20230330Client from alibabacloud_credentials.client import Client as CredentialClient from alibabacloud_tea_openapi import models as open_api_models from alibabacloud_fc20230330 import models as fc20230330_models from alibabacloud_tea_util import models as util_models # 1. 创建账号Client credential = CredentialClient() config = open_api_models.Config(credential=credential) config.endpoint = f'<账号ID>.<Endpoint>' client = FC20230330Client(config) # 2. 准备 NAS 挂载配置 nas_mount_config = fc20230330_models.NASMountConfig( mount_dir='/mnt/data', # 实例内的挂载路径 server_addr='<YOUR-NAS-SERVER-ADDR>:/<tenant-a-path>' # NAS 文件系统地址及租户专属子目录 ) # 3. 配置 NAS 和用户身份(为租户A分配独立UID/GID) nas_config = fc20230330_models.NASConfig( mount_points=[nas_mount_config], user_id=1001, # 为此会话指定独立的 POSIX User ID group_id=1001 # 为此会话指定独立的 POSIX Group ID ) # 4. 构造 CreateSession 请求 create_session_input = fc20230330_models.CreateSessionInput( nas_config=nas_config, session_ttlin_seconds=3600, session_idle_timeout_in_seconds=600 ) create_session_request = fc20230330_models.CreateSessionRequest( body=create_session_input ) # 5. 发起请求 runtime = util_models.RuntimeOptions() response = client.create_session_with_options('<函数名称>', create_session_request, {}, runtime) # 6. 从响应中获取 sessionId print(response.body.to_map()) session_id = response.body.session_id print(f"Session created successfully. Session ID: {session_id}")运行代码
export ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI**************** export ALIBABA_CLOUD_ACCESS_KEY_SECRET=<yourAccessKeySecret> python3 creatSession.py参数说明:
ALIBABA_CLOUD_ACCESS_KEY_ID:阿里云账号或 RAM 用户的AccessKey ID;
ALIBABA_CLOUD_ACCESS_KEY_SECRET:阿里云账号或 RAM 用户的AccessKey Secret。
返回结果示例
{ 'containerId': 'c-********-********-************', 'createdTime': '2025-10-30T06:38:10Z', 'functionName': '****', 'lastModifiedTime': '2025-10-30T06:38:10Z', 'nasConfig': { 'groupId': 1001, 'mountPoints': [ { 'enableTLS': False, 'mountDir': '/home/test', 'serverAddr': '*-*.*.nas.aliyuncs.com:/test' } ], 'userId': 1001 }, 'qualifier': 'LATEST', 'sessionAffinityType': 'HEADER_FIELD', 'sessionId': '******************', 'sessionIdleTimeoutInSeconds': 600, 'sessionStatus': 'Active', 'sessionTTLInSeconds': 3600 } Session created successfully. Session ID: ************
步骤三:在函数中使用挂载的 NAS
现在,调用函数时携带上一步获取的 SessionID,函数实例内部即可访问到已挂载的 /mnt/data 目录。下面以Web函数为例:
修改函数内代码并重新部署
import os from flask import Flask, request, jsonify # ... (app setup) ... # 假设 NAS 挂载点在 /mnt/data NAS_MOUNT_PATH = '/mnt/data' app = Flask(__name__) @app.route('/<path:path>', methods=['GET', 'POST']) def handle_nas_request(path): rid = request.headers.get('x-fc-request-id') print(f"FC Invoke Start RequestId: {rid}") # 构造文件在 NAS 上的完整路径 # 注意:需要防止路径穿越攻击 safe_path = os.path.normpath(os.path.join(NAS_MOUNT_PATH, path)) if not safe_path.startswith(NAS_MOUNT_PATH): return "Path traversal attempt detected!", 400 response_data = {} if request.method == 'POST': # 写入文件 body = request.data.decode('utf-8') try: with open(safe_path, 'w') as f: f.write(body) response_data['message'] = f"Successfully wrote to {safe_path}" print(f"Wrote to {safe_path}") except Exception as e: return str(e), 500 elif request.method == 'GET': # 读取文件 try: if os.path.exists(safe_path) and os.path.isfile(safe_path): with open(safe_path, 'r') as f: content = f.read() response_data['content'] = content print(f"Read from {safe_path}") else: return f"File not found: {safe_path}", 404 except Exception as e: return str(e), 500 print(f"FC Invoke End RequestId: {rid}") return jsonify(response_data) # 使用 jsonify 返回 JSON 格式的响应 # ... (if __name__ == '__main__': block) ...调用函数并验证
调用API InvokeFunction- 调用函数,使用会话调用函数。
核心代码示例及说明:
InvokeFunctionHeaders:构造请求头,在请求Header中携带上一步返回的sessionId,Header Key的值与开启会话亲和设置的值保持一致(如:x-affinity-header-v1),实现会话绑定路由。# -*- coding: utf-8 -*- from alibabacloud_fc20230330.client import Client as FC20230330Client from alibabacloud_credentials.client import Client as CredentialClient from alibabacloud_tea_openapi import models as open_api_models from alibabacloud_fc20230330 import models as fc20230330_models from alibabacloud_tea_util import models as util_models session_id = '************' function_name = 'my-session-nas' # 1. 创建账号Client credential = CredentialClient() config = open_api_models.Config(credential=credential) config.endpoint = f'<账号ID>.<Endpoint>' client = FC20230330Client(config) # 2. 构造请求头。Header Key(x-affinity-header-v1)必须与函数配置的会话亲和 Key 一致。 headers = fc20230330_models.InvokeFunctionHeaders( common_headers={ "x-affinity-header-v1": session_id } ) # 3. 构造调用请求(可根据需要传入 body) invoke_request = fc20230330_models.InvokeFunctionRequest( body='your_request_payload'.encode('utf-8') # 示例 payload ) runtime = util_models.RuntimeOptions() try: # 4. 发起调用 invoke_response = client.invoke_function_with_options( function_name, invoke_request, headers, runtime ) # 5. 处理响应 print(f"Status Code: {invoke_response.status_code}") print(f"Response Body: {invoke_response.body.decode('utf-8')}") except Exception as error: print(error.message)
后续步骤:删除会话
在任务完成后调用 DeleteSession - 删除会话资源,释放会话资源
生产环境建议
UID/GID 规划:为确保隔离性,必须为每个租户分配唯一的 POSIX UID;
目录配额:为防止单个租户耗尽共享存储空间,建议在 NAS 侧为每个租户的根目录配置目录配额(Directory Quotas);
数据垃圾回收 (GC):
DeleteSession - 删除会话资源操作不会自动删除 NAS 上的文件数据。需建立配套的异步垃圾回收机制,定期扫描并清理无主的文件目录,以回收存储空间。
计费说明
计费从CreateSession - 创建会话资源成功起开始,按实例运行时长计费。
即使会话处于空闲状态,只要未过期或删除,将持续产生费用。
DeleteSession - 删除会话资源后计费行为分以下两种情况:
非隔离模式下,DeleteSession 不终止正在进行的调用,Session 关联的实例资源费用将持续计费至执行结束。
隔离模式下,DeleteSession终止运行中的请求,销毁绑定的实例资源,终止计费。
会话Active期间,如果有请求,需要按照弹性实例(活跃)单价进行计费,如果无请求,则按照弹性实例(闲置)单价进行计费。
相关文档及API说明
功能列表 | 行为说明 |
| |
| |
列举指定函数下的会话列表,支持按 qualifier、状态、sessionID 过滤,并支持分页查询。
| |
更新会话的闲置过期时间SessionIdleTimeoutInSeconds和会话生命周期参数SessionTTLInSeconds,更新后立即生效,lastModifiedTime自动刷新。可用于动态延长或缩短会话有效期。 | |
|