Spectro Cloud Edge Clusters
Deploy and manage Kubernetes clusters on edge infrastructure using Palette.
Before Creating Clusters
Ask the user:
- Project name? (use
spectrocloud-commonskill for UID) - Cluster name?
- Infrastructure profile? (use
spectrocloud-cluster-profilesskill) - Add-on profile(s)? (optional)
- Edge host UIDs? (must be registered)
- Deployment mode? (agent, appliance, or 2-node HA)
- Network mode? VIP (needs IP) or Overlay
- SSH public key(s)?
- NTP server(s)?
Best Practice: Use separate infrastructure + add-on profiles.
Edge Deployment Modes
| Mode | Nodes | BYOOS | Use Case |
|---|---|---|---|
| Agent | 1 or 3+ | system.uri: "NA" |
Existing OS |
| Appliance | 1 or 3+ | Provider image URL | Immutable |
| 2-Node HA | Exactly 2 | Provider + K3s | HA edge |
2-Node: K3s only, appliance mode only, TWO_NODE=true in CanvOS.
API: Create Cluster
curl -s -X POST "https://api.spectrocloud.com/v1/spectroclusters/edge-native" \
-H "ApiKey: $PALETTE_API_KEY" \
-H "ProjectUid: $PROJECT_UID" \
-H "Content-Type: application/json" \
-d '{
"metadata": {"name": "my-cluster"},
"spec": {
"cloudType": "edge-native",
"profiles": [{"uid": "<infra-uid>"}, {"uid": "<addon-uid>"}],
"cloudConfig": {
"sshKeys": ["ssh-rsa AAAA..."],
"vip": "192.168.1.100",
"ntpServers": ["time.google.com"]
},
"machinePools": [{
"name": "control-plane-pool",
"size": 1,
"controlPlane": true,
"controlPlaneAsWorker": true,
"edgeHosts": [{"hostUid": "<edge-host-uid>"}]
}]
}
}'
Overlay Network (instead of VIP)
"cloudConfig": {
"sshKeys": ["..."],
"ntpServers": ["time.google.com"],
"overlayNetworkConfiguration": {"enable": true}
}
2-Node HA Configuration
Correct API structure (differs from standard cluster):
{
"spec": {
"cloudConfig": {
"isTwoNodeCluster": true,
"sshKeys": ["ssh-rsa ..."],
"vip": "192.168.1.100",
"ntpServers": ["time.google.com"]
},
"machinePoolConfig": [{
"cloudConfig": {
"edgeHosts": [
{"hostUid": "<node1-uid>", "twoNodeCandidatePriority": "primary"},
{"hostUid": "<node2-uid>", "twoNodeCandidatePriority": "secondary"}
]
},
"poolConfig": {
"name": "control-plane-pool",
"size": 2,
"isControlPlane": true,
"useControlPlaneAsWorker": true
}
}]
}
}
Key 2-node fields:
isTwoNodeCluster: trueincloudConfig(not at spec level)twoNodeCandidatePriority:"primary"or"secondary"per hostuseControlPlaneAsWorker: trueto run workloads on both nodes- Uses
machinePoolConfig(notmachinePools) with nestedcloudConfig/poolConfig
API: Other Operations
Get Cluster
curl -s "https://api.spectrocloud.com/v1/spectroclusters/$CLUSTER_UID" \
-H "ApiKey: $PALETTE_API_KEY" -H "ProjectUid: $PROJECT_UID"
Get Kubeconfig
curl -s "https://api.spectrocloud.com/v1/spectroclusters/$CLUSTER_UID/assets/kubeconfig" \
-H "ApiKey: $PALETTE_API_KEY" -H "ProjectUid: $PROJECT_UID" > kubeconfig.yaml
Delete Cluster
curl -s -X DELETE "https://api.spectrocloud.com/v1/spectroclusters/$CLUSTER_UID" \
-H "ApiKey: $PALETTE_API_KEY" -H "ProjectUid: $PROJECT_UID"
List Edge Hosts
curl -s "https://api.spectrocloud.com/v1/edgehosts" \
-H "ApiKey: $PALETTE_API_KEY" -H "ProjectUid: $PROJECT_UID" | \
jq '[.items[] | {name: .metadata.name, uid: .metadata.uid, state: .status.state}]'
Cluster Lifecycle
Update Profile
Create new profile version first (see spectrocloud-cluster-profiles), then:
curl -s -X PUT "https://api.spectrocloud.com/v1/spectroclusters/$CLUSTER_UID/profiles" \
-H "ApiKey: $PALETTE_API_KEY" -H "ProjectUid: $PROJECT_UID" \
-H "Content-Type: application/json" \
-d '{"profiles": [{"uid": "<new-version-uid>"}]}'
IMPORTANT: PUT replaces the cluster's entire attached-profile set. Any attached profile whose uid is left out of the body gets detached — including add-ons. On a cluster with more than one profile, read the complete current list from spec.clusterProfileTemplates (Get Cluster, below) and send all of it, or use PATCH instead (see "Attach an Additional Profile" below). Also remember: a new profile version resets that profile's variable overrides.
Attach an Additional Profile to a Running Cluster (Additive)
To attach a profile (e.g. an add-on) without touching what's already attached, PATCH instead of PUT:
curl -s -X PATCH "https://api.spectrocloud.com/v1/spectroclusters/$CLUSTER_UID/profiles?resolveNotification=false" \
-H "ApiKey: $PALETTE_API_KEY" -H "ProjectUid: $PROJECT_UID" \
-H "Content-Type: application/json" \
-d '{"profiles": [{"uid": "<addon-profile-uid>"}]}'
Verify — re-GET the cluster and confirm spec.clusterProfileTemplates grew from N to N+1 with the original uids unchanged:
curl -s "https://api.spectrocloud.com/v1/spectroclusters/$CLUSTER_UID" \
-H "ApiKey: $PALETTE_API_KEY" -H "ProjectUid: $PROJECT_UID" | \
jq '.spec.clusterProfileTemplates[] | {name, uid, type}'
Source (re-verify against these if this ever needs re-checking): spectrocloud/palette-sdk-go client/cluster.go (PatchClusterProfileValues, UpdateClusterProfileValues) and client/addon_deployment_update.go (CreateAddonDeployment).
Add Edge Host
curl -s -X PATCH "https://api.spectrocloud.com/v1/spectroclusters/$CLUSTER_UID/machinePools/worker-pool" \
-H "ApiKey: $PALETTE_API_KEY" -H "ProjectUid: $PROJECT_UID" \
-H "Content-Type: application/json" \
-d '{"edgeHosts": [{"hostUid": "<new-host-uid>"}]}'
Profile Variables
Override profile variables at cluster creation:
"profiles": [{"uid": "<uid>", "variables": [
{"name": "K8sPodCIDR", "value": "100.64.0.0/18"},
{"name": "K8sServiceCIDR", "value": "100.64.64.0/18"}
]}]
Additional Resources
references/api-examples.md- Full API examples (multi-node, 2-node)references/terraform-examples.md- Full Terraform patterns
Edge Host Token Creation
IMPORTANT: Edge host tokens are TENANT-scoped, not project-scoped. Do NOT include ProjectUid header.
# Create edge host token (NO ProjectUid header!)
curl -s -X POST "https://api.spectrocloud.com/v1/edgehosts/tokens" \
-H "ApiKey: $PALETTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"metadata": {"name": "my-token"}}'
# Token value is in spec.token (NOT status.token)
TOKEN=$(echo $RESPONSE | jq -r '.spec.token')
Troubleshooting
| Issue | Solution |
|---|---|
| "Enable two-node cluster to configure candidate priority" | Set is_two_node_cluster = true in Terraform cloud_config block |
| "Two-node candidate priority demands one primary and one secondary" | Add two_node_role = "primary" and "secondary" to each edge_host block (Terraform) |
| Terraform attribute not found | API vs Terraform naming differs - check with terraform providers schema -json |
| Cluster stuck provisioning | Check edge host logs: journalctl -u spectro-stylus-agent.service -f |
| Cluster stuck Provisioning, no nodes appearing | VMs likely in install loop - check boot order is disk-first (scsi0;ide2), power cycle VMs |
| 2-node cluster needs VIP | 2-node clusters cannot use overlay-only networking - must specify a VIP |
| Can't find latest K8s version | API paginates at 50 - use offset parameter to get all versions |
| "ProjectUidIsNotEmpty" on token creation | Edge host tokens are tenant-scoped - remove ProjectUid header |
| Profiles created but cluster creation fails | Check Terraform provider has project_name set (see spectrocloud-cluster-profiles skill) |
Quick Reference
| Operation | Endpoint |
|---|---|
| Create | POST /v1/spectroclusters/edge-native |
| Get | GET /v1/spectroclusters/{uid} |
| Delete | DELETE /v1/spectroclusters/{uid} |
| Kubeconfig | GET /v1/spectroclusters/{uid}/assets/kubeconfig |
| Edge Hosts | GET /v1/edgehosts |
| Update Profile (replaces set) | PUT /v1/spectroclusters/{uid}/profiles |
| Attach profile (additive) | PATCH /v1/spectroclusters/{uid}/profiles |