Introduction
ZEDEDA Edge Virtualization platform allows you to optionally configure edge application instances through the cloud-init mechanism. Cloud-init is the industry standard tool for automatically configuring newly deployed virtual machines by handling essential first-boot tasks like setting up networking, SSH keys, hostname, and user accounts.
The most common type of script to pass in is called a cloud-config script. This is a YAML formatted file that provides simple, readable methods of setting up common configuration items by declaration. It also has the ability to run arbitrary commands for other tasks. You can refer to (https://cloudinit.readthedocs.io/en/latest/topics/examples.html) for more details.
Cloud-Init Support for Virtual Machines and Containers
Both VM and container implementations support versioning of the user-data. This means that the cloud-init configuration is only reapplied (on a purge or reactivate) if the version of the user-data has changed. The version is specified in the meta-data file in the instance-id field.
Every cloud-init configuration must begin with the #cloud-config header:
#cloud-config
Support in virtual machines
For VMs, EVE-OS supports cloud-init configuration using the NoCloud data source. With NoCloud, EVE-OS provides both meta-data and user-data received from the controller to the VM. This is done by placing these files on a virtual CD-ROM in the form of an ISO image. If a workload includes cloud-init support, it can consume these files and processes the configurations or scripts defined in them.
Support in containers
As opposed to the VM implementation, cloud-init containers does not rely on a cloud-init daemon being present in the container image. Instead, the cloud-init configuration is parsed by EVE and manually applied to the container. EVE's implementation supports two formats for user-data
-
Legacy Format (Available in all supported LTS versions): This format only supports the definition of environment variables in the form of a simple key-value map. The equal sign "=" is used as a delimiter:
ENV1=value1 ENV2=value2
-
Original Cloud-Init Format (Available since EVE 11.3): In this format, the user-data is specified like in any standard cloud-init configuration. The current EVE implementation only supports two user-data fields: runcmd and write_files.
- runcmd is used to set environment variables, similar to the Legacy format. Note that the use of any command other than setting environment variables will result in an error. The env definitions must not be preceded by an export keyword, but its effect is implied in the implementation.
- write_files field supports parameters such as path, content, permissions and encoding. It is used to write one or more files to the container image prior to the container start.
#cloud-config runcmd: - ENV1=value1 - ENV2=value2 write_files: - path: /etc/injected_file.txt permissions: '0644' encoding: b64 content: YmxhYmxh
In case of containers there’s also a very important setting that is the cmdline startup command. In case of ZEDEDA cmdline may be overridden by setting of EVE_ECO_CMD environment variable. Thus, if defined, cmdline will contain EVE_ECO_CMD value. It may help to run specific command inside application without rebuilding and redeploy of it.In this way user can run entrypoint with specified arguments (i.e. debugging ones). The setting of EVE_ECO_CMD will replace the entrypoint command defined in the container manifest. Please note, that change of environment variables require restart of the application.
See more specific examples for #cloud-config in Custom Configuration Examples.
Custom Configuration
You can specify the application configuration script as part of 'custom configuration' during edge application creation. When you create an edge application instance from this edge application bundle, this custom configuration script is passed to the edge node as part of the edge application Instance configuration.
This allows instance-specific fields to be accepted by the user when they are created while allowing for the single and bulk of the configuration script—the common bits to be defined with the edge application.
You can further customize your edge application configuration script per instance but without manual intervention for each instance. You can define the common part of the configuration script as it is and pass instance-specific parameters using variables.
For example, if you want to set the hostname of your edge application instance to the name of the edge node it is running on, you can still use a single script for all your application instances but specify the hostname using a system variable and ZEDEDA will automatically replace the system variable with respective edge gateway name when it creates a configuration for the edge application instance.
ZEDEDA platform supports the following types of variables for custom configuration scripts:
Note: ZEDEDA supports cloud-init updates for purge/force updates, provided there are no edits/modifications done on the variable groups. After deploying the edge application instance:
- If the changes are made on the variable groups, the changes do not get reflected in the application instance (even after the purge/force update).
- The only way to update or make changes to cloud-init, in this case, is to delete and redeploy (with updated cloud-init) the application instance.
ZEDEDA platform system variables
ZEDEDA will automatically replace the system variables with values from the ZEDEDA platform.
Key | Description |
---|---|
$zri.system.edge-node.id | System generated unique id for the edge node in Zedcontrol |
$zri.system.edge-node.name | User defined name for the edge node in Zedcontrol |
$zri.system.edge-node.arch | Hardware architecture of edge node. Values : AMD64, ARM64 |
$zri.system.edge-app.id | System generated unique id for the edge application instance in Zedcontrol |
$zri.system.edge-app.name | User defined name for the edge application instance in Zedcontrol |
$zri.system.edge-instance.ip | Edge Application Instance interface IP address in ZedControl |
$zri.system.edge-instance.name | Edge Application Instance name in ZedControl |
$zri.system.edge-node.serial | Edge Node serial number in ZedControl as configured during onboarding |
ZEDEDA webhook variables
If your edge application is interacting with public IoT services like AWS, Azure, etc., you can define IoTHub or Device Provisioning common parameters as part of the webhook profile and ZEDEDA will automatically replace the variables with values from the configured webhook profile.
Key | Description |
---|---|
$webhook.azure.dps.scope_id | Azure DPS group enrollment scope |
$webhook.azure.dps.shared_key | Azure DPS group enrollment shared key |
$webhook.azure.iothub.ca_cert | Azure IoTHub Intermediate certificate for transparent gateway |
$webhook.azure.iothub.ca_key | Azure IoTHub Intermediate private key for transparent gateway |
$webhook.azure.iothub.ca_password | Azure IoTHub Intermediate certificate password for transparent gateway |
User input variables
Ask the user to provide values during edge application instance creation. Specify any variable name between specific delimiters
###my_user_variable### (here ### is the delimiter but any other sequence of 3 characters can be defined as delimiter)
Provide Custom Configuration at Application Instance
When deploying the application instance through the WebUI, the user will be asked to provide the values for the input variables defined in the cloud-init application marketplace template.
These values can be in the form of plain text, boolean values, passwords, dropdown options or text files.
The following example shows the variable inputs at application instance deployment:
Provide Custom Configuration at Application Instance Using ZCLI
It’s important to note that when operating with API automation, there’s no such concept of input variables as this is just a mechanism applicable to the WebUI.
When operating with API automation tools like Terraform or ZCLI, the full cloud-init script can be provided at the application instance time.
In order to enable that the user needs to set the option “Allow Edge App deployments to set entire configuration” to Yes in the Application Marketplace template:
With this option, the full cloud-init configuration must be given at the application instance deployment(this is also applicable to the WebUI).
In case of ZCLI the full cloud-init is given as input to the keyword custom-configuration. The user should provide a .json file where the full cloud-init script must be given as base64 encoded to the template leaf element of that .json file.
zcli ls
cloud-init.txt
Encode the content of the cloud-init text file with base64:
zclib64cloudinit=$(base64 -w 0 cloud-init.txt)
zcli echo $b64cloudinit
b250ZW50LVR5cGU6IG11bHRpcGFydC9taXhlZDsgYm91b……….--snip–
Update the template leaf in the .json file:
zcli cat cloud_init_exampe.json
{
"name": "cloud-init",
"add": true,
"override": true,
"allowStorageResize": true,
"fieldDelimiter": "",
"template": “####Update With the Base64 content of the previous step”####”
"variableGroups": []
}
Deploy the application instance and provide the above .json as argument to the –custom-configuration:
zcli> zcli edge-app-instance create example-app-instance --edge-app=ubuntu-app-template --edge-node=test-edge-node-1 --network-instance=eth0:defaultLocal-test-edge-node-1 --custom-configuration=./cloud_init_example.json
The application will then be instantiated with the custom cloud-init script.Custom Configuration Examples
Following are cloud-config examples.
#cloud-config
ssh_pwauth: Yes
hostname: $zri.system.edge-instance.name
users:
- name: pocuser
ssh-authorized-keys:
- ssh-ed25519 AAAAC3NzaL1lZFI1NTE5AAAAIKzW56UPME0tlYSSuJliChOm0kxGHdO6kVWkos4No6Wm zededa@adminisrsLaptop.home
shell: /bin/bash
lock_passwd: false
sudo: ALL=(ALL) NOPASSWD:ALL
chpasswd:
list: |
pocuser:pocuser
expire: false
write_files:
- path: /etc/netplan/01-network-config.yaml
content: |
network:
version: 2
ethernets:
alleths:
match:
name: en*
dhcp4: true
- path: /root/parameters
content: |
# Standard ZEDEDA variables available through cloudinit:
system_edge-node_id="$zri.system.edge-node.id"
system_edge-node_name="$zri.system.edge-node.name"
system_edge-node_arch="$zri.system.edge-node.arch"
system_edge-app_id="$zri.system.edge-app.id"
system_edge-app_name="$zri.system.edge-app.name"
system_edge-instance_ip="$zri.system.edge-instance.ip"
system_edge-instance_name="$zri.system.edge-instance.name"
system_edge-node_serial="$zri.system.edge-node.serial"
append: true
runcmd:
- rm /etc/netplan/50-cloud-init.yaml
- netplan generate
- netplan apply
See how to setup ZCLI at ZEDEDA CLI overview. Be aware that to enable MIME multi-part format in the EVE-OS device you need to execute the following command through ZCLI:
zcli>zcli edge-node update myedgedevice --config=process.cloud-init.multipart:true
You can generate the configuration using the write-mime-multipart utility. To install it in a ubuntu system you can run:$ sudo apt install cloud-image-utils
You should create the following folder structure, and include the configuration data in the user_data file and the license token under 000. Note that file structure is specific to the Fortinet appliance, other appliances might require a different file layout.
$ ls -R
.:
openstack
./openstack:
content latest
./openstack/content:
0000 #Fortinet License Token
./openstack/latest:
User_data #Fortinet Configuration Data
Execute the write-mime-multipart that will generate the full cloud-init script can you can then copy and paste under the ZEDEDA application custom configuration:
$write-mime-multipart openstack/latest/user_data:text/plain openstack/content/0000:text/plain
Content-Type: multipart/mixed; boundary="===============####Sep####=="
MIME-Version: 1.0
--===============####Sep####==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="openstack/latest/user_data"
config system interface
edit "port1"
set vdom "root"
set mode static
set ip 192.168.0.12 255.255.255.0
set allowaccess ping https ssh http
set type physical
set alias "WAN"
set snmp-index 1
next
edit "port2"
set vdom "root"
set ip 192.168.4.1 255.255.255.0
set allowaccess ping https ssh http
set type physical
set alias "LAN"
set snmp-index 2
next
end
config system admin
edit admin
set password admin
end
config router static
edit 1
set gateway 192.168.0.1
set device "port1"
next
end
config system dhcp server
edit 1
set ntp-service local
set default-gateway 192.168.4.1
set netmask 255.255.255.0
set interface "port2"
config ip-range
edit 1
set start-ip 192.168.4.2
set end-ip 192.168.4.254
next
end
set vci-match enable
set vci-string "FortiSwitch" "FortiExtender"
next
end
config system dns
set primary 8.8.8.8
set secondary 1.1.1.1
end
config firewall policy
edit 1
set name "Internet access"
set srcintf "port2"
set dstintf "port1"
set action accept
set srcaddr "all"
set dstaddr "all"
set schedule "always"
set service "ALL"
set logtraffic all
set nat enable
next
end
config system global
set admin-ssh-grace-time 3600
end
--===============####Sep####==--
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="openstack/content/0000"
LICENSE-TOKEN:YOURLICENSETOKEN
--===============####Sep####==--
See how to setup ZCLI at ZEDEDA CLI overview. Be aware that to enable MIME multi-part format in the EVE-OS device you need to execute the following command through ZCLI:
zcli>zcli edge-node update myedgedevice --config=process.cloud-init.multipart:true
You can generate the following configuration using the write-mime-multipart utility. In this case, the configuration must be provided in the “openstack/latest/user_data” file.
Content-Type: multipart/mixed; boundary="===============4459334010561113521=="
MIME-Version: 1.0
--===============4459334010561113521==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment; filename="openstack/latest/user_data"
hostname c8kv-ios_cfg
license smart enable
username lab priv 15 secret lab
ip domain-name cisco.com
interface GigabitEthernet1
ip address 10.0.0.5 255.255.255.0
no shut
exit
ip route 0.0.0.0 0.0.0.0 10.0.0.1
line vty 0 4
login local
exit
--===============4459334010561113521==--
In this example above the full device configuration is given in a text format as part of the custom-configuration text snippet.#cloud-config
# Add a user with ztest / zededaI0T credential and sudo permission
users:
- name: ztest
gecos: ZEDEDA Test User
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
lock_passwd: False
passwd: $6$qAI9dWKMTMLUZBB$vpL6vHitSiCVTx8ZHjIkdnerwEsoiWJIffUi65cp6tC/ic1e5GlcbBo0RI90tEvvvolLaUErvIsKyVZkkeyuj1
write_files:
- path: /greengrass/certs/###greengrass_core_name###.cert.pem
permissions: '0644'
encoding: b64
content: ###cloud_cert_pem###
- path: /greengrass/certs/###greengrass_core_name###.key.pem
permissions: '0444'
encoding: b64
content: ###cloud_key_pem###
# Modify AWS Greengrass config file with greengrass core certificates
runcmd:
- sed -i.orig 's|file://certs/\[ROOT_CA_PEM_HERE\]|file:///greengrass/certs/root.ca.pem|' /greengrass/config/config.json
- sed -i.orig 's|file://certs/\[CLOUD_PEM_CRT_HERE\]|file:///greengrass/certs/###greengrass_core_name###.cert.pem|' /greengrass/config/config.json
- sed -i.orig 's|file://certs/\[CLOUD_PEM_KEY_HERE\]|file:///greengrass/certs/###greengrass_core_name###.key.pem|' /greengrass/config/config.json
- sed -i.orig 's|\[ROOT_CA_PEM_HERE\]|root.ca.pem|' /greengrass/config/config.json
- sed -i.orig 's|\[CLOUD_PEM_CRT_HERE\]|###greengrass_core_name###.cert.pem|' /greengrass/config/config.json
- sed -i.orig 's|\[CLOUD_PEM_KEY_HERE\]|###greengrass_core_name###.key.pem|' /greengrass/config/config.json
- sed -i.orig 's|\[yes\|no\]|yes|' /greengrass/config/config.json
- sed -i.orig 's|\[AWS_REGION_HERE\]|###greengrass_region###|' /greengrass/config/config.json
- sed -i.orig 's|\[HOST_PREFIX_HERE\]|###iotcore_name###|' /greengrass/config/config.json
- sed -i.orig 's|\[THING_ARN_HERE\]|arn:aws:iot:###greengrass_region###:###greengrass_account###:thing/###greengrass_core_name###|' /greengrass/config/config.json
- echo -e "Removing backup AWS Greengrass config file"; rm /greengrass/config/config.json.orig
- echo -e "Restarting AWS Greengrass service \n"; sleep 90; systemctl restart greengrass
- echo -e "Checking AWS Greengrass service status \n"; systemctl status greengrass
# - echo -e "Checking AWS Greengrass module status"; sudo iotedge list
final_message: "AWS Greengrass Core gateway is finally up, after $UPTIME seconds"
#cloud-config
# Add a user with ztest / zededaI0T credential and sudo permission
users:
- name: ztest
gecos: ZEDEDA Test User
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
lock_passwd: False
passwd: $6$qAI9dWKMTMLUZBB$vpL6vHitSiCVTx8ZHjIkdnerwEsoiWJIffUi65cp6tC/ic1e5GlcbBo0RI90tEvvvolLaUErvIsKyVZkkeyuj1
write_files:
- path: /etc/iotedge/trusted_ca.cert.pem
permissions: '0644'
encoding: b64
content: $webhook.azure.iothub.ca_cert
- path: /etc/iotedge/trusted_ca.key.pem
permissions: '0644'
encoding: b64
content: $webhook.azure.iothub.ca_key
- path: /etc/iotedge/openssl_ca.cnf
permissions: '0644'
content: |
[ ca ]
default_ca = CA_default
[ CA_default ]
dir = /etc/iotedge
database = $dir/index.txt
serial = $dir/serial
policy = policy_loose
[ policy_loose ]
[ v3_intermediate_ca ]
# Extensions for a typical CA.
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
# Modify Azure IoTEdge config file with DPS group enrollment settings
runcmd:
- group_key_bytes=$(echo $webhook.azure.dps.shared_key | base64 --decode | xxd -p -u -c 1000)
- symmetric_key=$(echo -n $zri.system.edge-node.name | openssl sha256 -mac HMAC -macopt hexkey:$group_key_bytes -binary | base64)
- symmetric_key_replacement="s|\"{symmetric_key}\"|\"$symmetric_key\"|"
- sed -i.orig '31,33 s/^/#/' /etc/iotedge/config.yaml
- sed -i.orig '45,52 s/^##* //' /etc/iotedge/config.yaml
- sed -i.orig '48s|"{scope_id}"|"$webhook.azure.dps.scope_id"|' /etc/iotedge/config.yaml
- sed -i.orig '51s|"{registration_id}"|"$zri.system.edge-node.name"|' /etc/iotedge/config.yaml
- sed -i.orig $symmetric_key_replacement /etc/iotedge/config.yaml
- echo -e "Installing IoTEdge gateway certificate chains"
- cd /etc/iotedge
- sudo mkdir -p newcerts
- sudo rm index.txt
- sudo touch index.txt
- sudo rm serial
- sudo bash -c 'echo 1000 serial'
- openssl genrsa -out $zri.system.edge-node.name_ca.key.pem 4096
- chmod 444 $zri.system.edge-node.name_ca.key.pem
- openssl req -new -sha256 -key $zri.system.edge-node.name_ca.key.pem -subj "/CN=$zri.system.edge-node.name_ca" -out $zri.system.edge-node.name_ca.csr
- openssl ca -batch -config openssl_ca.cnf -extensions "v3_intermediate_ca" -days 365 -notext -md sha256 -in $zri.system.edge-node.name_ca.csr -cert trusted_ca.cert.pem -keyfile trusted_ca.key.pem -keyform PEM -passin pass:$webhook.azure.iothub.ca_password -out $zri.system.edge-node.name_ca.cert.pem -outdir newcerts
- chmod 444 $zri.system.edge-node.name_ca.cert.pem
- cat $zri.system.edge-node.name_ca.cert.pem trusted_ca.cert.pem $zri.system.edge-node.name_ca-full-chain.cert.pem
- chmod 444 $zri.system.edge-node.name_ca-full-chain.cert.pem
- cd -
- sed -i.orig 's|# certificates:|certificates:|' /etc/iotedge/config.yaml
- sed -i.orig 's|# device_ca_cert:| device_ca_cert:|' /etc/iotedge/config.yaml
- sed -i.orig 's|""|"/etc/iotedge/$zri.system.edge-node.name_ca-full-chain.cert.pem"|' /etc/iotedge/config.yaml
- sed -i.orig 's|# device_ca_pk:| device_ca_pk:|' /etc/iotedge/config.yaml
- sed -i.orig 's|""|"/etc/iotedge/$zri.system.edge-node.name_ca.key.pem"|' /etc/iotedge/config.yaml
- sed -i.orig 's|# trusted_ca_certs:| trusted_ca_certs:|' /etc/iotedge/config.yaml
- sed -i.orig 's|""|"/etc/iotedge/trusted_ca.cert.pem"|' /etc/iotedge/config.yaml
- echo -e "Removing backup IoTEdge config file"; rm /etc/iotedge/config.yaml.orig
- echo -e "Restarting IoTEdge service \n"; sleep 90; systemctl restart iotedge
- echo -e "Checking IoTEdge service status \n"; systemctl status iotedge
- echo -e "Checking IoTEdge module status"; sudo iotedge list
final_message: "Azure IoT Edge gateway is finally up, after $UPTIME seconds"
#cloud-config
runcmd:
- GODEBUG='netdns=cgo+2'
- HOSTNAME="grafana-app"
write_files:
- path: /etc/grafana/provisioning/dashboards/dashboard.json
permissions: '0777'
encoding: b64
content: ###MY_FILE###
If you need to change the entrypoint command and/or its arguments, just include the new entrypoint command and all its variables in the EVE_EVO_CMD variable.
#cloud-config
runcmd:
- GODEBUG='netdns=cgo+2'
- HOSTNAME="grafana-app"
- EVE_ECO_CMD="/run.sh –debug"
write_files:
- path: /etc/grafana/provisioning/dashboards/dashboard.json
permissions: '0777'
encoding: b64
content: ###MY_FILE###
Next Steps
You can view the status and details of an edge app's custom configuration in the edge app's details page in the Configuration section.