CloudVpc -- User Guide v1.0.0

Architecture, configuration reference, topologies, scaling, monitoring, security and troubleshooting for CloudVpc.

This guide is the operating reference for CloudVpc: how it is built, how to configure each supported topology, and how to scale, upgrade, monitor, secure, troubleshoot and remove it. It assumes you have read the Overview; the Quick Start walks through one deployment end to end.

Architecture

Components

ComponentWhereWhat it does
RelayEC2 instance in your VPCBoots the Cloud Spectra Gateway image in relay mode. Holds the private IPv4 address its worker wears, terminates the worker's WireGuard tunnel, forwards between the tunnel and the VPC without translating addresses, and serves its enrolment and health endpoints on port 28300. Systemd units: cloudspectra_relay (the datapath), cloudspectra_relay_dash (enrolment, health and dashboard), cloudspectra_relay_upgrade (upgrade at boot) and cloudspectra_relaymode (sets the image up as a relay).
WorkerLinux VM in OCI or DigitalOceanRuns your workload. At first boot it downloads the Cloud Spectra downloader, installs the client package with your licence, and runs cloudspectra_relay_attach bootstrap-worker, which pairs it with a relay and brings up its tunnel interface, cswg0, wearing the relay's private address.
Rendezvous blockIPv6 addresses in each relay subnetPre-allocated by Terraform. Each relay claims one at boot; workers use them to find relays.
Load balancerAWSAn Application Load Balancer whose target group is the relay Auto Scaling group. A request to a relay's private address is forwarded down the tunnel and answered by its worker.
Peer mapDynamoDB table (on-demand)Pool topologies. Each relay writes the row for its own worker, including the worker's host address, so workers can reach each other directly. Enabled by default.
ControllerEKS topologyScans relays and workers by tag, and launches, pairs and retires workers. See EKS topology.

