The module lifecycle stage: Experimental

The module has requirements for installation

Before enabling the module, you need to allow the use of experimental modules in the deckhouse ModuleConfig. To do this, set the allowExperimentalModules parameter to true.

v0.0.8

Release date: 2026-11-05

One BucketAccess can now grant a single key to several buckets, module resources record Kubernetes Events, and SeaweedFS stores get safer upgrades, per-node capacity alerts and working lifecycle expiration.

Highlights

Changes in this release:

  • BucketAccess.spec.buckets lists up to 16 Buckets of the namespace, and the access issues one key pair and one Secret for all of them instead of one per bucket.
  • SeaweedFSStore, SDSElasticStore, ObjectStore, BucketContents, Bucket and BucketAccess record a Kubernetes Event when their Ready condition changes.
  • SeaweedFS stores are upgraded one StatefulSet at a time behind PodDisruptionBudgets, alert when a single volume node fills up, and actually delete objects expired by spec.lifecycle.

New features

This release adds:

  • BucketAccess.spec.buckets names up to 16 Buckets of the access’s namespace and can be changed after creation; exactly one of spec.bucketRef and spec.buckets is set. An access moves from bucketRef to buckets by keeping the bucket it already holds, so no new key is issued, and cannot move back. The Secret gains S3_BUCKETS and S3_BUCKET_<NAME>; S3_BUCKET names the first bucket of the list. status.storeRef records the store that holds the key, and status.buckets reports the state of each bucket. Only buckets served by the store that holds the key are granted, and the admission webhook warns when the list spans several stores.
  • SeaweedFSStore, SDSElasticStore, ObjectStore, BucketContents, Bucket and BucketAccess record a Kubernetes Event when their Ready condition changes status or reason: a Warning when it turns False, a Normal one when it turns True again. The reason of the Event is the reason of the condition, for example ReconcileFailed, Reconciled or RetentionActive. A reconcile pass that changes nothing records no Event. The Events of a Bucket and a BucketAccess are in their namespace, those of the cluster-scoped kinds in the default namespace.
  • The annotation storage.deckhouse.io/scrub on a SeaweedFSStore, set to an RFC3339 time or a Unix timestamp, requests an extra integrity scrub without changing the schedule. The request survives controller restarts until a scrub completes and also works with spec.integrity.enabled: false; autoRepair applies to it as to a scheduled pass.
  • status.capacity.nodes of SeaweedFSStore and SDSElasticStore reports the capacity of each storage node when the backend exposes it (SeaweedFS volume servers); a Ceph-backed store keeps reporting the cluster as a whole.

Improvements

This release improves:

  • D8SdsObjectStoreFillingUp and D8SdsObjectStoreAlmostFull now fire on the fullest SeaweedFS volume node rather than on the store’s average, and name the node, so a single full node is reported before writes fail on it. Ceph-backed stores keep the cluster-wide check.
  • A Bucket carries the informational FeaturesApplied condition of its BucketContents, so the namespace tenant sees which requested features the backend does not enforce, for example spec.quota.maxObjects on SeaweedFS. It does not gate Ready.
  • Module upgrades roll SeaweedFS data planes through a global queue: one StatefulSet of one store at a time, each waiting until its new revision is fully ready, so an image update no longer restarts every S3 endpoint at once. A degraded store holds the queue, and its BackendReady message names the rollout being awaited.
  • The module creates a PodDisruptionBudget for every SeaweedFS master, volume-server and filer set: a voluntary disruption keeps a master quorum and at least one data-plane replica. Readiness and liveness probes use the SeaweedFS HTTP health endpoints instead of an open TCP port.
  • Copy counts of a SeaweedFS store keep being updated while BackendReady is False; only the full data scrub waits until the backend is ready again.

Fixes

