拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Label Studio Source Storage 配置指南:对象存储同步与排障实战

Label Studio Source Storage 配置指南:对象存储同步与排障实战

1. Source storage 到底是干嘛的:先搞懂同步模型,再动手点界面

做标注项目做到数据量上来之后,最烦的就是怎么把一堆文件喂给 Label Studio。小项目可以用页面批量上传,几十张图还能忍;到了几千、几万个文件,你一定会去找Add Source storage这个入口。它做的事情不是“把文件复制进平台”,而是让 Label Studio 直接去对象存储里列文件、把文件路径变成 task,等标注界面打开时再拉取真实内容。这个设计理论上很清爽,但很多人第一次用的时候会懵:明明桶里有文件,点完 Sync 怎么还是 0 个 task?

我负责过一个给自动驾驶数据做 2D/3D 联合标注的流程,每天会有新的采集包落到 S3,标注组直接在 Label Studio 里领任务。刚开始大家还在手动往项目里拖文件,后来彻底切到 Source storage,任务量从每次几十条变成上千条,标注同学不需要碰任何运维操作。本文就把我配置和排障过程中的经验完整写出来,包括字段含义、权限模型、同步触发逻辑、以及几个非常容易踩的坑。默认以 Label Studio 1.x 的开源社区版为例,后续版本字段名可能有细微差别,但逻辑一致。

1.1 一个桶能被多个项目共用,也能被 Source 和 Target 双向引用

Label Studio 里的存储设置分两种:一个是Source storage,负责“从远端存储导入数据”——简单说就是给项目补充任务;另一个是Target storage,负责“把标注结果导出到远端存储”。很多人只设置了 Source,以为标注完的东西会自动写回桶里,结果发现没有。真实生产里通常是成对配置的:Source 从raw/读原始图,Target 把标注结果写到annotated/,两个目录在同一个桶也行,但不能让 Target 和 Source 指向同一批文件,否则容易出现“标注完又被当新数据导回来”的循环。

一个桶也完全供多个项目共用。比如模型训练要区分 train 和 val,同一个桶下建images/train/和images/val/两个目录,分别在两个项目里配置不同的 prefix,各自的 Source storage 只会看到属于自己的文件。这块后面专门讲。

1.2 同步只发生在 Sync 触发,不是实时读桶

这是新手最先要纠正的认知:Label Studio 不是部署在桶上面的一层目录浏览器,它不会实时感知你在桶里新增了什么文件。你配置完 Source storage 后,需要手动点一次Sync,或者在等待后台定时扫描触达后才生成 task。默认配置下 Label Studio 会定时扫描,扫描不是实时的,存在延迟。所以如果你刚往桶里放好文件,马上打开项目页面看到空任务,不要怀疑权限问题,先手动点一下 Sync 再看。

每次同步都相当于“在某个时间点对存储做一次快照并拉取清单”,文件内容并没有被拷贝到 Label Studio 的数据库里,数据库里存的是文件 URL、路径、存储类型这些元信息。这个模型带来的好处是桶里面数据更新后,重新同步就能刷新;缺点是如果 URL 过期、路径变动,老 task 可能会变成死链。

1.3 什么时候你其实不需要 Source storage

不是所有项目都必须接 Source storage。文件量几百以内、偶尔标注一次,Web 页面上传完全够用;功能还没有线上需求,没必要为了“上云”而上云。还有一些团队的数据在数据库、共享盘里,这类也不是 Source storage 的擅长场景,Label Studio 的 Cloud Storage 只对接对象存储和部分云盘协议,不是万能的数据入口。Source storage 真正发挥价值的是:文件持续增长、需要多人协作、标注结果要回写、以及希望标注平台无状态化——哪天把实例重装了,数据仍然在桶里,平台重新连一下就能恢复。

2. 配置前哪些事情必须确认:凭证、权限、桶策略

我看过太多人卡在 Source storage 配置上,一半以上是权限问题。界面里的字段名再清楚,如果云端的 Access Key 没有权限,同步时就会静默失败或者只返回一个无信息的报错。所以先别急着填桶名,把下面的凭证逻辑理一遍。

2.1 S3 的 Access Key 权限要精确到 bucket 和 prefix

以 AWS S3 为例,Label Studio 要正常从桶里拉文件,至少需要两类权限:列出桶内容(s3:ListBucket)和读取文件(s3:GetObject)。如果你配置了 prefix,理论上权限可以收窄到指定目录,但实际很多团队图省事直接把*给了,不推荐。最小权限策略大概是下面这个样子:

{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:ListBucket"], "Resource": "arn:aws:s3:::label-bucket" }, { "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "arn:aws:s3:::label-bucket/*" } ] }

