zededa:sharing-persist-volume-by-two-apps
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| zededa:sharing-persist-volume-by-two-apps [2026/07/22 15:54] – mc | zededa:sharing-persist-volume-by-two-apps [2026/07/24 15:26] (current) – mc | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ====== Sharing a Persistent | + | ====== Sharing a Volume Between Multiple |
| - | ===== Can a persistent | + | ===== Can a volume be shared by two apps? ===== |
| - | **Yes.** ZEDEDA/EVE-OS supports attaching a single volume instance to **multiple | + | **Yes — but the rules differ for read-only vs read-write:** |
| - | application instances** via the **'' | + | |
| - | **'' | + | |
| - | node is one of the documented primary use cases of the storage feature. | + | |
| - | This is a supported, real-world pattern: a single block-storage | + | * **Read-only sharing** — **any** |
| - | by a shared | + | * **Read-write sharing** |
| - | '' | + | |
| - | ===== Access modes ===== | + | This is a documented ZEDEDA feature (" |
| - | The '' | + | ===== The common error (and its real cause) ===== |
| - | '' | + | |
| - | '' | + | |
| - | ^ Value (suffix) ^ Meaning ^ | + | If you attach |
| - | | '' | + | |
| - | | '' | + | |
| - | | '' | + | |
| - | | '' | + | |
| - | To share, | + | //Multiple app instances (N) are trying to use the same file-based volume < |
| - | ===== Important constraints ===== | + | This is a **deliberate safeguard**, |
| - | * **The node's ''/ | + | ===== How to set it up (writable shared |
| - | * **Same node only.** A persistent volume instance belongs | + | |
| - | * **One writer, many readers.** '' | + | |
| - | * **Safe pattern:** one app owns writes; the others mount **read-only** ('' | + | |
| - | ===== Node storage | + | - **Content-tree volume** — create a volume instance of type **Content Tree**, based on a **container image**. The image is **never executed**; its rootfs simply seeds the shared volume' |
| + | - **Block-storage | ||
| + | - **Attach to each app** — on every edge app (container or VM), add a drive that references the **block-storage volume by its label**, and set a **mount path**. | ||
| + | - **Deploy** all the app instances on the **same edge node**. | ||
| - | Whether an edge node can honor multi-attach depends entirely on how it backs volumes, | + | ===== Access modes ===== |
| - | which is set by ''/ | + | |
| - | * **ZFS persist** → block-storage volumes are **zvols** (real block devices) → multi-attach works. | + | The '' |
| - | * **ext4 persist** (typical single-disk box) → block-storage volumes are **qcow2 image files** → EVE **cannot** attach the same file to two live domains, regardless of the '' | + | |
| - | **Key facts:** | + | ^ Value (suffix) ^ Meaning ^ |
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| - | * The backend is decided at EVE **install / onboarding time**, driven by disk layout. It is **not** | + | For a writable shared content-tree volume, '' |
| - | * Roughly: **single disk → ext4**, **multiple disks → ZFS** (EVE builds a zpool for ''/ | + | |
| - | * Converting a node to ZFS is heavyweight: | + | |
| - | + | ||
| - | **Check what a node has:** '' | + | |
| - | '' | + | |
| - | + | ||
| - | **If the node is ext4** (no ZFS), do **not** use block-level multi-attach. Instead: | + | |
| - | + | ||
| - | * **Isolated per-VM volumes** — each app gets its own volume | + | |
| - | * **NFS / SMB share** — one app owns the volume and exports it; others mount it over a local network instance for live concurrent access. | + | |
| - | + | ||
| - | ==== How to: install EVE with a ZFS persist pool ==== | + | |
| - | + | ||
| - | There is **no in-place conversion** — '' | + | |
| - | type is chosen at **install / first boot**. To get ZFS you must (re)install EVE with ZFS | + | |
| - | targeted from the start; changing an existing ext4 node means **wipe + reinstall + re-onboard**. | + | |
| - | + | ||
| - | ZFS is selected via a grub install variable in the installer' | + | |
| - | '' | + | |
| - | + | ||
| - | < | + | |
| - | eve_install_zfs_with_raid_level=none | + | |
| - | </ | + | |
| - | + | ||
| - | Accepted values (default is '' | + | |
| - | + | ||
| - | ^ Value ^ Redundancy ^ ZFS layout / disks needed ^ | + | |
| - | | '' | + | |
| - | | '' | + | |
| - | | '' | + | |
| - | | '' | + | |
| - | + | ||
| - | **Notes: | + | |
| - | + | ||
| - | * **'' | + | |
| - | * There is **no** '' | + | |
| - | * The variable also applies on **first boot of a live image**, not just a full installer — so baking it into the config makes first boot lay down a ZFS persist pool instead of ext4. | + | |
| - | * Set it alongside the other install vars in the same '' | + | |
| - | * **Verify the variable/ | + | |
| - | + | ||
| - | After the ZFS install completes and the node re-onboards, | + | |
| - | and '' | + | |
| - | + | ||
| - | ===== Do the VMs have to stay one-reader / one-writer? ===== | + | |
| - | + | ||
| - | At any given time **only one app may mount the volume read-write; every other app must | + | |
| - | mount it read-only**. A few points that are easy to get wrong: | + | |
| - | + | ||
| - | * **It is not enforced automatically.** EVE will not stop you from setting '' | + | |
| - | * **The writer role can be changed, but not doubled.** To hand off the writer role, flip the flags (old writer → '' | + | |
| - | * **Readers do not see live updates.** A read-only block mount does not track the writer' | + | |
| - | + | ||
| - | **Rule of thumb:** | + | |
| - | + | ||
| - | * One app **produces** data, the other(s) only **consume** it → multiattach with writer/ | + | |
| - | * Multiple apps need **live concurrent read-write** on shared files → block-level multiattach is the **wrong** mechanism regardless of the flags. Run an **NFS / SMB server** app on the volume and have the others mount it over a local network instance for coherent, concurrent read-write. | + | |
| ===== Terraform (zedcloud provider) ===== | ===== Terraform (zedcloud provider) ===== | ||
| - | The '' | + | <code hcl> |
| - | so this is done entirely in Terraform. | + | # A tiny CONTAINER image — only used to seed the content-tree (never executed) |
| + | resource | ||
| + | | ||
| + | image_type | ||
| + | image_arch | ||
| + | image_format | ||
| + | image_size_bytes = 0 | ||
| + | name = " | ||
| + | title = " | ||
| + | image_rel_url = " | ||
| + | } | ||
| - | ==== Volume definition ==== | + | # 1) Content-tree volume seeded from that container image |
| + | resource " | ||
| + | device_id | ||
| + | type | ||
| + | image = zedcloud_image.shared_ct_image.name | ||
| + | label = " | ||
| + | name | ||
| + | title = " | ||
| + | } | ||
| - | <code hcl> | + | # 2) Block-storage volume connected to the content-tree (this is what apps attach) |
| - | resource " | + | resource " |
| - | device_id | + | device_id |
| - | | + | |
| - | multiattach = true | + | content_tree_id = zedcloud_volume_instance.shared_ct.id |
| - | | + | accessmode |
| - | label | + | multiattach |
| - | name = " | + | |
| - | size_bytes | + | label |
| - | title | + | name = " |
| - | type = " | + | title |
| - | lifecycle { | + | |
| - | ignore_changes = [device_id] | + | |
| - | } | + | |
| } | } | ||
| </ | </ | ||
| - | ==== Attaching to the writer | + | Then, on **each** |
| <code hcl> | <code hcl> | ||
| drives { | drives { | ||
| imagename | imagename | ||
| - | volumelabel = zedcloud_volume_instance.shared_persist.label | + | volumelabel = zedcloud_volume_instance.shared_blk.label |
| mountpath | mountpath | ||
| - | cleartext | ||
| - | ignorepurge = true | ||
| - | maxsize | ||
| - | preserve | ||
| target | target | ||
| drvtype | drvtype | ||
| - | readonly | ||
| - | } | ||
| - | </ | ||
| - | |||
| - | ==== Attaching to the reader app(s) (read-only) ==== | ||
| - | |||
| - | <code hcl> | ||
| - | drives { | ||
| - | imagename | ||
| - | volumelabel = zedcloud_volume_instance.shared_persist.label | ||
| - | mountpath | ||
| - | cleartext | ||
| - | ignorepurge = true | ||
| - | maxsize | ||
| preserve | preserve | ||
| - | | + | readonly |
| - | drvtype | + | |
| - | | + | |
| } | } | ||
| </ | </ | ||
| - | ===== Changing | + | ===== Mounting inside |
| - | Handing the writer role from one app to another is an **in-place update of the app | + | |
| - | instance config** — not a recreate. | + | * **VMs** — you must **mount the 9P share manually**. EVE exposes it as a virtio-9P device with mount tag **'' |
| - | ==== The Terraform operation ==== | + | <code bash> |
| + | modprobe 9pnet_virtio 2>/ | ||
| + | mkdir -p /data | ||
| + | # fstab (nofail so a missing share never blocks boot): | ||
| + | echo " | ||
| + | mount /data | ||
| + | </ | ||
| - | | + | Do this via cloud-init so it happens |
| - | - '' | + | |
| - | - '' | + | |
| - | ==== What happens on the device | + | ===== Requirements & gotchas ===== |
| - | The '' | + | * **EVE-OS >= 12.7.0.** |
| - | and launches QEMU** — it is **not** a live/hot change. EVE cannot just remount; it will | + | * **All sharing apps on the same edge node.** |
| - | **restart the affected app instance(s)** to bring the disk up in the new mode. Expect a | + | * **Writable share ⇒ container content-tree base.** A plain block-storage/ |
| - | brief VM restart on each VM whose flag changed. (Standard eve-kvm drives QEMU directly, no | + | |
| - | libvirt; libvirt is only in the picture in kube mode via KubeVirt.) | + | * **VMs mount manually |
| + | * **9P allows concurrent read+write** for all apps — no single-writer restriction, unlike a raw shared block device. | ||
| - | ==== Ordering matters | + | ===== Doing it via zcli / REST / WebUI ===== |
| - | Because | + | Same objects as Terraform: |
| - | where the **old writer is still RW while the new writer comes up RW**. | + | * **WebUI:** Library → Volume Instances → create a **Content Tree** (from a container image), then a **Block Storage** connected to it; add a drive on each edge app referencing the block-storage label + mount path. |
| + | * **REST: | ||
| + | * **zcli:** '' | ||
| - | * **Two-step (safest): | + | ===== Summary ===== |
| - | - Apply #1 — set the **old writer to '' | + | |
| - | - Apply #2 — set the **new writer to '' | + | |
| - | This guarantees the volume is never mounted RW by two VMs at once. | + | |
| - | * **One-step: | + | |
| - | ==== Notes ==== | + | * Read-only sharing: any volume type. |
| + | * Read-write sharing across VMs: **container content-tree** volume + block-storage connected to it, attached to each app; shared via **9P** (all apps read+write). | ||
| + | * **Not** a ZFS/ | ||
| + | * VMs mount the 9P share manually ('' | ||
| + | * Requires EVE-OS >= 12.7.0, all apps on the same node. | ||
| - | * No '' | + | ===== Worked example: |
| - | * After apply, confirm on the new writer | + | |
| - | ===== Doing it via zcli / REST API / WebUI ===== | + | The practical shape of "one app generates data, the others consume |
| + | consumers are **VMs** (which cannot share a writable volume among themselves): | ||
| - | The same two operations — **(a)** creating/ | + | |
| - | '' | + | * **Readers = VMs.** They mount the SAME volume over 9P (tag '' |
| - | can be done through any interface. The underlying behavior is identical to Terraform. | + | |
| - | ==== REST API ==== | + | Wiring: |
| - | Base: '' | + | - Shared **content-tree + block-storage** volume (as above), attached to the writer container AND the reader VMs. |
| + | - The writer needs a **LOCAL network instance** — '' | ||
| + | - Create a **patch envelope** ('' | ||
| + | - Optionally expose a web UI from the writer with a **portmap ACL** ('' | ||
| - | **Volume | + | Notes proven on-device: '' |
| + | **'' | ||
| + | and a full '' | ||
| + | appears under ''/ | ||
| + | start empty. | ||
| - | * '' | + | ===== Deployment gotchas |
| - | * Body includes ''" | + | |
| - | **Writer flip (app instance) — read-modify-write:** | + | |
| + | * **" | ||
| + | * **Patch envelope artifact changes = recreate, not update.** Editing the artifact triggers '' | ||
| + | * **Content-tree volume '' | ||
| + | * **Provider-computed drift** (e.g. content-tree '' | ||
| + | * **Build container images with '' | ||
| + | * **9P mount tag is '' | ||
| - | - '' | + | ===== Updating |
| - | - In the JSON, edit the target '' | + | |
| - | - '' | + | |
| - | - Device reconciles; optionally force with '' | + | |
| - | <color green>**A '' | + | Push a new config version |
| + | you only recreate the patch envelope. The writer auto-fetches it on its next poll and | ||
| + | drops a new versioned file into '' | ||
| - | ==== zcli ==== | + | <code bash> |
| + | # 1. Edit the config artifact | ||
| + | $EDITOR Demo-Hummingbird/ | ||
| - | zcli manages | + | # 2. Bump the version on the envelope in 6-Instances-Deploy-hum.tf |
| + | # user_defined_version = "2.0" | ||
| - | * **Volume:** '' | + | # 3. Recreate |
| - | * **Writer flip:** zcli does **not** expose a clean per-drive '' | + | terraform apply \ |
| + | | ||
| + | | ||
| - | //Verify exact flag names with// '' | + | # 4. Wait ~30-60s (controller |
| - | edge-app-instance update --help'' | + | |
| - | REST / Terraform are cleaner.// | + | |
| - | ==== WebUI (zedcontrol console) ==== | + | # 5. Verify |
| + | cat / | ||
| + | cat / | ||
| + | # writer log line: [pcw] new config.json (v=3.0) -> / | ||
| + | </ | ||
| - | * **Volume:** Volume Instances → **New/ | + | * **'' |
| - | * **Writer flip:** open each **Edge Application Instance** → **Edit** → **Storage / Drives** section → toggle | + | |
| + | * **You do NOT touch** the container image, | ||
| - | ==== Common to all interfaces | + | ==== First-time deploy (for reference) |
| - | Regardless of interface | + | <code bash> |
| + | # build + push the writer image (clean manifest — no attestation entries) | ||
| + | docker buildx build --platform linux/amd64,linux/arm64 --provenance=false --sbom=false \ | ||
| + | -t zedmanny/ | ||
| - | * '' | + | # bring everything up |
| - | * Respect the **ordering**: | + | terraform apply |
| - | ===== Common mistake ===== | + | # if the image was already created and you changed its tag, the image is immutable AND |
| + | # held by the app -> recreate the whole chain in one apply: | ||
| + | terraform apply \ | ||
| + | -replace=' | ||
| + | -replace=' | ||
| + | -replace=' | ||
| + | </ | ||
| - | Referencing the same volume from two app instances while the volume is still defined as | + | ===== Sources |
| - | plain '' | + | |
| - | valid shared config — it either fails to attach to the second app or produces two | + | |
| - | uncoordinated read-write mounts. Always set '' | + | |
| - | '' | + | |
| - | + | ||
| - | ===== Summary | + | |
| - | * Sharing a persistent volume across apps is **supported** | + | * ZEDEDA Help Center |
| - | * **Same edge node** for all attaching apps. | + | * EVE source: |
| - | * **One writer, others read-only**; | + | |
| - | * Fully configurable in the '' | + | |
zededa/sharing-persist-volume-by-two-apps.1784735649.txt.gz · Last modified: by mc
