User Tools

Site Tools


zededa:edge-node-clustering:how-zed-cloud-directs-enc:image-info

This is an old revision of the document!


Image -- deep dive

← Back to main page

This page documents how an Image object created in ZEDEDA Cloud is downloaded, stored, and made available on an EVE-K edge node cluster.

All content marked confirmed live has been validated on device e418565d-9d93-47ee-b7ea-aba4c9c7e912 using image TF-ATL-UBUNTU-24-IMAGE (Ubuntu 24.04 LTS server cloud image, ~590MB).

What an Image is in ZEDEDA Cloud

An Image in ZEDEDA Cloud is a reference to an artifact stored in a Datastore. It defines what to download, where to get it from, and how to verify it. On the device, an Image maps to a Content Tree – EVE's internal name for a downloaded and verified image artifact.

An Image object alone does nothing on the device. It is only pushed when an App Instance or Volume Instance referencing it is assigned to the device.

Overview diagram


Creating an Image in ZEDEDA Cloud

Terraform

resource "zedcloud_image" "ubuntu24" {
  name        = "TF-ATL-UBUNTU-24-IMAGE"
  title       = "TF-ATL-UBUNTU-24-IMAGE"
  image_arch  = "IMAGE_ARCH_AMD64"
  image_format = "MEDIA_FORMAT_RAW"
  image_rel_url = "noble-server-cloudimg-amd64.img"
  image_sha256 = "834af9cd766d1fd86eca156db7dff34c3713fbbc7f5507a3269be2a72d2d1820"
  image_size_bytes = 618925568
  datastore_id = zedcloud_datastore.my_datastore.id
}

Key fields

Field Confirmed value Meaning
image_arch IMAGE_ARCH_AMD64 Target CPU architecture
image_format MEDIA_FORMAT_RAW Raw disk image (maps to Format: 3 in ContentTreeConfig)
image_rel_url noble-server-cloudimg-amd64.img Path within the Datastore
image_sha256 834af9cd… Expected sha256 – verified by EVE after download
image_size_bytes 618925568 ~590MB – used as MaxDownloadSize
datastore_id UUID Reference to the Datastore where the image lives

How the image flows to the device

Step 1: Trigger

Assigning an App Instance or Volume Instance that references this Image to the device causes ZEDEDA Cloud to include the Image and its Datastore in the next DeviceConfig push.

Step 2: zedagent writes two pubsub objects

Both appear simultaneously in /run/zedagent/:

ls /run/zedagent/DatastoreConfig/
# 00151759-df21-47b1-b338-2430c7d0f7ef.json  <- datastore credentials
 
ls /run/zedagent/ContentTreeConfig/
# df534d27-c1cc-4ebc-a1c0-88cb775a86dc.json  <- image reference

ContentTreeConfig confirmed fields:

Field Confirmed value Meaning
ContentID df534d27-… UUID of this content tree
DatastoreIDList [“00151759-…”] Which datastore to pull from
RelativeURL noble-server-cloudimg-amd64.img Path within datastore
Format 3 Raw disk image
ContentSha256 834af9cd… Expected sha256
MaxDownloadSize 618925568 ~590MB
DisplayName TF-ATL-UBUNTU-24-IMAGE Human readable name
IsLocal true Set after download completes

Step 3: volumemgr orchestrates the pipeline

volumemgr subscribes to both ContentTreeConfig and DatastoreConfig. It resolves the datastore reference and writes:

ls /run/volumemgr/DownloaderConfig/
# <uuid>.json  <- tells downloader what to fetch and from where

Step 4: downloader fetches (transient)

downloader creates DownloaderStatus/<uuid>.json while fetching. This file is deleted immediately on completion – it is only present while the download is actively in progress.

Confirmed: the =DL= directory was briefly visible then disappeared, confirming the transient nature.

Step 5: verifier checks sha256 (transient)

verifier checks the downloaded blob against ContentSha256. VerifyImageStatus/<uuid>.json exists only while verifying, deleted on success.

Confirmed: VerifyImageStatus/ contained only the restarted sentinel after the download completed – verification had already finished and cleaned up.

Step 6: blobs written to containerd CAS

The image is stored as an ECI-format OCI image in containerd's content addressable store under /persist/vault/containerd/.


ECI format deep dive

ZEDEDA uses the LF Edge Edge Container Image (ECI) format for all images – both VM disks and containers. This allows EVE to use the same containerd infrastructure for both workload types.

What makes ECI different from a regular container image

A standard container image has layers of type application/vnd.oci.image.layer.v1.tar+gzip. An ECI VM disk image has a single layer of type application/vnd.lfedge.disk.layer.v1+raw. The org.lfedge.eci.role: disk-root annotation tells EVE this layer is the VM boot disk.

The three blobs (confirmed live)

Blob Size OCI role Confirmed content
3aa50f… 637B OCI manifest Points to config + disk layer · eve-downloaded=true GC label
47dd95… 300B OCI image config arch: amd64 · author: lf-edge/edge-containers · rootfs ref
834af9… 590MB Raw disk image noble-server-cloudimg-amd64.img · role: disk-root

OCI manifest (confirmed live)

cat /persist/vault/containerd/io.containerd.content.v1.content/blobs/sha256/3aa50fe4...
{
  "mediaType": "application/vnd.oci.image.manifest.v1+json",
  "config": {
    "mediaType": "application/vnd.oci.image.config.v1+json",
    "digest": "sha256:47dd95...",
    "size": 300
  },
  "layers": [{
    "mediaType": "application/vnd.lfedge.disk.layer.v1+raw",
    "digest": "sha256:834af9...",
    "size": 618925568,
    "annotations": {
      "org.lfedge.eci.role": "disk-root",
      "org.opencontainers.image.title": "disk-root-root"
    }
  }]
}

