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.bucketslists 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,BucketandBucketAccessrecord a Kubernetes Event when theirReadycondition 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.bucketsnames up to 16 Buckets of the access’s namespace and can be changed after creation; exactly one ofspec.bucketRefandspec.bucketsis set. An access moves frombucketReftobucketsby keeping the bucket it already holds, so no new key is issued, and cannot move back. The Secret gainsS3_BUCKETSandS3_BUCKET_<NAME>;S3_BUCKETnames the first bucket of the list.status.storeRefrecords the store that holds the key, andstatus.bucketsreports 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,BucketandBucketAccessrecord a Kubernetes Event when theirReadycondition changes status or reason: aWarningwhen it turnsFalse, aNormalone when it turnsTrueagain. The reason of the Event is the reason of the condition, for exampleReconcileFailed,ReconciledorRetentionActive. A reconcile pass that changes nothing records no Event. The Events of aBucketand aBucketAccessare in their namespace, those of the cluster-scoped kinds in thedefaultnamespace.- The annotation
storage.deckhouse.io/scrubon aSeaweedFSStore, 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 withspec.integrity.enabled: false;autoRepairapplies to it as to a scheduled pass. status.capacity.nodesofSeaweedFSStoreandSDSElasticStorereports 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:
D8SdsObjectStoreFillingUpandD8SdsObjectStoreAlmostFullnow 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
Bucketcarries the informationalFeaturesAppliedcondition of itsBucketContents, so the namespace tenant sees which requested features the backend does not enforce, for examplespec.quota.maxObjectson SeaweedFS. It does not gateReady. - 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
BackendReadymessage 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
BackendReadyisFalse; only the full data scrub waits until the backend is ready again.
Fixes
This release fixes:
- Objects expired by
Bucket.spec.lifecycleon SeaweedFS are now deleted: the module runs a lifecycle worker perSeaweedFSStorethat processes all shards every five minutes. Before, the lifecycle configuration was stored but nothing applied it. - Growing
SeaweedFSStore.spec.storage.sizePerNodeon the Deckhouse or Rancher local-path provisioners is rejected by the admission webhook. These provisioners advertiseallowVolumeExpansion: truebut cannot resize a claim, so the edit used to leave the store waiting for a resize forever. - A
BucketContentswithoutspec.bucketRef, left by module versions that predate the field, accepts updates again. The immutability rule ofspec.bucketReffailed on every write to such contents withno such key: bucketRef, so their status could not be updated and, once deleted, they stayed inTerminating.spec.bucketRefitself stays immutable. - A
BucketorBucketContentsbeing deleted can lose its finalizer again. The admission webhooks checked that update too, so lowering a class quota ceiling or switching itsreclaimPolicyleft the Buckets of that class hanging in deletion, and aBucketContentscollision made both objects undeletable. - A
BucketAccessno 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
RuntimeDefaultseccomp 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 theseaweedfs-data-planeSecurityPolicyException; the lifecycle worker runs as UID 64535. golang.org/x/netv0.62.0 (CVE-2026-78661, CVE-2026-97045),github.com/go-jose/go-jose/v4v4.1.6 (CVE-2026-116585, CVE-2026-116586, CVE-2026-116587) andgithub.com/klauspost/compressv1.18.7 (CVE-2026-63209);github.com/docker/distributionis 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:
USAGEdescribes oneBucketAccessfor several buckets, node failure windows of a SeaweedFS store, how aReleasedbucket really stops being public (recreate theBucketwithaccessPolicy: Private), and how SeaweedFS enforcesspec.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.sizePerNodecan 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.elasticClusterNamespacepoints a store at anElasticClusteroutsided8-sds-elastic, following sds-elastic’s move to one Ceph cluster per namespace.
New features
This release adds:
SeaweedFSStore.spec.storage.sizePerNodecan 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 haveallowVolumeExpansion: 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 the10Gidefault counts as shrinking.SeaweedFSStore.status.storagereports 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, andmessage, 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 at2Gi, 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 instatus.storage.message. Not applicable to a store whose metadata lives in an external database. SDSElasticStore.spec.elasticClusterNamespacenames the namespace of the referencedElasticClusterand defaults tod8-sds-elastic. The store’sCephObjectStore, RGW Service, credentials Secrets, KMS token copy and published route and listener are created in that namespace. The field is immutable after creation, likeelasticClusterRef, 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:viewandd8:system-capability:sds-object:editforSeaweedFSStore,SDSElasticStore,ObjectStore,BucketandBucketAccess. Earlier Deckhouse versions keep the unchangedd8:manage:permission:module:sds-object:*roles.
Improvements
This release improves:
- The admission webhook looks up the
ElasticClusterof anSDSElasticStoreinspec.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 totelemetry.seaweedfs.comby default, and a store no longer does.
Security updates
Security updates in this release:
- The SeaweedFS image was rebuilt with gRPC,
golang.org/x/modand 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
SDSElasticStoregetselasticClusterNamespace: d8-sds-elastic, where sds-elastic also migrates clusters created beforeElasticClusterbecame namespaced, so nothing moves. - A namespace other than
d8-sds-elasticinspec.elasticClusterNamespacerequires sds-elastic v0.1.0 or later, in whichElasticClusteris 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: trueon the StorageClass (or managed-postgres to resize the Postgres volume). Without it the store keeps serving at its current size and reports the shortfall instatus.storage.message.
Docs
Documentation changes:
USAGEdescribes 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.volumeIndexchooses where a volume server keeps its needle map:Memory(the default, unchanged behaviour) orLevelDB. WithLevelDBthe index cost is bounded by the volume count rather than by the number of objects, and the Pod reserves it.- The alert
D8SdsObjectStoreScrubIncompletereports 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.volumeServersmust now be at least the copy count inspec.replication, and a store that asks for fewer is rejected at creation instead of never provisioning a bucket.- The alert
D8SdsObjectStoreAlmostFullfires 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;
USAGEdocuments 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 leftwhile 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.sizePerNodewas 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.sizePerNodeandspec.mastersare immutable after creation, andspec.volumeServersmay only be increased. Edits that used to be accepted — and, in the case ofsizePerNode, quietly did nothing — are now rejected.- A SeaweedFSStore with fewer volume servers than
spec.replicationasks 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
D8SdsObjectStoreAlmostFullafter 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.volumeServersis below the copy count inspec.replicationkeeps 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
DESIGNwas 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