注意ListBucket的 Resource 是桶本身,GetObject的 Resource 是桶下面所有对象。如果后续要配置 Target storage 回写标注,则还要加上s3:PutObject。有的同事会顺手加s3:DeleteObject,非必要别加,避免误删。

2.2 GCS 用 service account JSON,别粘贴 API Key

GCS 的配置方式不太一样,它要求的是 service account 的 JSON 文件内容,不是让人手动填 Access Key 的。在 Google Cloud Console 里创建一个 service account,并授予存储对象的查看权限:至少需要storage.objets.list和storage.objects.get对应角色,通常用预置的roles/storage.objectViewer就够。如果你手抖创建成了 API Key,那没戏,Label Studio 不认识。这个 JSON 文件内容在你配置时直接粘贴进去,注意别把它提交到 Git 仓库里。

Service account 的管理有个隐藏点:如果一个 service account 被很多项目共用,将来回滚权限会殃及所有项目。建议给每个标注流程单独建一个 service account,或者按环境拆开,至少生产和非生产分开。

2.3 Azure Blob 的连接字符串与容器权限

Azure 的配置核心是 Storage Account 的连接字符串和容器名。拿到connection string后,在 Label Studio 的 Azure Blob Source storage 表单里填入,接口通常为了兼容 S3 限制,字段名会写成 Account Name / Account Key,但底层就是连接字符串。权限上需要至少“读取者”角色,最好用 SAS Token 而不是把整个 Storage Account 的 Key 暴露出来。SAS Token 的权限范围设置为“只读 + List”,有效期注意续期,过期后 Label Studio 的定时同步会开始报错。

2.4 公开读的桶可以省掉一半的坑

如果你所在的团队对数据安全性没那么敏感,而且文件本身不涉隐私,最简单的方式是把桶设为公开读。这样配置 Source storage 时可以直接关掉 Presign,用 Blob URL 指过去,权限问题和签名过期问题基本消失。公开读不是推荐所有场景都做,只是在你排查问题的时候可以临时用这种方式判断“问题到底出在权限还是配置”。一个小经验:我每次从 S3 换到 S3 兼容存储时,会先用公开读桶跑通配置,再切回私有桶,这样定位问题快很多。

3. 从零添加 Source storage:S3 逐个字段说清楚

权限准备好之后,进入 Project Settings -> Cloud Storage -> Add Source Storage。我先以 S3 为例把每个关键字段按我自己的理解拆开讲,因为很多字段名看着和直觉不太一致。

3.1 控制台里的字段逐个过一遍

下面是 S3 表单里最常涉及的字段:

字段含义我的建议
Display Name给这个存储源起的名字用类似source-s3-raw的格式,方便看日志
Bucket Name桶名只填桶名,不要带s3://前缀
Bucket Prefix只扫描桶里哪个前缀填目录路径,结尾建议加/
Regular Expression for filtering文件路径正则过滤用来只导入图片或特定文件类型
Region Name桶所在区域有些 S3 兼容存储这个字段可以不填
Access Key ID / Secret Access KeyS3 凭证建议使用独立子账号
Session Token临时凭证 Session只有 STS 临时凭证才需要
S3 Endpoint自定义 S3 服务地址用 MinIO / 别的对象存储时必填
Use Blob URLs任务里是否直接使用文件 URL桶公开读时强烈建议开启
Presign是否对 URL 做签名私有桶必须开
Enable Requester Pays请求放需付费一般不勾,除非你的桶开了 Requester Pays

这里最容易搞错的是Bucket Prefix。它本质上是一个“路径过滤前缀”,如果你填了images/train,Label Studio 只会导入对象 key 以这个前缀开头的文件。很多人以为填 bucket 路径就要带斜杠,其实 Label Studio 会把反斜杠、首尾空格都当成字符串的一部分,所以填错了一个空格都匹配不到。

3.2 添加之后第一次 Sync 做了什么

点完保存后,Label Studio 会立刻做一次同步。这次同步会列出符合 prefix 和正则条件的对象,然后为每个对象生成一条 task。task 数据里会带上文件 URL 和所属 storage 的编号,这个编号的作用后面排查时很关键。如果你看到 Sync 按钮转了一下,但任务数还是 0,优先看两件事:第一,日志里有没有权限相关的清一色 ListBucket 错误;第二,你的 prefix 是否真的能匹配到文件。

我自己的习惯是先不勾正则,用最小 prefix 同步一次,确认 task 能生成,再逐步收紧条件。一上来就把正则写得很复杂,出错后很难判断是权限问题还是正则有 bug。

