Skip to main content

ACSA Setup for ROS 2 Bag Sync

Deploy Azure Container Storage enabled by Azure Arc (ACSA) on Arc-connected edge clusters to automatically sync ROS 2 bag files to Azure Blob Storage. ACSA provides cloud-backed persistent volumes that handle ingest, caching, and eviction transparently β€” recording pods write to a local PVC and files sync to Blob Storage without application-level upload logic.

πŸ—οΈ Architecture​

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Edge Cluster (Arc-connected) β”‚
β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ ROS 2 β”‚ β”‚ ACSA Extension β”‚ β”‚
β”‚ β”‚ Recording Pod│───▢│ (Edge Volume) β”‚ β”‚
β”‚ β”‚ β”‚ β”‚ β”‚ β”‚
β”‚ β”‚ writes to β”‚ β”‚ IngestSubvolume β”‚ β”‚
β”‚ β”‚ /recording β”‚ β”‚ controller syncs β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ oldest-first β”‚ β”‚
β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚ β–Ό β”‚ β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚
β”‚ β”‚ PVC β”‚ β”‚ β”‚
β”‚ β”‚ recording- β”‚ β”‚ β”‚
β”‚ β”‚ data (50Gi) β”‚ β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ HTTPS
β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Azure Blob Storage β”‚
β”‚ datasets/recordings/ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Recording pods mount the recording-data PVC and write bag files to it. The ACSA IngestSubvolume controller detects new files and syncs them to a Blob Storage container using managed identity authentication. Local cache eviction removes files after a configurable delay, freeing disk space for continued recording.

πŸ“‹ Prerequisites​

RequirementDetails
Azure Arc-connected K8s clusterEdge cluster registered with Azure Arc (az connectedk8s show)
Azure CLI 2.60+With k8s-extension and connectedk8s extensions
Terraform outputsInfrastructure deployed via infrastructure/terraform/ with a storage account
kubectl + envsubstFor manifest rendering and application
Azure RBACContributor on the Arc cluster resource group; Storage Blob Data Owner on the storage account
Network connectivityDirect kubectl access or Arc proxy for private clusters

[!NOTE] The deploy script automatically installs missing Azure CLI extensions (k8s-extension, connectedk8s).

πŸš€ Quick Start​

cd data-pipeline/setup

# Preview configuration without making changes
./deploy-acsa.sh --config-preview \
--cluster-name <arc-cluster> \
--cluster-resource-group <rg> \
--storage-account <storage-account>

# Deploy ACSA with Terraform auto-discovery
./deploy-acsa.sh \
--cluster-name <arc-cluster> \
--cluster-resource-group <rg> \
--storage-account <storage-account>

The script reads storage account details from infrastructure/terraform/terraform.tfstate. Override any value via CLI arguments or environment variables.

βš™οΈ Configuration​

Script Arguments​

ArgumentEnvironment VariableDefaultDescription
--cluster-nameARC_CLUSTER_NAME(required)Arc-connected cluster name
--cluster-resource-groupARC_RESOURCE_GROUP(required)Resource group of the Arc cluster
-t, --tf-dirDEFAULT_TF_DIR../../infrastructure/terraformTerraform directory for output discovery
--storage-accountSTORAGE_ACCOUNT_NAMEAuto-discovered from TerraformStorage account name override
--storage-resource-groupSTORAGE_ACCOUNT_RESOURCE_GROUPSame as cluster resource groupStorage account resource group
--connectivity-modeACSA_CONNECTIVITY_MODEdirectdirect or proxy
--proxy-portACSA_PROXY_PORT47011Arc proxy port (proxy mode only)
--config-previewβ€”β€”Print configuration and exit

Defaults Configuration​

Central defaults live in data-pipeline/setup/defaults.conf. Override any value via environment variables before running the script.