This release fixes:

  • Objects expired by Bucket.spec.lifecycle on SeaweedFS are now deleted: the module runs a lifecycle worker per SeaweedFSStore that processes all shards every five minutes. Before, the lifecycle configuration was stored but nothing applied it.
  • Growing SeaweedFSStore.spec.storage.sizePerNode on the Deckhouse or Rancher local-path provisioners is rejected by the admission webhook. These provisioners advertise allowVolumeExpansion: true but cannot resize a claim, so the edit used to leave the store waiting for a resize forever.
  • A BucketContents without spec.bucketRef, left by module versions that predate the field, accepts updates again. The immutability rule of spec.bucketRef failed on every write to such contents with no such key: bucketRef, so their status could not be updated and, once deleted, they stayed in Terminating. spec.bucketRef itself stays immutable.
  • A Bucket or BucketContents being deleted can lose its finalizer again. The admission webhooks checked that update too, so lowering a class quota ceiling or switching its reclaimPolicy left the Buckets of that class hanging in deletion, and a BucketContents collision made both objects undeletable.
  • A BucketAccess no longer resets its key rotation time on reconcile passes that do not issue a new key.
  • On SeaweedFS the module removes bucket policies during reconciliation: public read is served by the module-managed anonymous identity, and policies written by tenant keys on older SeaweedFS versions could keep a bucket public. The ownership tags of a bucket the module provisioned are restored if an application replaced the tag set; the application’s other tags are kept.

Security updates

Security updates in this release:

  • The SeaweedFS master, volume, filer and lifecycle Pods drop all capabilities, forbid privilege escalation and use the RuntimeDefault seccomp profile, so they comply with the Pod Security Standards that DKP 1.79 enforces in system namespaces. The data-plane Pods still run as root, allowed by the seaweedfs-data-plane SecurityPolicyException; the lifecycle worker runs as UID 64535.
  • golang.org/x/net v0.62.0 (CVE-2026-78661, CVE-2026-97045), github.com/go-jose/go-jose/v4 v4.1.6 (CVE-2026-116585, CVE-2026-116586, CVE-2026-116587) and github.com/klauspost/compress v1.18.7 (CVE-2026-63209); github.com/docker/distribution is no longer part of the hooks (CVE-2026-85747). Images are built with Go 1.27.3.

Upgrade notes

Before upgrading, note the following:

  • Nothing has to be done by hand. After the upgrade the SeaweedFS data planes restart one StatefulSet at a time.
  • A single-replica SeaweedFS component (master, volume server or filer) can no longer be evicted by a node drain until the administrator changes or removes its PodDisruptionBudget; plan drains of such nodes accordingly.
  • Bucket policies on SeaweedFS buckets are removed at the next reconcile. Public read keeps working through accessPolicy: PublicRead; a policy set directly through the S3 API is not kept.

Docs

Documentation changes:

  • USAGE describes one BucketAccess for several buckets, node failure windows of a SeaweedFS store, how a Released bucket really stops being public (recreate the Bucket with accessPolicy: Private), and how SeaweedFS enforces spec.quota.maxSize.

v0.0.7

Release date: 2026-10-01

A SeaweedFS store can now grow its disks in place and sizes its metadata store from the objects actually written, and an SDSElasticStore can use a Ceph cluster in any namespace.

Highlights

Changes in this release:

  • SeaweedFSStore.spec.storage.sizePerNode can now be increased on a live store: the module expands the PersistentVolumeClaim of every volume server in place, without interrupting S3 service.
  • The filer’s metadata store is no longer fixed at 2Gi: it starts at one percent of the store’s usable capacity and grows with the number of objects written, so a store of small objects no longer stops serving with its data disks nearly empty.
  • SDSElasticStore.spec.elasticClusterNamespace points a store at an ElasticCluster outside d8-sds-elastic, following sds-elastic’s move to one Ceph cluster per namespace.

New features

This release adds:

  • SeaweedFSStore.spec.storage.sizePerNode can be increased after creation (it was immutable). The claims are expanded in place, the volume servers’ StatefulSet is rebuilt around its running pods so that new volume servers get the new size, and a volume server is restarted, one at a time, only if its CSI driver leaves the resize pending for five minutes, that is, can only grow an unmounted filesystem. The StorageClass must have allowVolumeExpansion: true; on one without it the edit is rejected by the admission webhook with the reason. Shrinking stays rejected, and removing the field from a store larger than the 10Gi default counts as shrinking.
  • SeaweedFSStore.status.storage reports the storage as it actually is: sizePerNode (the smallest volume server disk, which reaches the spec value once every disk has grown), metadataSize, volumeSizeLimitMB, slotsPerServer, and message, which explains why the disks are not yet the size the spec asks for. The volume budget SeaweedFS runs with follows the disks rather than the request.
  • The filer’s metadata store (the LevelDB volume on the filer’s claim, or the managed PostgreSQL instance with spec.metadataStore: Postgres) is sized automatically: one percent of the store’s usable capacity at creation, floored at 2Gi, then about 2 KiB per stored object as objects are written. It grows in place and never shrinks. If the StorageClass does not allow expansion, the store keeps serving and says so in status.storage.message. Not applicable to a store whose metadata lives in an external database.
  • SDSElasticStore.spec.elasticClusterNamespace names the namespace of the referenced ElasticCluster and defaults to d8-sds-elastic. The store’s CephObjectStore, RGW Service, credentials Secrets, KMS token copy and published route and listener are created in that namespace. The field is immutable after creation, like elasticClusterRef, and is shown with -o wide.
  • On Deckhouse 1.78 and later the module ships the capability/scope RBAC roles d8:system-capability:sds-object:view and d8:system-capability:sds-object:edit for SeaweedFSStore, SDSElasticStore, ObjectStore, Bucket and BucketAccess. Earlier Deckhouse versions keep the unchanged d8:manage:permission:module:sds-object:* roles.

