rustfs

Stars and mentions

Last 30 days, Oct 5 – Nov 3

GitHub stars948
mentions15
017345168Oct 5Oct 19Nov 3
UTC days · repositories rustfs owns7 repositories

Package downloads

Cumulative recorded downloads

crates.io—
01234Oct 5Oct 19Nov 3No observations in this range
Oct 5crates.io: 0/16 packages observed

Across 16 known packages in repositories rustfs owns. Cumulative totals include recorded downloads before the selected range. Missing observations leave gaps; totals may be incomplete.

Top repositories

rustfs’s repositories ranked by new forks, as on trending.

Loading

Contributes to

Repositories outside rustfs’s account where rustfs opened or reviewed pull requests in the past 365 days.

Who noticed rustfs

Everyone who starred or forked rustfs’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

16posts
Nov 41 post
  1. AI Data Storage: Building the Pipeline Behind the Model AI data storage is the layer that holds every file a model touches: raw training media, preprocessed shards, checkpoints, and logs. Most teams run it on S3-compatible object storage because the S3 API is the one interface every ML framework already speaks. | What | Value | Note | | :--- | :--- | :--- | | S3 layer license | Apache 2.0 (permissive, not AGPL) | RustFS README | | S3 API coverage | Core S3 + Object Lock, Versioning, ILM, Replication | RustFS Feature & Status | | Default ports | 9000 (API) / 9001 (console) | RustFS README | | Checkpoint size (derived) | 7B params x 2 bytes (fp16) = 14 GB | param count x dtype | | Table format option | S3 Tables (Iceberg REST) Preview; Polaris/Nessie in production | RustFS + Apache | What counts as AI data in storage terms In storage terms, AI data splits into four buckets that get treated very differently. Raw inputs are the source files: images, audio, video, PDFs, or sensor captures. Preprocessed shards are those inputs packed into training-ready chunks, usually tar for media or parquet for tabular, often already tokenized. Checkpoints are periodic snapshots of model weights plus optimizer state, written while training runs. Run logs and evaluation artifacts are the small, high-value files you regret not keeping. Each bucket has its own access pattern. Raw and prepared data is read at high concurrency during training. Checkpoints are write-heavy and bursty. Logs are tiny but you want them durable and queryable. Packing all four into one flat bucket is the most common early mistake, because it forces the storage layer to serve a streaming read workload and a bursty write workload from the same prefix. Why did object storage win the ML dataset layer Object storage won the ML dataset layer because it removed the filesystem from the conversation. A training cluster might span dozens of nodes, and a POSIX filesystem that survives that fan-out is expensive to run and easy to corrupt. S3-compatible object storage gives every worker the same URL for the same object, scales the read path horizontally, and costs a fraction of block storage per gigabyte. The kicker is the API itself: boto3, fsspec, PyTorch, and WebDataset all speak S3, so the same code that reads a cloud bucket reads a self-hosted one by changing one endpoint. That portability is why teams keep object storage even after they move off the public cloud. The trade is that object storage is not a filesystem. You do not get POSIX semantics, random in-place edits, or cheap directory renames. For training data, that is usually fine, and the next section shows why. How does a training-data pipeline actually flow A training-data pipeline moves through five stages, and most storage decisions happen before a single GPU wakes up. Collection lands raw inputs in an object-store bucket, usually with versioned prefixes so you can reproduce a dataset later. Preprocessing reads those inputs and writes packed shards back to a different prefix, often tar for images or parquet for tabular. Storage then holds both the raw and the prepared layers, which is why capacity planning matters early. Streaming is the hot path: each training worker pulls shards on demand instead of copying the whole set locally. Checkpointing writes weight snapshots on a schedule so a crashed job restarts from minutes, not days, behind. The pattern that works is to keep the raw layer immutable and let the prepared layer be rebuildable. If preprocessing is deterministic and the raw bucket is versioned, you can always regenerate shards after a schema change without re-collecting data. How do models read data during training Models read data through three layers, and the bottom one is almost always an S3 client. At the framework level, PyTorch feeds samples through a DataLoader that spins up worker processes to fetch and decode in parallel. Below that, the data is usually read with boto3 or fsspec pointed at an S3 endpoint, which is where a self-hosted store like RustFS slots in by accepting the same API. For large image or multimodal sets, WebDataset streams tar shards so each worker reads one shard at a time instead of listing a million objects. The bottleneck is rarely the model. It is the read path keeping N GPUs fed, which means parallel workers, prefetching, and shards sized so no single object becomes a hot key. A practical RustFS read looks like a standard boto3 client with the endpoint pointed at port 9000: import boto3 s3 = boto3.client( "s3", endpoint_url="http://localhost:9000", aws_access_key_id="rustfsadmin", aws_secret_access_key="rustfsadmin", ) resp = s3.list_objects_v2(Bucket="training-data", Prefix="shards/") for obj in resp.get("Contents", []): print(obj["Key"], obj["Size"]) The same client reads a cloud bucket by swapping the endpoint and credentials. Checkpoints and artifacts are the write-heavy half nobody plans for Checkpoints are where AI storage stops being a read problem and becomes a write problem. A single checkpoint holds the full model weights plus optimizer state, and at fp16 a 7-billion-parameter model is about 14 GB, while a 70-billion model is roughly 140 GB. Teams save them every few hundred steps, so during a long run the storage layer absorbs repeated multi-gigabyte writes while training reads the dataset in parallel. That bursty pattern is what breaks naive single-disk setups. Two habits make it survivable. First, write checkpoints to a bucket with versioning on, so a half-written file from a killed job does not overwrite the last good one. Second, use Object Lock (WORM) on the checkpoint bucket if you need to prove a snapshot is unaltered for audit or regulatory reasons. RustFS supports both versioning and Object Lock, and keeps the metadata for those operations inside the store itself. Where does RustFS fit as a self-hosted S3 layer RustFS fits where a team wants the S3 API without sending every training byte to a metered cloud account. It is Apache 2.0, S3-compatible, and built in Rust, with core S3 plus Object Lock, Versioning, Lifecycle, and Replication available today. Setup is one container on ports 9000 and 9001. Because the container runs as a non-root user (UID/GID 10001:10001), make the mounted data and log directories writable by that user before start: # RustFS runs as non-root UID/GID 10001:10001; make mounts writable first mkdir -p data logs chown -R 10001:10001 data logs docker run -d -p 9000:9000 -p 9001:9001 \ -v $(pwd)/data:/data \ -v $(pwd)/logs:/logs \ rustfs/rustfs:latest The README documents default credentials rustfsadmin/rustfsadmin; change them before any real deployment. One honest trade: RustFS has no FUSE or POSIX mount, so you use the S3 API, not a local filesystem. That suits pipelines built on boto3 or WebDataset and frustrates tools that demand a mount. The metadata for versioning, lifecycle, and locking lives inside RustFS. That is one fewer service to run, but it also means RustFS reliability is your durability, and backup and recovery are your responsibility, not a vendor's. The lakehouse edge: Iceberg and the catalog question If your AI work touches tabular features or training labels at scale, you will meet Apache Iceberg. Iceberg is a table format on top of object storage that tracks snapshots, schema, and partitioning, turning a bucket of parquet files into a queryable table. The catalog is the piece teams underestimate. RustFS ships S3 Tables as an Iceberg REST Catalog, but it is Preview, so I would not bet a production lakehouse on it yet. For that, run a standalone catalog such as Apache Polaris or Project Nessie in front of RustFS: both are Apache-2.0, independent of any storage engine, and mature enough that the Iceberg community builds against them daily. The choice is embed versus separate. Embedding the catalog in your storage keeps moving parts down; running it separately gives you one catalog that can track multiple buckets or storage backends. Choose separate once you have more than one store to manage. What we would tell a team starting fresh Start with object storage as the single source of truth and treat the local filesystem as a cache, not the system of record. Keep raw data immutable and versioned, pack it into shards before training, and size shards so no object becomes a hot key. Put checkpoints in their own versioned bucket, switch on Object Lock for auditability, and rehearse a restore before you trust the setup. If you self-host with RustFS, accept the S3 API as the interface and stop looking for a POSIX mount; build on boto3 or WebDataset from day one. Plan around two real gaps: S3 Tables on RustFS is Preview, so use Polaris or Nessie for a production lakehouse, and RustFS holds your metadata, so your backup discipline is the only thing between you and data loss. None of this is exotic. It is the same S3 pattern every cloud ML stack uses, pointed at infrastructure you control. FAQ What is the difference between a data lake and object storage for AI? A data lake is an organizing pattern; object storage is the substrate. You build a data lake by storing raw and prepared data in object storage with consistent prefixes, versioning, and lifecycle rules, then layering query engines or training pipelines on top. Object storage alone is just the bucket. Do I need a POSIX filesystem mount for training data? Most modern training pipelines do not. Frameworks read through boto3, fsspec, or WebDataset against an S3 endpoint, which avoids the cost and fragility of a distributed POSIX filesystem. RustFS has no FUSE or POSIX mount, so plan to use the S3 API directly. How big are model checkpoints, really? Checkpoint size is the parameter count times the bytes per parameter. At fp16, a 7-billion-parameter model is about 14 GB and a 70-billion model about 140 GB. Optimizer state can add roughly the same again, so budget for double those numbers on long runs. Can RustFS replace cloud object storage for training? For the data and checkpoint layers, yes, if you want to control cost and data residency. RustFS is Apache 2.0 and S3-compatible, so existing boto3 and WebDataset code runs against it by changing the endpoint. You take on durability and backup responsibility, since the metadata lives inside the store. What is S3 Tables and do I need it for AI? S3 Tables is RustFS's Iceberg REST Catalog for queryable tables on object storage. It is currently Preview, so for a production lakehouse use a standalone catalog like Apache Polaris or Project Nessie in front of RustFS. Most training pipelines do not need it; they read shards directly through the S3 API. A note on how this was verified Article type: this is a documentation-driven explainer, not a production deployment report. I pulled the RustFS commands, ports, license, and Feature & Status table verbatim from the project README (checked 2026-10-30); the docker run command is quoted exactly, not paraphrased, and the default credentials are the ones the README documents. The ML framework claims (PyTorch DataLoader, WebDataset, boto3) come from each project's official documentation. I did not stand up a live multi-GPU training cluster or run a throughput benchmark in this environment, and the sandbox here has no Docker daemon, so the RustFS command was not executed locally. The checkpoint size figures are derived from published parameter counts and standard dtype sizes (2 bytes per param at fp16), not measured on RustFS. Treat the architecture guidance as a starting point: validate against your own data sizes and network before committing, and keep versioned backups, because durability for a self-hosted store is a discipline you own. Sources - RustFS GitHub README (Feature & Status, Docker Quick Start, Accessing RustFS) — rustfs/rustfs (checked 2026-10-30) - RustFS documentation, installation — docs.rustfs.com - boto3 documentation — boto3.amazonaws.com - WebDataset documentation — webdataset.github.io - PyTorch DataLoader — pytorch.org - Apache Iceberg — iceberg.apache.org - Apache Polaris — polaris.apache.org - Project Nessie — projectnessie.org

  2. Spin Up a Self-Hosted S3 Bucket in 5 Minutes with RustFS Last month, Hacktoberfest 2026 had open-source contributors everywhere reaching for a place to stash build artifacts, datasets, and CI caches. The cloud bucket is the obvious answer until you remember it wants a credit card and bills you for egress. I stood up an S3-compatible bucket on my own laptop in about five minutes, no AWS account, and it speaks the exact same API my CI already used against S3. Here is the whole thing, and it works just as well in November as it did in October. TL;DR - RustFS is Apache 2.0 and S3-compatible, so your existing mc, aws, boto3, and rclone commands just work against it by changing one endpoint. - One container, ports 9000 (API) and 9001 (console), default creds rustfsadmin / rustfsadmin. - Spin it up with a single docker run, open the console, make a bucket, point your tooling at localhost:9000. - It runs as a non-root user (UID/GID 10001:10001), so bind-mounted dirs need the right owner. The compose stack fixes permissions for you if you would rather skip that step. - Want to give back instead of just using the tool? RustFS is open to contributions; the issues and discussions boards are public. Why a self-hosted S3 bucket Most open-source projects are run by people who do not want to paste cloud credentials into a fork. A self-hosted S3-compatible bucket solves three real problems at once: 1. No bill, no card. You store artifacts on hardware you already have. The bytes never leave your machine unless you decide they should. 2. Your tooling already speaks S3. If your CI uploads to S3 today, it uploads to RustFS by changing one endpoint string. No new client, no rewrite. 3. Reproducible by anyone. A teammate on a different continent can run the same three commands and get the same bucket. That matters more for an OSS project than for a closed internal one. The catch is the one we always name out loud: a self-hosted store makes you the durability and backup owner. RustFS keeps its metadata inside the store, so your discipline is the only thing between you and data loss. For a scratch bucket that is a fine trade. For production, rehearse a restore first. Prerequisites - Docker installed and running. - Enough disk for the image plus whatever you plan to store. That is it. No Kubernetes, no cluster, no config file to edit for the basic path. Step 1: Start RustFS The container runs as a non-root user (rustfs, UID/GID 10001:10001). If you bind-mount host directories, they must be writable by that user, or startup fails with a permission error. The simplest path is to create the dirs and hand them over first: mkdir -p data logs chown -R 10001:10001 data logs docker run -d -p 9000:9000 -p 9001:9001 \ -v $(pwd)/data:/data \ -v $(pwd)/logs:/logs \ rustfs/rustfs:latest That is the entire "spin it up" step. The image pull is the slow part; the container is up in seconds after that. If you would rather not think about the chown, the project ships a docker-compose-simple.yml that runs an Alpine permission-helper container before RustFS starts, then brings up rustfs-server with five named volumes (four data volumes plus a logs volume) and a health check. Same result, one fewer manual step. One caveat: that helper only chowns the compose's named volumes. If you edit the compose to use host bind mounts instead, you still have to chown those host paths yourself. Prefer not to touch Docker at all? There is also a one-line installer: curl -O https://rustfs.com/install_rustfs.sh && bash install_rustfs.sh Step 2: Open the console and make a bucket Point a browser at http://localhost:9001. Log in with the documented defaults, rustfsadmin / rustfsadmin, and create a bucket from the console UI. Call it something project-shaped, like hf2026-artifacts. Change the credentials before this touches anything real. The defaults are documented for exactly this quick-start situation, not for exposure. Set RUSTFS_ACCESS_KEY and RUSTFS_SECRET_KEY (the compose file marks them # CHANGEME) or use IAM policies once you are past the scratch phase. Step 3: Actually use it Because RustFS speaks S3, the MinIO client mc works unchanged. Point it at your local endpoint and push a file. (You already created hf2026-artifacts in the console in Step 2, so skip the bucket-creation command and go straight to the copy.) mc alias set rustfs http://localhost:9000 rustfsadmin rustfsadmin mc cp ./build.tar.gz rustfs/hf2026-artifacts/ Or from Python, with boto3, pointed at port 9000. Read the credentials from the environment so you are not pasting secrets into source: import os import boto3 s3 = boto3.client( "s3", endpoint_url="http://localhost:9000", aws_access_key_id=os.environ["RUSTFS_ACCESS_KEY"], aws_secret_access_key=os.environ["RUSTFS_SECRET_KEY"], ) s3.upload_file("build.tar.gz", "hf2026-artifacts", "build.tar.gz") If you changed the credentials in Step 2, set RUSTFS_ACCESS_KEY and RUSTFS_SECRET_KEY to those new values. If you left the defaults, they are rustfsadmin / rustfsadmin, the same ones the mc command above uses. The same client reads a cloud bucket by swapping the endpoint and credentials. That portability is the whole point: your project keeps one storage code path whether it runs on a contributor's laptop or in CI. Why RustFS specifically I am not claiming RustFS is the only option. MinIO, Garage, and SeaweedFS all have their place. What makes RustFS a clean fit for this job: - Apache 2.0, not AGPL. Unlike MinIO, which is AGPL-3.0, RustFS ships under the permissive Apache 2.0 license. That is the reason it is comfortable to drop into a forked OSS project without copyleft surprises. (SeaweedFS is also Apache 2.0, so the license is not a differentiator there. RustFS's edge is the Rust-built engine and the MinIO-shaped toolchain.) - S3-compatible, MinIO-shaped toolchain. If you have used MinIO, the commands are the same. RustFS keeps that simplicity and rebuilds the engine in Rust for memory safety and concurrency. - One container, no sidecar. Core S3 plus versioning, Object Lock (WORM), lifecycle, and replication are available today; the metadata lives inside the store, so there is one fewer service to run. - Honest gaps. No FUSE or POSIX mount. You use the S3 API, not a local filesystem. And S3 Tables (the Iceberg REST catalog) is still Preview, so I would not put a production lakehouse on it yet. Bonus: give back to RustFS (open source runs year-round) Using the tool is one thing; contributing is another. A timing note: Hacktoberfest 2026 changed its rules, pull requests no longer count toward the event's rewards, with the focus moving to in-person Fests and online challenges. There is no event credit to chase here regardless (the repo carries no hacktoberfest topic). That is fine: the repo is Apache 2.0 and open to contributions 365 days a year. The issues and discussions boards are public: - Issues: github.com/rustfs/rustfs/issues - Discussions: github.com/rustfs/rustfs/discussions If you are looking for a storage project to contribute to, filter the issues tab for good first issue or help wanted, or weigh in on an open discussion. A documentation fix or a reproducible bug report is as valuable as a feature PR, just do not expect swag for it. FAQ Do I need an AWS account to follow this? No. RustFS runs locally and emulates the S3 API. The aws CLI and boto3 work against it by pointing at localhost:9000 instead of s3.amazonaws.com. Is rustfsadmin / rustfsadmin safe to leave? No, not past a scratch run. It is the documented default for quick starts. Set your own keys via environment variables or IAM before anything sensitive touches the bucket. Can I use this as my CI artifact store? For a weekend or small-scale project, yes. Point your upload step at localhost:9000 (or the host's LAN IP) and the same S3 calls you would use for cloud storage apply. For anything long-lived, add versioning and a real backup target. What if I need a filesystem mount, not an API? RustFS has no FUSE or POSIX mount, by design. If your tooling demands a mount, this is not the right fit. Use the S3 API path or pick a store that offers one. A note on how this was verified I pulled the commands, ports, default credentials, license, and Feature & Status table verbatim from the RustFS README (checked 2026-11-01). The docker run line is quoted exactly, not paraphrased. The container's non-root UID/GID (10001:10001) comes from the README, which states the server runs as a non-root user rustfs; the compose permission-helper behavior comes from the project's docker-compose-simple.yml. This is a quick-start walkthrough, not a benchmark or a production deployment report. I did not run a throughput test or stand up a multi-node cluster, and the sandbox here has no Docker daemon, so the commands were not executed locally. Validate them on your own machine before relying on them. Sources - RustFS GitHub README (Quickstart, Accessing RustFS, Feature & Status, License) : rustfs/rustfs (checked 2026-11-01) - RustFS docker-compose-simple.yml : github.com/rustfs/rustfs/blob/main/docker-compose… - RustFS documentation, installation : docs.rustfs.com - MinIO client (mc) documentation : min.io - boto3 documentation : boto3.amazonaws.com - AWS CLI documentation : docs.aws.amazon.com - rclone S3 documentation : rclone.org - Hacktoberfest 2026 rules (pull requests no longer count toward rewards) : hacktoberfest.com --- What are you stashing your project's artifacts in: a cloud bucket, or something you control? If you stood up a local S3 bucket, what tripped you up?

  3. The RustFS launcher is poised to be rebuilt upon this foundation #rustfs rustfs/launcher

    28 views
  4. MinIO の代わりに RustFS でローカルに S3 互換ストレージを立て、バケットごとに権限を分けた はじめに Docker で動かしているセルフホストの GitLab について、前回はバックアップの取得・自動化・切り戻し・リストアの検証を練習しました。 zenn.dev 前回の時点では、バックアップが GitLab のデータと同じディスク・同じディレクトリに置かれているという弱点が残っていました。GitLab には、バックアップを取ると同時に S3 に送る機能(backup_upload_connection)があります。そこで次のステップとして、S3 互換のオブジェクトストレージをローカルに用意することにしました。 この記事では、次のことをまとめます。 MinIO の現状と、代わりに RustFS を選んだ理由 RustFS を Docker Compose で起動する AWS CLI から操作する(endpoint_url と path-style) 用途ごとにユーザーとポリシーを分け、自分のバケットにしか書き込めないことを確かめる 本物の AWS S3 との挙動の違い 最終的に、次の構成になりました。 バケット 用途 書き込めるユーザー gitlab-backups GitLab のバックアップの送り先 gitlab-backup だけ drive-stats 機械学習のデータ置き場 drive-stats だけ 環境 項目 内容 OS Windows + WSL2(Ubuntu 24.04、systemd 有効) Docker Docker Engine(WSL2 の中) RustFS rustfs/rustfs:1.0.3 AWS CLI 2.38.5 GitLab gitlab/gitlab-ce:18.8.11-ce.0(Docker Compose。gitlab-net という Docker のネットワークに参加) MinIO を選ばなかった理由 ローカルの S3 互換ストレージといえば MinIO が定番で、当初はこれを使う予定でした。しかし調べたところ、コミュニティ版の開発は終わっていました。 時期 出来事 2025 年 10 月 コミュニティ版の Docker イメージとバイナリの配布を終了。以降はソースコードのみ 2025 年 12 月 メンテナンスモードに 2026 年 4 月 GitHub のリポジトリがアーカイブ(読み取り専用)に 時期は、次のような複数の記事を参考にしました。 MinIO Is Done With Open Source, What Are Your Options? MinIO Just Got Archived. Here's What Self-Hosters Are Running Instead 既存のイメージを使い続けることはできますが、新しい機能もセキュリティ修正も来ません。これから使い始めるのは避けました。 代わりの候補を比べる 比較記事でよく挙がっていた 3 つを比べました。 Garage SeaweedFS RustFS 言語 / ライセンス Rust / AGPLv3 Go / Apache 2.0 Rust / Apache 2.0 成熟度 安定して開発が続いている 歴史が長く、実績が多い 2026 年に 1.0。新しい 構成 単一のバイナリ 部品(master / volume / filer)が分かれている 単一のバイナリ キーごとのバケット権限 独自の「キー × バケット」の仕組み 設定ファイルでキーごとに許可 AWS と同じ書き方の IAM ポリシー(JSON) バケットポリシー・ACL なし 一部 あり Garage の S3 互換性は、公式の互換表で確認しました。SeaweedFS と RustFS の権限の仕組みは、比較記事と公式ドキュメントによるものです。 RustFS を選んだ理由 一番の理由は、権限を AWS の IAM と同じ書き方で設定できることです。この環境は、仕事で AWS を扱うための練習台も兼ねているので、ポリシーの JSON を書く練習ができるのは大きな利点でした。 懸念は、1.0 になったばかりで実績が少ないことです。練習環境なので許容しました。 ! GitLab の backup_upload_connection から RustFS(や Garage、SeaweedFS)を使ったという報告は、事前には見つけられませんでした。GitLab から送れるかは、次のステップで確かめます。 RustFS を起動する 公式ドキュメントで確認したこと 項目 内容 イメージ rustfs/rustfs。執筆時点の最新の安定版は 1.0.3(2026-10-17) ポート 9000 = S3 API、9001 = 管理画面 管理者の認証情報 環境変数 RUSTFS_ACCESS_KEY と RUSTFS_SECRET_KEY。既定値の rustfsadmin は使わないよう注意書きがある 管理画面 RUSTFS_CONSOLE_ENABLE=true で有効になる データの置き場所 コンテナの中の /data 動くユーザー root ではなく rustfs(UID 10001) 出典: RustFS の README、Installing RustFS with Docker、Container ディレクトリの構成 GitLab や Runner と同じリポジトリの中に、storage/ を作りました。 <repo>/ ├── gitlab/ # GitLab の compose.yaml など ├── runner/ # GitLab Runner └── storage/ ├── compose.yaml # コミットする ├── .env # イメージのタグ(コミットする) ├── .env.local # 管理者の認証情報(Git 管理外) ├── policies/ # IAM ポリシーの JSON(コミットする) └── volumes/data/ # バケットの中身(Git 管理外) .gitignore には次の 2 行が入っています。 .gitignore .env.local storage/volumes/ 管理者の認証情報を作る 管理者のキーは、Git の管理外のファイルに置きます。 cd storage umask 077 cat > .env.local <<EOF RUSTFS_ACCESS_KEY=storage-admin RUSTFS_SECRET_KEY=$(openssl rand -base64 30 | tr -d '/+=') EOF ls -l .env.local # -rw------- になっていること umask 077 で、作るファイルの権限を 600 にしています tr -d '/+=' は、あとで URL や設定ファイルに貼るときに困らないよう、記号を消すためです Git から除外されていることは、git check-ignore で確認できます。 git check-ignore -v storage/.env.local .gitignore:12:.env.local storage/.env.local データのディレクトリの所有者を UID 10001 にする ! RustFS はコンテナの中で UID 10001 として動きます。mkdir しただけのディレクトリ(一般ユーザーの所有)をバインドマウントすると書き込めず、permission denied で起動に失敗します。 mkdir -p volumes/data sudo chown -R 10001:10001 volumes/data ls -ln volumes # 10001 10001 になっていること ls -n は、所有者を名前ではなく番号で表示するオプションです。 compose.yaml storage/compose.yaml services: rustfs: image: rustfs/rustfs:${RUSTFS_IMAGE_TAG} container_name: rustfs restart: unless-stopped env_file: - .env.local environment: RUSTFS_ADDRESS: ":9000" RUSTFS_CONSOLE_ENABLE: "true" RUSTFS_CONSOLE_ADDRESS: ":9001" command: /data ports: - "9000:9000" - "9001:9001" volumes: - ./volumes/data:/data networks: - gitlab-net networks: gitlab-net: external: true storage/.env RUSTFS_IMAGE_TAG=1.0.3 ポイントは次のとおりです。 設定 理由 イメージのタグを固定 公式の例は latest だが、勝手にバージョンが上がらないようにする env_file: .env.local 認証情報を compose ファイルに直接書かない。compose ファイルはコミットしても安全 networks: gitlab-net GitLab や CI のジョブのコンテナから、rustfs で届くようにする .env と env_file は、名前が似ていますが役割が違います。 ファイル 誰が読むか 用途 .env compose 自身 compose.yaml の ${...} を置き換える env_file: に書いたファイル コンテナ アプリの環境変数になる 起動して確認する docker compose config # 起動前に、最終的な設定を確認する docker compose up -d curl -s -o /dev/null -w '%{http_code}\n' localhost 200 ! docker compose config の出力には、env_file で渡したシークレットキーがそのまま表示されます。画面を共有したり、結果を貼り付けたりするときは注意してください。 ブラウザで localhost を開くと、管理画面にログインできます。 AWS CLI から操作する インストール AWS の公式のインストーラー(v2)を使いました。AWS のアカウントは不要です。送り先は RustFS で、AWS には一切つながりません。 curl -sSLo awscliv2.zip "awscli.amazonaws.com" unzip -q awscliv2.zip sudo ./aws/install aws --version aws-cli/2.38.5 Python/3.14.8 Linux/... exe/x86_64.ubuntu.24 ! AWS CLI 2.38.5 では、初回に次のように聞かれました。 Configure AWS skills and the AWS MCP server for your AI coding agent(s)? [y/n/never]: AI のコーディングエージェントに AWS を操作させるための設定のようです。今回は AWS 本体を使わないので、n と答えました。 プロファイルを用途ごとに分ける 認証情報のセットを「プロファイル」という名前で使い分けます。 プロファイル キー 使う場面 storage-admin 管理者 バケットやユーザーを作るときだけ gitlab-backup gitlab-backup ユーザー GitLab のバックアップ drive-stats drive-stats ユーザー 機械学習のデータ aws configure --profile storage-admin AWS Access Key ID: storage-admin AWS Secret Access Key: (.env.local の値) Default region name: us-east-1 Default output format: json aws configure set endpoint_url localhost --profile storage-admin aws configure set s3.addressing_style path --profile storage-admin 設定 意味 region RustFS はリージョンを使い分けないが、AWS CLI は region がないと動かないので us-east-1 にする endpoint_url 送り先を AWS ではなく RustFS にする。毎回 --endpoint-url を付けなくてよくなる s3.addressing_style path バケット名を URL のパスに入れる(localhost<バケット>/...)。既定のホスト名方式(http://<バケット>.localhost:9000/...)は、ローカルでは名前解決できない 設定は ~/.aws/config(秘密ではない)と ~/.aws/credentials(秘密、権限 600)に保存されます。 バケットを作る aws s3 mb s3://gitlab-backups --profile storage-admin aws s3 mb s3://drive-stats --profile storage-admin aws s3 ls --profile storage-admin 2026-10-24 11:10:08 drive-stats 2026-10-24 11:09:56 gitlab-backups 用途ごとにユーザーとポリシーを分ける ここがこの記事の本題です。「このキーは、このバケットにしか触れない」状態を作ります。 ユーザー gitlab-backup ──(ポリシー gitlab-backups-rw)──▶ gitlab-backups だけ読み書きできる ユーザー drive-stats ──(ポリシー drive-stats-rw) ──▶ drive-stats だけ読み書きできる GitLab のキーが漏れても、被害を gitlab-backups だけに抑えられます。管理者のキーをアプリに渡さないのが目的です。 ポリシーを書く storage/policies/gitlab-backups-rw.json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:ListBucket", "s3:GetBucketLocation", "s3:ListBucketMultipartUploads" ], "Resource": ["arn:aws:s3:::gitlab-backups"] }, { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:AbortMultipartUpload", "s3:ListMultipartUploadParts" ], "Resource": ["arn:aws:s3:::gitlab-backups/*"] } ] } drive-stats-rw.json は、gitlab-backups を 2 か所とも drive-stats に置き換えたものです。 Statement を 2 つに分ける理由 S3 のポリシーで一番つまずきやすいのが、Resource の書き方が 2 種類あることです。 Resource 対象 付ける操作 arn:aws:s3:::gitlab-backups バケット自体 中身の一覧(ListBucket)など arn:aws:s3:::gitlab-backups/* バケットの中のファイル 読む・書く・消す(GetObject、PutObject、DeleteObject) ListBucket を /* の方に書くと一覧が見られず、PutObject をバケット自体の方に書くと書き込めません。 そのほかの点です。 "Version": "2012-10-17" は、ポリシーの書き方のバージョンです。RustFS は、この値しか受け付けません(RustFS: Users, Groups, and Policies) MultipartUpload 系は、大きなファイルを分割してアップロードするときに使います。GitLab のバックアップが大きくなったときのために入れています s3:ListAllMyBuckets(バケットの一覧を見る)は、あえて入れていません 登録する前に、JSON の書き方の誤りを確認できます。 python3 -m json.tool storage/policies/gitlab-backups-rw.json > /dev/null && echo OK ポリシーとユーザーを登録する 管理画面(http://localhost:9001)で、次の順に登録しました。 ポリシー gitlab-backups-rw と drive-stats-rw を作り、JSON を貼り付ける ユーザー gitlab-backup と drive-stats を作り、それぞれのポリシーを割り当てる RustFS では、ユーザー名がそのままアクセスキーになります。ユーザー名は空白を含められず、管理者のアクセスキーと同じ名前にはできません(公式ドキュメント)。シークレットキーは、ランダムな値を作って入れました。 openssl rand -base64 30 | tr -d '/+=' 管理画面のほかに、管理用の REST API(/rustfs/admin/v3/...)でも登録できるようです(同じドキュメントより)。 最後に、ユーザーごとのプロファイルを作ります。 aws configure --profile gitlab-backup # アクセスキーはユーザー名 aws configure set endpoint_url localhost --profile gitlab-backup aws configure set s3.addressing_style path --profile gitlab-backup drive-stats も同様 ! シークレットキーは、あとから画面で表示し直せない前提で扱いました(AWS 本体でも、作ったときにしか表示されません)。今回は ~/.aws/credentials が控えを兼ねていて、次のコマンドで取り出せます。 aws configure get aws_secret_access_key --profile gitlab-backup 権限を検証する できることと、できないことの両方を確かめます。 echo "permission test" > /tmp/perm.txt aws s3 cp /tmp/perm.txt s3://gitlab-backups/perm.txt --profile gitlab-backup # ① 自分のバケットに書く aws s3 ls s3://gitlab-backups/ --profile gitlab-backup # ② 自分のバケットの一覧 aws s3 cp /tmp/perm.txt s3://drive-stats/perm.txt --profile gitlab-backup # ③ 他人のバケットに書く aws s3 ls s3://drive-stats/ --profile gitlab-backup # ④ 他人のバケットの一覧 aws s3 ls --profile gitlab-backup # ⑤ バケットの一覧 結果です(drive-stats のキーでも、逆向きに同じ結果になりました)。 # 操作 結果 ① 自分のバケットに書く 成功 ② 自分のバケットの一覧を見る 成功 ③ 他人のバケットに書く AccessDenied ④ 他人のバケットの一覧を見る AccessDenied ⑤ バケットの一覧 成功。ただし自分のバケットだけが見える ③と④が AccessDenied になったので、それぞれのキーは自分のバケットにしか書き込めないことを確認できました。 ⑤は予想と違った s3:ListAllMyBuckets を与えていないので、⑤は AccessDenied になると予想していました。実際には成功し、次のようにキーごとに見えるバケットが絞り込まれていました。 $ aws s3 ls --profile storage-admin 2026-10-24 11:10:08 drive-stats 2026-10-24 11:09:56 gitlab-backups $ aws s3 ls --profile gitlab-backup 2026-10-24 11:09:56 gitlab-backups $ aws s3 ls --profile drive-stats 2026-10-24 11:10:08 drive-stats ほかのバケットは名前すら見えていないので、権限の分離という目的は果たせています。 ! 本物の AWS S3 とは動きが違う可能性があります。 本物の AWS S3 RustFS(実測) s3:ListAllMyBuckets がないときの aws s3 ls AccessDenied になる(と理解しています) エラーにならず、権限のあるバケットだけを返す ローカルでは aws s3 ls が動いても、AWS では AccessDenied になることがありえます。AWS でバケットの一覧を見せたいときは、s3:ListAllMyBuckets を "Resource": "*" で許可します。 RustFS のこの動きは、MinIO に合わせたものだと思われます(RustFS のドキュメントでは確認できていません)。 最後に、テスト用のファイルを自分のキーで消して、DeleteObject も確認しました。 aws s3 rm s3://gitlab-backups/perm.txt --profile gitlab-backup aws s3 rm s3://drive-stats/perm.txt --profile drive-stats Docker のネットワークの中から届くか確かめる GitLab のコンテナや CI のジョブのコンテナから RustFS を使うので、gitlab-net の中から届くかを確かめました。 GitLab のコンテナから docker exec gitlab curl -s -o /dev/null -w '%{http_code}\n' rustfs 200 CI のジョブと同じ条件(gitlab-net に参加した使い捨てのコンテナ)から docker run --rm --network gitlab-net curlimages/curl:8.10.1 \ -s -o /dev/null -w '%{http_code}\n' rustfs 200 この環境の GitLab Runner は、ジョブのコンテナを network_mode = "gitlab-net" で動かす設定にしています。そのため、2 つ目のコマンドがジョブのコンテナと同じ条件になります。 ! コンテナの中では localhost ではなく、コンテナ名(rustfs)で指定します。コンテナの中の localhost は、そのコンテナ自身を指すためです。同じ Docker のネットワークに参加していれば、コンテナ名で相手に届きます。 ハマりどころのまとめ 症状・注意点 原因・対処 起動時に permission denied データのディレクトリの所有者を 10001:10001 にする 管理者のキーを既定値のまま使ってしまう rustfsadmin は使わず、ランダムな値にする docker compose config にシークレットキーが表示される env_file の中身も展開されるため。共有するときは伏せる バケット名入りのホスト名が名前解決できない s3.addressing_style path にする AWS CLI が region を求める us-east-1 にしておく ポリシーを書いたのに一覧が見られない・書き込めない Resource の 2 種類(バケット と バケット/*)と、操作の組み合わせを確認する ListAllMyBuckets なしでも aws s3 ls が成功する RustFS は権限のあるバケットだけを返す。AWS とは動きが違う可能性がある おわりに MinIO の開発が終わったことで、ローカルの S3 互換ストレージの選び方は変わりつつあるようです。RustFS は、AWS と同じ書き方の IAM ポリシーで権限を分けられるので、AWS の練習台としてちょうどよいと感じました。一方で、⑤のように本物の AWS と挙動が違う点もあるので、「ローカルで動いたから AWS でも動く」とは限らない点に注意が必要です。 次は、GitLab の backup_upload_connection で、バックアップをこの gitlab-backups バケットに送ってみます。RustFS で動くかどうかは、そこで確かめます。

  5. @majinghe1 @zouwuaoe @RustFSofficial Please help me getting this merged as fast as possible :) It helps to allow RustFS behind proxy with subpath. I have to deploy multiple tenants per cluster and can’t create individual FQDN for each deployment. s3s#1006 rustfs#8712

    7 views
  6. Apache Iceberg & Lakehouse: RustFS S3 Tables in Practice A lakehouse is not a product; it is a three-layer split — a table format (Apache Iceberg), a catalog that knows which tables exist, and object storage that holds the bytes. RustFS's S3 Tables feature contributes the middle layer directly: every bucket can expose a built-in Apache Iceberg REST catalog, on the same server that stores the data. This article walks the stack layer by layer, with results from a hands-on session on RustFS 1.0.1, including the part that failed, which is the part worth reading. | Fact | Value | | --- | --- | | Table format in RustFS S3 Tables | Apache Iceberg, format-version: 2 (first-hand: create-table response) | | Catalog | Built-in Iceberg REST catalog, enabled per bucket (/iceberg/v1/<bucket>) | | Metadata and data location | Same bucket, reserved prefix .rustfs-table/ (first-hand: metadata-location) | | Minimum RustFS version | 1.0.0 (GA 2026-09-16) | | License | Apache 2.0 | | PyIceberg 0.12.0 against it (first-hand) | Read path (list/load) works; append commit rejected (missing x-amz-content-sha256 header) | What a lakehouse actually is A lakehouse is an architecture, and the fastest way to understand it is to name the three layers it separates. The table format — Apache Iceberg in this article — defines how a table's rows, schema, and snapshots are represented as ordinary files. The catalog is the entry point that answers one question reliably: where is the current metadata for table X. Object storage holds both the data files and the metadata files, durably and cheaply. None of the three layers needs to know about the others' internals, and that is the point. A query engine speaks to the catalog, the catalog points at metadata, the metadata points at data files on object storage. Swap any one layer and the others keep working, which is exactly what has made this split the default design for analytics on top of object storage, and why an S3-compatible store with a built-in catalog changes the deployment math. One implementation detail is worth knowing before the architecture diagram hardens in your head: in RustFS's S3 Tables the catalog has no separate database behind it. The default object catalog backing persists catalog state in the same object store, under the reserved .rustfs-table/ prefix. The layers are conceptually separate and physically colocated — lose the bucket and the catalog state goes with it, which moves backup planning from optional to load-bearing. Why the table format became the battleground The table format is the layer where lakehouse projects compete, and Apache Iceberg is an open table format hosted by the Apache Software Foundation. Its design goal is treating a directory of Parquet files as a full database table: schema evolution, hidden partitioning, time travel via snapshots, and atomic swaps of metadata pointers so readers never see half-written state. Version 2 of the spec added row-level deletes, which is what let merge-on-read engines adopt it for upsert workloads. RustFS creates Iceberg tables with format-version: 2, confirmed in the create-table response during the hands-on run below. The version 3 specification exists in the Iceberg project, and RustFS's S3 Tables documentation states v3 is not yet supported. That is a plain fact of current scope, the kind worth knowing before you design a partition spec against features that are not there. Where object storage fits in the stack Object storage is the layer the lakehouse leans on hardest and trusts the least visibly. Every Iceberg table read touches the catalog, then one metadata file, then the manifest list, the manifest files it names, and finally the data files — a fan-out of small reads against the store. The Iceberg spec asks little of the file system: in-place writes, seekable reads, deletes. Consistency leans on the catalog's atomic metadata swap, not on the store. Engines nonetheless perform many small reads and listings per query, so storage that serves metadata-sized objects quickly is what keeps the architecture responsive. The storage layer also owns the failure modes. A lakehouse on object storage is only as durable as the store underneath it, which is precisely why the choice of store is an infrastructure decision, not a checkbox. Running it on hardware you administer means the durability story is your replication and backup plan, and no table format changes that arithmetic. What RustFS S3 Tables provides RustFS ships an Iceberg REST catalog as a built-in feature, enabled per bucket, on an Apache 2.0 licensed server (requires RustFS 1.0.0+). In a hands-on session on 2026-10-11 against RustFS 1.0.1 (single node, Windows x86_64), the full control-plane chain ran clean: enabling the catalog on a bucket returned enabled: true with catalog-uri: /iceberg/v1/my-bucket; creating a namespace and then a table both returned 200; and the table's metadata-location pointed into the same bucket under the reserved prefix .rustfs-table/, meaning metadata and data live together, with per-table UUID prefixes for isolation. Two facts from the same session matter for architecture planning. First, the table response reported rustfs.credential-vending: unsupported. Vending is disabled by default (the documentation describes an opt-in path negotiated via X-Iceberg-Access-Delegation), so this session's clients authenticated with long-lived keys and SigV4. The consequence is operational: every engine client holds a full set of S3 credentials, and rotation, scoping, and auditing of those keys are your key-management workload rather than something the catalog hands out per session. The documentation states the preconditions (a compatible client that negotiates the header, a caller permitted to request table credentials) and stops there; its own PyIceberg walkthrough still authenticates with explicitly configured credentials, and the 1.0.1 build behind the hands-on session reported the field as unsupported outright. If vended credentials are a deciding factor for your deployment, verify the full credential path on your build before committing to it. Second, namespaces are organizational, not security boundaries — a tenant cannot be fenced with a namespace alone. Isolation has to come from the storage layer: bucket policies, prefix-scoped IAM, and the table-scoped admin actions the documentation defines. Design multi-team deployments accordingly. Which engines connect — and the honest status The catalog speaks the Iceberg REST protocol, so any Iceberg client should be able to point at it. What was actually verified, on the record: PyIceberg 0.12.0 with a SigV4 adapter completed the read path (namespace listing and table load) against the RustFS catalog. The append path failed: the commit was rejected with a missing x-amz-content-sha256 header, because botocore's generic signer only sets that header for empty bodies. That failure is recorded here as-is; if you build on PyIceberg, pin your versions and run one append before you commit to the stack. The project's own support matrix tracks engine status: PyIceberg and DuckDB marked automated, Spark and Trino covered by a live test harness. Third-party signal cuts both ways. An issue against the catalog documented Spark hitting a SigV4 double-encoding 403 (Iceberg's signer double-encodes the URI path); it was filed in September 2026 and closed on October 5. Deploying a Spark-Iceberg release that contains the fix matters — older releases still exhibit the 403, so pin the version deliberately and verify it, rather than assuming a recent install includes it. Engines connect; the edges are exactly where you would expect them, and testing your engine against your storage version is not optional. One protocol-level note keeps expectations straight. The Iceberg REST catalog spec leaves authentication to implementation choice — the reference flow uses OAuth2, while this catalog authenticates requests with SigV4 signing, which is exactly why the PyIceberg session above needed a signing adapter. Authorization is not carried by the REST protocol here either: namespace and table operations map to RustFS admin actions enforced at the storage layer, and the documentation states plainly that full AWS S3 Tables control-plane compatibility is not claimed. Treat the REST interface as the common dialect between engines and catalogs, not a promise of identical semantics everywhere. What this stack does not do The honest list is short but load-bearing. RustFS S3 Tables includes no SQL engine. There is no embedded query processor; you bring DuckDB, Spark, or Trino, and they speak to the catalog. There are no multi-table transactions: each table commits independently, so cross-table invariants are your application's problem. Table creation is documented as direct create with v2 by default; staged table creation is listed as unsupported. Dropping a table removes the catalog entry but keeps the underlying objects in place, so cleanup of unreferenced files is a maintenance step you run separately. None of these are hidden defects; they are the documented scope of the feature, and every lakehouse stack makes similar trade-offs somewhere. The difference between a stack you can defend and one that pages you at 2 a.m. is whether the limitations were priced in before the first table was created. The ops reality of metadata-heavy tables Iceberg tables accumulate metadata by design: every commit writes a new metadata file, manifests multiply with partition counts, and snapshots pile up until something expires them. The maintenance verbs are part of the format (expire_snapshots to bound history, remove_orphan_files to collect abandoned writes), and both carry footguns documented in the Iceberg project itself: expiring snapshots destroys the time-travel window you may be relying on for recovery, and orphan-file removal with an aggressive older_than window can race with slow in-flight commits and delete live data. On self-hosted storage, add the capacity angle: metadata from many small commits is a real storage cost, and it lands on the disks you provision. Budget for the metadata prefix the way you budget for the data, and put snapshot expiry on a schedule from day one, not after the first "why is this bucket 3x my dataset" investigation. The cost is not only capacity. Commit-heavy workloads produce torrents of small reads and writes against the storage layer's metadata path, and the single-node Windows session behind this article exercises that path gently — a clustered deployment with concurrent writers is a different load profile, which is one more reason the verification note below lists multi-node and concurrent-commit testing as not performed. Where the ceiling sits is a fair question with no published answer: no RustFS small-object benchmark at metadata scale exists to cite, and the honest range depends on drives, erasure-set width, network, and client mix. The practical move is to run a small-object mixed-workload benchmark against the exact cluster hardware before a lakehouse lands on it, and treat the measured number, not a datasheet, as the design input. Backup deserves its own line in the runbook. Because catalog state and table metadata are ordinary objects under .rustfs-table/, a bucket-level backup covers the catalog automatically; a plan that copies only data files does not. It also pays to separate what the storage layer already gives you from what a backup adds. A distributed RustFS cluster stores every object as Reed-Solomon erasure-coded shards across an erasure set, with per-shard checksums and healing, which protects against disk, node, and bitrot loss inside the cluster. It does not protect against a DELETE that should not have happened, a pipeline writing garbage, or corruption applied consistently across shards; those are logical losses, and an independent copy is the only answer. Copy strategies differ in recovery point and blast radius: a scheduled snapshot copy bounds RPO at the schedule interval and restores by re-upload, while a continuously synced second bucket bounds RPO near zero at the cost of propagating mistakes just as fast, deletes included, unless the target keeps versions. The documentation reviewed for this article does not cover a cross-site replication feature, so treat that leg as something to verify on your build or to assemble with an S3-sync tool against the API. Two more facts from the documentation are worth pricing before you need them under pressure. Recovery has an escape hatch: Iceberg clients can register an existing metadata location as a table, so a lost catalog entry is recoverable when the metadata files survive. And there is an explicit upgrade red line: after the durable-strong snapshot format v2 is persisted, rolling back to a binary that only reads v1 fails closed — check the cutover runbook before upgrading any deployment that carries data you cannot lose. The two catalog backings are a deliberate choice, not a default to forget. The default object backing keeps catalog state in the bucket, and the documentation describes its concurrency model in one line: a commit validates its base metadata and referenced objects before conditionally updating the metadata pointer, a conflicting writer must reload the table and resolve the conflict, and the transaction boundary is one table. The durable-strong mode is the alternative, and the documentation prices its adoption rather than advertising it — a cutover runbook with migration preflight, coordinated writer fencing, and the rollback red line above. No threshold for making the switch is published, so the honest sequence is to run the default backing, watch how conflict retries behave under your real write concurrency, and pay the cutover cost only when the reload-and-resolve path is measurably in the way. Monitoring has a documentation anchor too. The counters that matter here are derivable from documented behavior: commit conflicts (the reload-and-resolve path), snapshot count per table, and object count plus byte size under the .rustfs-table/ prefix. One more fact raises the stakes: metadata deletion and background maintenance are disabled by default, and RustFS does not run a built-in periodic maintenance scheduler — expiry jobs you schedule yourself only happen if something is watching them happen. When a lakehouse on self-hosted object storage makes sense The stack earns its keep in three situations. First: your data governance or cost profile already points at self-hosted storage, and adding a table format plus catalog turns that store into an analytics substrate instead of a backup target. Second: you want Iceberg's engine portability without a hyperscaler's managed-table billing; the same table reads from DuckDB locally and Spark on a cluster. Third: your tables are already large enough that the metadata fan-out matters, which is where a storage layer you can size and monitor yourself pays off. The built-in catalog is also not the only catalog. The documentation ships a second, explicit pattern: RustFS as the object storage under a separate Iceberg REST catalog service (the external-catalog integration, demonstrated with Spark and a standalone REST container). The built-in catalog is scoped per bucket — its warehouse is a single bucket name — so if one catalog needs to span several buckets or stores, the external pattern is the documented route. The trade is one more stateful service that you run, upgrade, and back up yourself; which pattern fits depends mainly on whether your catalog needs to outlive one bucket. If none of those hold (small tables, one engine, no storage constraints), a plain bucket with files works, and the lakehouse layer is complexity without a customer. The format is not the product; the workload is. Frequently Asked Questions What is an Apache Iceberg lakehouse? An analytics architecture that separates three layers: the Apache Iceberg table format (how tables are represented as files), a catalog (where table metadata lives), and object storage (where data and metadata are durably kept). The separation lets engines like DuckDB, Spark, and Trino read the same tables without owning the storage. Do I need a separate catalog server for Iceberg on RustFS? No. RustFS S3 Tables enables a built-in Iceberg REST catalog per bucket — the hands-on run enabled it on an existing bucket with a single request, and the catalog URI was served from the same server at /iceberg/v1/<bucket>. Metadata files land in the same bucket under the .rustfs-table/ prefix. Can PyIceberg write to RustFS S3 Tables? Reads were verified working in a first-hand session (PyIceberg 0.12.0, SigV4). Appends were rejected with a missing x-amz-content-sha256 header error, a signing quirk between botocore's generic signer and the catalog endpoint. Test one append with your pinned versions before building on it. Does RustFS include a SQL query engine? No. RustFS S3 Tables provides the table format layer and the catalog; query engines are external — DuckDB, Spark, Trino, or any Iceberg REST client. The project's support matrix tracks which engines are exercised against the catalog. How is this different from AWS S3 Tables? AWS S3 Tables is a managed AWS feature: Amazon describes it as fully managed, meaning AWS operates the catalog and its maintenance and you never manage metadata infrastructure yourself. RustFS's implementation is the self-hosted path — the same Iceberg REST interface, on an Apache 2.0 server you run, with the default object catalog backing persisting catalog state in your own bucket. That shifts the metadata failure domain and the backup plan onto your side of the table, which is the deployment trade being made, not a defect ranking. A note on how this was verified Article type: architecture analysis anchored on a first-hand verification session. The hands-on results quoted here (catalog enable, namespace and table creation, PyIceberg read success and append failure) were run on 2026-10-11 against RustFS 1.0.1, single node on Windows, with curl SigV4 and PyIceberg 0.12.0; the full method and raw outputs are documented in the companion article on the S3 Tables catalog. Not verified for this article: Spark or Trino against the live catalog, multi-node clusters, concurrent commits, snapshot expiry behavior, and production-scale metadata volume. Iceberg spec and maintenance behaviors are cited from the Apache Iceberg project documentation; check current versions before designing partition specs. Sources - Apache Iceberg project (table format, spec, engines): iceberg.apache.org - RustFS S3 Tables documentation: docs.rustfs.com - RustFS S3 Tables engine support matrix: github.com/rustfs/rustfs/blob/main/docs/architect… - RustFS 1.0.0 GA announcement (2026-09-16): rustfs.com - Third-party issue: Spark SigV4 double-encoding 403 against the catalog (closed 2026-10-05): rustfs#8103 - AWS S3 Tables (managed Iceberg tables, for the FAQ comparison): aws.amazon.com - RustFS durable-strong catalog cutover runbook (upgrade red line): github.com/rustfs/rustfs/blob/main/docs/operation… - RustFS PyIceberg walkthrough (SigV4 signing adapter, explicitly configured credentials): docs.rustfs.com - RustFS external Iceberg catalog integration (Spark + standalone REST catalog): docs.rustfs.com - RustFS erasure coding (Reed-Solomon shards, checksums, healing): docs.rustfs.com - Companion article — RustFS S3 Tables: A Built-In Iceberg REST Catalog (same author profile, full method and raw outputs)

    1 reaction
  7. I have identified a bug in rustfs using h5i, one of the most popular object storage written in Rust: github.com/rustfs/rustfs/security/advisories/GHSA…

    2 likes · 245 views
  8. Agent Substrateの概要 はじめに Kubernetes Podcast の #272 Agent Substrate で、大量のAIエージェントをKubernetes上で動かすための新しいランタイムとして紹介されているのを聞いて気になったので、確認してまとめてみました。 Agent Substrate(リポジトリ名は substrate)は、Google が公開している OSS で、多数のエージェントを Kubernetes 上の sandbox で効率よく実行することを目指すシステムです。「数百万」という規模や低レイテンシは、プロジェクトが掲げる設計目標として扱います。Kubernetes自体を置き換えるものではなく、Pod・オートスケーリング・NetworkPolicyといった既存の仕組みの上に、エージェント特有のスケジューリング・スナップショット・ID管理・トラフィックのルーティングを追加するレイヤーとして設計されています。 Googleは2026年5月、Agent SandboxのGKEでのGA発表と合わせて、Agent Substrateをエージェント実行基盤のスタックの中でどう位置づけているかを次の図で示しています。 !Agent Substrateを中心としたエージェント基盤のレイヤー構成図。最上位にAgent Runtime/Agent Harnesses/Custom Agents&Toolsからなる Agent Application Layer、その下にSandbox Isolation(gVisor/MicroVM)を含むAgent Substrateと、Deployments/StatefulSets/LWSなどのOther k8s workloads、最下層にKubernetesとIaaS/Bare metal、Blob storageが並ぶ。 (参考: Bringing you Agent Sandbox on GKE and Agent Substrate | Google Cloud Blog, 2026-05-20) > Agent Substrate introduces a new level of abstraction, moving and placing agents in and out of running compute in real time. It incorporates Agent Sandbox's secure runtime and snapshotting capabilities, combined with a minimal control plane that works around Kubernetes's constraints. Where standard Kubernetes is optimized for thousands of long-running services, Agent Substrate is designed for millions of sub-second tool calls. (参考: Bringing you Agent Sandbox on GKE and Agent Substrate) この図の緑色のブロックに書かれているSandbox Isolation (gVisor / MicroVM)が、本記事の2.3節・3.3節で見るActorのcheckpoint/restoreの基盤であり、引用した文の通りAgent SubstrateはAgent Sandboxのセキュアなランタイムとスナップショット機能を取り込む位置づけです。 その後、2026年9月16日に GKE 向けの提供が発表されました。本記事では、この発表で示された構成も取り上げ、OSS v0.5.0 の実装と GKE 向けの提供条件を分けて整理します。 (参考: Agent Substrate brings high-density, scalable, trusted infrastructure to GKE | Google Cloud Blog, 2026-09-16) この記事では、Agent Substrateが解決しようとしている課題、全体のアーキテクチャ、そして各コンポーネントがKubernetesの既存の仕組みのどれに相当する/何を補完しているのかを整理します。 関連する仕組みも別記事にまとめています。 - Agent Executorの概要:Task を管理し、実行を Substrate の Actor に任せる仕組み - LeaderWorkerSet と DisaggregatedSet の概要:分散推論向けに Pod グループを管理する仕組み - Agent Sandboxの概要:単一 Pod のサンドボックス環境を管理する API 対象コンポーネントとバージョン | コンポーネント | バージョン | リポジトリ | 本記事での役割 | |---|---|---|---| | Agent Substrate | v0.5.0 | agent-substrate/substrate | 本記事全体のソースコード調査対象 | | Kubernetes | v1.37.1 | kubernetes/kubernetes | PodCertificateRequest API の定義・feature gate の状態を確認 | | kind | v0.33.0 | kubernetes-sigs/kind | 検証環境。ノードイメージは kindest/node:v1.37.0(Agent Substrateの hack/tools/kind/go.mod が固定するバージョン) | GKE 向けの提供条件は公式ドキュメントの確認時点(2026年10月20日)の情報です。以下で扱う OSS のタグと、GKE 向けインストーラの配布バージョンが同一であるとは限りません。 :::note info Agent Substrate は v1.0 未満で、後方互換性を保証していません。本記事の実装・設定・引用は v0.5.0 に固定します。本文でリポジトリ名を省略したソースコードの引用は、すべて agent-substrate/substrate のものです。 ::: TL;DR - Kubernetes 上に Actor 専用の control plane を追加します。Worker Pod の配置・起動は Kubernetes に任せ、Actor の状態管理と Worker への割り当ては独自 API と PostgreSQL で扱います。 - 少数の Worker で多数の Actor を扱います。Worker の容量に応じた同時実行と、Suspend/Resume による時間的な多重化を組み合わせます。 - atelet と ateom が sandbox の状態を保存・復元します。gVisor または micro-VM を利用し、Actor の状態を保ちながら Worker を使い回します。保存範囲は snapshot fidelity によって変わります。 - 外部 API の認証情報を Actor の外で注入できます。egress gateway と credential provider を使う構成では、Actor に実際の API キーを渡さずに呼び出せます。 - 設計目標と実装済みの機能は区別が必要です。認可は実験的な機能として存在しますが、通常の Control RPC への強制はデフォルトで無効です。自動アイドル判定や性能値も、個別に確認する必要があります。 第1章: Agent Substrateが解決しようとしている課題 この章では、Agent Substrateがなぜ「Kubernetesに乗せるだけ」ではなくcontrol planeごと新しく作る必要があったのか、その問題意識を整理します。 1.1 エージェントワークロードの特性 Agent Substrateのアーキテクチャドキュメントは、対象ワークロードの特性を次のように説明しています。 > Agents and "agent-like" workloads are generally very bursty, spending most of their time waiting for input or events, then handling those events, then going back to waiting. The time they spend actually doing work is often very short, and the time they spend waiting can be unbounded. Because they often run untrusted logic, they are often run in sandboxes, which means they are usually single-tenant instances, and there are a great many of them. (参考: architecture.md#L17-L23) ポイントは3つです。 - 実際に処理している時間が短く、待ち時間(LLMやツール呼び出しの応答待ち)が大半を占める - 信頼できないコードを実行するためsandbox実行が前提になり、1インスタンス=1テナントになりがち - そのインスタンスの数が非常に多い この「アイドル時間が長い・数が多い・sandboxが要る」という3条件が、後述するスナップショットベースの多重化モデルの動機になっています。 1.2 Kubernetesの標準機能で足りない理由 READMEとarchitecture.mdは、Kubernetesをそのまま使わない理由を具体的に挙げています。 > * The Kubernetes API server is not designed to handle millions of resources. It excels at reconciling resources asynchronously across many controllers, but it is not so good at storing very large numbers of discrete resources, or for handling a huge volume of write traffic. > * Scheduling a workload on Kubernetes requires several asynchronous processes to converge, plus several network hops, plus image pulling and other steps, which can add up. Getting a Pod running in a couple of seconds is great when that Pod will run for hours or days, but for a workload that will run for milliseconds to low-digit seconds, that latency is unacceptable. > * State management is difficult. Kubernetes provides an API for managing state (PersistentVolumes), but it is not designed for millions of volumes, with widely varying amounts of data, being attached and detached at high Speed. (参考: architecture.md#L160-L172) これは、Agent Substrate が想定する規模と応答時間に対する、プロジェクト側の設計判断です。Kubernetes の一律の上限や、すべての環境で Pod 起動に数秒かかるという測定結果ではありません。一方で、ワーカーPodそのものの起動・ワークロード分離・インフラのプロビジョニングはKubernetesに任せたままにしています(「Agent Substrate leverages Kubernetes for infrastructure provisioning and worker lifecycle management (Kubernetes Pods)」、参考: architecture.md#L153)。Agent Substrateが新しく作っているのは、あくまで「Actorの状態管理とスケジューリングだけに特化した、もう1段速いcontrol plane」です。 なぜetcdへの大量の書き込みが台数を増やしても速くならないのかというメカニズム(Raftはリーダー1台が書き込みを確定させる仕組みであること)は、以前別記事Kubernetes の Control Plane と etcd、何台にすべきかで整理しました。ただし、Agent Substrate の設計理由を Raft だけで説明することはできません。非同期な reconciliation や Pod の準備時間も含め、Actor の再開要求で Kubernetes の更新完了を待たない構成にしている点が重要です。 第2章: 全体アーキテクチャ この章では、Actor のリソースモデルと、起動要求を受けてから応答するまでの流れを整理します。まず、GKE 向け発表の図で、Kubernetes の上に追加される機能を確認します。 !Kubernetes・Compute・Storage の上に、Gateway、Suspend/Resume、gVisor / MicroVM による分離を置く Agent Substrate の全体像。 (参考: Agent Substrate brings high-density, scalable, trusted infrastructure to GKE | Google Cloud Blog, 2026-09-16) この図を実装上のコンポーネントへ対応づけたものが、次の脅威モデルの構成図です。 <p align="center"> <img src="raw.githubusercontent.com" width="700" alt="Substrate Architecture Diagram"> </p> Agent Substrateのアーキテクチャ図。ate-api-server・atelet・ateom・atenet-router・Object Storage・PostgreSQL(Substrate Database)が、Kubernetes上でどう配置されるかを示している。 (参考: threat-model.md#L30-L32) この図に登場するコンポーネントは、threat-model.mdの「Key Components」節で次のように説明されています。 > * ate-api-server: Actor creation and scheduling and credential issuance. > * atelet: Per-node daemon, performs snapshot/resume. > * ateom: Per-worker Pod sidecar, running inside the worker Pod. ... > * Worker: Preprovisioned Pods that actors get scheduled to. > * Actor: The core compute primitive, gets scheduled to/from worker via Run for cold start and Resume for snapshot resume. (参考: threat-model.md#L36-L40) 以降の節では、この図の個々のコンポーネントがどう実装されているかを、ソースコードから具体的に見ていきます。 2.1 ActorとWorkerの多重化モデル Agent Substrateの用語は次のように定義されています。 - ActorTemplate: actorの「クラス」の定義(コンテナイメージやスナップショット設定)。immutableで、Kubernetesオブジェクトではなく、ate APIのgRPC経由で作成され、control plane側のデータベースに保存される(参考: glossary.md#L10-L19) - WorkerPool: Kubernetesの CRD。atecontrollerによって Deployment へreconcileされる(参考: glossary.md#L21-L23) - Atespace: Actor が属する論理的なグループと識別範囲。Kubernetes namespaceではなくglobal-scopedなcontrol plane DB上のレコード(参考: glossary.md#L38-L43) - Actor: ActorTemplateから作られる1インスタンス。suspend/resumeの単位で、resumeのたびに別のworkerへ移ることがある(参考: glossary.md#L45-L48) - Worker: WorkerPool内の1つのworker Podを表すレコード。1つのWorkerは複数のActorを同時にホストする(参考: glossary.md#L50-L52) これらの関係は、architecture.mdのクラス図に整理されています。 classDiagram namespace kube-apiserver { class WorkerPool { <<CRD>> } class Deployment class WorkerPod { ateom runsc } } namespace ate-api-server { class ActorTemplate { <<record>> } class Actor { <<record>> status snapshotRefs } class Worker { <<record>> actorAssignments podIP } } ActorTemplate ..> WorkerPool : worker_selector (labels) WorkerPool ..> Deployment : reconciled by atecontroller Deployment "1" *-- "*" WorkerPod : manages Actor "*" --> "1" ActorTemplate : actor_template Actor "0..*" --> "0..1" Worker : runs on Worker "1" --> "1" WorkerPod : maps to API リソースの関係。元の図を基に、Worker が複数 Actor をホストできる実装に合わせて多重度を補正したもの。 (参考: architecture.md#L268-L303) WorkerPoolはkube-apiserver、ActorTemplate/Actor/Workerはate-api-serverと、管理するAPIサーバーが分けて設計されている点が、このアーキテクチャの核心です。WorkerPoolはKubernetesのDeploymentとして実体化されますが、「どのActorがどのWorkerで動いているか」という高頻度に変化する状態は、Kubernetesリソースではなくate-api-server側のレコードとして管理されます。 2.2 2層のリソースモデル: 宣言的なCRDと、頻繁に更新される状態の専用ストア architecture.md は、この使い分けの理由を次の 3 点にまとめています。引用中の 100ms は設計上の目標であり、本記事で測定した性能値ではありません。 > 1. Scalability: Offloading the high-frequency, high-scale management of millions of actors to a specialized state store prevents overwhelming the primary cluster control plane with thousands of updates per second. > 2. Latency: Achieving 100ms resumption requires low-latency state lookups and atomic worker assignments that bypass the eventual consistency and variable latency of standard Kubernetes API servers. > 3. Governance: Keeping infrastructure resources (WorkerPools, SandboxConfigs) as Kubernetes objects allows platform teams to apply familiar RBAC, auditing, and policy enforcement to the underlying infrastructure, while workload definitions (ActorTemplates) are managed through the substrate API instead, which authenticates callers itself but does not yet implement authorization (参考: architecture.md#L247-L261) 「インフラ寄りのリソース(WorkerPool・SandboxConfig)はKubernetesに残してRBACや監査を使い回し、ワークロード定義(ActorTemplate)やその実行状態(Actor/Worker)はetcdの外に出して専用ストアに置く」という切り分けです。ただし、このドキュメントの「認可はまだ実装されていない」という説明は、同じタグの実装と一致していません。v0.5.0 には OpenFGA を使う認可機能があり、--experimental-enable-authz (デフォルト false) で通常の Control RPC にも認可を強制できます。AccessPolicy RPC には、このフラグによらずチェックが行われます。 (参考: main.go#L84-L88、interceptor.go#L28-L60) 2.3 Actorのライフサイクル(Resume/Suspend) !Worker 上の Active な Agent Environment と、Storage に状態を保存した Idle な環境を Suspend/Resume で切り替える図。 (参考: Agent Substrate brings high-density, scalable, trusted infrastructure to GKE | Google Cloud Blog, 2026-09-16) 図の Active は実行中、Idle は状態を保存して計算資源から外した環境を表しています。単にプロセスを待機させる構成とは異なり、再開時には保存した状態を Worker へ復元します。図は概念図であり、Worker と Actor が常に1対1という意味ではありません。 リクエストを受けてからActorが応答するまでの流れは、architecture.mdのHigh-Level Designに説明されています。 > When a request comes in for that actor instance, the Agent Substrate's proxy intercepts the request. It looks in the control-plane to determine if that actor is currently running or not. If not, it assigns the actor to a worker, which involves telling a node-level component (the "atelet") to restore the actor's most-recent snapshot into the worker on that node. The request is then forwarded to the actor. > > Eventually, the user (or the higher-level system) is done with the actor, or it has been idle for a while. It can request the Agent Substrate to suspend the actor, taking a snapshot and freeing up the worker. The next time a request comes in for that actor, it will be resumed again, possibly on a different worker. (参考: architecture.md#L193-L204) つまり、Envoyベースのルーター(atenet-router)がリクエストごとにcontrol planeへ再開(Resume)を依頼し、Actorが止まっていればcontrol planeがスナップショットから復元してから、リクエストが転送される、という流れです。5.1節で見る通り、atenet-router自身はActorが動いているかを判断していません。Actorは特定のworkerに固定されず、suspend/resumeのたびに別のworkerへ移動する可能性があります。 第3章: 主要コンポーネントの役割とKubernetesとの対応関係 ここからは、各コンポーネントの実装コードを見ながら、それぞれがKubernetesの既存コンセプトのどれに対応する/何を代替・補完しているのかを整理します。先に全体像として、主要コンポーネントと対応関係の一覧を示します。 | コンポーネント | 役割 | Kubernetesとの対応関係 | |---|---|---| | ate-api-server(ateapi) | Actorのライフサイクル管理・スケジューリング・スナップショット調整を行う独自control plane | Kubernetes とは別の Actor 用 API・scheduler・workflow をまとめた制御層。状態は PostgreSQL に保存 | | atecontroller | WorkerPool/SandboxConfigなどのCRDをDeployment等へreconcileする | sigs.k8s.io/controller-runtime を使った標準的な Kubernetes Controller | | atelet / ateom | ノードごとのDaemonSet(atelet)とworker Pod内のsandbox駆動プロセス(ateom)で、sandboxのcheckpoint/restoreを行う | Worker Pod の内側で Actor の sandbox を管理する追加の実行層 | | atenet(router / egress) | Actorの起動要求(ingress)とegressトラフィックのルーティング・制御を行う | Service/Ingress/Gatewayに相当するが、宛先Actorの起動待ち合わせを行う追加のオーケストレーション層を持つ | | podcertcontroller | コンポーネント間mTLS用の短命証明書を発行する | PodCertificateRequest APIに対する、Kubernetes本体にまだ存在しないsignerの実装 | 上の表は役割ごとに整理したもので、agent-substrate/substrateリポジトリには、これ以外にもsidecarやインストーラ、CLIなどが同梱されています。v0.5.0のcmd/配下とtools/配下を、実体ごとに並べると次の通りです。 | ディレクトリ | 種別 | 役割 | 本記事の節 | |---|---|---|---| | cmd/ateapi | control plane(gRPC API server) | Actor/Workerのライフサイクル管理とスケジューリング | 3.1節 | | cmd/atecontroller | Kubernetes Controller | WorkerPoolなどのCRDをDeploymentなどへreconcileする | 3.2節 | | cmd/atelet | DaemonSet | ノード上のworker Podを監督し、スナップショットと状態転送を調整する | 3.3節 | | cmd/ateom-gvisor | worker Pod内のプロセス | runscのcheckpoint/restoreを実行する | 3.3節 | | cmd/ateom-microvm | worker Pod内のプロセス | ateom-gvisorのmicro-VM版。Cloud Hypervisorの仮想マシンとしてActorを動かす | 3.3節 | | cmd/objectstore-plugin | sidecar | GCS/S3へのスナップショット転送を担う。nodeモードはatelet、controlモードはate-api-serverのsidecarとして動く | 3.3節 | | cmd/atenet | Deployment(router)など | Envoyのルーティングとproxy sidecarを提供するネットワーキングのデーモン | 3.4節 | | cmd/dataplane/envoy | コンテナイメージ | atenet-egressが使うEnvoyのイメージ。egress policyを強制するRust製のEnvoy Dynamic Moduleを含む | 3.4節, 4.2節 | | cmd/credential-provider/kubernetes-secrets | リファレンス実装 | Kubernetes Secretを解決するegress credential provider | 4.2節 | | cmd/podcertcontroller | Deployment | PodCertificateRequestのsigner(Kubernetes本体にまだ無いものを補う「polyfill」) | 3.5節 | | cmd/kubectl-ate | CLI | Agent Substrateのリソース(Actorなど)を操作する | 7.3節 | | cmd/ate-setup | CLI(インストーラ) | クラスタへのAgent Substrateのインストールと削除 | 6章 | | cmd/benchmarking | 負荷試験用ワークロード | RAM・ディスク・ファイルディスクリプタを任意に消費するgluttonなど | 本記事では扱わない | | tools/setup-gcp | CLI(プロビジョニング) | GKE・GCS・IAMなどGCP側のリソースを用意する | 6章 | (参考: README.md#L260-L272、cmd/objectstore-plugin/main.go#L15-L19、cmd/ate-setup/README.md#L3、cmd/dataplane/envoy/README.md) READMEの「Tour」節の一覧には、objectstore-plugin・credential-provider・dataplane・ate-setupは載っていません。この4つは、cmd/配下のディレクトリと各ディレクトリのREADMEやソースコード冒頭のコメントから補っています。 3.1 ate-api-server: 独自control plane ate-api-server(バイナリ名 ateapi)は、Actorのライフサイクル全体を司るcontrol planeです。 > ate-api-server (binary ateapi): the control plane. It owns the Actor lifecycle, schedules Actors onto Workers, and coordinates their snapshots, all backed by the state store. The kubectl-ate CLI talks to it. (参考: glossary.md#L56-L58) 実装(cmd/ateapi/main.go)を見ると、PostgreSQLへの接続(connectStore)、Kubernetesクライアントの生成(newKubeClients、WorkerPool/SandboxConfig/CSIDriverConfigのinformerはこのクライアントから取得する)、mTLS用のサーバー証明書構築(buildServerCreds)などを単一のgRPCサーバープロセスが担っています(参考: main.go#L509-L521、main.go#L560-L576、main.go#L578-L586)。 templateReconciler := controlapi.NewActorTemplateReconciler(persistence, controlSrv, *templateResyncInterval) templateReconciler.Start(shutdownCtx) workerAssignmentReconciler := controlapi.NewWorkerAssignmentReconciler(persistence, workerCache) workerAssignmentReconciler.Start(shutdownCtx) (参考: main.go#L326-L330) ここで出てくるActorTemplateReconcilerやWorkerAssignmentReconcilerは、名前に"Reconciler"と付いていますが、controller-runtimeのReconcilerとは別物です。Kubernetesのカスタムリソースを監視するものではなく、ate-api-serverプロセス内部で動く独自のループで、前者はActorTemplateから「golden snapshot」を作るフローを、後者はateomの再起動で失われたActorをクラッシュ扱いにする処理を担っています。 Kubernetes との対応関係: ate-api-server は Actor/Worker 専用の gRPC API server で、スケジューリングとライフサイクル調整も担当します。状態の保存先は PostgreSQL です。kube-apiserver と一対一に対応するものではなく、Kubernetes とは別に Actor の制御を引き受ける位置づけです。 3.2 atecontroller: WorkerPool CRDのController atecontrollerは、ここまでのコンポーネントとは対照的に、Kubernetesの標準的なControllerパターンをそのまま踏襲しています。sigs.k8s.io/controller-runtime の ctrl.NewManager を使い、WorkerPoolReconciler など4つのReconcilerを登録しています(参考: main.go#L233-L276)。 WorkerPoolReconciler.Reconcile は、典型的な「Get → DeletionTimestampチェック → reconcile処理」の構造です。 func (r *WorkerPoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := log.FromContext(ctx) wp := &atev1alpha1.WorkerPool{} if err := r.Get(ctx, req.NamespacedName, wp); err != nil { if k8errors.IsNotFound(err) { return ctrl.Result{}, nil } return ctrl.Result{}, fmt.Errorf("failed to get worker pool %q: %w", req.NamespacedName, err) } if !wp.GetDeletionTimestamp().IsZero() { log.Info("WorkerPool is being deleted") return ctrl.Result{}, nil } if err := r.reconcileWorkerPool(ctx, wp); err != nil { log.Error(err, "Failed to reconcile worker pool") return ctrl.Result{}, err } return ctrl.Result{}, nil } (参考: workerpool_controller.go#L105-L129) 実際の処理(reconcileWorkerPool)は、WorkerPoolをDeploymentへ変換するだけでなく、WorkerPoolが参照するSandboxConfig(クラスタスコープのCRDで、gVisorやmicro-VMのランタイムバイナリを保持する)に保護用のfinalizerを付与する処理も行っています。コメントにはその設計意図が明記されています。 > A class that switches to a new SandboxConfig does so in two status writes. This makes sure status never names a config after its protection finalizer was released. (参考: workerpool_controller.go#L134-L136) SandboxConfigへのfinalizer付与をWorkerPool側のReconcilerが行い、解放は別のSandboxConfigProtectionReconcilerが行うという2段階の設計で、Owns(&Deployment{}) / Watches(&SandboxConfig{}, ...) という標準的なOwns/Watchesパターンで結び付けられています(参考: workerpool_controller.go#L416-L425)。 Kubernetes との対応関係: atecontrollerは「CRD→下位リソースへの変換」というDeployment Controllerと同じパターンを踏襲した、controller-runtime を使った標準的な Kubernetes Controller です。Agent Substrateの他のコンポーネントが独自のcontrol planeを持つのに対し、WorkerPoolとSandboxConfigに関してはKubernetesネイティブな仕組みをそのまま使っています。 3.3 atelet / ateom: ノードエージェントとsandboxのcheckpoint/restore ノード側の実行基盤は、DaemonSetのateletとworker Pod内で動くateomの2層構造です。 > atelet: A lightweight supervisor running on each node as a DaemonSet. It acts as the "Herder," managing a pool of physical pods and communicating with the Control Plane. > > ateom: A specialized sandbox-herder container image — one per sandbox class (ateom-gvisor, ateom-microvm) — that runs inside the physical worker pods. It provides a gRPC interface for atelet to trigger RunWorkload, CheckpointWorkload, and RestoreWorkload operations. This separation ensures that the physical pod lifecycle remains decoupled from the sandboxed agent process. (参考: architecture.md#L324-L326) atelet はノードごとの DaemonSet として動き、イメージキャッシュやCSIボリュームの管理、kubeletのcredential-provider機構を再利用したイメージPull認証を実装しています(--image-credential-provider-config、参考: main.go#L103-L104)。一方でスナップショット本体のアップロード/ダウンロードはatelet本体ではなく、サイドカーの「object-store plugin」が担います。 > External snapshots move through an object-store plugin, a sidecar that serves the objectstore.v1 gRPC API over a Unix socket in its pod. atelet's sidecar (NodeProvider) uploads and fetches a snapshot's files; ate-api-server's sidecar (ControlProvider) copies and deletes whole snapshots. Neither main process talks to object storage for snapshots. (参考: architecture.md#L330) スナップショット転送は object-store plugin に分離され、atelet・ate-api-server 本体はその gRPC API を呼びます。オブジェクトストレージへのアクセス設定はプラグイン側で扱います。これは本体プロセスが mTLS や DB 接続用の認証情報も持たない、という意味ではありません。 sandboxのcheckpoint/restoreは、ateomがgVisorのrunscまたはCloud Hypervisor(micro-VM)を直接操作して実装しています。 > Lifecycle Management: The ateom process invokes the sandbox runtime to checkpoint or restore processes within the physical pod boundaries — runsc for gVisor, or the Kata + Cloud Hypervisor stack for micro-VMs. (参考: architecture.md#L328) gVisor版はcmd/ateom-gvisor/runsc.goのcmdCheckpoint/cmdRestoreがrunsc checkpoint/runsc restoreコマンドを直接起動しています。micro-VM版はcmd/ateom-microvm/checkpoint.goのCheckpointWorkloadとKata Containers連携パッケージ(internal/kata)を使い、Cloud HypervisorのVM状態をスナップショットし、復元時はuserfaultfdによるメモリのデマンドページングで読み戻しています(参考: architecture.md#L338)。 Kubernetes との対応関係: atelet/ateom は、Worker Pod の内側で動く Actor の sandbox を管理します。Worker Pod 自体の実行は引き続き kubelet とコンテナランタイムが担当します。 Kubernetes にも Kubelet Checkpoint API と CRI の checkpoint 操作はあります。Agent Substrate の特徴は checkpoint の存在自体ではなく、保存先・Worker の割り当て・restore・ネットワークの再接続までを Actor のライフサイクルとして調整することです。 (参考: Kubelet Checkpoint API) 3.4 atenet: ルーティングとegress atenetは、ActorのResume要求とトラフィックのルーティングを兼ねるネットワーキングスタックです。 > Ingress Routing: atenet-router runs Envoy with an ext_proc external processor. A higher-order system connects to the router and supplies the Actor target in ate-target-actor as <atespace>/<actor>. The ext_proc calls the Control Plane to resume the Actor and resolve its current worker assignment. (参考: architecture.md#L344-L349) 実装では、cmd/atenet/internal/router/ingress/ingress.goがActorResumer.ResumeActorを呼び出しており、ここでate-api-serverへResumeActor RPCを投げています。つまりatenet-routerは、Envoyのext_proc拡張機構を使って「ルーティング先が止まっていれば先にResumeしてから転送する」という、通常のKubernetes Service/Ingressにはないオーケストレーションを行っています。 workerへの転送は、mTLSのCONNECTトンネル経由です。 > After resolving the assignment, atenet-router opens an mTLS CONNECT tunnel to the worker's atunnel listener on port 8443. atunnel, hosted by ateom, forwards the request to the active Actor over its private veth interface. Worker pod port 80 is not a direct Actor ingress path. (参考: architecture.md#L351-L354) Kubernetes との対応関係: KubernetesのService/Ingress/Gatewayに相当する「エンドポイントへのルーティング」をEnvoyで行う点は同じですが、通常のServiceが単純な負荷分散であるのに対し、atenet-routerは「宛先のActorが止まっていれば先に起動する」という追加のオーケストレーション層を持っています。 3.5 podcertcontroller: PodCertificateRequestのsigner podcertcontrollerは、コンポーネント間の相互TLS認証に使う短命な証明書を発行するコントローラーです。 > Command podcertcontroller is a pod certificate controller that implements three signers. > > - servicedns.podcert.ate.dev/identity: Issues certificate for Kubernetes service DNS names, backed by a local CA. > - podidentity.podcert.ate.dev/identity: Issues certificates equivalent to KSA tokens, backed by a local CA. > - postgres.podcert.ate.dev/identity: Issues PostgreSQL login certificates to ate-api-server. (参考: main.go#L15-L22) このコントローラーは、certificates.k8s.io/v1 の PodCertificateRequest を監視し、証明書の発行・拒否を判断する signer です。API 自体は Kubernetes v1.37 で GA になりました。 PodCertificateRequest: { {Version: version.MustParse("1.34"), Default: false, PreRelease: featuregate.Alpha}, {Version: version.MustParse("1.35"), Default: false, PreRelease: featuregate.Beta}, {Version: version.MustParse("1.37"), Default: true, PreRelease: featuregate.GA}, // LockToDefault in 1.38 }, (参考: kube_features.go#L1945-L1949) PodCertificateRequestのAPI定義には、signer名についてこう書かれています。 > signerName indicates the requested signer. > > All signer names beginning with kubernetes.io are reserved for use by the Kubernetes project. There is currently one well-known signer documented by the Kubernetes project, kubernetes.io/kube-apiserver-client-pod, which will issue client certificates understood by kube-apiserver. It is currently unimplemented. (参考: types.go#L437-L444) つまり、PodCertificateRequestというAPI自体(kubeletがリクエストを作り、証明書をPodへprojected volumeとしてマウントする仕組み)はKubernetes本体がGA機能として持っていますが、API 定義で挙げられている組み込み signer kubernetes.io/kube-apiserver-client-pod は未実装です。PodCertificateRequest を処理する signer は、利用者や外部コンポーネントが用意します。podcertcontrollerは、このsigner不在の部分を、Substrate独自の3種類のID(Service DNS、Pod Identity、PostgreSQLログイン証明書)向けに自前で実装したものです。READMEがこれを次のように説明しているのは、この実装状況と整合しています。 > cmd/podcertcontroller: A "polyfill" that provides Pod Certificate signers that will eventually ship in upstream Kubernetes (with different names). (参考: README.md#L268-L269) Kubernetes との対応関係: podcertcontrollerは、KubernetesのCertificateSigningRequest(CSR)機構と同じ発想の、PodCertificateRequestに対するsigner controllerです。kube-controller-managerが提供する組み込みsignerがまだ存在しない領域を、Substrate固有の3種類のID向けに自前実装で埋めています。 第4章: セキュリティモデル この章では、sandbox の分離と、外部 API の認証情報を Actor に渡さずに使う仕組みを整理します。設計原則、実装状況、デモの設定は分けて説明します。 4.1 常時sandbox実行とゼロクレデンシャル !Agent Environment の周囲に Identity-based Ingress/Egress、Secrets Management、Kernel Isolation、Rapid Suspend/Resume を配置した図。 (参考: Agent Substrate brings high-density, scalable, trusted infrastructure to GKE | Google Cloud Blog, 2026-09-16) カーネルの分離と通信制御、認証情報の管理は別々の役割です。sandbox を選ぶだけで外部 API の資格情報まで保護されるわけではなく、4.2節の gateway と credential provider の構成も必要になります。 security.mdのStatusテーブルには、各セキュリティ原則の実装状況が「Met」「In Progress」「Future」「Out of Scope」の4段階で整理されています。それぞれの定義は次の通りです。 - Met: Fully implemented in the current codebase. - In Progress: Partially implemented or under active development. - Future: Planned architecture and roadmap capabilities not yet implemented. - Out of Scope: Not substrate's job, handled by a layer outside substrate. (参考: security.md#L30-L33) 以下は、そのうちゼロトラストの中核にあたる4つの原則です。 | 原則 | 内容 | 状態 | |---|---|---| | Always Sandboxed | すべてのActorは強力なコンピュートsandbox内で実行される。Linuxコンテナ単体では不十分 | Met (gVisor, CHV micro-VM) | | Zero Ambient Credentials | Actor に基盤の権限や外部 API キーを暗黙に引き継がせない。明示的に環境変数へキーを渡すデモとは区別する | Met (no credentials inside sandbox) | | No Access by Default | Actor から control plane へのアクセスを制限する。worker ノード上の atelet など、基盤コンポーネントに必要な通信とは区別する | Met (Loopback, private netns)<br>In-Progress (DNS filtering) | | Fail-Closed Policy-Enforced Egress Gateway | outbound トラフィックを共有 egress proxy で制御する。fail-closed は設計原則であり、UDP passthrough など未完了の項目がある | Met (atenet-egress)<br>In-Progress (fail closed, UDP currently passthrough) | (参考: security.md#L41、security.md#L59、security.md#L75、security.md#L77) :::note warn 同じテーブルでは、Atespace 単位の API 認可 (ReBAC) は「In Progress」、Node Blast Radius Containment やストレージの暗号化は「Future」です。認可のコードは存在するものの、通常の Control RPC への強制は実験的なフラグで有効化する段階です (2.2 節)。zero-trustという設計思想自体は固まっていますが、個々の統制がすべて実装済みというわけではありません。 ::: 4.2 egress gatewayによる認証情報注入 Actorが外部APIを呼び出す際の認証情報は、Actor自身ではなくegress gatewayが注入します。 > Egress credential injection puts a secret into an actor's outbound HTTPS requests without the actor ever holding it. ... The actor never holds the secret. It sends the header with a placeholder value, which the gateway discards, so it cannot read, snapshot, or exfiltrate the secret, nor choose the value that leaves the cluster. (参考: egress-credential-injection.md#L1-L15) 具体的な流れは次の4ステップです(docs/egress-credential-injection.mdのmermaid図および本文を要約)。 1. Actorがプレースホルダー値を入れたヘッダー付きでHTTPSリクエストを送る 2. egress gatewayがActorのTLSを終端し、EgressPolicyのreplace_headersルールに一致すればcredential providerへ問い合わせる 3. credential providerがそのActorにその認証情報を渡してよいか判断し、シークレットストアから読み出す(シークレットストアへの直接アクセス権を持つのはprovider だけ) 4. egress gatewayがプレースホルダーを実際の認証情報に置き換え、新しいTLSコネクションで送信する HTTPS を終端するため、Actor 側が egress gateway の CA を信頼する設定も必要です。また、注入対象のヘッダーがリクエストに含まれなければ、認証情報の取得・置き換えは行われません。 この仕組みは API キーそのものを Actor から隔離しますが、許可された API を使った不適切な操作まで自動的に防ぐわけではありません。宛先や操作の権限、アプリケーション側の認可も必要です。 (参考: egress-credential-injection.md#L41-L54) リファレンス実装であるcmd/credential-provider/kubernetes-secretsはKubernetesのSecretを解決するcredential providerで、デフォルトでは空のdeny-allポリシーから始まり、atespaceごとに読み取り可能なnamespaceを明示的に許可する必要があります(参考: README.md#L132-L136)。Actorが直接Secretを読みに行くことはなく、egress gatewayを経由した時だけ、しかも許可された宛先向けのリクエストに限ってクレデンシャルが挿入される設計です。 第5章: AIエージェントがタスクを実行するまでの流れ ここまでの章は、Actorという「箱」がどう起動・停止するか(2.3節)、egressがどう認証情報を守るか(4.2節)を別々に説明してきました。この章では、実際にAIエージェントとして動くActorが、利用者からのリクエストを受けてから外部LLM APIを呼び出し、応答を返すまでの一連の流れを1つの図にまとめます。 5.1 HTTP リクエストを起点に Actor を Resume する場合 Counter デモのように atenet-router 経由でリクエストを受ける構成では、Actor の起動・復元を待ってから通信を転送します。以下は、その流れに外部 LLM API の呼び出しを組み合わせた例です。Counter デモ自体が LLM を呼ぶわけではありません。 sequenceDiagram actor User as 利用者 / 上位システム participant Router as atenet-router participant API as ate-api-server participant Atelet as atelet participant Tunnel as ateom / atunnel participant Agent as Actor participant Egress as atenet-egress participant Cred as credential provider participant LLM as 外部 LLM API User->>Router: HTTP リクエスト (ate-target-actor) Router->>API: ResumeActor alt Actor が停止中 API->>Atelet: 復元要求 Atelet->>Tunnel: RestoreWorkload Tunnel-->>Atelet: 復元完了 Atelet-->>API: 完了 else Actor が実行中 Note over API: 既存の割り当てを返す end API-->>Router: Worker の割り当て Router->>Tunnel: mTLS CONNECT トンネルで転送 Tunnel->>Agent: リクエストを転送 Agent->>Egress: HTTPS (プレースホルダーのヘッダー) Egress->>Cred: 認証情報を要求 Cred-->>Egress: 認可後、実際の API キーを返す Egress->>LLM: ヘッダーを差し替え、別の TLS 接続で送信 LLM-->>Egress: 応答 Egress-->>Agent: 応答 Agent-->>Tunnel: 処理結果 Tunnel-->>Router: 応答 Router-->>User: 応答 Note over User,API: 上位システムなどが SuspendActor を要求すると保存・解放する 前半は architecture.md の Resume シーケンス、後半は egress credential injection を基に簡略化しています。Actor がスナップショットを使わず cold boot する場合などは省略しています。 atenet-router は転送前に ResumeActor を呼び、実行中なら既存の割り当てを使います。停止中なら、control plane が復元を調整してから割り当てを返します。ResumeActor は実行中の Actor に対して冪等です。 (参考: workflow_resume.go#L67-L113) 外部 LLM API との通信では、Actor 側と egress gateway の間、gateway と LLM API の間に別々の TLS 接続があります。gateway が HTTPS を終端してヘッダーを置き換えるため、エンドツーエンドの 1 本の TLS 接続ではありません。 応答を返しただけでは、Actor が自動的に Suspend されるとは限りません。いつ処理が終わったか、どの待機区間で Suspend できるかは、上位システムやワークロードとの連携も含めて設計する必要があります。LLM の応答待ち中であることと、安全に Suspend できることも同じではありません。 5.2 Claude Code デモで実装されていること すべてのエージェントが外部からのHTTPリクエストを起点に動くわけではありません。READMEが挙げているデモの1つclaude-code-multiplexは、3体のClaude Codeエージェント(luna/mars/orion)を2 Podのworker poolで共有する構成です。 > Three Claude Code agents (luna, mars, orion) registered as Substrate actors. > A WorkerPool of two pods. > > This demo uses the ateapi control plane to manage Actors and does not send workload HTTP traffic through atenet-router, so it does not use the ate-target-actor routing header. (参考: demos/claude-code-multiplex/README.md#L14-L21) このデモのActorTemplateは、タスクの内容(TASK)と実行間隔(INTERVAL_SECONDS)を環境変数としてあらかじめ焼き込んでおくだけのシンプルな作りです。 env: - name: ACTOR_NAME value: "luna" - name: TASK value: "Tell me one short, surprising fact about the Moon. One sentence." - name: INTERVAL_SECONDS value: "45" - name: ANTHROPIC_API_KEY value: "${ANTHROPIC_API_KEY}" (参考: agent-luna-template.yaml.tmpl#L34-L42) コンテナのエントリポイントは、claude --printでタスクを1回実行してはINTERVAL_SECONDS秒だけアイドルになる、というループを回し続けるだけです。 while true; do ... claude --print "${TASK}" 2>&1 || echo "[demo-actor:${ACTOR_NAME}] claude exited non-zero" ... sleep "${INTERVAL_SECONDS}" done (参考: run.sh#L38-L49) READMEは、このアイドル区間こそが多重化の余地になると説明しています。 > The idle window is what substrate uses to suspend this actor and multiplex its worker onto another actor. (参考: run.sh#L17-L19) ただし、このデモの README の説明と UI の実装は区別が必要です。README はタスクの投入・完了と自動 Suspend を説明していますが、v0.5.0 の UI は次の動作になっています。 | 処理 | UI の実装 | |---|---| | 「Give a task」 | Actor とタスクを選び、UI サーバー内の assignment に記録する | | queued / running / completed の表示 | 作成時刻とランダムに決めた待ち時間・実行時間から計算する | | Actor の実行状態・ログの表示 | ate API と Kubernetes API から取得する | giveTask はタスクを Actor に送信したり、ResumeActor / SuspendActor を呼び出したりしません。computeState にも、タスク状態が時間経過による表示であり、Substrate 側の状態ではないとコメントされています。 (参考: server.go#L252-L315) したがって、デモ画面のタスク状態表示 (queued / running / completed) が切り替わっても、LLM タスクの実行完了や自動多重化が行われたことを示すわけではありません。このデモから読み取れるのは、Claude Code を起動する ActorTemplate と、周期的に claude --print を実行するワークロード、状態を表示する UI の構成です。Suspend/Resume の動作確認は、7.3 節のように実際の API 操作と Actor の状態で確かめます。 5.3 エージェントが外部LLM APIを呼び出す部分の実体: EgressPolicy 5.1 節の通信では、EgressPolicy が到達先とヘッダー置き換えのルールを定めます。参照された認証情報をその Actor に使わせてよいかは、credential provider 側でも判断します。ate APIのプロトコル定義では、ルールに一致しない通信はすべて拒否されると明記されています。 > Rules are an unordered set of allows. Traffic is denied when no rule matches. (参考: ateapi.proto#L609-L610) したがって、Actorが外部のLLM API(例: api.anthropic.com)へHTTPSで到達するには、そのホスト名を許可するEgressPolicyを別途作成しておく必要があります。ドキュメントが挙げている例は、api.example.comへのHTTPSを許可しつつAuthorizationヘッダーを実際の認証情報に差し替えるものです。 rules: - https: hostnames: ["api.example.com"] effects: replaceHeaders: - header: Authorization prefix: "Bearer " credentialUri: ate-secret://k8s.io/default/ns1/example-api/token (参考: egress-credential-injection.md#L69-L76) EgressPolicy は ActorTemplate の設定ではなく、Actor に対応づけて作成するリソースで、kubectl ateから個別に作成・更新します。 # Create an egress policy. kubectl ate create egress-policy <actor-name> -a <atespace> -f policy.yaml (参考: cmd/kubectl-ate/README.md#L247-L253) :::note info 5.2節のclaude-code-multiplexデモは、ANTHROPIC_API_KEYをActorTemplateの環境変数にそのまま埋め込む方式を取っており、4.2節で説明したegress credential injection(Actorはプレースホルダーしか持たず、egress gatewayが認証情報を差し替える方式)とは異なります。どちらの方式でも、Actorがapi.anthropic.comのようなホストにHTTPSで到達するには、該当ホストを許可するEgressPolicyがそのActorに設定されている必要がある点は共通です。 ::: 第6章: 構築にあたって追加で用意するもの ここまでの章で、Agent SubstrateはKubernetesのPod・CRD・オートスケーリングの上に成り立っていると説明してきましたが、実際に構築するには、Kubernetes クラスタに加えていくつかの周辺コンポーネント・前提条件を別途用意する必要があります。この章では、7章のkindでの検証を通じて実際に目にしたものも含めて、その一覧を整理します。 | 追加で用意するもの | 役割 | Agent Substrate側の扱い | |---|---|---| | PostgreSQL | Actor/Workerの頻繁に更新される状態を保持する専用state store(2.2節) | 開発/kind向けにはhack/install-ate-kind.shがPostgreSQLをPod内に同梱してデプロイする。本番では外部DBの接続文字列(ATE_API_POSTGRES_OWNER_CONNECTION_STRING等)を運用者が用意する必要がある | | オブジェクトストレージ(GCS/S3) | Actorのスナップショット(checkpoint)を永続化する保存先 | 外部スナップショットの保存先が必要。通常のインストールでは storage.bucketName (BUCKET_NAME) を指定し、kind 環境ではスクリプトが ate-snapshots を設定する。kind 環境ではS3互換のrustfsをクラスタ内に建てて代用する | | egress credential provider | Actorへの認証情報注入(4.2節)の実体となるSecret解決サービス | --credential-providerの指定自体が必須(注入を使わない場合も{"enabled":false}の明示が必要)。参照実装はKubernetes Secretを解決するkubernetes-secretsで、{"name":"k8s.io"}を指定するとインストーラが同梱のものをデプロイする | | gVisor / micro-VMのsandboxバイナリ | Actorを実際に動かすsandboxランタイム本体 | クラスタスコープのSandboxConfigリソースで指定する(7.2節で例示)。デフォルト設定はgs://gvisor/...という公開GCSバケットから取得する | | Envoy または agentgatewayのデータプレーンイメージ | atenet-router/atenet-egressが使うプロキシ本体 | --atenet-dataplaneでenvoy(カスタムビルドが必要)かagentgateway(公開イメージをpull)かを選ぶ | | オブジェクトストレージへのアクセス権限 | atelet・ate-api-serverのsidecar(objectstore-plugin)がスナップショットを読み書きするための認証 | プラグインはPodが持つ認証情報(ambient credentials)でGCSまたはS3にアクセスするため、利用するストレージに合わせてその権限をプラグインが使えるように設定する必要がある。GKE(GCS)の場合は、Workload Identity連携のIAMバインディングが前提で、無いと最初のsuspend/resumeでGCSから403が返る。kindではrustfs+ローカル認証情報で代用され、この手当ては不要 | | ストレージ用のCSIドライバ(条件付き) | CSI経由のVolume(永続ストレージ)をActorにアタッチする場合にのみ必要 | 今回使ったdurableDirのようなデフォルトのVolumeタイプには不要。使う場合は、通常のKubernetes CSIと異なりControllerをネットワーク越しのgRPCとして公開する必要があるなど、独自の追加要件がある | PostgreSQLが必須である点は、ドキュメントにも明記されています。 > External databases are never provisioned by ateapi or ate-setup. Their operators must create the database identities and schema described below before deploying Substrate. (参考: postgres.md#L8) オブジェクトストレージのバケット名(storage.bucketName/BUCKET_NAME)も、設定一覧の表で他の多くの設定のようなデフォルト値(—)を持たない項目として記載されています(参考: operator-install.md#L314)。 なお、3.5節のpodcertcontrollerが使うPodCertificateRequest・ClusterTrustBundleは、v1.37でGAになりデフォルトで有効です(PodCertificateRequestは3.5節、ClusterTrustBundleはkube_features.go#L1417-L1421を参照)。本記事はKubernetes v1.37以降を対象としているため、この2つのAPIを有効化するための追加作業はなく、表にも含めていません。podcertcontroller自体も、Agent Substrateがマニフェスト(manifests/ate-install/pod-certificate-controller.yaml)で同梱しており、別途用意する必要はありません。 オブジェクトストレージへのアクセス権限が無いとどうなるかは、GKE(GCS)で構築する場合について、セットアップツールのドキュメントに明記されています。 > On a fresh GCP project these bindings do not exist, and nothing else creates them — without them atelet cannot read or write external snapshots (403 from GCS on the first suspend/resume), ate-api cannot copy or collect them, and nodes cannot pull images. (参考: tools/setup-gcp/README.md#L154-L159) egress credential providerについても、「指定しない」という選択肢自体が無いことが明記されています。 > The provider is selected when the egress gateway is deployed, with --deploy-ate-system or --deploy-atenet, for either the Envoy or agentgateway dataplane. The selection is required: an install that wants no injection says so with {"enabled":false}. (参考: egress-credential-injection.md#L128-L131) :::note info Actorに永続ストレージをアタッチするためのストレージ用CSIドライバ(CreateVolumeなどを持つもの)については、Agent Substrateが通常のKubernetesとは異なる要件を課す点が特徴的です。CSI Controllerを「Podの中のUnixソケット」ではなく「クラスタネットワーク越しのgRPCサービス」として公開する必要があり、ドキュメントはこれを通常のKubernetesのCSI運用との違いとして明示しています。 > Substrate directly orchestrates storage operations from its control plane (ateapi) and node daemon (atelet), introducing specific deployment and networking requirements for both the CSI Controller and the CSI Node DaemonSet. (参考: csi-deployment.md#L5) ::: 7章で実際にkindクラスタ上に構築した際も、ここに挙げたPostgreSQL・オブジェクトストレージ(rustfs)・SandboxConfig・credential providerはすべて何らかの形で登場しています。kind向けのインストーラはこれらの多くを、開発用に同梱された代替物で自動的に用意してくれますが、本番環境では、外部 DB・ストレージ・IAM など利用する構成に応じた準備が必要です。provider や proxy など、インストーラがデプロイするものもあるため、すべてを別途手動で導入するという意味ではありません。 6.1 GKE で利用する場合の提供範囲 GKE 版は GKE Standard に OSS をインストールする構成です。Autopilot は対象外で、評価・非本番用途は利用できますが、本番サポートは allowlist による private GA です。Agent Substrate 自体の追加料金はなくても、作成する GKE などのリソースには料金がかかります。 (参考: Install Agent Substrate on GKE) 発表では、基盤側の ComputeClass と、任意の Filestore agent volumes 連携も紹介されています。後者は共有ファイルシステム向けで、sandbox の実行状態を保存するスナップショットとは役割が異なります。 また、再開500ms未満・毎秒500超の Suspend/Resume・従来比10倍の密度という性能値は Google の公表値です。OSS の設計目標や、この原稿の kind での確認結果とは分けて読みます。 (参考: Agent Substrate brings high-density, scalable, trusted infrastructure to GKE | Google Cloud Blog, 2026-09-16) 第7章: kindクラスタで実際に動かす この章では、公開記事に掲載していた kind での確認手順と実行結果を整理します。出力は既存の検証記録です。 7.1 手順 ビルドには、対象タグの go.mod が指定する Go 1.27.0 と、Docker などの開発環境が必要です。micro-VM を試す場合は /dev/kvm と仮想化の要件もあります。以下は gVisor を使う Counter デモの手順です。 (参考: go.mod#L1-L5、create-kind-cluster.sh#L74-L87) v0.5.0 を使うため、専用ディレクトリと検証用クラスタ名を指定します。クラスタ作成スクリプトは同名の既存クラスタを削除して作り直すため、既存環境と重複しない名前を使います。以降は、そのディレクトリで実行する手順です。 git clone --branch v0.5.0 --depth 1 https://github.com/agent-substrate/substrate.git substrate-v0.5.0 cd substrate-v0.5.0 export KIND_CLUSTER_NAME=ate-overview # create cluster and local registry (IPv4; IP_FAMILY=dual|ipv6 overrides) hack/create-kind-cluster.sh # install ate, PostgreSQL, rustfs hack/install-ate-kind.sh --deploy-ate-system --credential-provider='{"name":"k8s.io"}' # install counter demo hack/install-ate-kind.sh --deploy-demo-counter # install kubectl-ate go install ./cmd/kubectl-ate # create a counter actor in the demo's atespace (--template names the # actor template, resolved in the actor's atespace) kubectl ate create actor my-counter-1 -a ate-demo-counter --template counter # port-forward the network router to bind to local port `8000` kubectl port-forward -n ate-system svc/atenet-router 8000:80 (参考: README.md#L111-L129) hack/create-kind-cluster.shは、ローカルのDockerレジストリを建てたうえでkindest/node:v1.37.0のkindクラスタを作成します。cmd/podcertcontrollerが依存するClusterTrustBundle・PodCertificateRequestのfeature gateも、このスクリプトがkindのクラスタ設定で明示的に有効化しています。 スクリプトのコメントには Kubernetes v1.36 時点の説明が残っていますが、本記事の v1.37 ではこれらの機能は GA・デフォルト有効です。スクリプトは ClusterTrustBundleProjection なども明示的に設定しています。 (参考: create-kind-cluster.sh#L104-L113) hack/install-ate-kind.sh --deploy-ate-systemは、koでateapi・atecontroller・atelet・podcertcontrollerなどのGoバイナリをビルドしてローカルレジストリにpushし、PostgreSQL・rustfs(オブジェクトストレージ)・CRD・SandboxConfigを含めて一括でデプロイします。--deploy-demo-counterはさらにCounterデモ用のWorkerPoolとActorTemplateをデプロイし、golden snapshotの作成を待ちます。 7.2 デモのWorkerPoolとActorTemplate Counter デモが使う WorkerPool の抜粋です。テンプレート内のプレースホルダーや ko:// の参照は、インストーラが解決してから適用します。 apiVersion: ate.dev/v1alpha1 kind: WorkerPool metadata: name: counter namespace: ate-demo-counter labels: workload: counter spec: replicas: 3 workerImage: ko://github.com/agent-substrate/substrate/cmd/ateom-gvisor sandboxClasses: - name: gvisor template: resources: limits: cpu: "1" memory: 1Gi requests: cpu: 250m memory: 1Gi (参考: counter.yaml.tmpl#L26-L63) ActorTemplateは前述の通りKubernetesオブジェクトではなく、ate API の protobuf フィールドに対応した YAML として kubectl ate create actor-template に渡します。CLI が読み込んで API に送信します。 metadata: atespace: ate-demo-counter name: counter workerSelector: matchLabels: workload: counter containers: - name: counter image: ko://github.com/agent-substrate/substrate/demos/counter wakeupProbe: httpGet: path: /readyz port: 80 volumeMounts: - name: data mountPath: /home/counter resources: limits: - name: cpu quantity: "1" - name: memory quantity: 512Mi snapshotConfig: preferredFidelity: SNAPSHOT_FIDELITY_MEMORY storageLocation: gs://${BUCKET_NAME}/ate-demo-counter/ sandboxConfig: sandboxClass: gvisor configName: gvisor-default volumes: - name: data durableDir: {} (参考: counter-template.yaml.tmpl#L22-L69) snapshotConfig.preferredFidelity: SNAPSHOT_FIDELITY_MEMORYが、Suspend時にプロセスメモリごとスナップショットを取る設定です。sandboxConfigが名指ししているgvisor-defaultは、インストーラが標準で用意する SandboxConfig (クラスタスコープの CRD) で、gVisorのバイナリをGCSの公開バケットから取得する設定を持っています。 apiVersion: ate.dev/v1alpha1 kind: SandboxConfig metadata: name: gvisor-default annotations: sandboxconfig.ate.dev/is-class-default: "true" spec: sandboxClass: gvisor defaultVersion: v3 versions: - name: v3 state: Enabled pauseImage: "registry.k8s.io/pause:3.10.2@sha256:f548e0e8e3dc1896ca956272154dde3314e8cc4fde0a57577ee9fa1c63f5baf4" assets: arm64: gvisor: url: "gs://gvisor/releases/nightly/2026-10-10/aarch64/gvisor.tar.zstd" sha256: "aaf414f06eea7960238e43165c691f0a36c

    1 like
  9. Data Ownership and Privacy: Why Self-Hosted Object Storage Wins Self-hosted object storage does not make your data legally immune, but it changes every channel through which other parties reach it: the physical disk is yours, the legal process has to come to you, and the exit door has no per-GB toll. This article puts numbers on those three claims — CLOUD Act reach, GDPR fine ceilings, and S3 egress pricing — and is honest about what self-hosting costs you in return. | Fact (sourced, checked 2026-10-12) | Value | | --- | --- | | GDPR administrative fine ceiling (Art. 83(5)) | €20,000,000 or 4% of total worldwide annual turnover of the preceding financial year, whichever is higher | | Largest single GDPR fine to date (Meta, May 2023, EU-US transfers) | €1.2 billion | | CLOUD Act scope for US-based providers | Data in custody or control must be produced regardless of where it is stored | | AWS S3 data transfer out to internet | $0.09/GB after the first 100 GB/month (us-east-1) | | Moving 5 TB out of S3 in one month | (5,000 − 100) GB × $0.09 = $441 | | RustFS license and deployment surface | Apache 2.0; Linux, Docker, Podman, Kubernetes (Helm/Operator), macOS, Windows | What data ownership actually means Data ownership comes down to three channels of control. The first is physical: whose disks hold the bytes, and in which building. The second is legal: whose jurisdiction the operator answers to, and what process another government must follow to reach the data. The third is economic: what it costs you to leave, which is set almost entirely by egress pricing. When you upload an object to a managed cloud bucket, you keep the first channel only partially — the object is on hardware you will never touch, in a region you chose from a menu. You keep none of the second and third channels: the operator's legal exposure becomes your exposure, and leaving is metered. Self-hosting collapses all three channels onto infrastructure you administer. That is the entire argument, and every section below prices one piece of it. The legal examples here draw on EU and US frameworks because they are the pair most often cited in multi-jurisdiction deals; the analysis itself travels — a UN CTED report counts 128 states with some form of data protection or privacy legislation in force. Who can legally read your cloud bucket? A US-based provider can be compelled to produce data in its custody or control regardless of where the data physically sits. That is the Clarifying Lawful Overseas Use of Data (CLOUD) Act, enacted March 23, 2018 (Pub. L. 115-141, Division V), and it grew out of Microsoft Corp. v. United States — the 2013 case where the FBI sought email stored on Microsoft servers in Ireland. The Department of Justice states the principle directly: a company subject to US jurisdiction can be required to produce data it controls "regardless of where it chooses to store that data at any point in time." For a US-headquartered cloud operator, region selection inside that operator's menu does not remove this channel. It changes which building the disk is in, not who can be served a warrant. This is a fact about the operator's legal position, not a claim that any particular request will target your data. What does GDPR expect from your storage layer? GDPR does not care where your object storage runs; it cares who is accountable. If you self-host, you are the controller in full — the physical security of the disks, the encryption at rest, and the breach notification clock are all yours. That is more responsibility, and the fines scale to match: Article 83(5) caps administrative fines at €20,000,000 or 4% of total worldwide annual turnover, whichever is higher. The ceiling is not theoretical. In May 2023, Meta was fined €1.2 billion over EU-to-US personal data transfers, the largest GDPR fine issued to date. So self-hosting neither guarantees compliance nor removes the exposure. What it changes is legibility: when the data center is yours, data residency is a fact you can verify with a walkthrough of the rack, not a promise in a region table. For teams whose compliance posture requires proving where bytes physically live, that difference is the point. Egress fees are the quiet lock-in Lock-in is usually discussed as API compatibility, but the binding constraint is the exit invoice. AWS charges $0.09 per GB for data transfer out to the internet after the first 100 GB per month, aggregated across services. Moving a 5 TB dataset out of S3 Standard in one month costs (5,000 − 100) × $0.09 = $441 before you have paid any destination storage. A 50 TB archive costs roughly $4,490. The storage itself is $0.023 per GB-month for the first 50 TB; requests add $0.005 per 1,000 PUTs and $0.0004 per 1,000 GETs. Self-hosted storage has no egress meter. If the cluster sits on your own network, pulling 50 TB back is a file copy. This is why exit-cost modeling belongs in the original architecture decision, not the migration postmortem: the per-GB toll is the mechanism that turns "we can always move later" into "we will re-evaluate next year." What self-hosting gives you — and what it takes back The honest list starts with the costs. Self-host an object store and you inherit every operational duty the cloud vendor used to absorb: hardware capital, spares, power and cooling, physical security, capacity planning, disk replacement, durability engineering, patching, backup verification, and the on-call rotation when replication falls behind. You also stay the controller for legal purposes — a subpoena served on your company reaches your servers just as it would have reached your provider's, and a seizure order can take the hardware itself. Ownership is not anonymity. What you get is decision rights over all three channels. Physical: the disks are in rooms you control, and decommissioning means shredding media, not filing a support ticket. Legal: requests arrive addressed to you, where you can see them and respond (or litigate) under your own counsel. Economic: no egress meter, no per-request meter, and a license that does not expire — RustFS is Apache 2.0, so the storage layer itself costs nothing to keep running. The grey zone: self-hosted software on rented hardware Between "fully cloud" and "fully self-hosted" sits the deployment most teams actually build first: the storage software runs on a rented virtual machine. RustFS on an EC2 instance is the canonical example — you administer the server, hold the keys, and set the policies, but the physical disks belong to Amazon, and the CLOUD Act reaches the data exactly as it did before, through the provider. The egress meter comes back too, because the bytes still leave AWS over a billed pipe when you migrate. The three channels follow the hardware and the operator's jurisdiction, not the software license. Colocated machines you own, or racks in a facility you control, are what actually move the channels; a VM labeled "self-hosted" does not. Where RustFS fits in a self-hosted stack RustFS is a distributed object storage server written in Rust, S3-compatible, and licensed under Apache 2.0. The deployment surface in its documentation covers Linux (single-node or distributed), Docker, Podman, Kubernetes via Helm chart and Operator, macOS, and Windows. This means the "own hardware" in this article can be a rack of bare-metal servers, an on-premises Kubernetes cluster, or a single workstation. The feature table in its README (checked 2026-10-12) lists versioning, object lock, server-side encryption, and IAM/policies as available, with S3 Tables and MinIO on-disk compatibility marked as preview. Two things it does not do, stated plainly: it has no FUSE/POSIX file-system mode, and it does not come with a compliance certification — running it inside a regulated environment is your work, not a checkbox on a vendor's SOC 2 page. Version 1.0.0 reached GA on 2026-09-16; current releases are in the 1.0.x line. How to move without betting the archive Teams rarely regret self-hosting because of the software; they regret it when the migration plan assumed a perfect weekend. The workable pattern is incremental, and S3 compatibility is what makes it possible: stand up the self-hosted cluster, keep it secondary, and dual-write new objects to both locations while the primary stays on the cloud. Backfill historical data at whatever rate your uplink allows: a 50 TB backfill over a 1 Gbps link takes about five days, and it costs $0 in egress if it runs from a self-hosted copy, or roughly $4,490 if it must be pulled out of AWS. Verify by checksumming the way backup tools do, not by counting objects: object counts hide partial uploads. Cut over reads per bucket, per prefix, or per application — S3 client libraries make the endpoint a configuration value, which is the one genuinely portable part of the S3 ecosystem. A decision checklist before you sign the next cloud renewal Work through these five questions with numbers, not adjectives. First: what is your current monthly egress invoice, and what would one full migration cost at $0.09/GB? Second: which jurisdiction is your provider headquartered in, and does your compliance team accept that channel? Third: what is the durability story for self-hosting — N disks, N servers, N sites, and who replaces hardware at 2 a.m.? Fourth: can your team run versioning, retention, and encryption policies yourself, or were you relying on the vendor console? Fifth: what is the rehearsal plan — has anyone actually restored the archive end to end? If the answers to three or more of those are "we have not checked," the cloud invoice is not the risk. The unstated assumptions are. One cost the checklist must not skip is total cost of ownership. This article prices the exit channel — egress — because that number is published and uniform. Hardware capital, cross-site redundancy, power, spares, and staffing dominate the self-hosted side, vary by orders of magnitude between a homelab and a datacenter presence, and cannot be modeled generically without inventing figures. If the business case needs a TCO comparison, build it from your own hardware quotes and your team's actual payroll — not from any article, including this one. Frequently Asked Questions Is self-hosted object storage automatically GDPR compliant? No. Self-hosting changes where the data sits and who operates it, but you remain the data controller with the full Article 32 security obligation and the Article 83(5) fine exposure — up to €20,000,000 or 4% of worldwide turnover of the preceding financial year. Compliance comes from your controls (encryption, access, retention, breach response), not from the deployment location. Does the CLOUD Act apply to my own servers? The CLOUD Act compels US-based providers, not disks. If you operate your own hardware outside a US provider's tenancy, there is no provider to serve; legal process has to be addressed to you, under the laws of your jurisdiction. That is a real difference in the legal path — not a guarantee against government access, which no storage architecture provides. If your "self-hosted" cluster runs on a rented cloud VM, the answer flips back: the provider is subject to the Act, and the disks are the provider's. How much does it cost to migrate data out of S3? Egress is $0.09/GB after the first 100 GB per month. A 5 TB exit costs (5,000 − 100) × $0.09 = $441; a 50 TB archive costs about $4,490, charged by AWS whether or not your destination is cheaper. Migration tooling and destination storage are separate line items. What do I lose by self-hosting object storage? Vendor-managed durability, capacity elasticity, and the operations staff implicit in them. You inherit hardware replacement, replication monitoring, patching, and backup verification. Teams without an on-call rotation or a verified restore drill should treat that as a staffing decision before it becomes a storage decision. Is RustFS really free for commercial use? Yes — Apache 2.0, with no license meter and no seat count. What is not free is the infrastructure it runs on and the operational labor it requires. The README feature table lists versioning, object lock, server-side encryption, and IAM as available; S3 Tables and MinIO on-disk compatibility are marked preview. A note on how this was verified Article type: this is a documented-facts analysis piece, not a production deployment report. Legal texts (GDPR Art. 83, CLOUD Act / Pub. L. 115-141) were checked against primary sources in October 2026; AWS prices are published us-east-1 list rates as of 2026-10-12 and the migration examples are arithmetic derived from those rates, not measurements. RustFS deployment targets were checked against docs.rustfs.com; feature-status claims come from the project README and have not been re-tested hands-on for this article. No benchmark, latency, or durability number in this article was measured by the author. Verify current prices and the feature table before acting on any figure here. Sources - GDPR Article 83 — General conditions for imposing administrative fines: gdpr-info.eu - EDPB, "1.2 billion euro fine for Facebook as a result of EDPB binding decision" (22 May 2023): edpb.europa.eu - US Department of Justice, CLOUD Act resources and white paper announcement: justice.gov - Congressional Record, Sen. Hatch on the CLOUD Act (Feb 5, 2018): govinfo.gov - UN CTED report, "The State of International Cooperation for Lawful Access to Digital Evidence" (128-state count): un.org - Amazon S3 pricing (us-east-1 list rates): aws.amazon.com - RustFS documentation — installation targets: docs.rustfs.com - RustFS GitHub README (license and feature status): rustfs/rustfs

    1 reaction
  10. rustfs: RustFS is an open-source, S3-compatible high-performance object storage system supporting migration and coexistence with other S3-compatible platforms such as MinIO and Ceph. ★34977 rustfs/rustfs

Achievements

Awards earned by the repositories rustfs owns. Select an award to embed its badge.

Gold
11st place finishes
Silver
22nd place finishes
Bronze
13rd place finishes
Awarded repositories
1Owned by rustfs now

Repositories

Ordered by gold, then silver, then bronze.

  1. rustfsBest: 1st by stars, 358 stars121

Every award

Top-three finishes of these repositories in completed trending days and weeks, newest first.

  1. rustfs
  2. rustfs
  3. rustfs
  4. rustfs

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.