VariableDefaultDescription
ACSA_EXTENSION_VERSION2.11.2ACSA Arc extension version
ACSA_RELEASE_TRAINstableExtension release train
ACSA_DISK_STORAGE_CLASSdefault,local-pathBacking disk storage classes
ACSA_PVC_NAMErecording-dataPVC name for recording volume
ACSA_PVC_SIZE50GiPVC storage request
ACSA_STORAGE_CLASScloud-backed-scACSA storage class name
BLOB_CONTAINER_NAMEdatasetsTarget Blob Storage container
SUBVOLUME_NAMErecordingsIngestSubvolume resource name
SUBVOLUME_PATHrecordingsPath prefix within the Blob container
ACSA_INGEST_ORDERoldest-firstFile ingest order (oldest-first)
ACSA_INGEST_MIN_DELAY_SEC30Minimum delay before ingesting a file
ACSA_EVICTION_ORDERunorderedCache eviction order
ACSA_EVICTION_MIN_DELAY_SEC600Minimum time (seconds) before evicting cached files
ACSA_ON_DELETEtrigger-immediate-ingestBehavior when the subvolume is deleted
EDGE_NAMESPACEdata-pipelineKubernetes namespace for ACSA resources

Sync Behavior​

ACSA IngestSubvolume controls how files move from edge to cloud:

ParameterDefaultBehavior
Ingest orderoldest-firstOldest files sync first, preserving recording chronology
Ingest delay30 secondsWait before syncing β€” avoids uploading files still being written
Eviction delay600 seconds (10 minutes)Keep cached files locally after upload for re-reads
On deletetrigger-immediate-ingestUpload all remaining data immediately when the subvolume is deleted

πŸ“¦ Deployment Steps​

The deploy-acsa.sh script executes these steps in order:

  1. Read Terraform outputs to discover the storage account and resource group
  2. Validate cluster connectivity (direct kubectl or Arc proxy)
  3. Create the data-pipeline namespace
  4. Install the arc-cert-manager extension (ACSA dependency)
  5. Wait for cert-manager to reach Succeeded state
  6. Install the azure-arc-containerstorage extension
  7. Wait for ACSA extension to reach Succeeded state
  8. Retrieve the ACSA managed identity principal ID
  9. Assign Storage Blob Data Owner role to the ACSA identity on the storage account
  10. Create the datasets Blob container
  11. Render and apply the PVC and IngestSubvolume manifests
  12. Wait for the PVC to bind and EdgeVolume to deploy

πŸ”Œ Connectivity Modes​

Direct Mode (Default)​

Use when kubectl can reach the cluster API server directly β€” either via VPN, public endpoint, or local network.

./deploy-acsa.sh \
--cluster-name my-edge-cluster \
--cluster-resource-group rg-edge \
--storage-account mystorageaccount

Proxy Mode​

Use when the cluster API server is unreachable from the dev machine. The script starts an Arc proxy tunnel automatically and cleans it up on exit.

./deploy-acsa.sh \
--connectivity-mode proxy \
--cluster-name my-edge-cluster \
--cluster-resource-group rg-edge \
--storage-account mystorageaccount

[!NOTE] Arc proxy requires the connectedk8s CLI extension and an authenticated Azure session. The proxy creates a temporary kubeconfig and listens on port 47011 by default.

πŸ“„ Manifest Templates​

Two Kubernetes manifest templates in data-pipeline/arc/ are rendered using envsubst during deployment.

PVC Template (acsa-pvc.yaml)​

Creates a ReadWriteMany PersistentVolumeClaim backed by the ACSA cloud-backed-sc storage class.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ${ACSA_PVC_NAME}
namespace: ${EDGE_NAMESPACE}
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: ${ACSA_PVC_SIZE}
storageClassName: ${ACSA_STORAGE_CLASS}

IngestSubvolume Template (acsa-ingest-subvolume.yaml)​

Defines the sync policy between the edge volume and Blob Storage.