3.3 以 GCS 和 Azure 为例的差异点

GCS 表单里多一个“Google Cloud Credentials”字段,你把 service account JSON 原文粘进去即可。其他字段和 S3 差不多,也有 Bucket Name、Prefix、Regex Filter,也有 Presign / Use Blob URLs 这对选项。Azure 表单则是 Container Name、Connection String 这类字段。总体逻辑一致,只是凭证形态不一样。如果你在多个云厂商之间切换,只要把 S3 那一套映射关系换算过去,剩下的其实只是填写位置不同。

需要提醒的是,不同版本的 Label Studio 在 UI 上会把 “S3 Endpoint” 放在一个叫 “Region” 的下拉框附近。真正用 S3 兼容存储时这个 endpoint 通常长这样:

http://minio.example.com:9000

不要只填域名而漏掉端口,也不要加上https://后又在后面拼 bucket 名。Endpoint 只负责告诉 Label Studio“去哪儿连接服务”,桶名是单独一个字段。

4. 用 Prefix + Regex 做多项目数据分流

配置 Source storage 最大的好处就是能自动化数据分发。同一个桶,通过 prefix 和正则切成不同项目的输入,标注组不用关心文件搬运,只要上游把文件放到约定目录,Label Studio 同步后自然就有任务进来。

4.1 一个桶拆成 train / val / test 三个项目

我实际用过的一个场景:同一个数据桶下有三套目录:

dataset-example/ ├── train/image/ ├── train/json/ ├── val/image/ ├── val/json/ └── test/image/

我在 train 项目里的 Source storage 配Bucket Prefix = dataset-example/train/image/,val 项目配dataset-example/val/image/,test 项目配dataset-example/test/image/。每个项目互不干扰,而且每个项目可以有自己的 Target storage,把标注结果写到各自的train-output/、val-output/目录下。这个做法比“先全量导进一个项目再拆 task”清晰得多,权限也容易收口。

4.2 用正则只导入你真正要的文件

对象存储里同一个目录可能混着图片和对应的 JSON 标注样本、缩略图、隐藏文件。这时候用 Regular Expression for filtering 很合适。举个例子,只想导入 jpg、png、webp 图片:

^dataset-example/train/image/.*\.(jpg|jpeg|png|webp)$

注意正则里的^和$要小心使用。Label Studio 匹配的是对象完整 key,如果你只写.*\.(jpg|jpeg|png)$,那同一层目录下的.jpg.json也可能被匹配进去。如果正则写不出来自己想要的结果,可以先在本地用一个极小的测试目录验证,而不是在生产目录上反复试,否则日志会被刷得很杂。

另外有个细节:正则的语法风格接近 PCRE 但又没完全支持所有特性,反向引用、环视这类复杂结构不一定可靠。实际项目里用简单的字符组、量词就够了。

4.3 定时同步任务的执行规则

配置好 Source storage 后,Label Studio 后台会按周期扫描。你每次手动点 Sync 是一次性强制同步,而定时扫描是自动的。下面几个典型场景对应的处理方式:

  • 文件已经在桶里,第一次创建 Source storage:自动同步立即执行,也可以手动按 Sync。
  • 我往里追加新文件,希望马上导入:推荐直接手动 Sync,不要等周期扫描。
  • 我改了 prefix 或正则需要全量重扫:直接把配置改好后,手动 Sync 一次。
  • 我不希望某类文件再被导入:调整正则或者把文件挪出 prefix 范围,然后别再去手动 Sync 旧目录。

这里有个很容易掉进去的坑:不要在桶里删掉几个文件就想让 Label Studio 的任务跟着消失。Source storage 的同步机制主要是按扫描到的对象新建 task,并不会因为你删除了源文件而自动清理已经存在的 task。你需要在项目里处理 task,或者把数据目录从 prefix 中移走。否则你会看到桶里已经没文件了,但 Label Studio 里那些指向旧文件的 task 还在,点开会得到 404。

5. 使用中的高频故障:表现、原因、排查链路

配置 Source storage 很少一次成功,尤其是混合云、私有对象存储场景。下面五个问题是我自己或别人在我边上踩过的,按频率排序。

5.1 图片打不开:Presign 和 Blob URL 的切换

症状:同步正常,任务也创建了,但打开标注页面时图片加载不出来,浏览器报 403 或者 CORS 错误。

