
最近折腾 FSx for ONTAP,在文件门户里塞了 182 个 ONTAP 操作。结果跑起来之后,集群告诉我一堆文档里压根没写的东西。每个都是错误码的形式出现,不是文档里能提前看到的问题。
这篇跟之前那篇不一样。之前那篇聊的是界面设计,这次是实打实的测量记录。读者定位是直接调 FSx for ONTAP ONTAP REST API 的开发者——不一定要做门户,只要调同样的 API,踩的坑是一样的。
这次覆盖的内容:FlexGroup 创建、容量重平衡、容量数字怎么读、删不掉的卷、以及 S3 兼容 API 的四个坑。每个都附带系统实际返回的响应。
不覆盖的内容:ONTAP 内部实现(没法验证)、绝对吞吐数字(只聊可行性和前提条件,但影响测量可信度的默认配置会专门说)、SnapLock 合规模式(不可逆,会让测试文件系统好几个月删不掉,所以故意没碰)、实际冷数据分层到容量池(需要不同的聚合配置,不在这次测试范围内)。
先说结论:
use_tiered_aggregate),但 volume 创建这边还没跟进max_runtime 有上下界,API 参考里没写。用 ONTAP 的默认值,永远不会启动space.used 在不同卷上含义不同。快照占的预留空间不算进去| 项目 | 值 |
|---|---|
| 测试时间 | 2026年8月中旬到2026-09-02(JST) |
| 区域 | ap-northeast-1 |
| ONTAP 版本 | 9.18.1P3D1 |
| 部署类型 | SINGLE_AZ_1 |
| 调用方 | VPC Lambda(Python 3.13)调 ONTAP REST API |
| 权限 | fsxadmin |
| 清理 | 所有测试卷已删除 |
这两个概念混着搜错误文本是找不到答案的。
| 术语 | 出现位置 |
|---|---|
FabricPool、use_tiered_aggregate |
ONTAP 错误文本和 REST 字段名 |
| 分层、容量池分层 | AWS 文档和控制台 |
指的都是同一个机制。但错误是用 ONTAP 的术语返回的,文档解释用的是 AWS 的词汇,所以在 AWS 文档里搜错误字符串什么都搜不到。门户的措辞遵循 AWS 那边,只有 API 字符串是原样引用。
FlexGroup 把多个"成员"(内部是真正的卷)作为一个命名空间对外暴露。ONTAP 默认会自己选聚合,除非你指定。但 FSx for ONTAP 上这么操作一定会失败。
Aggregates not matching FabricPool requirements: aggr1
FSx for ONTAP 有主存储层(SSD)和容量池存储层,不常用的数据会分层到后者。启用这个配置之后,聚合不在 ONTAP 自动放置的范围内。
这个问题是踩一次失败才知道的。
| 步骤 | 结果 |
|---|---|
| 用默认参数创建 | 失败。Volumes of this type must be at least 50GB |
| 重试,设为 50 GiB | 失败。Aggregates not matching FabricPool requirements: aggr1 |
| 重试,手动指定聚合 | 失败。Minimum size is "400GB"(4个成员 × 100 GiB) |
400 GiB、手动指定聚合、tiering.policy=none、精简配置 |
成功 |
手动指定聚合能跑。但这个聚合名在 AWS 控制台和 FSx API 里都找不到。必须调 GET /storage/aggregates 去查。没人会主动给租户一个聚合名,所以这个额外调用是必须的。
同样的根因在 FlexCache 那侧已经被认知了——有个标志位 use_tiered_aggregate,默认值是 false。但 volume 创建这边还没加这个逻辑。同一个问题在别处露出不同面孔,是托管服务包装上游软件时常见的形态。
界面上加了这么一条说明:
从这个界面创建 FlexGroup 时,显式指定聚合而不是让系统自动放置。FSx for ONTAP 的聚合启用了分层,不在自动放置范围内(实测:不指定的话创建失败,报"Aggregates not matching FabricPool requirements")。默认四个成员的情况下最小是 400 GB。
FlexGroup 按哈希把文件分布到各个成员,时间长了分布会不均匀。当某个成员满了,整个卷都会报"空间不足",虽然其他成员还有余量。容量重平衡就是用来纠正这个的。
在真实集群上跑了一遍,发现 max_runtime 有两个 API 参考里没写的约束。
| 约束 | 详情 | 错误码 |
|---|---|---|
| 下界 | 低于 30 分钟会被拒绝 | 144182221 |
| 上界 | 必须短于距下次快照的时间 | 13107433 |
也就是说,启动窗口是:
30 min <= max_runtime < (距下次快照的时间)
ONTAP 默认的 max_runtime 是 6 小时。在默认快照策略(每小时:05)下,6 小时永远超过上界。门户作为默认值提供的就是 6 小时,一次都没启动成功。 默认策略下唯一的窗口是每小时:05到:35这30分钟。如果快照策略更频繁(比如 5min),那就得先卸掉策略才能启动。
边界是通过 A/B 测试确定的,间隔一秒。
| 时间 | maxRuntime |
距下次快照 | 结果 |
|---|---|---|---|
| 16:06:12 | PT1H(60分钟) |
58分47秒 | 拒绝(13107433) |
| 16:06:13 | PT30M(30分钟) |
58分46秒 | 成功 |
60分钟 > 58分47秒,拒绝;30分钟 < 58分46秒,成功。由于两次调用只差一秒,决定因素是 max_runtime 值而不是流逝的时间。ONTAP 自己的提示提到了两个解法——"减小 -max-runtime 或禁用快照策略"——正是因为这个结构。
完整的观测记录在 FlexGroup 容量重平衡验证文档里。参考文档里 volume 侧没列的状态值也在那里出现了:idle 表示在跑但没东西要移动,scheduled 表示存在预留。门户之前把"running"以外的状态都显示为"未知"。
关于不可逆性的说明
启动重平衡会在该卷上启用
granular data。这是重平衡必需的,会为每个被移动的文件创建两个多部分 inode。停止不会撤销它。厂商文档没有列出回退方式,只能删除卷或从启用前的快照恢复。停止也不是回滚:已经移动的文件不会回到原位。两个都测过(停止后
granular_data仍然是true)。界面的确认对话框里把这两点都说了。
如果直接读卷列表里的用量数字,很容易读错,因为 space.used 在不同卷上含义不同。
space.used 里实测数据。一个 100 GiB 的卷,used 显示 18.1 MiB,但里面实际有 77.3 MiB 的快照。因为快照在 5% 预留(5 GiB)之内,用量条不会动。在另一个预留为 0% 的卷上,used 是 83,677 MiB:81,934 MiB 实时数据 + 1,743 MiB 快照。11 个卷里有 8 个,快照占用超过实时数据。
所以把一个用量数字拆成了三个:实时数据、快照、预留溢出。这样就能从界面上直接看出空间回不来是因为快照还是因为数据确实写满了。
有时候原因不在当前卷上,而它自己的行不会显示。问题出在 FlexClone。克隆基于父卷的某个快照,在克隆存在期间那个快照被锁住了。删父卷上的文件不会释放空间,因为被锁的快照还在引用那些块。只看父卷的行是看不出这点的,所以把这个信息写在 FlexClone 面板上。
我复现了"删除父卷时被拒绝,报 has one or more clones"的状态。尴尬的地方在于,API 看不到那个克隆。它不在克隆列表里,直接查询返回 entry doesn't exist,但父卷就是拒绝删除,说有克隆存在。
根因是 ONTAP 的卷恢复队列,默认保留已删除卷 12 小时。同环境下的 A/B 测试,只差是否先拆分克隆:
| 步骤 | 删除父卷 |
|---|---|
| 删除未拆分的克隆,再删除父卷 | 失败(7分钟、15分钟后仍在失败) |
| 先拆分克隆,删除克隆,再删除父卷 | 几秒内成功 |
清理方案也测过了。GET /api/private/cli/volume/recovery-queue 读队列。POST .../purge 用 fsxadmin 权限执行,条目大约 20 秒后从队列消失,父卷删除随即成功。
这个地方我之前发过错误的东西。更早的表述说 purge 需要 diag 权限,fsxadmin 够不着,所以只能等 12 小时。错了。我没试过权限就甩锅给权限。
界面的报错信息写明了因果关系和两条出路(先拆分,或者 purge)。Purge 不可撤销,所以也提示要先确认这个卷是自己的再执行。
从浏览器或 SDK 调 FSx for ONTAP S3 访问点之前需要知道的行为。提前知道能省不少调查时间。
PutObject 带 if-none-match: * 返回 501 NotImplemented,消息体是 A header you provided implies functionality that is not implemented。
| 请求 | 结果 |
|---|---|
PUT + if-none-match: * |
501 NotImplemented |
PUT + x-amz-checksum-crc32(不带 if-none-match) |
200 OK |
GET / ListObjectsV2 |
200(两个 header 都没带) |
CRC32 弹性校验和能过。只有 if-none-match 失败。读操作不受影响,所以症状是"列举正常但所有写入都失败"。
这不是别人的问题。Amplify 的 Storage Browser 把"不覆盖"选项翻译成了这个 header,直接用就会让所有上传和文件夹创建失败。如果需要防覆盖,写之前先查一下 key。不给 if-none-match 的原子性保证,两个并发的相同 key 写入可能都判断出 key 不存在。
删除写了一天保留期的对象:
| 操作 | GOVERNANCE | COMPLIANCE |
|---|---|---|
| 不带绕过删除 | AccessDenied ... object protected by object lock |
完全相同的文本 |
带 BypassGovernanceRetention |
成功 | 以完全相同的文本拒绝 |
因为文本完全一样,响应本身看不出绕过为什么没生效。首先要查的是 get-object-lock-configuration 和 head-object 的 ObjectLockMode,不是 IAM 策略。顺序搞反了会在权限上浪费很多时间。
给策略里的日程配置了 retentionPeriod,但锁定特性标志位还是 false;只有分配的策略名变了。单独看状态会以为不会锁定任何东西,实际上那个策略创建的每个快照都是锁定的。判断需要同时看分配的策略的 retentionPeriod。
把别名复制到配置文件里,对于已删除的访问点和状态为 MISCONFIGURED 的访问点看起来完全一样。fsx describe-s3-access-point-attachments 返回 Lifecycle 和 Internet 或 VPC 来源,所以从这里推导清单。一定要处理分页:只读第一页的话,存在的访问点和不存在的访问点的行为是无法区分的。
有两样东西实现之前读文档以为是这样,跑起来发现不是。都改了界面措辞。
| 主题 | 文档里的预期 | 实测(ONTAP 9.18.1P3D1) |
|---|---|---|
| 删除已分配的 QoS 策略 | CLI 参考说不加 -force 会拒绝 |
REST 直接接受了,卷上的分配被静默移除(所有上限变成 0,即无限制) |
| 删除配额规则后的生效 | REST 参考说删除后规则继续生效,需要关闭再打开执行才能清除 | 删除规则的配额上限在用量报告里立即消失 |
QoS 确认文本重写了。正确的警告不是"无法删除",而是"删除会移除所有使用该策略的卷的配额上限"。加了一条说明:释放单个卷应该分配 none,而不是删除策略。
前面说的默认配置决定了操作能不能跑通。测量本身也有默认配置,而且性质一样。 五个都不报错:每个都返回一个看起来合理的数字,但实际上在测量别的东西。
这些数字的来源:另一组测量,与上述环境不同。2026-09-01 到 02,ap-northeast-1,ONTAP 9.18.1P3D1,SINGLE_AZ_1,吞吐层级 128 MBps 和 2048 MBps,客户端 c5n.9xlarge。同一次测量的重复运行之间波动 0.02–0.14%。
| 默认配置 | 后果 | 怎么发现 | 怎么处理 |
|---|---|---|---|
| Linux NFS 用单 TCP 连接 | 卡在 590 MB/s 左右,增加连接数无效 | 1/4/8 个连接的测出来一样 | nconnect=16 |
dd if=/dev/zero |
零块跳过磁盘,测出来是公布上限的 4 倍 | 超过公布的上限 | 用不可压缩数据 |
| 卷内联效率开启 | 可压缩或相同内容会被压缩(读 4 倍,写不到 5%) | space_savings.dedupe_percent 很高 |
写入之前关掉 |
DiskIopsConfiguration: AUTOMATIC |
3 IOPS/GiB,所以 IOPS 成为瓶颈 | MB/s 除以 IOPS 小得不合理 | USER_PROVISIONED |
| 读的数据量略超过缓存大小 | 缓存服务的部分占主导,磁盘路径根本没测到 | DiskReadBytes 除以 DataReadBytes 只有几个百分点或更低 |
一次读至少两倍缓存大小的数据 |
第五个是怎么暴露的。我以为 280 GiB 不会落在缓存里。读了一遍 CloudWatch AWS/FSx 命名空间的 DiskReadBytes,除以 DataReadBytes,得出来是 1.4% / 0.9% / 1.5%。98.5-99.9% 的字节根本没碰磁盘。
2048 MBps 层级的内存缓存是 256 GB,也就是 238 GiB(FSx for ONTAP 性能文档;ap-northeast-1 属于第一代 Single-AZ "其他区域"表)。280 GiB 比它多 18%,不是两倍三倍。"超过缓存"和"不被缓存服务"是两回事。
内联效率可以恢复,只要等。
# 效率状态不是 idle 会被拒绝
PATCH /api/storage/volumes/{uuid} {"efficiency": {"compression": "inline"}}
PATCH /api/storage/volumes/{uuid} {"efficiency": {"dedupe": "both"}}
但只能在可以丢弃的卷上关掉。刚写完数据的时候后台效率操作正在跑,等待时间不可控。
"测试通过"不等于"生产能用"。单元测试只跑 handler 逻辑,看不到真实 ONTAP REST 响应的形状、AppSync 授权、VPC 连通性、Cognito 组传播的真实延迟。
所以验证结果分四路:真实硬件端到端、真实硬件只读、只有自动化测试、DemoMode。保留这个区分是为了以后能问自己"这东西我真的见过跑吗"。这篇文章里的一处修正——恢复队列 purge 的权限——就是从区分模糊的地方挖出来的。
| # | 要写进去的内容 |
|---|---|
| 1 | FlexGroup 创建时,用 GET /storage/aggregates 查出聚合并指定。 依赖自动放置的运行手册在 FSx for ONTAP 上一定会失败 |
| 2 | 重平衡 max_runtime 至少 30 分钟且低于距下次快照的时间。 不要直接用默认值 |
| 3 | 不要把启动重平衡当成随时能按的操作。 granular data 是不可逆启用的 |
| 4 | 不要把用量显示成一个数字。 把实时数据和快照分开,否则没人能找到"删了文件空间没回来"的原因 |
| 5 | 有克隆的环境,拆除流程必须包含拆分或恢复队列 purge。 "克隆删了,父卷可以删了"不成立 |
| 6 | 错误用 ONTAP 的术语返回。 搜索之前把 AWS 到 ONTAP 的术语映射放在手边 |
| 7 | 测量性能的手册要把怎么绕过默认配置放在最前面。 零填充数据、内联效率、不指定 nconnect 或 AUTOMATIC IOPS,随便漏掉哪个,出来的数字测的都不是想测的东西 |
"未验证"不等于"不可能"。跟之前一样,把边界标清楚:
| 项目 | 状态 |
|---|---|
| 重平衡的实际效果(倾斜度被抹平多少) | 未测。只测了启动条件 |
启用 granular data 后的性能影响 |
未测 |
| 改了保留期的恢复队列 | 未验证。按 12 小时默认值测的 |
| 实际冷数据分层到容量池 | 未验证。需要不同的聚合配置 |
| 跑 SnapLock 合规模式 | 故意没做。不可逆,作用于整个文件系统 |
| 5 GiB 以上的副本走 NFS 或 SMB 的替代方案 | 未测。门户的处理是直接拒绝 |
第6篇聊另一面:界面上没放什么。ONTAP 在 S3 访问点路径之外还有多少可用,以及哪些运维工作交给了定时任务而不是按钮。
这篇的内容在读文档时一个都看不到。FlexGroup 创建只要让 ONTAP 选聚合就一定失败,容量重平衡用默认值永远不启动,space.used 在不同卷上含义不同,拒绝删除的克隆在 API 里是透明的。
共同点是错误文本不点明原因。所以因果关系和解法都在界面上。像重平衡那一秒间隔的 A/B 测试一样,另一个解释看起来同样合理,直到加了一个对照变量才看出问题。
还有一处修正。发过"purge 需要 diag 权限,所以等 12 小时",后来发现能跑通。没试过权限就甩锅给权限。
所有数字来自特定环境和配置,工作负载和设置不同结果也会不同。错误码在 ONTAP 9.18.1P3D1 上观察到的。