apiVersion: arccontainerstorage.azure.net/v1
kind: IngestSubvolume
metadata:
name: ${SUBVOLUME_NAME}
namespace: ${EDGE_NAMESPACE}
spec:
edgevolume: ${ACSA_PVC_NAME}
path: ${SUBVOLUME_PATH}
authentication:
authType: MANAGED_IDENTITY
storageAccountEndpoint: "https://${STORAGE_ACCOUNT_NAME}.blob.core.windows.net/"
containerName: ${BLOB_CONTAINER_NAME}
ingest:
order: ${ACSA_INGEST_ORDER}
minDelaySec: ${ACSA_INGEST_MIN_DELAY_SEC}
eviction:
order: ${ACSA_EVICTION_ORDER}
minDelaySec: ${ACSA_EVICTION_MIN_DELAY_SEC}
onDelete: ${ACSA_ON_DELETE}

πŸ” Verification​

After deployment, verify the resources are healthy:

# Check PVC is bound
kubectl -n data-pipeline get pvc recording-data
# Expected: STATUS = Bound

# Check EdgeVolume is deployed
kubectl -n data-pipeline get edgevolumes recording-data
# Expected: STATE = deployed

# Check IngestSubvolume exists
kubectl -n data-pipeline get ingestsubvolumes recordings

# Check ACSA extension status
az k8s-extension show \
--name azure-arc-containerstorage \
--cluster-name <arc-cluster> \
--resource-group <rg> \
--cluster-type connectedClusters \
--query provisioningState -o tsv
# Expected: Succeeded

# Verify blob container exists
az storage container show \
--account-name <storage-account> \
--name datasets \
--auth-mode login \
--query name -o tsv
# Expected: datasets

Test Sync​

Write a test file to the PVC and confirm it appears in Blob Storage:

# Create a test pod that writes to the PVC
kubectl -n data-pipeline run acsa-test \
--image=busybox \
--restart=Never \
--overrides='{
"spec": {
"containers": [{
"name": "acsa-test",
"image": "busybox",
"command": ["sh", "-c", "echo test > /data/test.txt && sleep 60"],
"volumeMounts": [{"name": "recording", "mountPath": "/data"}]
}],
"volumes": [{
"name": "recording",
"persistentVolumeClaim": {"claimName": "recording-data"}
}]
}
}'

# Wait for ingest delay (30s default), then check Blob Storage
az storage blob list \
--account-name <storage-account> \
--container-name datasets \
--prefix recordings/ \
--auth-mode login \
--query "[].name" -o tsv

# Clean up test pod
kubectl -n data-pipeline delete pod acsa-test

πŸ”§ Troubleshooting​

PVC Stuck in Pending​

The ACSA extension may not have finished provisioning the storage class.

# Check storage classes
kubectl get storageclass cloud-backed-sc

# Check ACSA extension pods
kubectl -n azure-arc-containerstorage get pods

# Check extension events
kubectl -n azure-arc-containerstorage get events --sort-by='.lastTimestamp'

Extension Provisioning Failed​

# View extension details
az k8s-extension show \
--name azure-arc-containerstorage \
--cluster-name <arc-cluster> \
--resource-group <rg> \
--cluster-type connectedClusters \
--query '{state: provisioningState, error: errorInfo}'

Files Not Syncing​

  1. Confirm the IngestSubvolume exists and has the correct storage account endpoint
  2. Verify the ACSA managed identity has Storage Blob Data Owner on the storage account
  3. Check that files are older than ACSA_INGEST_MIN_DELAY_SEC (30s default)
# Check ACSA identity role assignment
az role assignment list \
--scope "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/<account>" \
--query "[?roleDefinitionName=='Storage Blob Data Owner'].{principal:principalId, role:roleDefinitionName}" \
-o table

Arc Proxy Connection Failures​

# Verify Arc agent is connected
az connectedk8s show \
--name <arc-cluster> \
--resource-group <rg> \
--query connectivityStatus -o tsv
# Expected: Connected

# Check port availability
lsof -i :47011
ResourceDescription
Chunking and Compression ConfigurationROS 2 bag chunking and compression settings for edge recording
Azure Container Storage enabled by Azure ArcMicrosoft documentation for ACSA
IngestSubvolume specificationCRD reference for IngestSubvolume