OCI image config (confirmed live)

cat /persist/vault/containerd/io.containerd.content.v1.content/blobs/sha256/47dd95...
{
  "created": "2026-05-30T16:01:39Z",
  "author": "lf-edge/edge-containers",
  "architecture": "amd64",
  "os": "linux",
  "config": {
    "Labels": { "org.lfedge.eci.artifact.root": "/disk-root-root" }
  },
  "rootfs": {
    "type": "layers",
    "diff_ids": ["sha256:834af9..."]
  }
}

GC protection (confirmed live)

The manifest blob carries the label eve-downloaded=true set by EVE. This prevents containerd's garbage collector from deleting the blobs. The gc.ref.content.* labels on the manifest create a reference chain so the config and disk blobs survive as long as the manifest does.

Confirmed via:

ctr --address /run/containerd-user/containerd.sock content ls | \
  grep 3aa50f
# sha256:3aa50f...  637B  eve-downloaded=true,
#   gc.ref.content.0=sha256:834af9...,
#   gc.ref.content.1=sha256:47dd95...

All blobs are read-only (r–r–r–) and stored persistently under /persist/vault/.

BlobStatus (confirmed live)

volumemgr publishes a BlobStatus JSON for each blob:

ls /run/volumemgr/BlobStatus/
# 834af9cd...json  3aa50fe4...json  47dd958b...json
Field Value Meaning
State 108 LOADED – verified and available
HasVerifierRef false Verification complete, ref released
Size blob size Matches containerd CAS entry

Re-download and cache hit behaviour

Cache hit (confirmed live)

Once an image is downloaded its blobs remain in /persist/vault/containerd/ across reboots and across app deployments. EVE does not re-download an image that is already in the containerd CAS.

Confirmed: when a Persistent Volume was deployed after the Ubuntu image was already in the CAS:

  • =DL= (DownloaderStatus) did not appear
  • =VER= (VerifyImageStatus) did not appear
  • Blob timestamps on 834af9… were unchanged
  • ContentTreeStatus showed State: 108 immediately

Fresh download (not yet observed live)

A fresh download (image not yet in CAS) would show:

  • =DL= briefly populated with a UUID.json (transient)
  • =VER= briefly populated (transient)
  • =BLOB= populating as blobs arrive
  • =CTS= State progressing from downloading → verifying → 108 (LOADED)

This will be captured when a new image not previously downloaded is deployed.


What appears in k3s

An Image / Content Tree alone produces nothing in k3s. k3s is only involved when the image is materialised into a volume for an App Instance.

When an App Instance using this image is deployed (not yet observed live on this device – pending App Instance deployment):

k3s object Namespace When
CDI DataVolume import job cdi During import from containerd CAS to Longhorn – transient
PersistentVolumeClaim eve-kube-app After import complete
Longhorn volume longhorn-system After PVC bound

The CDI import reads the raw disk blob (834af9…) from the containerd CAS and writes it into a Longhorn PVC. No network download occurs at this stage – the bytes are already on disk.


How to inspect

# All image-related pubsub dirs:
ls /run/zedagent/DatastoreConfig/
ls /run/zedagent/ContentTreeConfig/
cat /run/zedagent/ContentTreeConfig/<uuid>.json | jq
 
# volumemgr status:
ls /run/volumemgr/ContentTreeStatus/
cat /run/volumemgr/ContentTreeStatus/<uuid>.json | jq '{State, TotalSize, CurrentSize}'
ls /run/volumemgr/BlobStatus/
cat /run/volumemgr/BlobStatus/834af9cd766d1fd86eca156db7dff34c3713fbbc7f5507a3269be2a72d2d1820.json | jq
 
# Actual blobs in containerd CAS:
ls -lh /persist/vault/containerd/io.containerd.content.v1.content/blobs/sha256/ | grep -E "834af9|47dd95|3aa50f"
 
# containerd view with GC labels:
ctr --address /run/containerd-user/containerd.sock content ls 2>/dev/null | grep -E "834af9|47dd95|3aa50f"
 
# Read manifest and config blobs directly:
cat /persist/vault/containerd/io.containerd.content.v1.content/blobs/sha256/3aa50fe43cd8c01014cb4fabee4014b9aeb7e6d995b19587bca0570fc3ce74e4
cat /persist/vault/containerd/io.containerd.content.v1.content/blobs/sha256/47dd958b3a950d2dd2ac55d5587817c807396a8ad0ddf64dee314e7be290c3f1
 
# Transient dirs (empty when idle -- only populated during active download):
ls /run/downloader/DownloaderStatus/
ls /run/verifier/VerifyImageStatus/
 
# k3s (nothing until app deployed):
kubectl get pvc -A
kubectl get datavolumes -A 2>/dev/null
kubectl get volumes.longhorn.io -n longhorn-system

Log filter (no python3 on EVE)

tail -f /persist/newlog/collect/current.device.log | \
  grep -o '"msg":"[^"]*"' | \
  grep -iE 'contenttree|blob|download|verify|datastore|image'

Source references

  • pkg/pillar/cmd/volumemgr/ – volumemgr implementation
  • pkg/pillar/cmd/downloader/ – downloader implementation
  • pkg/pillar/cmd/verifier/ – verifier implementation
  • lf-edge/edge-containers – ECI format specification
zededa/edge-node-clustering/how-zed-cloud-directs-enc/image-info.1780231289.txt.gz · Last modified: (external edit)