Documentation

Storage

Fast, predictable local block storage through LVM and TopoLVM, and S3-compatible object storage through Garage.

Overview

Community Cloud offers two complementary storage layers:

Layer Technology Use case
Local TopoLVM (LVM-based CSI driver) Fast, node-local volumes. Default StorageClass cc-local-ssd-fast
Object Garage S3-compatible buckets for uploads, media, and backups

Local storage is deliberately local rather than distributed: it's simpler, it's faster, and most applications would rather have speed than the ability to move a volume between machines. Anything that needs its data in more than one place — a database with replicas, object storage — handles that itself.

Local storage with LVM

Local storage is built on the Logical Volume Manager (LVM), which provides a great deal of flexibility:

  • Grow volumes as data grows
  • Encrypt all data
  • RAID, for resilience even in single-node setups
  • Live migration of volumes for zero-downtime upgrades

TopoLVM is a CSI driver that hands a pod a logical volume from a volume group on the node it's scheduled to.

Device classes

Storage performance should be predictable rather than a lottery. A database asking for fast storage shouldn't land on a spinning disk because that's what had room. So volumes are carved out of a volume group chosen for what the disks in it actually are:

Device class Volume group For
ssd cc-ssd-vg NVMe. The fastest, and the default.
ssd-sata cc-ssd-sata-vg Solid state behind a SATA controller.
ssd-cache cc-ssd-cache-vg An SSD fronting spinning disks. Reserved — not yet implemented.
hdd cc-hdd-vg Spinning disks. The slowest and the largest.

k8s/storage/TopoLVM/TopoLVM.values.yaml is the source of truth. The installer reads the device classes out of it and creates exactly those volume groups, so adding a class there is all it takes.

Storage classes

Each device class is exposed through a Kubernetes StorageClass, so mapping a service to the right storage is a matter of naming a class:

Storage class Device class
cc-local-ssd-fast ssd
cc-local-ssd-sata ssd-sata

Both use XFS, Retain, allowVolumeExpansion, and WaitForFirstConsumer — which matters for local storage, since the volume can only be made once the pod's node is known.

cc-local-ssd-fast is the cluster default, so a PVC that doesn't ask for a class gets fast local storage. Services in Community Cloud use sensible classes by default: databases land on solid state.

Retain means deleting a PVC leaves the volume behind. Reclaiming it is a manual step, on the grounds that losing data is worse than tidying up by hand.

How the volume groups get made

The lvm bundle looks at every block device on a node, works out which are free, sorts them by what they are, and builds the groups:

lvm
$ community-cloud run-bundle lvm <node> cc.config.json

A disk is only touched if it is demonstrably unused: no partitions, no filesystem signature, not mounted, nothing holding it, not removable, not read-only. Anything else is reported with the reason and left alone. There's no flag to override that — if you meant to reuse a disk, wipe it yourself first.

Disks are sorted by hardware — NVMe to ssd, other solid state to ssd-sata, spinning to hdd — and which disks are considered can be narrowed or reclassified per node with the storage configuration.

Everything the installer creates carries LVM tags: community-cloud on both the physical volumes and the volume groups, plus cc-class-<name> saying which device class it serves. Ownership is a property of the object rather than a guess from its name:

lvm
$ sudo vgs @community-cloud # only ours
$ sudo pvs @cc-class-hdd # only the bulk storage

Which nodes offer storage

TopoLVM's controller, lvmd, and node components all schedule only onto nodes labelled node-role.kubernetes.io/storage-local=storage-local, which the installer applies from the roles on a node. A node without that role isn't running out of storage — it simply doesn't offer any.

Nodes that can't serve every class

Not every machine has one of each kind of disk, and a Community Cloud is usually built from whatever hardware people have. A node with SSDs and nothing spinning is a perfectly good node for the classes it does have.

lvmd disagrees. It's handed a list of device classes and refuses to start unless every volume group in that list is on its node, so one missing group means the node serves none of them and crash-loops:

"msg":"volume group not found","volume_group":"cc-hdd-vg","error":"not found"

Until that's fixed upstream, the installer works around it. Once a node's volume groups exist, the lvm bundle records which device classes it can serve and nodeLabels puts them on the node as a single label:

storage.community-cloud.technology/classes=hdd.ssd

Installing the charts then reads those labels back and renders one lvmd configuration per distinct combination, each offering only the classes those nodes actually have. So run the bundles in that order, and re-run helm-charts after the storage on a node changes:

storage
$ community-cloud run-pipeline lvm,nodeLabels <node> cc.config.json
$ community-cloud run-bundle helm-charts <node> cc.config.json

A cluster where nothing has been labelled yet — a first install — offers every node every class. When a node's shape does change it moves between lvmd DaemonSets, and topolvm-node may crash once or twice while the socket is absent before reconnecting on its own.

On ssd-cache

LVM can put an SSD in front of a spinning disk, and the ssd-cache device class is reserved for that. It isn't implemented because TopoLVM can't deliver it: a device class has no cache support of any kind, and an LVM cache attaches to exactly one logical volume, so it can't serve a class that creates volumes on demand.

Doing it properly means caching below LVM, where TopoLVM can't see it — bcache or dm-writecache over the spinning disk, with the volume group on top. Every volume in the group is then transparently cached. That's a kernel module and a device format, so it's a decision rather than a detail.

Object storage with Garage

Garage is S3-compatible object storage built for exactly this shape of cluster — a handful of machines in different places, on ordinary connections. Its Helm values are in k8s/storage/Garage/GarageValues.yaml.

It currently runs as a single replica using cc-local-ssd-fast for metadata and data, with the S3 API on NodePort 3900 and the web interface on 3902. For anything beyond a single node, raise replicaCount and set replicationFactor to match.

Applications request buckets through ObjectBucketClaim manifests alongside their other resources.