多半是 Presign 和 Use Blob URLs 的组合不对。桶私有且没有开启 Blob URL 时,任务里如果是原始 URL,Label Studio 去访问就会因为没有签名而被拒;如果你开了 Presign,因为 URL 有过期时间,创建任务后的 URL 会在几小时或一天后失效,过期后同样打不开。建议按下面的组合去配:

  • 桶私有:开启 Presign,不依赖长期 URL,每次需要用的时候签名。
  • 桶公开:开启 Use Blob URLs,直接使用对象存储的公网 URL,不 Presign。
  • 对象存储签了 CDN 或自定义域名:优先使用自定义域名,避免存储服务商的默认域名被限制。

5.2 S3 兼容存储的 Endpoint 没有传给 Presign

症状:用 MinIO / Ceph 这类 S3 兼容服务时,同步和列文件都没问题,但生成的 URL 指向s3.amazonaws.com,访问 403,或者 URL 参数里带着云服务商特有的签名算法。

这个问题的根源是:Label Studio 知道你的 Access Key 和桶,但“如何生成 Presign URL”它默认还是按 AWS 的地址去算。如果你配置了自定义 S3 Endpoint,要确认 Endpoint 被正确识别。不同版本里,该项目字段可能在 Advanced 配置里,也可能需要在环境变量或 Admin 面板里设置。我遇到过一次:界面里明明填了内网 Endpoint,但 presign 结果仍指向公网域名,原因是我没在源 storage 配置里勾选 “Use Blob URLs + Presign” 的联动,而是只勾了 Presign,任务生成时没有带上 Endpoint 信息,导致 URL 拼接错乱。配置完,最好自己打开一个 task 的 data 字段看看 URL 前缀是不是你的 Endpoint。

5.3 权限报错全堆在 Info 日志里

Label Studio 同步失败时,前端有时只弹一个 toast,不会把具体失败原因写在界面上。真正的日志在软件进程里。如果是 Docker 部署:

docker logs -f <label-studio-container> 2>&1 | grep -i storage

如果是本地进程,则设置环境变量LOG_LEVEL=INFO后重启,再手动 Sync 一次。最常见的日志行长这样:AccessDenied/AccessDeniedException/The AWS Access Key Id you provided does not exist in our records。含义很简单:凭证错误或凭证权限不足。别急着在 UI 反复点,先看日志。

我个人建议在排查任何 Source storage 问题时,第一步永远是“手动 Sync + 拉日志”,而不是检查网络。因为网络不通时 UI 通常很直接地报连接超时,而凭证权限问题的表现更隐蔽,经常表面上 Sync 成功但 0 个任务。

5.4 任务重复或消失的根源

有段时间我们项目里 task 数量每隔一次同步就翻倍,排查之后发现是同一个 bucket 被配置成了两个 Source storage,而且 prefix 互相重叠。两个 storage 都扫到了同一个文件,于是每个文件生成了两个 task。如果你发现任务重复,先检查项目设置里是不是有多个 Source storage,或者同一个 storage 是否被集群里的多个定时任务触发同步。正确做法是每个数据目录对应一个独立 prefix,不要重叠。

反过来,任务“消失”大多时候不是真的消失,而是你切换了 Source storage 或改了 prefix,老 task 的数据 URL 指向的路径已经不在新同步范围内。Label Studio 本身不会自动删除 task,除非你手动批量删。排查时要先确认 task 的storage_source被赋值成了哪个存储 ID,再去看那个存储现在的配置。

5.5 文件名里的特殊字符

文件名里有空格、中文、+、%、&这类字符时,Source storage 很容易出问题。原因有两点:一是正则匹配时对特殊字符不友好;二是 Presign 生成 URL 时,文件名需要做百分号编码,一旦编码环节处理不一致,前端打开文件时就会出现 URL 和实际对象 key 不一致。

我的建议是:在数据入桶之前就统一命名规范,比如只用字母、数字、连字符和下划线,禁止空格。如果数据源不可控,那么配置正则时就明确排除特殊字符路径。另一个经验是别把 Label Studio 的 task ID 和源文件名绑定得太死,task 是平台内部主键,文件名只是外部对象标识,改造命名规范时不需要迁移数据库里的 task。

6. 用 API 批量管理 Source storage

界面操作适合单个项目,但如果你有几十个项目,每个项目要配一个 Source storage,手动点会点到手断。Label Studio 提供了完整的存储相关 REST API,我在自动化部署流程里就是这么用的。

6.1 创建和同步的接口速写

S3 类型创建 Source storage 的接口路径是:

POST /api/storages/s3

一个最精简的请求体可以长这样:

{ "project": 1, "bucket": "my-label-bucket", "prefix": "raw-images/", "use_blob_urls": true, "presign": false, "title": "s3-source-raw" }

如果你用的是 S3 兼容存储,多传一个s3_endpoint:

