Automate Deployments with Application Profiles

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

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. 

Was this article helpful?
0 out of 0 found this helpful