Skip to content

Commit 031ded7

Browse files
authored
feat(seaweedfs): alert before an instance stops taking writes (#4573)
## What this PR does A SeaweedFS instance that runs out of room stops taking writes without anything paging. A volume server below its `minFreeSpacePercent` reserve turns every volume it holds read-only; before that, it can run out of slots, and a bucket whose volumes are all full then cannot get a new one, even with half the disk free. Backups archiving into the instance fail in turn, and a Postgres cluster doing continuous WAL archiving keeps the segments on its own volume until that fills too. The generic `KubePersistentVolumeFillingUp` alert does not catch this: it pages below 3% free, while a volume server stalls at its 5% reserve or, when slots run out, with the disk still half empty; and its early warning needs the disk to keep filling, which stops once writes fail. This PR adds per-instance rules to the SeaweedFS system chart, on signals the servers already publish and the platform already scrapes, rendered under the same `monitoring.enabled` gate as the chart's ServiceMonitors: - `SeaweedFSVolumeServerDiskLow` (warning): a volume server has less than 10% of its disk left above the reserve. - `SeaweedFSVolumeServerFull` (warning): a volume server cannot host another volume, because it is at the reserve or has no slot left. - `SeaweedFSWritesFailing` (critical): the master has failed to place a write in every 5-minute window for 15 minutes. Writes may still land in between; the usual cause is a bucket with no writable volume left that the master cannot grow. A new bucket's first writes fail a few picks while its volumes grow, which the 15-minute hold does not reach unless new buckets keep arriving. Every expression is pinned to the release namespace, because the root vmalert evaluates each instance's rules against every namespace's metrics. It also adds `docs/operations/seaweedfs-capacity-for-backups.md`: how to size an instance for continuous WAL archiving, what the rules mean, and how to grow an instance. The expressions were validated live rather than by unit tests, since the repository has no rule-evaluation tooling: on a test cluster, a standalone SeaweedFS 4.45 on a 1 GiB volume was scraped by the platform's vmagent and the rules rendered from this chart were evaluated by the root vmalert. All three loaded healthy and each reached `firing`: `SeaweedFSVolumeServerDiskLow` with 5.7% of the disk left above the reserve, `SeaweedFSVolumeServerFull` both at the reserve and on slot exhaustion, which stopped one bucket at 50% disk use, and `SeaweedFSWritesFailing` under continuous failing writes, resolving once they stopped. A new bucket's first successful writes on a free disk did fail a few picks, which is why that rule holds for 15 minutes. On the healthy platform instance all three stay quiet. helm-unittest pins what a render can break: the namespace selector of each rule, the shared label set of the two arms of the full-server rule, the gate, the critical rule's hold, and that alert templating reaches vmalert unrendered; each assertion was checked to fail when its line is mutated. ### Screenshots Not a UI change. ### Downstream repositories - [x] No downstream repository is affected by this change - [ ] [cozystack/website](https://github.com/cozystack/website) - follow-up: - [ ] [cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack) - follow-up: - [ ] [cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack) - follow-up: - [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up: - [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up: - [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) - follow-up: - [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) - follow-up: - [ ] [cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server) - follow-up: - [ ] [cozystack/external-apps-example](https://github.com/cozystack/external-apps-example) - follow-up: - [ ] [cozystack/examples](https://github.com/cozystack/examples) - follow-up: - [ ] [cozystack/community](https://github.com/cozystack/community) - follow-up: The diff touches a system chart template, its test and a document under `docs/operations/`; no app values, platform values, ApplicationDefinition, tooling or `hack/` path from the trigger map. ### Release note ```release-note feat(seaweedfs): alert when a SeaweedFS volume server nears its free-space reserve, cannot host another volume, or keeps failing to place writes, and document how to size an instance for continuous WAL archiving. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added namespace-scoped SeaweedFS monitoring alerts for low or exhausted disk capacity, unavailable volume slots, and write-placement failures. Capacity alerts use a 10% disk threshold, and alerts trigger after 15 minutes; write failures are marked critical. - **Documentation** - Added guidance on estimating SeaweedFS storage needs for Postgres backups, interpreting capacity alerts, and expanding storage when supported by the StorageClass. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2 parents 97fc435 + 35e3593 commit 031ded7

3 files changed

Lines changed: 149 additions & 0 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# SeaweedFS capacity for backups
2+
3+
A SeaweedFS instance starts with two volume servers of `volume.size: 10Gi` each and `replicationFactor: 2`, so every byte it stores lands on both servers and the instance holds a little under 10 GiB of objects. That is enough for occasional backups and restores. It is not enough for continuous WAL archiving from a Postgres cluster that writes steadily: WAL is archived around the clock and kept for the whole retention window, so the bucket grows until the window is full, and an instance sized by default runs out long before then.
4+
5+
A volume server stops taking writes in one of two ways. Once its free disk falls below `minFreeSpacePercent` (5%), which it checks every minute, every volume on it turns read-only and it takes no new volume, so every bucket with a copy there stops at once. Before that, it can run out of slots: the volumes that already exist keep taking writes until each reaches `master.volumeSizeLimitMB`, a bucket whose volumes are all full needs a new one, and a server with no room left for another volume of the size limit cannot host it. When no server can, the master logs `Not enough data nodes found` and every upload into that bucket fails. The slots can run out while the disk is still half empty: a volume server counts every volume it holds at the full size limit, so the nearly empty volumes of other buckets keep one bucket from growing. `barman-cloud-wal-archive` then fails on every segment, WAL segments stay on the Postgres volume, and the database goes down when that volume is full.
6+
7+
## Estimating what a bucket needs
8+
9+
For each Postgres cluster that archives into the instance, the bucket holds the WAL of the whole retention window plus every base backup inside it:
10+
11+
```text
12+
bucket ≈ WAL per day × retention days + database size × (base backups in the window + 1)
13+
```
14+
15+
The WAL rate of a running cluster, in bytes per day before compression, is roughly the number of segments it archived over a day times the 16 MiB segment size. The count also includes the small history and backup-label files, and the sum covers every pod that was primary during the day:
16+
17+
```promql
18+
sum by (namespace, job) (increase(cnpg_pg_stat_archiver_archived_count{namespace="<namespace>"}[1d])) * 16 * 1024 * 1024
19+
```
20+
21+
The platform's default backup strategy compresses both WAL and base backups with gzip, so the stored size is lower, by an amount that depends on the data; a cluster backed up to an object store of its own choosing stores them uncompressed unless its ObjectStore says otherwise. The default `retentionPolicy` is `30d`, so a cluster that writes 4 GB of WAL a day needs about 120 GB of WAL alone before its base backups are counted.
22+
23+
Each volume server must hold the sum of those buckets, since at `replicationFactor: 2` with two servers each server carries a full copy. Size `volume.size` so that this sum stays below 95% of it, and leave room on top for every bucket that is still growing: at `replicationFactor: 2` a grow claims three volumes of `master.volumeSizeLimitMB` on each server.
24+
25+
## Alerts that watch the instance
26+
27+
The generic `KubePersistentVolumeFillingUp` alert does not cover this. It pages below 3% free, while a volume server stops at 5% or, out of slots, with its disk half empty, and its early warning needs the disk to keep filling, which it stops doing once writes fail. Each instance carries its own rules instead:
28+
29+
- `SeaweedFSVolumeServerDiskLow` (warning): a volume server has less than 10% of its disk left before the `minFreeSpacePercent` reserve.
30+
- `SeaweedFSVolumeServerFull` (warning): a volume server cannot host another volume, because it is at the reserve, where all its volumes are read-only, or has no slot left, where buckets whose volumes on it are full stop taking writes.
31+
- `SeaweedFSWritesFailing` (critical): the master has failed to place a write in every 5-minute window for 15 minutes. Other writes may still succeed in between. The usual cause is a bucket with no writable volume left that the master cannot grow, whose uploads all fail; a steady stream of newly created buckets, each failing a few picks while its first volumes grow, looks the same.
32+
33+
On the Postgres side, `LastFailedArchiveTime` fires while WAL archiving keeps failing, and `KubePersistentVolumeFillingUp` fires as the WAL backlog fills the Postgres volume.
34+
35+
## Growing an instance
36+
37+
Raise `volume.size` on the SeaweedFS application. The upgrade expands each volume server's PVC in place, which needs a StorageClass that allows volume expansion. Postgres keeps retrying the segments that waited on its volume, so archiving catches up once the master can grow the bucket again.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
{{- if .Values.global.seaweedfs.monitoring.enabled }}
2+
{{- /*
3+
Every SeaweedFS instance renders this chart into its own namespace, and the
4+
root vmalert evaluates every rule it finds against every namespace's metrics.
5+
The selectors pin each expression to this release's namespace so N instances
6+
yield one alert per affected instance rather than N copies of each.
7+
*/}}
8+
{{- $ns := .Release.Namespace }}
9+
apiVersion: monitoring.coreos.com/v1
10+
kind: PrometheusRule
11+
metadata:
12+
name: {{ .Release.Name }}-alerts
13+
spec:
14+
groups:
15+
- name: seaweedfs
16+
rules:
17+
- alert: SeaweedFSVolumeServerDiskLow
18+
annotations:
19+
summary: A SeaweedFS volume server is close to its free-space reserve.
20+
description: {{`Volume server {{ $labels.pod }} in Namespace {{ $labels.namespace }} has {{ $value | humanizePercentage }} of its disk left before the minFreeSpacePercent reserve, at which every volume on it turns read-only.`}}
21+
expr: |-
22+
(
23+
SeaweedFS_volumeServer_resource{namespace="{{ $ns }}", type="avail"}
24+
/ ignoring (type)
25+
SeaweedFS_volumeServer_resource{namespace="{{ $ns }}", type="all"}
26+
) < 0.10 > 0
27+
for: 15m
28+
labels:
29+
severity: warning
30+
- alert: SeaweedFSVolumeServerFull
31+
annotations:
32+
summary: A SeaweedFS volume server has no room for another volume.
33+
description: {{`Volume server {{ $labels.pod }} in Namespace {{ $labels.namespace }} cannot host a new volume. Either it is at its free-space reserve and all its volumes are read-only, or it has no slot left and a bucket whose volumes on it are all full stops taking writes.`}}
34+
expr: |-
35+
max by (namespace, pod) (SeaweedFS_volumeServer_resource{namespace="{{ $ns }}", type="avail"} == 0)
36+
or on (namespace, pod)
37+
(
38+
SeaweedFS_volumeServer_max_volumes{namespace="{{ $ns }}"}
39+
- on (namespace, pod)
40+
sum by (namespace, pod) (SeaweedFS_volumeServer_volumes{namespace="{{ $ns }}", type="volume"})
41+
) <= 0
42+
for: 15m
43+
labels:
44+
severity: warning
45+
- alert: SeaweedFSWritesFailing
46+
annotations:
47+
summary: SeaweedFS keeps failing to place writes.
48+
description: {{`The SeaweedFS master in Namespace {{ $labels.namespace }} has failed to assign a volume for a write in every 5-minute window for 15 minutes. Writes may still land in between. The usual cause is a bucket with no writable volume left that the master cannot grow, whose uploads all fail; a steady stream of newly created buckets looks the same.`}}
49+
expr: |-
50+
sum by (namespace) (increase(SeaweedFS_master_pick_for_write_error{namespace="{{ $ns }}"}[5m])) > 0
51+
for: 15m
52+
labels:
53+
severity: critical
54+
{{- end }}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
suite: seaweedfs capacity alerts
2+
3+
templates:
4+
- templates/alerts.yaml
5+
6+
release:
7+
name: seaweedfs-system
8+
namespace: tenant-foo
9+
10+
tests:
11+
# The root vmalert loads every instance's rules and evaluates each against
12+
# every namespace, so a selector that loses its namespace turns one full
13+
# instance into an alert from every instance. Each rule is pinned on its own.
14+
- it: scopes every rule to the release namespace
15+
asserts:
16+
- matchRegex:
17+
path: spec.groups[0].rules[0].expr
18+
pattern: '(?s)^[^{]*\{namespace="tenant-foo", type="avail"\}.*\{namespace="tenant-foo", type="all"\}'
19+
- matchRegex:
20+
path: spec.groups[0].rules[1].expr
21+
pattern: '(?s)\{namespace="tenant-foo", type="avail"\} == 0.*max_volumes\{namespace="tenant-foo"\}.*volumes\{namespace="tenant-foo", type="volume"\}'
22+
- matchRegex:
23+
path: spec.groups[0].rules[2].expr
24+
pattern: 'pick_for_write_error\{namespace="tenant-foo"\}'
25+
26+
# A server can move from one arm of the "full" rule to the other while it
27+
# stays full; if the arms carried different label sets, the alert would
28+
# resolve and restart its hold at that moment.
29+
- it: gives both arms of the full-server rule the same labels
30+
asserts:
31+
- matchRegex:
32+
path: spec.groups[0].rules[1].expr
33+
pattern: '(?s)^max by \(namespace, pod\) \(SeaweedFS_volumeServer_resource\{[^}]*\} == 0\)\s+or on \(namespace, pod\)\s+\(\s+SeaweedFS_volumeServer_max_volumes\{[^}]*\}\s+- on \(namespace, pod\)\s+sum by \(namespace, pod\)'
34+
35+
- it: pages only on writes that keep failing, not on a single failed pick
36+
asserts:
37+
- equal:
38+
path: spec.groups[0].rules[2].alert
39+
value: SeaweedFSWritesFailing
40+
- equal:
41+
path: spec.groups[0].rules[2].for
42+
value: 15m
43+
- equal:
44+
path: spec.groups[0].rules[2].labels.severity
45+
value: critical
46+
47+
- it: leaves alert templating to vmalert
48+
asserts:
49+
- matchRegex:
50+
path: spec.groups[0].rules[1].annotations.description
51+
pattern: '^Volume server \{\{ \$labels\.pod \}\} in Namespace \{\{ \$labels\.namespace \}\}'
52+
53+
- it: renders nothing when the instance is not monitored
54+
set:
55+
global.seaweedfs.monitoring.enabled: false
56+
asserts:
57+
- hasDocuments:
58+
count: 0

0 commit comments

Comments
 (0)