{ "project": 1, "bucket": "my-label-bucket", "prefix": "raw-images/", "s3_endpoint": "http://minio.example.com:9000", "access_key_id": "minio-admin", "secret_access_key": "minio-admin-secret", "use_blob_urls": true, "presign": true }

创建成功后会返回包含id的存储对象。拿到id后再触发同步:

POST /api/storages/s3/{id}/sync

GCS 和 Azure 的对应路径分别是/api/storages/gcs和/api/storages/azure-blob,请求体字段换成各自凭证即可。删除一个 Source storage 用DELETE /api/storages/s3/{id},这个操作只会切断存储与平台关联,不会删除桶里文件,放心。

6.2 配置多个项目的自动化脚本

我自己写过一个简单的 Python 脚本,从配置表里读项目编号、数据目录、凭证,批量创建 Source storage。大致长这样:

import requests BASE_URL = "https://label-studio.example.com" API_TOKEN = "your-token" headers = { "Authorization": f"Token {API_TOKEN}", "Content-Type": "application/json", } projects = [ {"project": 10, "bucket": "ml-data", "prefix": "train/image/"}, {"project": 11, "bucket": "ml-data", "prefix": "val/image/"}, {"project": 12, "bucket": "ml-data", "prefix": "test/image/"}, ] for item in projects: resp = requests.post( f"{BASE_URL}/api/storages/s3", headers=headers, json={**item, "presign": True, "use_blob_urls": True} ) print(resp.status_code, resp.json().get("id", resp.text))

批量创建后,建议脚本再自动触发一次同步。这里要注意顺序:先创建 storage,再同步。如果 sync 请求比 storage 创建还早,后端会报存储不存在,所以并发批量提交时要做依赖等待。

6.3 查询任务来自哪个存储

排查任务问题时,可以在项目任务列表里看每个 task 的storage_source字段。接口返回的任务数据中,这个字段就是来源存储的 ID。你可以通过它快速判断任务到底由哪个 Source storage 拉进来的。注意:任务数据本身也包含文件 URL,不要只看任务列表,最好直接看 task 的data对象,里面有时候还带着原始对象的 metadata 或云存储返回的last_modified信息。

7. 回到现场:我推荐的落地组合

配置不是终点,关键是把它跑成一个可持续的流程。最后分享一套我在项目里验证过的组合方式。

7.1 数据流入:原始文件 + 预测结果 + 人工审核

我喜欢把 Source storage 分成两条线:一条是原始图片,另一条是模型预测结果。预测结果通常是一个和图片同名的 JSON 或一个批次文件,Label Studio 可以通过导入 JSON 的方式把预测结果作为预标注附到 task 上。我的做法是把原始图片放在raw/,预测结果放在preannotations/,两个 Source storage 的 prefix 分别指向这些目录。人工审核时打开任务,图片是正常文件,预标注信息已经在界面上显示。要注意的是,预标注 JSON 的内容格式要符合 Label Studio 的要求,特别是任务数据里用来定位图片的键名必须和 project settings 里配置一致。

7.2 数据流出:Target storage 与 Source storage 联动

标注完成的任务导出时,我直接把 Target storage 指向另一个 prefix。比如源是raw/image/,目标就是output/annotations/。这样每次导出标注结果,后台会生成一个以任务或标注 ID 命名的 JSON 文件放入目标目录,下游训练脚本只监听这个目录就够了。Target storage 的权限配置最好和 Source storage 分开,Source 用只读凭证,Target 用带写权限的凭证,避免一个 Access Key 同时具备读写权限导致误操作。

这里提醒一下:Source storage 和 Target storage 是两条独立配置,不要指望它们自动配对。你把 Target 配置好后,需要手动触发导出或等待计划任务;Target 不是单纯地在每次标注保存后实时写回。两者同步周期和触发机制不同,设计流程时要把这点考虑进去。

7.3 最后提醒:权限是最贵的一课

我踩过最贵的一个坑,是给 Source storage 配了一个权限过大的 Key,后来因为安全审计要求强制轮换,结果忘了及时更新 Label Studio 里的配置,第二天整个团队的同步全部失败,所有人问了同一句话“为什么没任务了”。从那以后,我把所有存储凭证都放进统一的密钥管理,同时加了一个健康检查脚本,定期用最小权限的只读 Key 去调ListObjectsV2,一旦权限失效立刻告警。配置 Source storage 的过程其实不复杂,真正复杂的是把存储权限、命名规范、同步机制和团队协作流程串起来。每次你遇到“明明配置是对的但就是不同步”的问题,先怀疑权限,再怀疑 prefix,最后再怀疑正则,这个顺序能帮你少走很多弯路。

返回列表