Skip to content

docs: add the Blockstor storage backend and its migration guide - #667

Open
Andrei Kvapil (kvaps) wants to merge 8 commits into
mainfrom
docs/blockstor-storage-backend
Open

docs: add the Blockstor storage backend and its migration guide#667
Andrei Kvapil (kvaps) wants to merge 8 commits into
mainfrom
docs/blockstor-storage-backend

Conversation

@kvaps

Copy link
Copy Markdown
Member

Documents Blockstor as a second, selectable storage control plane, and how to move an existing cluster onto it.

Goes to content/en/docs/next/ because Blockstor is unreleased.

What changes

The platform install page presented LINSTOR as the only option. It now presents the choice as tabs under a new 2.3. Choose a Storage Backend — LINSTOR first and marked as the default, Blockstor second and marked experimental. Subsequent sections are renumbered.

The storage section index said these guides describe "the LINSTOR storage system"; it now names both backends, links to the choice and the migration, and states that unless a page says otherwise it describes LINSTOR.

Adds Migrating from LINSTOR to Blockstor.

About the migration guide

It is written from an actual run against a live cluster rather than from the design, so it leads with the parts that are easy to get wrong and that only a real migration surfaces:

  • Adoption comes after the switch, not before. The converted resources have nowhere to live until the CRDs are installed, and those arrive with the backend.
  • The controller has to be stopped while the converted resources are applied. Applying them in one pass makes the resource definitions visible before the replicas further down the file exist; a running controller reacts by auto-placing replicas with freshly allocated ports, and the real replicas are then refused because a DRBD port is settable-once. The result is replicas whose ports disagree with the live mesh.
  • The live DRBD ports must be read from the kernel. LINSTOR does not store them in its custom resources, so the converter cannot recover them from a dump; the guide gives the drbdsetup show incantation, including the detail that the port sits inside the connection's path block rather than on an address line.
  • A cluster coming from LINSTOR hits a Service ownership collision that a fresh install never sees, because piraeus-operator already owns the name the compatibility shim wants.
  • A version requirement, stated plainly. A Blockstor build that cannot read a pool name from StorDriver/StorPoolName logs unknown storage pool and adopts nothing — that is every release up to and including v0.1.17.

Status of the procedure

The steps up to and including the backend switch were executed against a live 3-node cluster with 41 volumes: the control plane was replaced with no data loss and no service disruption, DRBD kept serving throughout, and all peers stayed UpToDate.

Adoption itself was not completed, because no published Blockstor release contains the storage-pool fix above. The guide reflects what the run established, and its ordering and controller-stop steps are written precisely because the failure modes were observed. It should be re-verified end to end once a release with that fix exists — and this PR should not merge ahead of the platform change it documents, cozystack/cozystack#2942.

Testing

hugo --gc --environment development builds clean. The tabs render as real tab controls with both labels, the migration page renders, and the cross-link anchor #23-choose-a-storage-backend exists on the target page.

Cozystack is gaining a second storage control plane, so the platform
install page now presents the choice as tabs rather than assuming
LINSTOR. LINSTOR stays the default and is the first tab; Blockstor
carries an experimental warning.

Adds a migration guide for moving an existing cluster across. It is
written from a run against a live cluster, so it leads with the parts
that are easy to get wrong: the adoption happens after the switch, not
before, because the resources have nowhere to live until the CRDs
exist; the controller has to be stopped while the converted resources
are applied, or it auto-places replicas on freshly allocated ports and
the real ones are then refused; the live DRBD ports have to be read
from the kernel because LINSTOR does not keep them in its custom
resources; and a cluster coming from LINSTOR hits a Service ownership
collision a fresh install never sees.

The guide also states the version requirement plainly: a Blockstor
build that cannot read a pool name from StorDriver/StorPoolName adopts
nothing, which is every release up to and including v0.1.17.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
@netlify

netlify Bot commented Aug 25, 2026

Copy link
Copy Markdown

Deploy Preview for cozystack ready!

Name Link
🔨 Latest commit 49cecf1
🔍 Latest deploy log https://app.netlify.com/projects/cozystack/deploys/6a8ee2a4a684a800084149fb
😎 Deploy Preview https://deploy-preview-667--cozystack.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4154d631-890c-468c-95c6-2c46357d310c


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

