zededa:sharing-persist-volume-by-two-apps
Differences
This shows you the differences between two versions of the page.
| Next revision | Previous revision | ||
| zededa:sharing-persist-volume-by-two-apps [2026/07/21 19:46] – created 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 | + | * **Read-only sharing** |
| - | '' | + | * **Read-write sharing** (multiple apps read //and// write the same data) — the volume **must be backed by a CONTAINER |
| - | volumes with '' | + | |
| - | ===== Access modes ===== | + | This is a documented ZEDEDA feature (" |
| - | The '' | + | ===== The common error (and its real cause) ===== |
| - | ^ Value ^ 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**, |
| - | * **Same node only.** A persistent volume instance belongs | + | ===== How to set it up (writable shared volume) ===== |
| - | * **One writer, many readers.** '' | + | |
| - | * **Safe pattern:** one app owns writes; the others mount **read-only** ('' | + | |
| - | ===== Terraform | + | - **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 volume** — create a Block Storage volume instance **connected to that content-tree** ('' | ||
| + | - **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**. | ||
| - | The '' | + | ===== Access modes ===== |
| - | so this is done entirely in Terraform. | + | |
| - | ==== Volume definition | + | The '' |
| + | |||
| + | ^ Value (suffix) ^ Meaning ^ | ||
| + | | '' | ||
| + | | '' | ||
| + | | '' | ||
| + | |||
| + | For a writable shared content-tree volume, '' | ||
| + | |||
| + | ===== Terraform (zedcloud provider) ===== | ||
| <code hcl> | <code hcl> | ||
| - | resource "zedcloud_volume_instance" "shared_persist" { | + | # A tiny CONTAINER image — only used to seed the content-tree (never executed) |
| - | | + | resource "zedcloud_image" "shared_ct_image" { |
| - | | + | |
| - | | + | |
| - | | + | image_arch |
| - | | + | |
| - | name = " | + | |
| - | | + | |
| - | title | + | |
| - | type = " | + | image_rel_url = " |
| - | | + | } |
| - | | + | |
| - | | + | # 1) Content-tree volume seeded from that container image |
| + | resource " | ||
| + | device_id = zedcloud_edgenode.my_node.id | ||
| + | type = " | ||
| + | image = zedcloud_image.shared_ct_image.name | ||
| + | label = " | ||
| + | | ||
| + | title | ||
| + | } | ||
| + | |||
| + | # 2) Block-storage volume connected to the content-tree (this is what apps attach) | ||
| + | resource " | ||
| + | device_id | ||
| + | type = " | ||
| + | | ||
| + | | ||
| + | | ||
| + | size_bytes | ||
| + | label = " | ||
| + | name = " | ||
| + | title = " | ||
| } | } | ||
| </ | </ | ||
| - | ==== 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) | + | ===== Mounting inside |
| - | <code hcl> | + | |
| - | drives { | + | * **VMs** — you must **mount the 9P share manually**. EVE exposes it as a virtio-9P device with mount tag **'' |
| - | imagename | + | |
| - | volumelabel = zedcloud_volume_instance.shared_persist.label | + | <code bash> |
| - | | + | modprobe 9pnet_virtio 2>/ |
| - | | + | mkdir -p /data |
| - | | + | # fstab (nofail so a missing share never blocks boot): |
| - | | + | echo "share_dir |
| - | | + | mount /data |
| - | | + | |
| - | drvtype | + | |
| - | | + | |
| - | } | + | |
| </ | </ | ||
| - | ===== Common mistake ===== | + | Do this via cloud-init so it happens on every boot. **No '' |
| - | Referencing | + | ===== Requirements & gotchas ===== |
| - | plain '' | + | |
| - | valid shared | + | * **EVE-OS >= 12.7.0.** |
| - | uncoordinated read-write mounts. Always set '' | + | * **All sharing apps on the same edge node.** |
| - | '' | + | * **Writable share ⇒ container content-tree base.** A plain block-storage/ |
| + | * **The content-tree image is a seed, not a workload** — it is never run; its rootfs is the volume's starting content. Use a tiny/empty base. | ||
| + | | ||
| + | | ||
| + | |||
| + | ===== Doing it via zcli / REST / WebUI ===== | ||
| + | |||
| + | Same objects as Terraform: | ||
| + | * **WebUI:** Library → Volume Instances → create a **Content Tree** (from a container image), then a **Block Storage** connected | ||
| + | * **REST: | ||
| + | * **zcli: | ||
| ===== Summary ===== | ===== Summary ===== | ||
| - | * Sharing a persistent | + | * Read-only sharing: any volume |
| - | * **Same edge node** for all attaching apps. | + | * Read-write sharing |
| - | * **One writer, | + | |
| - | * Fully configurable | + | * VMs mount the 9P share manually ('' |
| + | * Requires EVE-OS >= 12.7.0, all apps on the same node. | ||
| + | |||
| + | ===== Worked example: a container writer | ||
| + | |||
| + | The practical shape of "one app generates data, the others consume it" when the | ||
| + | consumers are **VMs** (which cannot share a writable volume among themselves): | ||
| + | |||
| + | * **Writer = a CONTAINER app.** Containers get the writable 9P overlay, so the writer mounts the shared content-tree volume at '' | ||
| + | * **Readers = VMs.** They mount the SAME volume over 9P (tag '' | ||
| + | * **Data source = a patch envelope.** '' | ||
| + | |||
| + | Wiring: | ||
| + | |||
| + | - 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** ('' | ||
| + | | ||
| + | |||
| + | Notes proven on-device: '' | ||
| + | **'' | ||
| + | and a full '' | ||
| + | appears under ''/ | ||
| + | start empty. | ||
| + | |||
| + | ===== Deployment gotchas (learned on-device) ===== | ||
| + | |||
| + | * **Container image tag is immutable AND the app holds it.** To bump an image tag you must recreate the image AND every app/ | ||
| + | * **" | ||
| + | * **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 the config (runbook) ===== | ||
| + | |||
| + | Push a new config version **without** redeploying the image, app, VMs, or volumes — | ||
| + | you only recreate the patch envelope. The writer auto-fetches it on its next poll and | ||
| + | drops a new versioned file into ''/ | ||
| + | |||
| + | <code bash> | ||
| + | # 1. Edit the config artifact | ||
| + | $EDITOR Demo-Hummingbird/ | ||
| + | |||
| + | # 2. Bump the version on the envelope in 6-Instances-Deploy-hum.tf | ||
| + | # user_defined_version = " | ||
| + | |||
| + | # 3. Recreate the envelope + its binding (in-place update is rejected -> -replace) | ||
| + | terraform apply \ | ||
| + | -replace=' | ||
| + | -replace=' | ||
| + | |||
| + | # 4. Wait ~30-60s (controller -> device propagation + the writer' | ||
| + | |||
| + | # 5. Verify (on the writer container or any reader VM) | ||
| + | cat / | ||
| + | cat / | ||
| + | # writer log line: [pcw] new config.json (v=3.0) | ||
| + | </ | ||
| + | |||
| + | * **'' | ||
| + | * **Bumping '' | ||
| + | * **You do NOT touch** the container image, the writer app, the VMs, or the volumes. That is the whole point of the patch-envelope design: config changes with nothing redeployed. | ||
| + | |||
| + | ==== First-time deploy (for reference) ==== | ||
| + | |||
| + | <code bash> | ||
| + | # build + push the writer image (clean manifest — no attestation entries) | ||
| + | docker buildx build --platform linux/ | ||
| + | -t zedmanny/ | ||
| + | |||
| + | # bring everything up | ||
| + | terraform apply | ||
| + | |||
| + | # 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=' | ||
| + | </ | ||
| + | |||
| + | ===== Sources ===== | ||
| + | |||
| + | * ZEDEDA Help Center — " | ||
| + | * EVE source: '' | ||
zededa/sharing-persist-volume-by-two-apps.1784663202.txt.gz · Last modified: by mc