Improvements

This release improves:

  • The admission webhook looks up the ElasticCluster of an SDSElasticStore in spec.elasticClusterNamespace, so a store referencing a name that exists only in another namespace is warned that its dependency is missing, and status messages name the cluster as <namespace>/<name>.
  • SeaweedFS masters run with -telemetry=false: SeaweedFS 4.41 and later send usage reports to telemetry.seaweedfs.com by default, and a store no longer does.

Security updates

Security updates in this release:

  • The SeaweedFS image was rebuilt with gRPC, golang.org/x/mod and OpenTelemetry versions that close the findings the vulnerability scan reported against it.

Upgrade notes

Before upgrading, note the following:

  • Nothing has to be done by hand. An existing SDSElasticStore gets elasticClusterNamespace: d8-sds-elastic, where sds-elastic also migrates clusters created before ElasticCluster became namespaced, so nothing moves.
  • A namespace other than d8-sds-elastic in spec.elasticClusterNamespace requires sds-elastic v0.1.0 or later, in which ElasticCluster is namespaced.
  • An existing SeaweedFSStore whose usable capacity exceeds about 200Gi, or which already holds more than about a million objects, has its metadata store expanded after the upgrade. This needs allowVolumeExpansion: true on the StorageClass (or managed-postgres to resize the Postgres volume). Without it the store keeps serving at its current size and reports the shortfall in status.storage.message.

Docs

Documentation changes:

  • USAGE describes how to grow a SeaweedFS store’s disks, how the metadata store sizes itself, and the new error messages for resizing.

Dependencies

Dependency updates:

  • SeaweedFS: 4.39 → 4.47 (changelog)
    • Master telemetry, on by default since 4.41, is turned off by the module.

v0.0.6

Release date: 2026-09-19

A SeaweedFS store now sizes its volume budget and its integrity checks from the capacity it was asked for, instead of from constants that capped every volume server at 256 GiB and every scrub at twelve hours.

Highlights

Changes in this release:

  • A store no longer stops taking writes at 256 GiB per volume server: the volume size and the slot count follow spec.storage.sizePerNode, so a full store is a full disk.
  • The capacity a store publishes is bounded by what its volume slots can address, so the fill alerts describe the state the store is actually in.
  • An integrity check budgets its time by how much there is to read, and a pass that read nothing no longer counts as a check.

New features

This release adds:

  • SeaweedFSStore.spec.volumeIndex chooses where a volume server keeps its needle map: Memory (the default, unchanged behaviour) or LevelDB. With LevelDB the index cost is bounded by the volume count rather than by the number of objects, and the Pod reserves it.
  • The alert D8SdsObjectStoreScrubIncomplete reports an integrity check that left volume servers unchecked — a store can be scrubbed on schedule and still have part of it never read.

Improvements

This release improves:

  • SeaweedFSStore.spec.volumeServers must now be at least the copy count in spec.replication, and a store that asks for fewer is rejected at creation instead of never provisioning a bucket.
  • The alert D8SdsObjectStoreAlmostFull fires above 90% instead of 95% — the point where the SeaweedFS master stops placing new volumes on a disk, so the warning no longer arrives after the store has already stopped taking buckets.
  • A store that has run out of places for a volume says so in terms of what breaks: writes into existing buckets fail too, not only bucket creation.
  • The API types and the CRDs now carry the same validation, including nine rules that previously existed only in the manifests; USAGE documents which fields a live store will and will not let you change.

Fixes