linstor-csi keeps reconciling while the control plane is being replaced,
and a volume it cannot find is a volume it re-provisions: it creates a
fresh resource definition through the LINSTOR-compatible API, carrying
newly allocated DRBD minors and ports and a different node ID, for a
volume that already exists and holds data. Those definitions also lack
spec.initialized, so the satellite treats them as new.

Observed on a live migration, where two already-migrated volumes were
re-provisioned this way during the switch window.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Two things claim space during adoption, and both surfaced on a live
migration only after the switch had started.

Blockstor's thick provider reserves the full size of every volume it
adopts. LINSTOR allows a pool declared thick to hold sparse volumes via
StorDriver/ZfscreateOptions, so a comfortably oversubscribed pool can
fill up mid-migration and leave the last volumes unadoptable.

Blockstor also reconciles replica count against the resource group's
placeCount continuously, where LINSTOR only places on request, so every
under-replicated volume is topped up as soon as the controller starts.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The runbook stopped only the CSI provisioner, and only at the switch.
Two writers were left running through the backup and the dump.

piraeus-operator owns the linstor-controller Deployment and the satellite
DaemonSets, so scaling the controller down while the operator runs
achieves nothing — it puts it straight back. And the controller itself
keeps writing to its database, so a backup taken underneath it does not
match the dump taken a moment later, and the converted manifest then
describes a cluster that no longer exists.

The stop moves to its own first step, in dependency order, ahead of the
backup. The CSI scale-down moves there with it; step 6 now explains the
window rather than repeating the command.

Two prerequisites corrected while here. The Blockstor version is not the
operator's to choose — it arrives pinned inside the Cozystack release, so
the check is a Cozystack version check and the remedy is an upgrade, not
an image swap. And the thick-pool capacity warning is gone: the converter
now carries a sparse ZFS pool over as thin, so adoption no longer
reserves the full size of volumes LINSTOR created sparse. What remains of
that prerequisite is the placeCount top-up, which is real.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
A {{% %}} shortcode renders its body as Markdown, so an alert nested
inside a tab runs its body through twice: the inner shortcode receives
the HTML the outer already produced, and escapes it. The notice came out
as a literal <strong> inside a code block.

It lives outside the tabs now, which also means the reader sees it
whichever tab is open.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Step 2.3 lets the operator choose Blockstor, and then step 3 walked them
into commands that cannot work there. Every command in 3.1 and 3.2 runs
through `kubectl exec deploy/linstor-controller`, and on backend:
blockstor the control plane runs in external mode — that Deployment is
never created, so each one fails with NotFound.

3.1 and 3.2 now say which backend they are for, and 3.4 covers the same
ground for Blockstor: list the nodes, create the ZFS pool through the
satellite (which carries the tooling), register it as a StoragePool
custom resource, check the result. It is kubectl throughout, because
Blockstor keeps its state in custom resources rather than behind a
controller pod — there is nothing to exec into.

Storage classes are backend-independent, so 3.3 is shared and both paths
lead into it.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The first version of 3.4 configured storage by applying custom resources
by hand, on the reasoning that Blockstor keeps its state in CRDs. That
missed the point: Blockstor ships a client that speaks the same command
grammar as LINSTOR, short forms included, so the steps mirror 3.1 and
3.2 one for one and an operator moving between backends types almost the
same thing.

What actually differs is the client. `blockstor` is a standalone binary
that talks to the Kubernetes API directly — no controller pod to exec
into, no alias to set, it reads the kubeconfig the way kubectl does.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
The backend choice was a tab set in 2.3, and the steps that act on it
were a separate section further down. An operator picking Blockstor had
to remember that and navigate to a different heading.

The `tabs` shortcode gains an opt-in `sync` group. Tab sets sharing a
group switch together and the choice is remembered, so picking a backend
anywhere on the page switches every command that follows. Tab sets
without the parameter behave exactly as before — nothing else on the
site changes.

Storage now reads as one flow: 3.1 and 3.2 are backend tabs in that
group, and the separate Blockstor section is gone. ZFS/LVM inside 3.2
became plain headings rather than an inner tab set: the shortcode
collects tabs through a page Scratch, so a nested set absorbs content
from beyond its own boundary — the inner tabs came out carrying labels
lifted from later paragraphs.

Assisted-By: Claude
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant