Introduction
Application Profiles help you leverage built-in application orchestration functionality by using API or Terraform. Using these interfaces to ZEDEDA Cloud, you can automate your deployments at scale but you cannot see them in the GUI at this time. If you’re looking to automate the deployment of individual application instances or control them through the GUI, see Deploy an Edge Application Instance or Zero Touch Deployments Overview instead.
App Profiles, Asset Groups, and Profile Deployments work together to streamline application delivery to edge nodes.
- An App Profile acts as a comprehensive bundle, encapsulating applications, network settings, and volume configurations.
- An Asset Group is a logical grouping of edge nodes.
- A Deployment Profile can deploy an App Profile to the edge nodes identified in the Asset Group. This reduces manual effort, enhances consistency across devices, and enables automatic deployment to new edge nodes, thereby simplifying the scaling and maintenance of your edge infrastructure.
Common phrases
You’ll see these following phrases commonly used throughout this article.
| Object | Purpose |
| Device | (Existing) Describes an edge node. |
| Project | (Existing) Grouping of devices with a shared RBAC policy. |
| Asset Group | (New) Describes a generalized concept of grouping a set of objects. In this use case, an Asset Group is a subset of devices in a project. |
| App Profile | (New) Describes the applications, network instances, and volumes to be deployed. They are defined through app policies, network policies, and volume policies. |
| Deployment Profile | (New) Describes the association of a given App Profile and an Asset Group. It effectively enables the deployment of an app stack on a group of devices. |
Prerequisites
- You must have either the SysManager or SysAdmin role in your ZEDEDA Cloud enterprise.
- You have already Onboarded an Edge Node to ZEDEDA Cloud.
- You have already added an Edge App Image to ZEDEDA Cloud.
- The Data Store that contains the edge app image needs to have access to all the projects.
- If your application profile defines network settings, you need to make sure that compatible Network Instances are either already created or can be created to support the network configurations specified in your app profile.
- If your application profile defines volume settings, you need to make sure that compatible Volume Instances are either already created or can be created to support the network configurations specified in your app profile.
- You have already created at least one Project in your enterprise, and typically, all related resources belong to the same project.
Manage with API or Terraform Provider
See the App Profiles Service API documentation for more information about how to create, retrieve, update, delete, and more.
See the App Profile Terraform Provider documentation for more information about how to create, retrieve, update, delete, and more.
Example
Consider a scenario where you have an Nginx web server app that you want to deploy on all the edge nodes in your east coast retail stores. You might create a "Nginx Web Server App Profile" to centralize the configuration for the application, including its binaries, network settings, and persistent volumes, acting as a reusable blueprint. Then you might create an Asset Group, such as "retail-store-nodes-east," that logically organizes your east coast edge nodes, allowing you to manage them all as a single unit, whether by device IDs or shared tags. Then, you might create a Profile Deployment to specify that a particular App Profile (for example, the Nginx Web Server) should be instantiated and deployed onto the edge nodes you identified within a chosen target (like those within the "retail-store-nodes-east" group). This approach will automate the application rollouts across your distributed edge infrastructure in a way that is consistent and scalable.
Note: The API and Terraform samples that follow are not comprehensive. Do not use them "as-is". You will need to customize them for your own environment.
App Profile
The App Profile defines the application stack. Every update to the application stack automatically triggers an incremental version. The version is used as part of the Deployment Profile to select which version of the stack you want to deploy. Incrementing a version, which happens behind the scenes, requires an application purge only in case of an image or cloudinit change.
The following API and Terraform objects show an example of how you could centralize the configuration for an application by creating an app profile such as web_server_app_profile.
API
{
"id": "0590557b-b519-475d-865d-d635955d619b",
"name": "web_server_app_profile",
"title": "my web server app profile",
"appPolicies": [
{
"metaData": {
"id": "96e42c46-dc01-418b-9e28-19b5f98c380e",
"name": "nginx",
"title": "nginx app",
"tags": {}
},
"appConfig": {
"networks": 2,
"drives": 1,
"cpus": 2,
"memory": 2048000,
"manifestJSON": {
"acKind": "VMManifest",
"acVersion": "1.1.2",
"name": "nginx",
"displayName": "",
"description": "simple nginx server",
"images": [
{
"imagename": "nginx",
"imageid": "34d0161d-d4d0-409f-806e-d9bab3fc8a28",
"maxsize": 20000000,
"preserve": true,
"target": "Disk",
"drvtype": "HDD",
"params": [],
"readonly": false,
"volumelabel": "",
"ignorepurge": false,
"cleartext": false,
"mountpath": ""
}
],
"interfaces": [
{
"name": "eth0",
"optional": false,
"directattach": false,
"privateip": false,
"acls": [
{
"matches": [
{
"type": "host",
"value": ""
}
],
"actions": [],
"name": "rule1"
}
]
}
],
"vmmode": "HV_HVM",
"resources": [
{
"name": "resourceType",
"value": "Tiny"
},
{
"name": "cpus",
"value": "1"
},
{
"name": "memory",
"value": "524288.00"
},
{
"name": "storage",
"value": "104857600.00"
}
]
},
"interfaces": [
{
"intforder": 1,
"intfname": "eth0",
"directattach": false,
"ipaddr": "",
"macaddr": "",
"accessVlanId": 0,
"defaultNetInstance": false,
"netinsttag": {"hello": "world"},
"acls": [],
"io": null
},
],
"startDelayInSeconds": 0,
"remoteConsole": false,
}
}
],
"networkPolicies": [
{
"metaData": {
"id": "a6c832b3-68a9-4cf9-986c-65dbd9602c6b",
"name": "default-network-policy-name",
"title": "default-network-policy-title",
"tags": {
"hello": "world"
}
},
"networkConfig": {
"tags": {
"hello": "world"
},
"port": "111",
"type": "NETWORK_INSTANCE_DHCP_TYPE_V4",
"kind": "NETWORK_INSTANCE_KIND_LOCAL"
}
}
],
"volumePolicies": [
{
"metaData": {
"id": "a6c832b3-68a9-4cf9-986c-65dbd9602c6c",
"name": "default-volume-policy-name",
"title": "default-volume-policy-title",
"tags": {}
},
"volumeConfig": {
"cleartext": false,
"image": "nginx",
"label": "default-label",
"accessmode": "VOLUME_INSTANCE_ACCESS_MODE_READONLY",
"type": "VOLUME_INSTANCE_TYPE_CONTENT_TREE"
}
}
]
}
Terraform
Note that app profile also depends on resources such as zedcloud_datastore and zedcloud_image.
resource "zedcloud_app_profile" "web_server_app_profile" {
depends_on = [ zedcloud_image.nginx_image ]
name = "web_server_app_profile"
title = "my web server app profile"
app_policies {
# meta_data ID is typically assigned by the API upon creation, so omitted here.
meta_data {
name = "nginx"
title = "nginx app"
tags = {} # Empty tags object
}
app_config {
networks = 2
drives = 1
cpus = 2
memory = 2048000 # Memory in KB
manifest_json {
ac_kind = "VMManifest"
ac_version = "1.1.2"
name = "nginx"
display_name = "" # Empty string from API example
description = "simple nginx server"
vmmode = "HV_HVM" # Hypervisor Virtual Machine mode
images {
imagename = zedcloud_image.nginx_image.name
imageid = zedcloud_image.nginx_image.id
imageformat = zedcloud_image.nginx_image.image_format
maxsize = zedcloud_image.nginx_image.image_size_bytes # Link to image size
preserve = true
target = "Disk"
drvtype = "HDD"
params = [] # Empty array
readonly = false
volumelabel = ""
ignorepurge = false
cleartext = false
mountpath = ""
}
interfaces {
name = "eth0"
optional = false
directattach = false
privateip = false
# ACLs can be defined here, matching the API's empty structure
acls {
name = "rule1"
matches {
type = "host"
value = ""
}
actions = [] # Empty array
}
# Note: The API had 'io: null'. Terraform often omits null fields or uses default values.
}
# Resources block mapping the API's "resources" array
resources {
name = "resourceType"
value = "Tiny"
}
resources {
name = "cpus"
value = "1"
}
resources {
name = "memory"
value = "524288.00" # Memory in KB for VM manifest
}
resources {
name = "storage"
value = "104857600.00" # Storage in bytes for VM manifest
}
}
interfaces { # This is the app_config.interfaces block, separate from manifest_json.interfaces
intf_order = 1
intf_name = "eth0"
direct_attach = false
ip_addr = ""
mac_addr = ""
access_vlan_id = 0
default_net_instance = false
net_inst_tag = { "hello" = "world" }
acls = [] # Empty array
# io = null # Terraform often omits null values
}
start_delay_in_seconds = 0
remote_console = false
}
}
network_policies {
# meta_data ID is typically assigned by the API upon creation, so omitted here.
meta_data {
name = "default-network-policy-name"
title = "default-network-policy-title"
tags = { "hello" = "world" }
}
network_config {
tags = { "hello" = "world" }
port = "111"
type = "NETWORK_INSTANCE_DHCP_TYPE_V4"
kind = "NETWORK_INSTANCE_KIND_LOCAL"
}
}
volume_policies {
# meta_data ID is typically assigned by the API upon creation, so omitted here.
meta_data {
name = "default-volume-policy-name"
title = "default-volume-policy-title"
tags = {} # Empty tags object
}
volume_config {
cleartext = false
image = zedcloud_image.nginx_image.name # Reference the image name
label = "default-label"
accessmode = "VOLUME_INSTANCE_ACCESS_MODE_READONLY"
type = "VOLUME_INSTANCE_TYPE_CONTENT_TREE"
}
}
}
Asset Group
The Asset Group is a collection of devices within a project. An edge node belongs to only a single Asset Group. You can define the Asset Groups by ID or by tag.
The following API and Terraform objects show an example of how you could put multiple edge nodes into a group to manage them as a single unit, by creating an asset group such as retail_store_nodes_east.
API
{
"name": "retail_store_nodes_east",
"title": "East Region Retail Store Edge Nodes",
"description": "All edge nodes deployed in retail stores across the Eastern region.",
"projectId": "o7j4g2l2-f329-2wx3-ok3m-96183z3hq6ln",//ID of project
"assetIds": {
"ids": [
"d2c8f1e0-4b5c-4d6a-8b9e-0a1b2c3d4e5f",//IDs of edge nodes
"e6a9f0b1-c2d3-4e5f-6a7b-8c9d0e1f2a3b",
"f0a1b2c3-d4e5-6f7a-8b9c-0d1e2f3a4b5c"
]
},
// Alternatively, you could target by tags:
/*
"assetTags": {
"assetTag": [
{
"tag": {
"region": "east",
"location_type": "retail-store"
}
},
{
"tag": {
"model": "edge-pro-3000"
}
}
Terraform
resource "zedcloud_project" "retail_project" {
name = "retail-east-project"
title = "East Region Retail Project"
type = "TAG_TYPE_PROJECT"
tag_level_settings {
flow_log_transmission = "NETWORK_INSTANCE_FLOW_LOG_TRANSMISSION_DISABLED"
interface_ordering = "INTERFACE_ORDERING_DISABLED"
}
}
# Data source to fetch the created project's ID (or an existing one)
data "zedcloud_project" "retail_project_data" {
name = zedcloud_project.retail_project.name
title = zedcloud_project.retail_project.title
type = zedcloud_project.retail_project.type
depends_on = [
zedcloud_project.retail_project
]
}
resource "zedcloud_brand" "qemu_brand" { # Renamed for clarity
name = "qemu_standard_brand"
title = "QEMU Standard"
description = "Standard QEMU virtual machine brand"
origin_type = "ORIGIN_LOCAL"
}
resource "zedcloud_model" "edge_pro_3000_model" {
brand_id = zedcloud_brand.qemu_brand.id
name = "edge-pro-3000-model"
title = "Edge Pro 3000"
type = "AMD64"
origin_type = "ORIGIN_LOCAL"
state = "SYS_MODEL_STATE_ACTIVE"
attr = {
memory = "8G"
storage = "100G"
Cpus = "4"
}
io_member_list {
ztype = "IO_TYPE_ETH"
phylabel = "firstEth"
usage = "ADAPTER_USAGE_MANAGEMENT"
assigngrp = "eth0"
phyaddrs = {
Ifname = "eth0"
PciLong = "0000:02:00.0"
}
logicallabel = "ethernet0"
usage_policy = {
FreeUplink = true
}
cost = 0
}
depends_on = [
zedcloud_brand.qemu_brand
]
}
resource "zedcloud_edgenode" "retail_node_1" {
onboarding_key = "" # placeholder
serialno = "d2c8f1e0-4b5c-4d6a-8b9e-0a1b2c3d4e5f"
name = "retail-node-east-001"
model_id = zedcloud_model.edge_pro_3000_model.id
project_id = data.zedcloud_project.retail_project_data.id
title = "Retail Node East 001"
admin_state = "ADMIN_STATE_ACTIVE"
interfaces {
cost = 0
intf_usage = "ADAPTER_USAGE_MANAGEMENT"
intfname = "ethernet0"
tags = {}
}
description = "Edge node for East Retail Store 001."
tags = {
"region" = "east"
"location_type" = "retail-store"
"model" = "edge-pro-3000"
}
}
resource "zedcloud_edgenode" "retail_node_2" {
onboarding_key = "" # placeholder
serialno = "e6a9f0b1-c2d3-4e5f-6a7b-8c9d0e1f2a3b"
name = "retail-node-east-002"
model_id = zedcloud_model.edge_pro_3000_model.id
project_id = data.zedcloud_project.retail_project_data.id
title = "Retail Node East 002"
admin_state = "ADMIN_STATE_ACTIVE"
interfaces {
cost = 0
intf_usage = "ADAPTER_USAGE_MANAGEMENT"
intfname = "ethernet0"
tags = {}
}
description = "Edge node for East Retail Store 002."
tags = {
"region" = "east"
"location_type" = "retail-store"
"model" = "edge-pro-3000"
}
}
resource "zedcloud_edgenode" "retail_node_3" {
onboarding_key = "" # placeholder
serialno = "f0a1b2c3-d4e5-6f7a-8b9c-0d1e2f3a4b5c"
name = "retail-node-east-003"
model_id = zedcloud_model.edge_pro_3000_model.id
project_id = data.zedcloud_project.retail_project_data.id
title = "Retail Node East 003"
admin_state = "ADMIN_STATE_ACTIVE"
interfaces {
cost = 0
intf_usage = "ADAPTER_USAGE_MANAGEMENT"
intfname = "ethernet0"
tags = {}
}
description = "Edge node for East Retail Store 003."
tags = {
"region" = "east"
"location_type" = "retail-store"
"model" = "edge-pro-3000"
}
}
# Data sources to fetch the IDs of the created edge nodes
data "zedcloud_edgenode" "retail_node_1_data" {
name = zedcloud_edgenode.retail_node_1.name
model_id = zedcloud_edgenode.retail_node_1.model_id
project_id = zedcloud_edgenode.retail_node_1.project_id
depends_on = [zedcloud_edgenode.retail_node_1]
}
data "zedcloud_edgenode" "retail_node_2_data" {
name = zedcloud_edgenode.retail_node_2.name
model_id = zedcloud_edgenode.retail_node_2.model_id
project_id = zedcloud_edgenode.retail_node_2.project_id
depends_on = [zedcloud_edgenode.retail_node_2]
}
data "zedcloud_edgenode" "retail_node_3_data" {
name = zedcloud_edgenode.retail_node_3.name
model_id = zedcloud_edgenode.retail_node_3.model_id
project_id = zedcloud_edgenode.retail_node_3.project_id
depends_on = [zedcloud_edgenode.retail_node_3]
}
resource "zedcloud_asset_group" "retail_store_nodes_east" {
name = "retail_store_nodes_east"
title = "East Region Retail Store Edge Nodes"
description = "All edge nodes deployed in retail stores across the Eastern region."
project_id = data.zedcloud_project.retail_project_data.id
asset_ids {
ids = [
data.zedcloud_edgenode.retail_node_1_data.id,
data.zedcloud_edgenode.retail_node_2_data.id,
data.zedcloud_edgenode.retail_node_3_data.id
]
}
asset_tags {
tag = {
"region" = "east"
"location_type" = "retail-store"
}
}
asset_tags {
tag = {
"model" = "edge-pro-3000"
}
}
}
Deployment Profile
The Deployment Profile associates a given App Profile version to an Asset Group. Updating a Deployment Profile will roll out the changes to all the nodes in the Asset Group. There is no per-device trigger required.
The following API and Terraform objects show an example of how you could deploy a web_server_app_profile to the edge nodes identified in retail_store_nodes_east by creating a deployment profile such as deploy_web_profile_east_retail.
API
{
"name": "deploy_web_profile_east_retail",
"title": "Deploy Web Server Profile to East Retail Stores",
"description": "Deploys the Nginx Web Server Application Profile to all edge nodes in the East Region Retail Stores asset group.",
"projectId": "o7j4g2l2-f329-2wx3-ok3m-96183z3hq6ln",
"appProfileInfo": {
"appProfileId": "uuid-of-web-server-app-profile",
"version": 1 // Assuming version 1 of our "web-server-app-profile"
},
"targetAssetGroup": {
"group_id": "a1b2c3d4-e5f6-7890-1234-56789abcdef0" // Reference to the ID of the Asset Group
}
// "revision" and "status" fields are typically system-managed and omitted from create requests.
}
Terraform
resource "zedcloud_project" "retail_project" {
name = "retail-project-o7j4g2l2"
title = "East Region Retail Project"
type = "TAG_TYPE_PROJECT"
}
# Data source to fetch the created project's ID
data "zedcloud_project" "retail_project_data" {
name = zedcloud_project.retail_project.name
title = zedcloud_project.retail_project.title
type = zedcloud_project.retail_project.type
depends_on = [
zedcloud_project.retail_project
]
}
resource "zedcloud_datastore" "nginx_datastore" {
ds_fqdn = "http://147.75.33.217"
ds_path = "images"
ds_type = "DATASTORE_TYPE_HTTP"
name = "nginx_ds"
title = "Nginx Datastore"
description = "Datastore for Nginx images"
region = "eu"
}
resource "zedcloud_image" "nginx_app_image" {
depends_on = [zedcloud_datastore.nginx_datastore]
name = "nginx_image"
datastore_id = zedcloud_datastore.nginx_datastore.id
image_arch = "AMD64"
image_format = "CONTAINER"
image_rel_url = "nginx:latest"
image_size_bytes = 20000000 # Example size
image_type = "IMAGE_TYPE_APPLICATION"
title = "Nginx Official Image"
}
resource "zedcloud_app_profile" "web_server_app_profile" {
depends_on = [zedcloud_image.nginx_app_image]
name = "web_server_app_profile"
title = "my web server app profile"
app_policies {
meta_data {
name = "nginx-app-policy"
title = "Nginx Application Policy"
}
app_config {
networks = 2
drives = 1
cpus = 2
memory = 2048000
manifest_json {
ac_kind = "VMManifest"
ac_version = "1.1.2"
name = "nginx-vm"
description = "simple nginx server VM"
vmmode = "HV_HVM"
images {
imagename = zedcloud_image.nginx_app_image.name
imageid = zedcloud_image.nginx_app_image.id # Reference the actual image ID
cleartext = false
drvtype = "HDD"
ignorepurge = true
maxsize = zedcloud_image.nginx_app_image.image_size_bytes
target = "Disk"
}
}
interfaces {
intf_name = "eth0"
net_inst_tag = {"hello" = "world"}
}
}
}
network_policies {
meta_data {
name = "web-server-network-policy"
title = "Web Server Network Policy"
}
network_config {
port = "111"
kind = "NETWORK_INSTANCE_KIND_LOCAL"
type = "NETWORK_INSTANCE_DHCP_TYPE_V4"
}
}
volume_policies {
meta_data {
name = "web-server-volume-policy"
title = "Web Server Volume Policy"
}
volume_config {
image = zedcloud_image.nginx_app_image.name
label = "nginx-data"
type = "VOLUME_INSTANCE_TYPE_CONTENT_TREE"
accessmode = "VOLUME_INSTANCE_ACCESS_MODE_READONLY"
}
}
}
# Data source to fetch the created App Profile's ID
data "zedcloud_app_profile" "web_server_app_profile_data" {
name = zedcloud_app_profile.web_server_app_profile.name
title = zedcloud_app_profile.web_server_app_profile.title
depends_on = [
zedcloud_app_profile.web_server_app_profile
]
}
resource "zedcloud_brand" "generic_brand" {
name = "generic-edge-brand"
title = "Generic Edge Brand"
origin_type = "ORIGIN_LOCAL"
}
resource "zedcloud_model" "generic_model" {
brand_id = zedcloud_brand.generic_brand.id
name = "generic-model-type"
title = "Generic Edge Model"
type = "AMD64"
origin_type = "ORIGIN_LOCAL"
state = "SYS_MODEL_STATE_ACTIVE"
}
resource "zedcloud_edgenode" "retail_node_for_group" {
onboarding_key = "" # placeholder
serialno = "d2c8f1e0-4b5c-4d6a-8b9e-0a1b2c3d4e5f"
name = "retail-node-group-member"
model_id = zedcloud_model.generic_model.id
project_id = data.zedcloud_project.retail_project_data.id
title = "Retail Node for Group Example"
}
data "zedcloud_edgenode" "retail_node_for_group_data" {
name = zedcloud_edgenode.retail_node_for_group.name
model_id = zedcloud_edgenode.retail_node_for_group.model_id
project_id = zedcloud_edgenode.retail_node_for_group.project_id
depends_on = [zedcloud_edgenode.retail_node_for_group]
}
resource "zedcloud_asset_group" "east_retail_asset_group" {
name = "retail_store_nodes_east"
title = "East Region Retail Store Edge Nodes"
description = "All edge nodes deployed in retail stores across the Eastern region."
project_id = data.zedcloud_project.retail_project_data.id
asset_ids {
ids = [
data.zedcloud_edgenode.retail_node_for_group_data.id # Referencing the ID of the node created above
# Add more IDs if you have more edgenode resources
]
}
# Include asset_tags if you want to define them for the group as well.
}
# Data source to fetch the created Asset Group's ID
data "zedcloud_asset_group" "east_retail_asset_group_data" {
name = zedcloud_asset_group.east_retail_asset_group.name
title = zedcloud_asset_group.east_retail_asset_group.title
depends_on = [
zedcloud_asset_group.east_retail_asset_group
]
}
resource "zedcloud_profile_deployment" "deploy_web_profile_east_retail" {
name = "deploy_web_profile_east_retail"
title = "Deploy Web Server Profile to East Retail Stores"
description = "Deploys the Nginx Web Server Application Profile to all edge nodes in the East Region Retail Stores asset group."
project_id = data.zedcloud_project.retail_project_data.id
app_profile_info {
app_profile_id = data.zedcloud_app_profile.web_server_app_profile_data.id # Use the ID from our app profile data source
version = 1
}
target_asset_group {
group_id = data.zedcloud_asset_group.east_retail_asset_group_data.id
}
# 'status' and 'revision' fields are outputs or managed by the API, not set on creation.
depends_on = [
zedcloud_app_profile.web_server_app_profile,
zedcloud_asset_group.east_retail_asset_group
]
}
Next Steps
Manage your Edge Application Instances to check the status of the profile deployment and resources to make sure they’re running.