This release fixes:

  • A SeaweedFSStore whose volume servers held more than 256 GiB each stopped accepting writes with No writable volumes and no free volumes left while its disks were nearly empty, and the store’s own status reported plenty of room.
  • An integrity check over a store larger than roughly three volume servers of 2Ti was cut off mid-pass, and the volume servers it never reached were reported as unreachable rather than as unchecked.
  • spec.storage.sizePerNode was accepted and silently ignored on an existing store: it is the size of a StatefulSet volume claim template, so a new value reached no disk. It is now immutable, with the reason in the rejection message.
  • The controller’s metrics endpoint could not authenticate scrapes, so the module’s own metrics were missing.

Security updates

Security updates in this release:

  • The dependencies of the controller and SeaweedFS images were upgraded to the versions that close the findings the vulnerability scan reports.

Breaking changes

Changes that affect backward compatibility:

  • spec.storage.sizePerNode and spec.masters are immutable after creation, and spec.volumeServers may only be increased. Edits that used to be accepted — and, in the case of sizePerNode, quietly did nothing — are now rejected.
  • A SeaweedFSStore with fewer volume servers than spec.replication asks copies for is rejected. Such a store could never place a volume, so nothing that worked before stops working.

Upgrade notes

Before upgrading, note the following:

  • Nothing has to be done by hand. Existing stores keep their disks and their data: the masters and volume servers restart to pick up the new budget, which only ever raises a store’s ceiling, and the volumes already written are directed to again.
  • A store sitting between 90% and 95% full will raise D8SdsObjectStoreAlmostFull after this upgrade. That is the point at which the master has stopped placing new volumes on its disks, which is what the alert now reports.
  • A store whose spec.volumeServers is below the copy count in spec.replication keeps running and keeps its status updated, but any other edit to it is refused until the count is raised.

Docs

Documentation changes:

  • The Russian translation of the CRD reference and of DESIGN was brought back in step with the English text.

v0.0.5

  • Fix: spec.placement (nodeSelector and tolerations) is now finally applied. The field has been in the API from the start and documented as affecting data plane pods, but it was never read — the API accepted it, but pods were scheduled as if nothing was specified. It now applies to all pods deployed by the module: masters, volume servers, and filers.
  • Replicas of the same component are spread across different nodes. For masters and volume servers, the rule is strict: three volume servers on the same node cannot survive the failure of that node, and the replication code promised copies on other servers. A replica that does not get its own node remains in Pending — this is the visible form of an impossible promise. Filers prefer to spread out, but will still start even when they cannot: they sit in front of metadata, which resides elsewhere.
  • The update will affect already deployed storage: masters and volume servers will be restarted, and on a cluster where there are fewer nodes than replicas, some pods will not come up until the number of replicas is reduced or nodes are added.
  • A new field spec.postgresClassName has been added — the name of the PostgresClass from which the managed metadata database is deployed. This is the only way to influence the placement of its pods: the Postgres resource has no scheduling fields at all; tolerations and nodeSelector belong to the PostgresClass, so spec.placement does not reach the database. If empty, the default class is used.

v0.0.4

  • Storage reports its capacity: status.capacity with total, used, free, and used fraction — on both backends. Until at least one node responds, the field is not populated: “unknown” and “empty” are different things
  • Module metrics, alerting rules, and dashboard: storage readiness and its fill level. Alerts warn about fill above 85% and above 95%, and storage that is not Ready for half an hour is reported separately from storage that has simply stopped reporting
  • Filer metadata can be kept in an external PostgreSQL: metadataStore External and a Secret with connection parameters (host, port, database, username, password, sslmode, ca.crt), including server verification with a custom CA. The connection is verified before filers start, and sslmode disable and values unknown to libpq are rejected — passwords and all object names travel over this connection
  • Bucket lifecycle expiration rules: spec.lifecycle with expireAfterDays, expireNoncurrentAfterDays, and abortIncompleteUploadsAfterDays and an optional prefix; each rule requires a unique id. Rules are removed from a bucket when they are deleted from the Bucket. There is no storage tier transition: one of the engines does not perform it, and a field that silently does nothing is not introduced
  • A bucket that already exists in the backend and is not owned by the module is no longer picked up by name: the module marks its own buckets and refuses to touch another’s, reporting this in the BucketNotOwnedByModule condition
  • The documentation now includes a matrix of S3 operation support across backends, indicating the engine versions for which it is filled
  • Fix: filers restart when the metadata database connection changes — previously, password rotation updated the configuration, but running filers continued to use the old credentials
  • Fix: the Secret with filer configuration is no longer overwritten on every reconciliation cycle
  • Internal changes: unit tests for reconcilers, e2e scenarios, and internal project documentation