Data path

  VPC client or load balancer                        worker (OCI or DigitalOcean)
            |                                                     ^
            v  to 10.43.0.21 (the relay's private IPv4)           |  cswg0 wears 10.43.0.21
   relay --- eBPF redirect into the WireGuard tunnel -------------+
            (no address translation)

  worker --- 169.254.169.254 (metadata) --- tunnel --- relay --- VPC metadata service
  worker --- VPC range -------------------- tunnel --- relay --- VPC
  worker --- other destinations ----------- the worker's own cloud (split tunnel)
  worker --- another worker --------------- direct, GENEVE over UDP 6081 (fallback: via the relays)
  • North-south. Traffic addressed to the relay's private IPv4 is redirected into the tunnel by eBPF programs on the relay and delivered to the worker, which holds that address on its tunnel interface and answers as it.
  • Metadata. The worker routes 169.254.169.254/32 through the tunnel. The relay forwards without consuming a hop and caps the metadata service's replies to one hop, so a process on the worker host gets an answer and a container behind it does not.
  • Split tunnel. The tunnel carries the VPC's range and the metadata address. The ranges are issued by the relay when the worker enrols and refreshed afterwards. The relay never issues a default route; a full tunnel is a per-client choice that the CloudVpc configurations do not make.
  • East-west. Workers that know each other's host address exchange traffic directly, in GENEVE over UDP port 6081, spread across four source ports (49152-49155). Until a peer's address is known, traffic goes through the relays.

What happens at boot

A relay boots the Cloud Spectra Gateway image with its configuration in user data, and first upgrades itself in place if the image is older than package_version. When it has a rendezvous block, it claims an address from its own Availability Zone's block before it serves anything; if it cannot hold one, it refuses to start, its health check fails, and the Auto Scaling group replaces it. A running relay re-asserts its claim every 3 minutes. The relay fetches its datapath from delivery_url and proves its licence; a relay without a valid licence keeps running but is parked and forwards nothing.

A worker runs a short cloud-init document: it writes /etc/cloudspectra/licence and /etc/cloudspectra/worker.yaml (both mode 0600), downloads the downloader, installs the client, and runs cloudspectra_relay_attach bootstrap-worker. That command executes an ordered plan and stops at the first step that fails: wait for the distribution's package jobs, install base packages, install the operator SSH key, write the client environment, pair with a relay and bring the tunnel up, wait until the VPC metadata service answers through the tunnel, and then -- depending on the configuration -- join the Kubernetes cluster or start the demo application on port 8080. It narrates each step to the serial console and to /var/log/cloudspectra-worker-bootstrap.log. bootstrap-worker -dry-run prints every step, file and command without changing anything.

Addressing

Each relay announces which of its addresses does what:

FieldMeaning
Endpoint and portThe public transport address the worker dials -- a public IPv4 or an IPv6 global address -- and the WireGuard port. Never transferred.
Public keyThe relay's WireGuard public key.
IdentityThe private IPv4 address the relay holds and transfers to its worker.
IPv6 identityAn IPv6 global address transferred alongside the private IPv4, only when the endpoint is a public IPv4 address.
Host addressThe worker's own address on its own network, reported by the worker. It lets worker-to-worker traffic bypass the relays; when it is missing, that traffic still flows through the relays.

In the autoscaling topologies the identity is disposable: AWS assigns each relay's private IPv4 at launch, so a replacement relay has a new one. What stays stable is the load balancer and the rendezvous block.

The rendezvous block

Each relay subnet has its own IPv6 /64, and Terraform pre-allocates addresses in it: slots_per_az per Availability Zone in the pool configurations (default 20, at most 200), and 8 in the EKS configuration. A relay claims an address with the EC2 AssignIpv6Addresses call. AWS assigns an IPv6 address to exactly one network interface, so a second relay asking for the same address is refused and tries the next. Nothing else records who holds what; run the watch_claims output to see the current claims. AWS releases an address when the relay's interface is deleted.

Size the block for the most relays one zone can hold. A relay in a zone whose block is exhausted cannot hold an address and refuses to start.

Enrolment and pairing

  • The fleet enrolment token. Terraform generates one token per fleet and seeds a copy into every relay at its first boot. In the pool configurations each relay accepts it once, within enrol_token_ttl (default 168h) of its own boot: the first worker to reach a relay pairs with it, and the next is refused and moves on. That is how N workers spread over N relays. A replacement relay seeds a fresh copy. The token authorises enrolment and a read-only download, nothing else.
  • Keys stay on the machine that uses them. A worker generates its own WireGuard key pair and sends only the public half when it enrols. The relays in these configurations also generate their own keys, so no WireGuard private key is written to Terraform state.
  • Workers spread their attempts. Each worker walks the rendezvous list from a starting point derived from its host name, so workers booting together do not all try the same relay first.

How the number of workers is kept equal to the number of relays depends on the topology:

TopologyWho keeps the counts equal
OCI poolAn agent on every worker (cloudspectra-client-agent.service) reads the relay group's size every 15 seconds. Any worker may grow the OCI instance pool to the relay count. Only a worker whose own relay has gone removes itself -- with a detach that also decrements the pool -- because only it knows which instance is redundant. A worker concludes that its relay is gone only when another relay answers, so a network partition never shrinks the pool, and the pool never goes below worker_floor.
DigitalOcean poolNobody: the number of Droplets is fixed by worker_count. Keep it equal to the relay count yourself.
EKSThe controller. Every reconcile_interval_seconds (default 60) it assigns existing unpaired workers first, launches a worker for each relay still unpaired, and retires workers whose relays AWS reports terminated -- within per-pass limits.

On scale-in, the relay group's lifecycle hook (notify-worker-on-terminate) holds a terminating relay so that its worker can be withdrawn first -- for up to terminate_hook_seconds (default 120) in the pool configurations. The hook is best effort and lets the termination continue on timeout; the worker's own check, or the controller's next pass, is what guarantees that no worker is left orphaned.

Topologies

These docs cover three Terraform configurations. Each creates its own dual-stack AWS VPC for the relays.

ConfigurationRelaysWorkersPairingKubernetes
aws-asg-oci-poolEC2 Auto Scaling groupOCI instance pool that follows the relay groupEnrolment; no controllerNo
aws-asg-do-poolEC2 Auto Scaling groupDigitalOcean Droplets, fixed countEnrolment; no controllerNo
eks-oci-asgSelf-managed EKS node group (an Auto Scaling group)OCI or DigitalOcean, launched by the controllerReconciled by the controllerYes -- workers are EKS nodes

OCI pool -- aws-asg-oci-pool

The topology of the Quick Start. Terraform creates the dual-stack VPC (one public subnet and one IPv6 /64 per Availability Zone, in at least two zones), the relay Auto Scaling group behind an Application Load Balancer, the peer map table, an IAM user for the workers, and an OCI VCN with IPv6 holding an instance pool of workers. Workers use the latest Canonical Ubuntu 24.04 image for the chosen shape. After creation Terraform ignores the pool's size; the workers own it.

  • The workers resize their pool with OCI instance-principal credentials, which needs a tenancy-level dynamic group and policy. The configuration creates them unless manage_oci_iam = false (see below).
  • Each worker holds the access key of an IAM user named <name>-client-reader, which can call autoscaling:DescribeAutoScalingGroups and nothing else, to read the relay count.
  • There is no fleet-wide safety rail: each worker acts on what it sees. The rules above -- act only on positive readings, never go below the floor, size from the group rather than from probes -- are what keep that safe.

DigitalOcean pool -- aws-asg-do-pool

The same AWS half as the OCI pool; the workers are a fixed number of DigitalOcean Droplets in a DigitalOcean VPC. It takes the same inputs as the OCI pool, without the oci_*, worker_floor and worker_initial_size settings, and adds:

VariableDefaultNotes
do_tokenemptyDigitalOcean API token. Leave it empty to use DIGITALOCEAN_TOKEN from the environment. terraform destroy needs it too.
do_regionnyc2Region for the Droplets. Size availability changes over time; choose a region that currently offers do_size. The configuration checks this at plan time.
do_sizes-2vcpu-4gbDroplet size. Droplets are x86_64; the client package is chosen to match.
do_imagedebian-13-x64Stock Debian 13.
do_vpc_cidrderived from nameDigitalOcean refuses overlapping VPC ranges across an account, so the range is derived from the name. Give each deployment its own name.
do_ssh_key_ids[]Account SSH keys to add to the Droplets.
worker_count2Number of Droplets. It cannot exceed the number of rendezvous addresses allocated, and it does not follow the relay group.
ssh_public_key_overrideemptyYour SSH public key; empty reads ~/.ssh/id_rsa.pub.
Add a firewall. This configuration does not create a DigitalOcean Cloud Firewall for the worker Droplets. Apply one that allows only what your workload needs, plus UDP 6081 between the workers for direct worker-to-worker traffic.

EKS -- eks-oci-asg

One Amazon EKS cluster. Its relays are an Auto Scaling group that is a self-managed node group, and its workers run in OCI or DigitalOcean (worker_cloud = "oci" or "do") and join the cluster as nodes, each named after the relay whose identity it wears and labelled cloudspectra.ai/compute-location. The configuration also creates a small native node group (native_count, default 1) and installs Calico in VXLAN mode as the CNI (calico_version, default v3.29.1; pod_cidr, default 192.168.128.0/17).

Nothing is paired at plan time. With in_cluster_controller = true, the controller runs as the cloudspectra-master Deployment in the cloudspectra-system namespace; the relays reach it on a NodePort inside the VPC, and it has no public endpoint. Exactly one of in_cluster_controller or master_endpoint (an external controller) must be set; the plan is refused otherwise.

VariableDefaultNotes
package_version-- (required)The exact Cloud Spectra version. A release version also selects the release download and API hosts, so delivery_url and the download hosts need not be set.
license_keyemptySet it explicitly. When it is empty the relays fall back to ~/.cloudspectra/credentials, but the workers and the controller do not.
in_cluster_controllerfalseSet true to run the controller in the cluster.
worker_cloudocioci or do. With do, set the do_* variables and DIGITALOCEAN_TOKEN.
fleet_namedefaultBecomes the cs_fleet tag that relays and workers carry. Give each fleet in the same accounts its own name.
relay_min / relay_desired / relay_max0 / 2 / 6The relay group's size, and so the number of worker nodes. Relays are placed in one subnet with 8 rendezvous addresses, so keep relay_max at 8 or below.
relay_instance_type / relay_archt4g.small / arm64Keep both consistent with the relay image. Use 4 vCPUs or more on the Enterprise lane.
relay_ami_idemptyThe relay image; set it explicitly.
oci_shape / oci_ocpus / oci_memory_gbsVM.Standard.A1.Flex / 2 / 12OCI worker size. The OCI tenancy and region are read from ~/.oci/config; the compartment defaults to the tenancy.
reconcile_max_terminations_per_pass2The most workers one controller pass may terminate.
reconcile_refuse_terminate_fraction0.34A pass that would remove more than this share of the fleet is refused. Only workers whose relays merely disappeared from a reading count; a worker whose relay AWS reports terminated is retired without spending this budget.
reconcile_max_pairs0Ceiling on pairs: beyond it new pairings are refused and nothing running is removed. 0 means no ceiling.
wireguard_mtu1380Tunnel MTU; Calico's pod MTU is 50 less.
admin_cidrs[]Ranges for recovery SSH and the EKS public endpoint. Empty leaves the EKS endpoint open and SSH closed.
fleet_manifest_binarycloudspectra_fleet_manifestThe Cloud Spectra tool that renders the controller's manifest on the machine running Terraform -- at plan, apply and destroy. It must be on PATH, or given by path.

On the machine that runs Terraform you also need Python 3, the AWS CLI and kubectl, which the configuration uses to bootstrap the cluster, and the Python boto3 package -- plus the oci Python SDK for OCI workers -- which it uses to drain the fleet before a destroy. Useful outputs: kubeconfig_command, verify_nodes, scale_command, relay_asg, fleet_id, app_url.

eval "$(terraform output -raw kubeconfig_command)"
eval "$(terraform output -raw verify_nodes)"

NAME                         STATUS  VERSION  COMPUTE-LOCATION  INSTANCE-TYPE
ip-10-42-0-31.ec2.internal   Ready   v1.32.x  oci               VM.Standard.A1.Flex
ip-10-42-0-87.ec2.internal   Ready   v1.32.x  oci               VM.Standard.A1.Flex

Configuration reference

Each configuration ships a generated terraform.tfvars.example listing every variable with its default, required ones first. The tables below cover the pool configurations; aws-asg-do-pool differs as listed above, and the EKS configuration's main inputs are listed above.

Required

VariableDescription
license_keyYour Cloud Spectra licence key (CSTW1-...). Sensitive. Relays park without a valid one; workers use it to download the client.
delivery_urlThe Cloud Spectra API: relays fetch their datapath from it, and the workers' downloader exchanges the licence there for a short-lived download token. For a release: https://api.gateway.cloudspectra.ai. Must be https://.
package_versionThe exact Cloud Spectra version to run, for example 1.0.0. Never latest.
client_apt_base_urlDeclared and required, but not used by the install path, which downloads the client through the downloader. Any value is accepted.
oci_tenancy_ocid, oci_compartment_ocidOCI pool only. The tenancy OCID itself (availability domains are listed against the tenancy) and the compartment for the workers.

Optional

VariableDefaultDescription
namecs-asgpoolPrefix for every resource, including IAM names; 24 characters or fewer. Use a different name for each deployment in the same accounts.
aws_regionus-east-1Region for the relays.
vpc_cidr10.43.0.0/16The new VPC's IPv4 range; an IPv6 block is added automatically.
az_count2Availability Zones for the relays; at least 2, because the load balancer needs two subnets.
slots_per_az20Rendezvous addresses per zone, 1 to 200.
relay_min / relay_desired / relay_max2 / 2 / 6Relay Auto Scaling group size.
relay_instance_typet4g.smallRelay size. The relays in these configurations are Graviton (arm64) instances. On the Enterprise lane, use at least 4 vCPUs, for example t4g.xlarge.
relay_ami_idemptyThe relay image (arm64). When it is empty the configuration searches relay_ami_owner (default self) for images named like relay_ami_name_filter (default cloudspectra-gateway-*.*-*); set it explicitly.
relay_guard_vendorstripeThe billing channel the relay proves its licence against. Leave it as stripe for a licence bought from Cloud Spectra; an empty value makes the relay exit.
public_artifact_basehttps://downloads-dev.cloudspectra.aiPublic host the workers download the downloader from. Set it to https://downloads.cloudspectra.ai for a release. Must be https://.
admin_cidrs[]Ranges allowed to reach the load balancer and the relays' recovery SSH ports.
lb_internaltrueInternal load balancer. Set false to publish it; it still admits only admin_cidrs.
listener_port / app_port80 / 8080The load balancer's listener, and the port the workload listens on behind each relay.
wireguard_port / dash_port28302 / 28300The relays' WireGuard port (UDP) and enrolment and health port (TCP).
imds_hop_limit1Metadata hop limit on the relays. 1 lets the worker host reach the metadata service and keeps containers on it from doing so.
enrol_token_ttl168hHow long after its boot a relay accepts the fleet's enrolment token.
health_check_grace_seconds600Grace before the group acts on a failing load balancer health check.
terminate_hook_seconds120How long a terminating relay has to tell its worker.
relay_key_nameemptyEC2 key pair for recovery SSH to the relays. Not needed if you supply an SSH public key.
relay_extra_env, tags{}Extra relay environment variables, and tags for every resource.
pair_scaling_enabled / pair_cpu_targetfalse / 70Scale the relay group on pair load. See Scaling on pair load.
oci_regionus-ashburn-1OCI region for the workers. With manage_oci_iam = true it must be your tenancy's home region.
oci_shape / oci_ocpus / oci_memory_gbsVM.Standard.A1.Flex / 1 / 6Worker size. A1 shapes are arm64 and E-series shapes are x86_64; the client package is chosen to match.
oci_vcn_cidr / oci_subnet_cidr10.97.0.0/16 / 10.97.1.0/24The workers' own network.
oci_ssh_public_keyemptySSH key for the workers and the relays; empty reads ~/.ssh/id_rsa.pub.
worker_initial_size2Starting size of the OCI pool only; the workers manage it afterwards.
worker_floor1The pool never shrinks below this. It must be at least 1: a pool with no workers has nobody left to grow it again.
manage_oci_iamtrueCreate the OCI dynamic group and policy. See OCI IAM without tenancy rights.

Outputs

OutputWhat it gives you
app_urlThe demo application through the load balancer.
verifyA command that sends ten requests and prints, for each, the cloud that served it and the AWS instance ID it presents as.
watch_targetsA command showing every relay in the target group and its health.
watch_claimsA command listing each interface's IPv6 rendezvous address and private IPv4 address.
asg_name, scaleThe relay Auto Scaling group, and the command that resizes it.
rendezvous_addressesThe pre-allocated IPv6 addresses, by zone.
worker_pool_nameThe worker pool's name.
peermap_table, peermap_fleetThe peer map's DynamoDB table and fleet key.
pair_metrics_namespace, pair_metrics_dimensionWhere the relays publish pair load in CloudWatch.
pair_scaling_policy, relay_throttled_alarmThe scaling policy and the alarm, or null when pair scaling is off.

OCI IAM without tenancy rights

The OCI pool's workers resize their own pool with instance-principal credentials, which needs a dynamic group and a policy at tenancy level. OCI accepts these only from the tenancy's home region, and only from a user who may change tenancy IAM; otherwise the apply fails (for example with 403-NotAllowed and a request to use the home region). In that case set manage_oci_iam = false and have a tenancy administrator create the equivalents:

dynamic group  ALL {instance.compartment.id = '<compartment OCID>'}
policy         Allow dynamic-group <name> to use instance-pools in compartment id <compartment OCID>
               Allow dynamic-group <name> to inspect instance-pools in compartment id <compartment OCID>

Without them the fleet pairs correctly, but the pool never follows the relay group.

The cloudspectra/gateway Terraform provider

The public provider's cloudspectra_ecmp_pipe resource accepts mode = "cloud_vpc" and reports at plan time what that mode derives to -- for example, that the client end runs the Cloud Spectra datapath and how the pipe is billed. It is a planning check only: it creates no relays, workers or networks. CloudVpc itself is deployed with the configurations above.

Scaling

The relay Auto Scaling group is the only place the fleet size is decided. Resize it with the scale output's command, with terraform apply -var relay_desired=N, or with a scaling policy.

  • OCI pool: the workers follow within a poll interval.
  • DigitalOcean pool: set worker_count to match and apply.
  • EKS: the controller follows within a reconcile interval.

Scaling on pair load

A relay's EC2 metrics describe the relay, not the worker running the workload. So, in the pool configurations, each relay publishes its pair's load to CloudWatch every 30 seconds, and pair_scaling_enabled = true creates:

  • a target-tracking policy on PairCPUPercent -- the higher of the relay's and the worker's CPU, never their average -- that holds it at pair_cpu_target (default 70%);
  • an alarm on the relays' packets-per-second and bandwidth-out allowance-exceeded rates that adds one relay when either has been above zero for two consecutive minutes.

A pair publishes only while its worker is reporting: a relay without a fresh worker reading withholds the pair metrics instead of publishing half of them. Leave pair scaling off wherever something else sets the group's size; the EKS configuration does not offer it.

Licence limits that affect scaling

  • Developer lane: an account may run at most 16 vCPUs. Relays beyond the limit park and carry no traffic until room frees -- for example, eight 2-vCPU relays fit.
  • Enterprise lane: no account limit, but a relay with fewer than 4 vCPUs is refused.

Upgrades

Every configuration pins an exact package_version; nothing follows a "latest" pointer.

  • Relays read the version they were given at first boot and, at every boot, upgrade themselves in place to it from the release host when the image is older. The download is checked against its SHA-256 and the release signature. Upgrades across a major version are refused in place; use a new relay image (relay_ami_id) for those.
  • Workers install the client version given by package_version once, at first boot. There is no in-place client upgrade.

To move a fleet to a new release, change package_version (and relay_ami_id if the image changes) and apply, then replace instances so they boot with the new configuration. Running relays keep what they booted with: replace them one at a time -- for example by terminating a relay and letting the group launch its successor. In the OCI pool and the EKS topology, a replaced relay's worker is retired and its successor is launched with the new client. In the DigitalOcean pool the apply itself replaces the Droplets, because their user data changes.

Monitoring

What to watchHow
Relay healthThe load balancer target group (watch_targets). The health check is the relay's own /healthz on port 28300 over HTTPS; /readyz on the same port says whether the relay is ready and why not.
Rendezvous claimswatch_claims.
End-to-end serviceThe verify output: every line should name the worker cloud and an AWS instance ID.
Pair state on a workercloudspectra_relay_client status shows the relay the worker enrolled with and the address it wears. In the pool configurations, /run/cloudspectra-client-state holds the agent's current view.
Pair load (pool configurations)CloudWatch namespace CloudSpectra/CloudVpc/<name>, dimension AutoScalingGroupName = <name>-relay: PairCPUPercent, PairMemoryPercent and PairDiskPercent (the busier half of each pair); RelayCPUPercent and WorkerCPUPercent (which half is busy); relay and worker packet and drop rates; and the relays' allowance-exceeded rates (for example RelayBandwidthOutExceededPerMinute and RelayPacketsExceededPerMinute).
East-west peersThe peer map table (peermap_table): one row per worker, written by its relay while the worker's tunnel is up and removed when it stops handshaking.
Kubernetesverify_nodes, and the controller's log: kubectl -n cloudspectra-system logs deploy/cloudspectra-master, where reconcile passes are logged with the prefix cloudvpcrecon:.

Logs

WhereWhat to read
Relaysudo journalctl -u cloudspectra_relay -u cloudspectra_relay_dash -u cloudspectra_relaymode, and /var/log/cloud-init-output.log for first boot. Note the underscores: the hyphenated unit names print no entries on these relays.
Worker, at boot/var/log/cloudspectra-worker-bootstrap.log and the serial console, where bootstrap-worker narrates every step.
Worker, runningjournalctl -u cloudspectra-client-agent -u cloudspectra-client-attach in the pool configurations; journalctl -u cloudspectra-relay-enrolled in the EKS configuration.

On the relay, wg show shows nothing: the relay runs WireGuard inside its eBPF datapath rather than as a kernel interface. On a worker, the tunnel interface is cswg0.

Relay dashboard

Each relay serves a small HTTPS dashboard on port 28300 with a self-signed certificate. In the pool configurations that port is open to the internet over IPv6 only, so reach it at the relay's rendezvous address. The login password is generated at first boot into /var/lib/cloudspectra/credentials on the relay; read it over SSH with sudo cat. The Monitoring tab shows the relay's CPU, memory, storage, network throughput and egress allowance.

Security model

  • Accounts are the boundary. Relays, their IAM role and the peer map live in your AWS account; workers live in your OCI or DigitalOcean account. Tags select resources; they do not grant access.
  • The worker holds the relay's IAM role. Any process on the worker host that can reach the metadata service obtains temporary credentials for the relay's instance profile -- the role is <name>-relay in the pool configurations -- including the permissions CloudVpc itself grants the relay. Add your workload's permissions to that role, and nothing more.
  • Containers do not. With the default imds_hop_limit = 1, and with the relay capping metadata replies to one hop, a container one forwarding hop away from the worker host cannot reach the metadata service. The relays require IMDSv2.
  • Encrypted transport. All relay-worker traffic is inside WireGuard. Workers and relays generate their own keys, and no WireGuard private key is stored in Terraform state. Direct worker-to-worker GENEVE traffic is not encrypted by CloudVpc; it stays between the workers on their own cloud network, and the OCI configuration allows it only from the worker subnet.
  • Narrow enrolment. The fleet's enrolment token authorises only enrolment and a read-only download; changes in the dashboard need a signed-in session. In the pool configurations each relay accepts the token once, within enrol_token_ttl of its boot.
  • Exposed ports. Each relay accepts the WireGuard port (UDP 28302) and the enrolment and health port (TCP 28300) from anywhere, because workers in another cloud cannot be known by address in advance. Recovery SSH (22 and 28301) is limited to admin_cidrs, empty by default. There is no IPv6 NAT: a relay is reachable at its rendezvous address as soon as it claims it, and the security group is what limits that.
  • Worker firewalls. In the OCI pool the workers' security list admits only SSH from admin_cidrs and GENEVE (UDP 6081) from the worker subnet; application traffic arrives through the tunnel the worker opened. The DigitalOcean pool configuration creates no DigitalOcean firewall; add one.
  • Secrets on the worker. The licence key (/etc/cloudspectra/licence) and worker.yaml -- which holds the enrolment token and, in the OCI pool, the read-only AWS key -- are written with mode 0600. They travel in the worker's launch metadata and are present in Terraform state; protect the state accordingly.

Access for debugging

The relays and workers run no SSM agent, so SSH is the way in. It is closed unless you open it.

  • Relays. Supply your SSH public key (oci_ssh_public_key, or ssh_public_key_override on DigitalOcean; ~/.ssh/id_rsa.pub by default) or an EC2 key pair (relay_key_name), and set admin_cidrs. Log in as admin: on port 22 while the relay boots, and on port 28301 once it is running, because the relay moves SSH there.
  • Workers. The same key is installed for the workers' login users. Log in as ubuntu on OCI and as root on DigitalOcean.

Troubleshooting

SymptomLikely causeWhat to do
terraform plan fails with "No relay AMI was resolved"relay_ami_id is empty and nothing in your account matches the name filter.Set relay_ami_id.
OCI apply fails with 403-NotAllowed asking for the home regionThe dynamic group and policy can only be created from the tenancy's home region.Set oci_region to the home region, or see OCI IAM without tenancy rights.
Relays launch and are replaced over and overThe relay never becomes healthy: the image is not arm64, the relay cannot hold a rendezvous address, or the relay exited.SSH to a relay during boot and read its journal. "refusing to start: this relay cannot hold its rendezvous address" means the zone's block is exhausted (slots_per_az) or the claim failed; "no vendor configured" means relay_guard_vendor is empty.
Relays are healthy but carry no trafficThe relay is parked: no licence or an invalid one, a Developer account over its 16-vCPU limit, or -- on the Enterprise lane -- a relay with fewer than 4 vCPUs.Read the relay's journal: a licence problem is logged as "no licence key was found -- the relay is PARKED" or "NOT ENTITLED -- serving nobody". Fix license_key, reduce the vCPUs the account runs, or on the Enterprise lane use a relay with at least 4 vCPUs, such as t4g.xlarge.
Workers never pairThe worker cannot download the client, cannot reach a relay over IPv6, or no relay will accept its token.Read /var/log/cloudspectra-worker-bootstrap.log or the serial console. A failure at the first download usually means a wrong public_artifact_base, delivery_url, package_version or licence. Check that the worker reaches an address from rendezvous_addresses. "that enrolment token is not valid" from every relay means each relay's single use of the token is spent or its enrol_token_ttl has passed; a replaced relay accepts the token again.
Fewer workers than relays (OCI pool)The workers cannot resize their pool: the OCI dynamic group or policy is missing.See OCI IAM without tenancy rights.
Fewer workers than relays (DigitalOcean pool)This pool does not follow the relay group.Set worker_count to the relay count.
No workers launched (EKS)The controller refuses to launch, for example because a relay has not announced itself or the pair ceiling is reached.Read the controller's log; each refusal is logged with a reason such as relay_not_announced or ceiling. Check that license_key is set.
Containers on a worker cannot get AWS credentialsThe metadata hop limit is 1, by design.Run code that needs the relay's role on the worker host, or raise imds_hop_limit deliberately, knowing that every container then holds the role.
Worker-to-worker traffic is slowThe workers do not know each other's host address yet, so traffic goes through the relays.Check that the peer map table has a row for each worker, and that UDP 6081 is allowed between the workers.
Large transfers stall while small requests work (EKS)An MTU mismatch between the tunnel and the pod network.Keep wireguard_mtu at its default of 1380; Calico's pod MTU is derived from it.
terraform destroy stops with DependencyViolation ("mapped public address")The relays are still terminating, so the internet gateway cannot be detached yet.Wait a minute and run terraform destroy again.
terraform destroy stops with "Can not delete VPC with members" (DigitalOcean, EKS)Droplets the controller launched are still in the DigitalOcean VPC; they are not in Terraform state.Set DIGITALOCEAN_TOKEN for the destroy so the drain can remove them, or delete the fleet's Droplets, wait until they are gone, and destroy again.

When you contact support@cloudspectra.ai, include the configuration name, its terraform output (without secrets), the relay journal and the worker's bootstrap log.

Teardown

Run terraform destroy in the configuration you applied, with the same variables and credentials.

  • Pool configurations. Everything is in Terraform state, including the OCI pool and the DigitalOcean Droplets. If the destroy stops on a DependencyViolation about mapped public addresses, run it again after a minute.
  • EKS. The workers are launched by the controller, not by Terraform, so the configuration drains the fleet first: a destroy-time step scales the relay group to zero, releases the relays' lifecycle hooks, waits for the controller to retire the workers (drain_timeout_seconds, default 300), terminates any that remain, and only then lets the cluster, the relays and the worker network go. It needs Python 3 with boto3, the oci SDK for OCI workers or DIGITALOCEAN_TOKEN for DigitalOcean workers, and cloudspectra_fleet_manifest. If the drain cannot finish, the destroy still runs and reports what it could not delete.
  • Afterwards, confirm in each cloud that no instance tagged with your fleet remains. Destroying the deployment does not change your Cloud Spectra subscription; manage it on your account page.