Skip to content

Rune: file contents written to a gce-pd volume are lost #270

Description

@jolaolurin

Rune: first writes to a freshly-provisioned gce-pd volume are lost — files keep metadata, lose contents

Severity: a service that writes state on first boot and reads it back later
comes up permanently broken. Silent — nothing in rune logs or status indicates
a problem, and the service reports Running 1/1 right up until it is restarted.

Versions: rune server + CLI v0.0.1-dev.146 (8c34e706), single edge node
(e2-standard-2, europe-west2-a), gce-pd CSI driver, StorageClass
pd-ssd-euw2a (diskType: pd-ssd, fsType: ext4, reclaimPolicy: retain).

Summary

On the first boot after a gce-pd volume is newly provisioned, files written
by the service end up on disk with correct name, owner and mode but zero
length
. Two unrelated files in our case (SYSTEM, flo.pid) — directory
entries and inodes persisted, file contents did not.

The service keeps running and reports healthy. The damage only surfaces on the
next restart, when it tries to read what it thinks it wrote.

Once the directory is manually cleared and the service restarted, the same
volume works correctly and survives subsequent restarts, including the
detach/attach cycle a rune restart performs. So this is specific to the
initial-provisioning window, not to ongoing use of the volume.

Evidence

Same cast, same configmap, same image (ghcr.io/floruntime/flo:0.1.0-dev.9),
changing only storageClassName:

storage SYSTEM after first boot outcome
pd-ssd-euw2a (gce-pd), freshly provisioned 0 bytes dies on next boot
local, freshly provisioned 90 bytes fine
pd-ssd-euw2a, after manual wipe + sync 90 bytes fine, survives restarts

Control ruling out the workload: the same image with the same config file, run
directly under docker on the same host with a /tmp bind mount, writes 90
bytes and survives both docker stop (SIGTERM) and docker kill (SIGKILL) with
data intact. It is crash-consistent.

Reproduction

rune storageclass create -f storageclass-pd-ssd-euw2a.yaml   # once

# any service with a claimTemplate on that class that writes a file on boot
rune cast flo.yaml -n shared            # storageClassName: pd-ssd-euw2a
rune exec flo -n shared --no-tty -- stat -c '%s' /data/flo/SYSTEM
#   -> 0          (expected 90)

rune restart flo -n shared
rune logs flo -n shared --tail=5
#   Error starting runtime: error.UnexpectedEndOfInput      (crash-loops)

Swap to storageClassName: local and the same step returns 90.

Recovery — note that rune volume delete is not sufficient (see below):

rune stop flo -n shared
# on the host:
sudo rm -rf /var/lib/rune/mounts/flo-data-flo-0/{00000,SYSTEM,flo.pid} && sync
rune scale flo 1 -n shared
#   -> SYSTEM = 90 bytes, and restarts now work

On-disk state at failure

$ mount | grep flo-data
/dev/sdc on /var/lib/rune/mounts/flo-data-flo-0 type ext4 (rw,relatime)

$ sudo ls -la /var/lib/rune/mounts/flo-data-flo-0
drwxr-xr-x 4 ubuntu ubuntu  4096 Aug 29 00:00 00000
-rw-r--r-- 1 ubuntu ubuntu     0 Aug 29 00:00 SYSTEM      <-- 0 bytes
-rw-r--r-- 1 ubuntu ubuntu     0 Aug 29 00:00 flo.pid     <-- 0 bytes
drwx------ 2 ubuntu ubuntu 16384 Aug 29 00:00 lost+found

Mount options are unremarkable and identical to the MongoDB volume on the same
StorageClass. A disk snapshot of the failed state is preserved as
flo-corrupt-repro (source disk rune-shared-flo-data-flo-0) and can be shared.

What we have NOT established

Being explicit, because an earlier draft of this report guessed wrong: we first
suspected the volume detach was discarding unflushed page cache. That is
disproven
— after the manual wipe, the same volume survives rune restart
(which detaches and reattaches) with contents intact.

The remaining suspicion is the provisioning path: format (mkfs.ext4),
mount, and hand-off to the container, where the earliest writes appear not to
reach the device. We have not instrumented that path and cannot say more from
outside. Worth checking whether the container is allowed to start before
mkfs/mount has fully settled, and whether there is a sync between format
and first use.

Secondary observations

  1. rune volume delete does not give a clean slate under reclaimPolicy: retain. Deleting the volume row and letting the claimTemplate reprovision
    re-binds the same underlying GCE disk with its old contents, so the obvious
    recovery silently returns you to the broken state. This cost real debugging
    time. A warning on the delete path, or docs, would help.

  2. rune cast on a stateful service can lose the PD attach race. Cast
    deletes and recreates the instance; the new one intermittently fails with
    LaunchFailed: failed to resolve volume mount "flo-data": context canceled
    while the old one is still detaching. The volume stays Available and bound
    and rune restart clears it, so nothing is lost — but a retry/backoff on
    attach would avoid an alarming failure mode. Roughly 1 in 3 casts.

  3. rune volume delete has no --yes while rune release delete requires
    one. Minor inconsistency.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions