数据湖想回滚到上周的版本?用 lakeFS 给 RustFS 桶加 Git 式版本管理
数据湖里的对象一旦被覆盖或误删,想回到上周那份干净状态,往往只能从备份里捞,还未必对得上。lakeFS 在对象存储之上加了一层 Git 式版本管理:分支、提交、回滚,对象本身仍存在底层存储里。把 RustFS 当成 lakeFS 的 S3 blockstore,等于给桶里那份数据湖直接装上版本能力,提交即版本,回滚就是切分支。
blockstore 指向 RustFS,path-style 是硬前提
lakeFS 把仓库元数据和已提交的数据文件都按存储命名空间写入 blockstore,桶里是一个repo/前缀,含_lakefs/元数据和data/对象,全部走 S3 API。先在 RustFS 建桶:
rcaliassetrustfs http://<your-rustfs-endpoint>:9000<your-access-key><your-secret-key>rc mb rustfs/lakefs-dataconfig.yaml 里把 blockstore 指到 RustFS:
database:type:locallocal:path:/lakefs/datablockstore:type:s3s3:endpoint:http://rustfs:9000region:us-east-1force_path_style:trueauth:encrypt:secret_key:change-me-to-a-random-stringgateways:s3:domain_name:rustfs-gateway.locallogging:format:textlevel:infoforce_path_style: true是必需的。缺了它,lakeFS 会按虚拟主机式寻址,把lakefs-data.<主机名>当成主机名,所有请求都因 DNS 错误失败。1.x 的配置 schema 还拒用旧的path_style键,只能写force_path_style。凭证走标准的AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY环境变量。
官方这条路径用 lakeFS 的treeverse/lakefs:1.58.0和rustfs/rustfs-x86-musl:v2.3.1验证过。
起服务、建仓库、提交对象
服务凭证放在.env里,一共六个变量。前两个是 RustFS 侧凭证,lakeFS 拿它们访问 blockstore。中间三个定义初始管理员,其中LAKEFS_INSTALLATION_ACCESS_KEY_ID/LAKEFS_INSTALLATION_SECRET_ACCESS_KEY这一对,就是后面所有 curl 里-u用的那对——首次启动时 lakeFS 按这两个值把管理员建好。最后一个关掉匿名统计上报。
AWS_ACCESS_KEY_ID=<your-access-key>AWS_SECRET_ACCESS_KEY=<your-secret-key>LAKEFS_INSTALLATION_USER_NAME=adminLAKEFS_INSTALLATION_ACCESS_KEY_ID=<your-lakefs-access-key>LAKEFS_INSTALLATION_SECRET_ACCESS_KEY=<your-lakefs-secret-key>LAKEFS_STATS_ENABLED=falselakeFS 和 RustFS 同网起服务,等/api/healthcheck返回 200,再建仓库,存储命名空间指向桶内前缀:
dockerrun-d--namelakefs--networkoo-rustfs_default\-p8000:8000\-v"$PWD/config.yaml":/etc/lakefs/config.yaml:ro\--env-file .env\treeverse/lakefs:1.58.0 runcurl-s-u<lakefs-access-key>:<lakefs-secret-key>\-XPOST http://localhost:8000/api/v1/repositories\-H"Content-Type: application/json"\-d'{"name": "rustfs-demo", "storage_namespace": "s3://lakefs-data/repo"}'往main分支传一个对象并提交:
echo"hello from lakefs on rustfs">hello.txtcurl-s-u<lakefs-access-key>:<lakefs-secret-key>\-XPOST"http://localhost:8000/api/v1/repositories/rustfs-demo/branches/main/objects?path=hello.txt"\--data-binary @hello.txtcurl-s-u<lakefs-access-key>:<lakefs-secret-key>\-XPOST"http://localhost:8000/api/v1/repositories/rustfs-demo/branches/main/commits"\-H"Content-Type: application/json"\-d'{"message": "add hello"}'curl-s-u<lakefs-access-key>:<lakefs-secret-key>\"http://localhost:8000/api/v1/repositories/rustfs-demo/refs/main/objects?path=hello.txt"在 RustFS 这边列桶,能看到 lakeFS 的元数据和数据文件都落在repo/下:
rclsrustfs/lakefs-data/-r分支和回滚,各一个动作
建一条实验分支,往分支上做的任何改动都不碰 main,验证完再合回来:
curl-s-u<lakefs-access-key>:<lakefs-secret-key>\-XPOST"http://localhost:8000/api/v1/repositories/rustfs-demo/branches"\-H"Content-Type: application/json"\-d'{"name": "etl-experiment", "source": "main"}'往这条分支传对象、提交,把 URL 里的main换成etl-experiment就行。验证没问题,合并回 main:
curl-s-u<lakefs-access-key>:<lakefs-secret-key>\-XPOST"http://localhost:8000/api/v1/repositories/rustfs-demo/refs/etl-experiment/merge/main"\-H"Content-Type: application/json"\-d'{}'回滚单独走一遍。先模拟一次写坏:覆盖 hello.txt 再提交一次,然后 revert 这次误提交,再读回验证内容:
echo"oops wrong data">hello.txtcurl-s-u<lakefs-access-key>:<lakefs-secret-key>\-XPOST"http://localhost:8000/api/v1/repositories/rustfs-demo/branches/main/objects?path=hello.txt"\--data-binary @hello.txtcurl-s-u<lakefs-access-key>:<lakefs-secret-key>\-XPOST"http://localhost:8000/api/v1/repositories/rustfs-demo/branches/main/commits"\-H"Content-Type: application/json"\-d'{"message": "bad write"}'curl-s-u<lakefs-access-key>:<lakefs-secret-key>\-XPOST"http://localhost:8000/api/v1/repositories/rustfs-demo/branches/main/revert"\-H"Content-Type: application/json"\-d'{"ref": "main", "parent_number": 1}'curl-s-u<lakefs-access-key>:<lakefs-secret-key>\"http://localhost:8000/api/v1/repositories/rustfs-demo/refs/main/objects?path=hello.txt"revert 会生成一个反向提交,把指定提交的改动撤掉——对象本身不动。
为什么是给桶加版本,而不是换存储
RustFS 仍是数据的唯一落点,lakeFS 只在上面管元数据和提交记录,对象没被搬走,回滚只是切换分支引用。对已经把数据湖放在 RustFS 的团队,这套加法不用动现有计算链路。但版本管理的范围是存储命名空间:仓库建在s3://lakefs-data/repo,只有经 lakeFS 写进这个前缀的数据才有提交和回滚。你以前直接传到桶里的存量对象不在其中,要纳入就走 import 接口:
curl-s-u<lakefs-access-key>:<lakefs-secret-key>\-XPOST"http://localhost:8000/api/v1/repositories/rustfs-demo/branches/main/import"\-H"Content-Type: application/json"\-d'{ "paths": [ {"path": "s3://lakefs-data/legacy-data/", "destination": "legacy/", "type": "common_prefix"} ], "commit": {"message": "import legacy objects"} }'paths里每项的type二选一:common_prefix导整个前缀,object导单个对象;commit是这次导入要生成的提交。import 完成后,这些对象就在版本管理内了。
边界也要说清:官方明说这套部署用于本地集成测试、不适用于生产环境,元数据库database.type: local跑在容器本地就是其中一个体现。另外 lakeFS 要求auth.encrypt.secret_key,1.x 本地库加密密钥缺了会启动失败。
上生产要补的两件事
桶和凭证分开。给 lakeFS 建专用桶lakefs-data,RustFS 的 IAM 凭证只授权这个桶,别用管理员 key。访问密钥在官方访问密钥管理页创建。
元数据库外置。database.type: local的库在容器本地,容器一删仓库就废;生产把它改成postgres、接一个独立实例。官方没点名选型,这条是我们的建议。
排障顺序
- 建仓库报
failed to access storage并带 DNS 错:查force_path_style: true有没有写对。 - 启动报
missing required keys: [auth.encrypt.secret_key]:补auth.encrypt.secret_key段。 - 容器报
mkdir /lakefs: permission denied:lakeFS 以非 root 运行,本地测试加-u 0,或挂可写目录。 - 数据到底在不在:
rc ls rustfs/lakefs-data/ -r看repo/_lakefs与repo/data。 - 删全部内容:
rc rb rustfs/lakefs-data --force,元数据和数据都在桶里。
给 RustFS 桶装上 lakeFS 之后,数据湖也有了提交和回滚,覆盖错对象不再需要从备份硬捞。流程出自 lakeFS 集成指南,lakeFS 项目源码在 treeverse/lakeFS,S3 兼容性细节看 S3 兼容性说明。文中分支、合并、回滚、import 的接口与请求体依据 lakeFS v1.58 的 API 参考,与文中所用 1.58.0 版本对应。