v0.0.3

  • The resource model has been redesigned and is incompatible with the previous version — the module no longer manages resources created under the old model; they must be recreated
  • The System and Lightweight profiles have been removed along with the Garage backend. Two backends remain — SeaweedFS and Ceph RGW
  • ObjectStore no longer deploys storage; instead, it describes a class modeled after StorageClass: it references a store via spec.storeRef, while the storage itself is described by the new SeaweedFSStore and SDSElasticStore resources
  • BucketClaim is now called Bucket and remains in the namespace, while the previous cluster-wide Bucket is now called BucketContents. Data saved with reclaimPolicy Retain waits in the Released phase and is picked up by a newly created Bucket
  • BucketClaimPolicy has been removed. Along with it, shared access to a single bucket from multiple namespaces and binding to an existing bucket via regex are gone
  • Publishing the S3 endpoint externally: spec.publish on the store defines the hostname, Gateway, and TLS certificate; the module creates a listener and route and publishes the external address in status.endpoint.external. Publishing without TLS is prohibited
  • The spec.endpointScope field in BucketAccess selects which address — internal or external — is placed in the Secret with credentials; when the endpoint changes, issued Secrets are reissued
  • Setting PublicRead in the bucket’s spec.accessPolicy enables anonymous object reading on both backends. Listing bucket contents is not provided in this case
  • Versioning and Object Lock on both backends: GOVERNANCE and COMPLIANCE modes, retention period, and legal hold. Deleting data under an unexpired retention is blocked, and the reason for failure is recorded in the status
  • Data integrity report: status.integrity and condition IntegrityHealthy. On SeaweedFS, the module automatically runs scrubbing on a schedule (spec.integrity) and, with separate permission, replaces a damaged copy; on Ceph RGW, the scrub state is taken from the cluster
  • Data copy accounting: status.redundancy and condition RedundancyHealthy report volumes that have fewer copies than requested by replication
  • Metrics, alerts, and a Grafana dashboard for data integrity, copy shortage, and overdue scrubbing
  • Stored data encryption: spec.encryption. On SeaweedFS — server-side encryption with a key from a Secret; key rotation is not applied silently but held for confirmation, otherwise already written objects would become unreadable. On Ceph RGW, keys are taken from Deckhouse Stronghold
  • Data plane components are secured with network policies
  • Fix: the RGW user identifier fits within Rook’s label length limit — previously, a bucket on Ceph RGW could not become Ready
  • Fix: the bucket policy is assembled from the entire cluster state, not via incremental patches — eliminating races when multiple accesses target the same bucket
  • Fix: the ‘data is held by retention’ failure is determined from the bucket itself, not its spec — previously, any access error looked the same and permanently held the finalizer
  • Fix: deleting a Bucket wakes up its content controller, and a deleting Bucket is no longer considered a live owner — otherwise, a bucket could remain in the backend without a single reference to it
  • Fix: the number of slots for SeaweedFS volumes is no longer calculated based on disk size; a volume grows one per bucket
  • Fix: a store does not report readiness if it cannot accept a bucket
  • Fix: scrubbing is performed outside the reconciliation loop — previously, it did not fit within its budget and did not complete; a pass that covers no volumes is not considered a check
  • Unified logger and metrics server for the controller
  • Internal changes to the module build

v0.0.2

  • Bugfix: the data-plane images (Garage, SeaweedFS) are pulled with the module-scoped registry secret - previously the pull failed in clusters with a private registry
  • CVE fixes
  • lib-helm updated to 1.72.13

v0.0.1

  • Initial release of the module in the Experimental stage. The module manages S3-compatible object storage through a set of ObjectStore, Bucket, BucketClaim, BucketAccess and BucketClaimPolicy resources
  • ObjectStore deploys and maintains the storage itself according to one of four profiles: System (Garage on control-plane nodes, local PVs), Lightweight (Garage on PVC), Full (SeaweedFS) and Heavy (Ceph RGW on an existing sds-elastic cluster)
  • BucketClaim requests a bucket — either its own private one or a binding to a shared bucket allowed by a BucketClaimPolicy resource in the bucket owner’s namespace (deny by default)
  • BucketAccess issues a separate access key and writes a Secret with standard connection variables; the key is rotated using the storage.deckhouse.io/rotate annotation
  • Module resources publish status.conditions and status.observedGeneration