CloudConnector -- User Guide v1.0.0

Topology, configuration reference, routing, scaling, operations and troubleshooting.

This guide covers running CloudConnector: how a deployment is shaped, every input and output of the Terraform roots, how traffic is routed into the pipe, how it is sized and scaled, how it fails and recovers, how to watch it, and how to troubleshoot it. For a first deployment, start with the Quick Start; for what CloudConnector is, see the Overview.

Topology and deployment shapes

A pipe always has the same parts: an AWS relay fleet, a Google Cloud relay fleet, and one controller that both fleets register with under a single fleet id. Two Terraform shapes build them:

ShapeRootsUse it for
Evaluationexamples/cloudconnector, which calls terraform/cloudspectra-relay-deployTrying CloudConnector. One apply creates new networks in both clouds, the controller and both fleets, and generates every credential; they are kept in Terraform state.
Productionterraform/cloudspectra-relay-controller, then terraform/cloudspectra-relay-pipeA pipe between networks you already own. You supply the networks, subnets, route tables and credentials; the controller is deployed once and can serve several fleets.

In the production shape the two roots are joined by one value: the pipe root's sensitive controller_side_tokens output is the controller root's pipe_side_tokens input. It carries each side's announce credential, so the side a relay is placed on and the side the controller recognises come from one definition.

Zones

Each end starts in one zone: <region>a on AWS and <region>-a on Google Cloud. A one-zone end has nowhere to place a replacement during a zone outage, so production deployments should use two or more:

  • AWS. Every zone gets its own Auto Scaling group, its own GWLB endpoint and its own routes, and each zone runs at least one relay -- a zone without one would drop the traffic entering through its endpoint. Cross-zone load balancing is off, so the load balancer adds no inter-zone hop. The AWS end therefore rounds its relay count up across zones: a target of 5 relays over 3 zones runs 6. Set aws_network_azs (created networks) or aws_zone_subnets (your networks).
  • Google Cloud. The managed instance group is regional and the internal load balancer is regional, so the group spreads exactly fleet_size relays over the zones you list in gcp_zones.

Relay addressing

Relays dial each other directly over the internet; the load balancers only steer traffic into the local relays. When it registers, a relay announces the address the far side should dial: its IPv6 global address if it has one, otherwise its public IPv4 address, otherwise its private address. A private address is not reachable from the other cloud, so every relay needs one of the first two:

  • Google Cloud relays get an external address of the family set by pipe_external_addressing: ipv6 (the default, because both clouds bill public IPv4 per address per hour), ipv4 or dual. Created networks are dual-stack and have Cloud NAT, so relays without an external IPv4 can still reach IPv4 destinations.
  • AWS relays sit in the zone subnets you give them and must be reachable by the Google Cloud relays and able to reach the controller, the Cloud Spectra licence service and the far side.

The two ends must share an address family the far side can reach.

Sides and the fleet id

Both ends register under one fleet id (fleet_id, which defaults to name). The ends are named aws and gcp (aws_side_id, gcp_side_id); these names must match the side ids in the pipe configuration, which the pipe root writes from the same variables.

Configuration reference

Only the inputs and outputs listed here are part of the interface. Every default shown is the code's.

The cloudconnector example

InputDefaultMeaning
name"cloudconnector"Names the deployment and becomes the fleet id both ends register under.
release"1.0.0"The release whose relay images are deployed; the AWS AMI and the Google Cloud image are looked up from it.
aws_region"us-east-1"Region of the AWS end and of the controller.
gcp_region"us-central1"Region of the Google Cloud end.
target_gbps10Capacity to size each end for (see Sizing).

Its outputs are controller_url, aws_endpoint_service, gcp_ilb_ip, wg_port_range, relay_target_per_end, relay_count_aws, relay_count_gcp and relay_count, each described under the deployment root below. Any other input of the deployment root can be set by adding it to the example's module "cloudconnector" block.

Deployment root: cloudspectra-relay-deploy

Every input is optional.

InputDefaultMeaning
name"cc-demo"Prefix for every resource on both clouds; also the fleet id.
aws_region, gcp_region"us-east-1", "us-central1"Where each end is built.
gcp_project""The Google Cloud project. Empty takes the project of the service-account key in gcp_credentials_file, else the Google provider's own default (for example GOOGLE_PROJECT).
gcp_zones[]Zones for the Google Cloud group. Empty takes one zone, <gcp_region>-a.
release"1.0.0"Release whose images are deployed.
arch"amd64"amd64 or arm64. Selects the images and the relay instance types.
aws_ami_id, gcp_source_image""Override the images looked up from release; setting one skips that lookup.
controller_ami_id""Image for the controller. Empty uses the relay AMI, which also carries the controller.
image_manifest_bucket, image_manifest_regionCloud Spectra's releases bucket, "us-east-1"Where the per-release image manifest is read from, with your AWS credentials. Point it at a mirror to deploy images published elsewhere.
aws_advertise_cidrs["10.60.0.0/16"]What is reachable through the AWS end. Defaults to the AWS network this root creates.
gcp_advertise_cidrs["10.70.0.0/16"]What is reachable through the Google Cloud end. Defaults to the network this root creates.
target_gbps1Sizes both fleets.
tunnel_members2Width of each relay's WireGuard port band, 2-32 (see The bond ceiling).
gcp_service_account_email""Service account the Google Cloud relays run as. Empty uses the project's default Compute Engine service account.
delivery_url""Where relays fetch their signed datapath. Empty is the ordinary case; set it only to serve the object from your own mirror.
aws_credentials_file, aws_profile"~/.secrets/.aws.credentials", ""An AWS shared-credentials file and profile for the root's own provider, used only if the file exists.
gcp_credentials_file"~/.secrets/.gcp.credentials"A service-account key for the root's own Google provider, used only if the file exists.
license_key"" (sensitive)The licence key. Empty reads the first file in license_key_files that exists.
license_key_files["~/.secrets/.cloudspectra.relay.credentials", "~/.secrets/.cloudspectra.relay.test.credentials"]Where the licence is looked for, in order.
OutputMeaning
controller_urlThe controller both fleets register with: http://<elastic-ip>:8080.
fleet_idThe one fleet id both ends register under.
aws_endpoint_serviceThe GWLB endpoint service of the AWS end; its per-zone endpoints are your AWS route targets.
gcp_ilb_ipThe internal load balancer of the Google Cloud end.
wg_port_rangeThe UDP band both ends listen on, 28302 to 28302 + tunnel_members - 1.
relay_target_per_endRelays per end that target_gbps derives.
relay_count_aws, relay_count_gcp, relay_countRelays each end runs, and both ends together -- the number that is metered.
credentialsSensitive: read_token, write_token, config_token, aws_announce_token, gcp_announce_token.

Production pipe root: cloudspectra-relay-pipe

Builds both fleets, sizes them with cloudspectra_ecmp_pipe, creates the steering routes and writes the pipe configuration to the controller. Inputs without a default are required.

InputDefaultMeaning
Networks
create_networksfalsetrue creates a VPC and a network instead of joining yours. Set it, or give your own networks below -- not both and not neither.
aws_network_cidr, gcp_network_cidr, aws_network_azs"10.60.0.0/16", "10.70.0.0/16", []Only with create_networks. The two ranges must not overlap; empty zones take one, <aws_region>a.
aws_vpc_id, aws_vpc_cidr""Your VPC and its range.
aws_zone_subnets{}Availability zone to the one subnet the relays use in it.
aws_zone_route_tables{}Availability zone to a route table to steer. For each listed zone, every Google Cloud range gets a route to that zone's GWLB endpoint. A zone not listed gets no route.
gcp_network, gcp_subnetwork""Your network and subnetwork.
gcp_zones[]Zones of the regional group; empty takes <gcp_region>-a.
gcp_client_tagsrequiredNetwork tags of the Google Cloud VMs whose traffic uses the pipe. Must not include the relays' own tag.
pipe_external_addressing"ipv6"External address family of the Google Cloud relays: ipv6, ipv4 or dual.
What each side carries
aws_advertise_cidrsrequiredRanges reachable through the AWS end -- its own ranges. They become what the Google Cloud end steers into the pipe.
gcp_advertise_cidrsrequiredRanges reachable through the Google Cloud end, steered into the pipe by the AWS end.
Sizing and the bond
target_gbps3Sizes both fleets, rounding up.
tunnel_members2Width of the WireGuard port band on both ends' firewalls, 2-32.
aws_per_zone_max4Maximum size of each AWS zone group.
wg_port28302First UDP port of the band.
compressionfalseHave both fleets negotiate tunnel compression.
Instances
aws_region, gcp_region, gcp_projectrequiredWhere each end runs.
arch"amd64"Image architecture; the instance types derive from it.
aws_ami_id, gcp_source_imagerequiredThe relay images.
aws_instance_type, gcp_machine_type""Override the derived shapes (c5n.large / c6gn.medium, n2-standard-4 / t2a-standard-4). The plan warns if a shape does not match arch.
aws_instance_profile_namerequiredInstance profile for the AWS relays. It must let a relay turn off source/destination checking on itself at boot; without that, the AWS end forwards nothing.
aws_ssh_key_namenullOptional EC2 key pair.
gcp_service_account_emailrequiredService account the Google Cloud relays run as.
package_version""Exact package version each relay installs at boot (see Upgrades). Empty keeps the image's own.
Controller and credentials
name, fleet_id"relay-pipe", ""Resource prefix; the fleet id both ends register under (empty means name).
fleet_controller_url""The controller both fleets register with. Empty leaves the relays standalone.
fleet_roster_token, fleet_write_token"" (sensitive)The fleet read and write credentials; they must differ.
fleet_config_token"" (sensitive)The credential that writes the pipe configuration. Empty falls back to the write token, which is on every relay.
aws_announce_token, gcp_announce_token"" (sensitive)The credentials that place each fleet on its side. They must differ: one value would place both fleets on one side.
aws_side_id, gcp_side_id"aws", "gcp"Side ids; must match the pipe configuration.
write_pipe_configtrueWrite the pipe configuration to the controller. Set false only when something else owns it.
license_key"" (sensitive)The licence both fleets run under.
delivery_urlrequiredPass "" to fetch the datapath from Cloud Spectra; set a URL only for your own mirror.
relay_privkey, client_pubkey""Leave empty: a pipe end generates its own key pair.
tags, labels{}Extra AWS tags and Google Cloud labels.
OutputMeaning
plan_summaryWhat the intent derives to; read it before applying.
fleet_sizeRelays per end that target_gbps derives -- a target.
aws_instance_count, gcp_instance_count, instance_countRelays each end runs, and both together.
billed_ascloud_connector.
aws_endpoint_service, aws_endpoints_by_zoneThe GWLB endpoint service, and each zone's endpoint id (your route targets).
gcp_ilb_ip, wg_port_rangeAs in the deployment root.
controller_side_tokensSensitive. Feed it to the controller root's pipe_side_tokens.

Production controller root: cloudspectra-relay-controller

The inputs that matter for a pipe:

InputDefaultMeaning
name, region, ami_id"cloudspectra-relay-controller", required, requiredThe controller runs from the relay image, which carries the controller software.
instance_type"t4g.small"An arm64 shape: it must match the architecture of ami_id.
create_network, network_cidrfalse, "10.240.0.0/16"Create a VPC with public subnets in two zones, or use vpc_id and subnet_ids.
assign_public_ipfalseThe controller must reach DynamoDB and the EC2 API before it takes its Elastic IP: give it a public address or a subnet with a NAT gateway.
listen_port8080The port relays and operators call.
relay_cidrs[]Sources allowed to reach listen_port. Empty means no ingress rule at all, so list the addresses your relays (and any monitoring) reach the controller from.
admin_cidrs[]Sources allowed to SSH (TCP 22).
fleet_id, read_token, write_token""The fleet and its read and write credentials: at least 24 characters each, and different from each other.
announce_token, config_token""Optional narrower credentials for registering relays and for writing the pipe configuration; both fall back to the write token.
pipe_side_tokens[]Each side's side_id, announce_token and read_token. Without them the controller places no relay on any side; a side without a read_token has its relays placed but served no far side, so give that side's relays the same read token.
pipe_alert_webhook""URL that pipe health changes are POSTed to (see Monitoring).
scaling, scalable_groups, scaler_cloud, autoscale_min, autoscale_max, autoscale_high, autoscale_lowoffLets the controller resize relay groups (see Scaling by the controller).
protect_durable_statetrueProtects the Elastic IP and the DynamoDB table against deletion. Decide it when you create the controller: it cannot be switched off on a live deployment. Set false for a controller you intend to destroy.

Outputs include controller_url (http://<elastic-ip>:<listen_port>), elastic_ip, eip_allocation_id, table_name and security_group_id.

The cloudspectra provider

Both pipe resources live in the Cloud Spectra Gateway provider, cloudspectra/gateway. Only cloudspectra_pipe_config talks to anything; it needs two provider settings:

provider "cloudspectra" {
  controller_url = "http://203.0.113.10:8080"   # or CS_FLEET_CONTROLLER
  config_token   = var.fleet_config_token        # or CS_FLEET_CONFIG_TOKEN
}

Resource cloudspectra_ecmp_pipe

The pipe's intent, and the product's own derivation of it. It runs the same sizing and validation rules the relays are built from, at plan time, and creates no infrastructure: its delete is empty and it calls no cloud API. The deployment roots use it to size both fleets; on its own it is a sizing calculator (see Size a pipe before you build it).

ArgumentRequiredValues and meaning
modeyescloud_connector for this product. Never defaulted: the four modes build different infrastructure and bill differently, so an unset mode is refused at plan.
endpoint_a, endpoint_byesOne block per end: cloud and region (both required; the region is the cloud's own spelling), network_scope_id (the VPC or network), subnet_id, advertise_cidrs (the ranges reachable through that end -- its own ranges, not the far side's).
addressingyesserver = direct or nlb; client = direct or nat. There is deliberately no nat for the server: a NAT gateway does not preserve the source.
ingressyesmanaged_lb (the cloud's passthrough load balancer in front of an autoscaled fleet -- what the deployment roots build) or single_instance. With single_instance the fleet is pinned to one relay whatever target_gbps says, and nlb server addressing is refused.
target_gbpsnoThroughput to size each end for. Must not be negative.
compressionnoRequest tunnel compression. Adds the compression licence capability.
ipv6, fleet_manage, licence_keynoAccepted, but they do not change the derived plan in this release. The licence the relays run under is supplied to the deployment, not to this resource (see Quick Start, Step 2).
Attribute (computed)Meaning
fleet_sizeRelays per end that target_gbps derives -- a target, not a count (see Sizing).
billing_variantcloud_connector.
capabilities_requestedtunnel-bond, plus compression when requested.
plan_summaryWhat the intent derives to: relays, addressing, whether the source survives, the licence capabilities, and any notes.
idDerived from the mode and the two ends (cloud_connector.aws-us-east-1.gcp-us-central1), so re-applying the same intent addresses the same pipe.

Resource cloudspectra_pipe_config

The pipe configuration stored on the controller. The controller builds each relay's roster from it -- which sides exist and what is reachable through each -- and refuses to serve any roster without it, so the deployment roots write it for you (write_pipe_config, default true). The resource talks to the controller named by the provider's controller_url with its config_token.

What reaches the relays. In this release relays take only the side blocks -- side ids, advertised ranges and kind -- from the stored configuration. The bond, MTU, weight-policy, compression and timing fields are validated and stored, but relays do not apply them: bond width follows the far side's relay count, weights come from instance types, the tunnel MTU is 1380, and compression is set on the fleets (the pipe root's compression input).
ArgumentRequiredValues and meaning
fleet_idyesThe fleet both ends register under. Changing it replaces the resource.
side (block)yesExactly two. side_id must equal the side the relays are placed on, or they are reported as stranded. advertise_cidrs (required): the IPv4 ranges reachable through this side -- at least one, no /0, no overlap with the other side; the controller accepts up to 64, but relays steer only the ranges their fleet was deployed with (at most 8 per side), so keep the two lists identical -- the pipe root writes both from the same variables. network_ids: up to 16, informational. kind: relay (the default) for a relay fleet; the deployment roots in this guide use relays on both sides.
bond_membersyes2-32. The controller refuses fewer than two.
bond_slotsnoWidth of the datapath's member table; 0 takes the default (32). Must be at least bond_members.
bond_base_portnoFirst UDP port of the band. Member m listens on base + m.
bond_weight_policynoequal (the default) or capacity.
compressionnoAsk both ends to negotiate tunnel compression. A request: the two relays agree an algorithm at runtime.
mtunoTunnel MTU, 576-9000. 0 takes the relay default.
roster_interval_seconds, stale_bound_seconds, park_budget_secondsnoHow often each end re-reads the roster, how long a held roster stays trustworthy, and how long an end may run without reaching the controller. 0 takes the default.
key_max_age_seconds, force_rotate_afternoRotate relay keys older than this (must exceed the 300-second rotation overlap; 0 disables age-based rotation), and a Unix time before which every relay key is due for rotation.
set_bynoRecorded beside the config so a conflict can name the other writer. Defaults to terraform.

Computed: revision (the compare-and-swap token), set_at, mode (always cloud_connector). Every write reads the stored revision first and names it; if someone changed the config in between, the controller answers HTTP 409 naming the other writer instead of overwriting. Destroying this resource only removes it from state: the controller has no delete verb, and removing a config under running relays would take the roster down with it.

Routing traffic into the pipe

Each side advertises its own ranges, and each fleet steers the other side's ranges into the pipe. The pipe roots wire that crossover once, from aws_advertise_cidrs and gcp_advertise_cidrs; everything else in your networks keeps its normal routing. A relay checks the ranges it was deployed with at start-up and refuses to start, with the reason in its log, unless they are IPv4, at most 8 per side, not 0.0.0.0/0, non-overlapping between the sides, and clear of 169.254.0.0/16 and 168.63.129.16.

AWS

  • Target the zone's GWLB endpoint. In the route table of each workload subnet, route every Google Cloud range to the GWLB endpoint in the subnet's own zone (aws_endpoints_by_zone). A route cannot target the load balancer or the endpoint service. The pipe root can create these routes for you: list the route tables in aws_zone_route_tables.
  • Never steer the relays' own subnets. The relay subnets' route table must not send the far side's ranges back to the endpoint.
  • Admit the far side. Packets arrive with their original Google Cloud sources, so workload security groups and network ACLs must allow those ranges. Allow ICMP from the relays too: path-MTU messages come from them.
  • Manage a table's routes one way. If you manage route tables with Terraform, do not mix inline route blocks with separate aws_route resources on the same table: Terraform then deletes the separate routes on a later apply, and the pipe silently stops carrying.

Google Cloud

  • Tag the workloads. The pipe root creates one route per AWS range, next hop the internal load balancer, priority 900, applying only to VMs with a tag in gcp_client_tags (the evaluation root uses <name>-client). Routes in Google Cloud apply network-wide unless tagged, which is why the relays' tag (<name>-gcp-relay) must never be a client tag: a relay would become the next hop for traffic it is forwarding.
  • Admit both directions. The relays' firewall rules admit the WireGuard band, Google's health checkers, ICMP and the AWS ranges. Add a rule letting your workload ranges reach the relays (target tag <name>-gcp-relay) and a rule letting the AWS ranges reach your workloads.

Which side opens connections

Connections opened from AWS workloads to Google Cloud workloads, and their replies, are the direction Cloud Spectra's live tests carry. On the AWS end the Gateway Load Balancer is used for ingress only: packets arriving from Google Cloud are delivered straight to the AWS host, so the load balancer never sees the first half of a connection opened from Google Cloud -- and a Gateway Load Balancer does not forward the reply half of a flow whose first half it never saw. Validate connections opened from the Google Cloud side before you depend on them.

Sizing and scaling

Sizing

Each relay is sized as 3 Gbps of pipe. target_gbps becomes a per-end relay count: ceil(target_gbps / 3), at least 1. That count is a target:

  • the AWS end runs ceil(count / zones) relays in every zone, so it can run more than the target;
  • the Google Cloud end runs exactly the target, spread over its zones.

The outputs report both: the target (relay_target_per_end or fleet_size) and what each end runs.

Capacity scales across many conversations, not within one. Every flow between one pair of addresses enters the pipe through a single relay, so one bulk transfer between two hosts gets one relay's worth of throughput however large the fleet. Many hosts talking across the pipe spread over all relays. And because each relay bonds with every relay on the far side, grow both ends together: growing one end alone adds little.

Size a pipe before you build it

The plan-only/ directory beside the cloudconnector example applies only cloudspectra_ecmp_pipe. It needs the cloudspectra/gateway provider and nothing else -- no cloud credentials -- and it creates nothing, so you can try shapes freely:

cd examples/cloudconnector/plan-only
terraform init
terraform apply \
  -var aws_vpc_id=vpc-0123456789abcdef0 \
  -var aws_subnet_id=subnet-0123456789abcdef0 \
  -var gcp_network=projects/my-project/global/networks/my-net \
  -var gcp_subnetwork=projects/my-project/regions/us-central1/subnetworks/my-subnet \
  -var target_gbps=10
terraform output plan_summary

With the defaults (ingress = "managed_lb", direct addressing on both sides) and target_gbps = 10, the summary reads:

mode cloud_connector: ECMP L3 forwarding across the fleet, source preserved
  servers:      4 (managed_lb ingress, direct addressing)
  client:       direct addressing, runs the CloudSpectra datapath
  source:       preserved end to end (no NAT)
  licence needs: tunnel-bond
  controller:   a peer-map container is stood up in your account
  billed as:    cloud_connector
  note: this mode stands up the peer-map controller container in your own account (roughly $15-50/mo); your client and peer data never leaves it

The same notes are printed as About this pipe warnings during terraform plan of the full deployment. A pipe sized to a single relay per end adds a note that it has no redundancy: losing that relay loses the pipe. The cost in the controller note is the derivation's rough estimate; what you pay for the controller is its AWS resources (see Cost considerations).

The bond ceiling

A relay's bond has one member per far-side relay, and member m listens on UDP wg_port + m. The firewalls on both ends open tunnel_members ports from wg_port (the wg_port_range output), so tunnel_members must cover the most relays either end will ever run. The plan warns when it is below the derived relay count or below what the AWS groups may reach (aws_per_zone_max times the number of zones). It can be 2 to 32; a relay bonds with at most 32 far relays in any case, and beyond that the highest-numbered far relays carry no traffic.

Changing the size

After the first apply the group sizes belong to the running system: Terraform ignores later changes to the AWS groups' desired capacity and to the Google Cloud group's target size, so that an apply never reverts a fleet that has scaled. Raising target_gbps raises the AWS groups' minimum size, which pulls them up; the Google Cloud group keeps its size until you resize it directly. Otherwise resize the groups directly or let the controller do it.

When a side changes size, the other side follows on its own. A new relay registers and the far side widens its bonds; a removed relay leaves the roster once its 90-second registration lapses, and the far side narrows. Each far relay rebuilds its bond with a brief restart of its datapath. In Cloud Spectra's measurements the far side widened about 2.5 minutes after a relay was added and narrowed about 7 minutes after one was removed; once the bond is rebuilt, traffic flows again within seconds.

Scaling by the controller

The controller can resize relay groups in one cloud (scaler_cloud), either on request (scaling = "manual") or from the relays' reported load (scaling = "auto"). It is off by default and acts only on the groups listed in scalable_groups. In automatic mode it grows a group above 70% utilisation and shrinks it below 30% (autoscale_high, autoscale_low), waiting 2 minutes before growing and 10 before shrinking, with a 5-minute cooldown, within autoscale_min and autoscale_max. Manual and automatic are exclusive. Keep tunnel_members above the largest size you allow.

High availability and failure behaviour

FailureWhat happens
A relay diesOn AWS, the target group's health check takes it out of the load balancer and existing flows are rebalanced to healthy relays; the Auto Scaling group replaces the instance when the instance itself fails (the groups use EC2 health checks, so a running instance whose datapath is down is taken out of the load balancer but not replaced). On Google Cloud, the managed group's autohealing replaces a relay that fails the load balancer's health check. On the far side, the bond member to the dead relay stays until its 90-second registration lapses and the bond is rebuilt, and the flows hashed onto that member are lost until then.
An end with one relay loses itThe pipe stops until the replacement registers. Use at least two relays per end for redundancy -- the plan notes when a pipe has none.
A zone failsA one-zone end has nowhere to place a replacement. Use two or more zones (see Zones).
The controller is unreachable or replacedThe controller is not on the data path. Relays keep forwarding with the roster they hold; after 300 seconds without a fresh one they log that it is stale, and they never tear the pipe down on their own. Scale events are not followed until the controller is back. The controller runs as an Auto Scaling group of exactly one: a replacement instance takes over the Elastic IP, and all state lives in DynamoDB.
The Cloud Spectra licence service is unreachableRelays check in every two minutes and keep forwarding through a 48-hour grace window, logging warnings as it runs out; after that they park and stop forwarding.

Monitoring and alerts

Pipe status

The controller derives the pipe's health each time it is asked:

curl -s -H "Authorization: Bearer <read-token>" \
  "http://<controller>:8080/fleet/v1/pipe/status?fleet=<fleet-id>"
FieldMeaning
healthup: every side has live relays and none is idle. degraded: it carries, but not as asked -- a side short of relays, an idle relay, or an unplaced relay. down: a side has no live relay, so the pipe cannot carry in at least one direction. unknown: the controller could not take a reading (no configuration, or no relays registered) -- a statement about the controller, not the pipe.
summaryOne sentence describing the state.
configured, compressionWhether a pipe configuration is stored, and whether it asks for compression.
sides[]Per side: side_id, advertise_cidrs, live_relays, idle_relays, capacity_hundredths_gbps, and each relay's relay_id, endpoint, live, instance_type, weight, idle, carried_bytes and last_carried_at.
unplacedRelays registered with a credential that matches no side. They dial nobody and are dialled by nobody.
strandedRelays placed on a side the pipe configuration does not describe -- usually a renamed side.
problemsEverything wrong, in words.

A relay is idle when it is live but has carried nothing for 270 seconds, so a pipe with no traffic reads degraded. Two more read-only endpoints use the same token: GET /fleet/v1/pipe?fleet=<fleet-id> returns the stored configuration and its revision, and GET /fleet/v1/relays?fleet=<fleet-id> returns the relay registry.

Alerts

The controller turns health changes into alerts: pipe_down (critical), pipe_degraded, pipe_idle_relay, pipe_unplaced_relay, pipe_not_visible (warnings) and pipe_recovered. Every alert is logged by the controller, and POSTed as JSON (kind, title, message, severity, fleet_id, at) to pipe_alert_webhook when it is set. Alerts are edge-triggered with a six-hour cooldown per kind, and they are evaluated when the status endpoint is read -- so poll /fleet/v1/pipe/status from your monitoring, every minute or so, to get them.

On a relay

  • Logs: journalctl -u cloudspectra-relay for the datapath agent; cloudspectra-relay-dash and cloudspectra-relay-upgrade are the dashboard and boot-time upgrade units. Look for relay: cloud connector pipe configured (it also names any far relays beyond the 32-member limit), relay: bond shape resolved and the line reporting the prefixes armed.
  • Datapath facts: /run/cloudspectra/relay-facts.json records what the datapath was loaded with, including cs_pipe_nlocal, cs_pipe_nremote, cs_pipe_mtu, cs_wg_nmembers and cs_wg_nslots.
  • Cloud health: the GWLB target group's health and the managed instance group's health show whether each relay's datapath is answering.

The relay's own dashboard (HTTPS on port 28300) has no pipe view; pipe health lives on the controller.

Upgrades

A relay runs the software in the image it booted from, and can move to a newer package at boot:

  • Images follow release. The deployment root looks up the AWS AMI and the Google Cloud image for the release you name. Changing it updates the AWS launch template and the Google Cloud instance template, but running relays are not replaced: the Auto Scaling groups launch from the template's $Latest version with no instance refresh, and the managed instance group's update policy is OPPORTUNISTIC. New relays -- scale-out or replacement -- boot the new image; existing ones keep the old one until you replace them.
  • In-place package upgrade at boot. Before it brings up its datapath, every relay runs a one-shot upgrade unit (cloudspectra-relay-upgrade.service). When a target version is set (package_version on the production pipe root), the relay downloads that exact package, verifies its checksum and signature, installs it and restarts into it. A failed upgrade is not fatal: the relay comes up on the software it already has. In-place upgrades never cross a major version.

To roll a fleet onto a new release without taking the pipe down, replace relays a few at a time on each side, keeping at least one relay in service per side and waiting for each replacement to register before the next. The far side picks up a new relay within a few minutes and drops a removed one after the registry's 90-second time-to-live (see Scaling):

# AWS: one Auto Scaling group per zone, named <name>-aws-<zone>
aws autoscaling start-instance-refresh --auto-scaling-group-name <name>-aws-us-east-1a

# Google Cloud: the regional managed instance group, named <name>-gcp-mig
gcloud compute instance-groups managed rolling-action replace <name>-gcp-mig \
  --region us-central1 --max-surge 1 --max-unavailable 0

Security model

Data in transit

Between the clouds, traffic travels only inside WireGuard tunnels between your own relays (kernel WireGuard; no pre-shared key is configured). Cloud Spectra is not on the data path. Inside each cloud, packets move between your hosts, the load balancers and the relays unencapsulated, as in any routed network. The datapath caps the TTL of forwarded packets at 16, so a routing loop dies quickly, and refuses compressed-protocol frames (IP protocol 253) arriving from outside the pipe.

Keys

Each relay generates its WireGuard key pair on first boot and stores the private key on the instance (/var/lib/cloudspectra/relay-self.key, mode 0600). Only the public key is sent to the controller and served to the far side, so no relay private key exists in Terraform state. A replacement instance gets a new key. Relay keys are not rotated automatically in this release.

Credentials

CredentialHeld byAllows
Read token (fleet and per side)Every relayReading the roster and the pipe status. A side's read token decides which far side a relay is served.
Write tokenThe relays in the shipped rootsFleet writes; the fallback for the two credentials below when they are unset.
Announce token, one per sideThat side's relaysRegistering a relay. The controller places the relay on the side whose token it presented; the two sides' tokens must differ.
Config tokenWhoever writes the pipe configuration (Terraform)Changing the pipe configuration, which redirects the pipe's traffic. Set it so that a relay's credentials cannot.
Licence keyEvery relay (instance user data)Running the relays. It is in Terraform state.

The evaluation root generates all of these in Terraform state and exposes them as a sensitive output. In production, generate them yourself, keep them in your secret store, and keep the state file encrypted and access-controlled.

The controller

The controller serves plain HTTP on listen_port (8080) at its Elastic IP; the deployment roots configure no TLS certificate for it, so the bearer tokens above cross the network unencrypted. Limit relay_cidrs to the addresses your relays and monitoring reach it from, and treat the read and write tokens accordingly. The controller instance requires IMDSv2.

The relays

  • The WireGuard band (UDP from 28302) is open to any IPv4 source on both ends; packets that do not authenticate as a known far relay are dropped by WireGuard.
  • AWS relays accept GENEVE (UDP 6081) and the health check only from the VPC's own range, require IMDSv2, and have encrypted root volumes.
  • The relay datapath runs under systemd with only the capabilities it needs (CAP_NET_ADMIN, CAP_BPF, CAP_SYS_ADMIN, CAP_NET_RAW) and a read-only system. Its eBPF datapath is fetched signed at boot.

What source preservation means for your rules

Nothing on the pipe hides the sender. The far side's firewalls, security groups and logs see every host's own private address -- which is the point, and also means each side must explicitly admit the other side's ranges, at whatever granularity you choose.

Troubleshooting

Terraform's check warnings appear during plan and apply but do not stop them, so read them first. On a relay, start with journalctl -u cloudspectra-relay; on the controller, start with the pipe status.

SymptomLikely causeWhat to do
Plan warns No licence. Looked in: ...No licence file or license_keyProvide the licence (Quick Start Step 2). Without it relays come up and forward nothing.
Plan warns No AMI for release ..., No GCP image for release ... or that an image is not an AMI idThe image manifest for that release and arch is empty or wrongCheck release and arch, or set aws_ami_id and gcp_source_image.
Plan warns tunnel_members (...) is below ...The port band is narrower than the fleet may growRaise tunnel_members (see The bond ceiling).
Plan warns that the announce tokens are the same valueOne credential for both sidesGive each side its own announce token.
Plan warns that the created networks start at the same addressaws_network_cidr and gcp_network_cidr collideChoose distinct ranges. The check only catches identical starts; a partial overlap must be avoided by you.
Plan warns Set create_networks = true OR give aws_vpc_id ...Both or neither network sources givenChoose one.
Plan warns that an availability zone is not offeredZone names are mapped per accountSet aws_network_azs explicitly.
Plan warns that an instance shape does not match archAn arm64 shape with an amd64 image, or the reverseFix aws_instance_type / gcp_machine_type; an image does not boot on the other architecture.
Apply fails writing the pipe configuration: the controller answered HTTP 409Someone changed the stored configuration since Terraform read itRun the apply again; it reads the current revision first. The message names the other writer.
Apply fails writing the pipe configuration: HTTP 400The configuration is invalid, or it names a side the controller holds no announce credential forThe message lists every problem at once (two sides, IPv4 ranges, no overlap, bond 2-32, and so on). For an unknown side, check the controller's pipe_side_tokens.
Apply fails: no controller URL is configured or reaching the controller at ...The provider cannot reach the controllerSet controller_url (or CS_FLEET_CONTROLLER) and make sure the machine running Terraform is admitted by relay_cidrs.
Status unknownNo configuration stored, or no relay has registeredCheck that relays can reach the controller (its security group admits only relay_cidrs) and that they have finished booting.
Status downOne side has no live relayLook at that side's relays: licence, controller reachability, boot logs.
Relays listed under unplacedTheir announce token matches no configured sideThe fleet's announce token and the controller's pipe_side_tokens must agree -- feed the pipe root's controller_side_tokens output to the controller.
Relays listed under strandedThe side they were placed on is not in the pipe configurationMake aws_side_id / gcp_side_id match the configuration's side ids.
Relays live and idle, nothing crossesNothing is steered in, or replies are droppedCheck the AWS route targets the zone's GWLB endpoint, the Google Cloud VMs carry a client tag, the firewall rules admit both directions, and the AWS relays' instance profile lets them turn off source/destination checking.
Ping crosses but TCP transfers stallPackets larger than the tunnel MTU (1380)Allow ICMP from the relays to your hosts so "fragmentation needed" messages arrive; TCP connections through the pipe have their MSS clamped automatically.
A relay exits at start-upIts pipe ranges are invalidThe journal names the rule broken (IPv4 only, at most 8 per side, no overlap, no /0).
Relays parkNo licence check-in for 48 hours, or the Developer lane's 16-vCPU account limitRestore access to the licence service, or reduce the relay vCPUs, or move to the Enterprise lane.
Relays refused at registration on the Enterprise laneAn instance below the lane's 4-vCPU minimumUse AWS relay shapes with at least 4 vCPUs (aws_instance_type).
terraform destroy refuses the controller's Elastic IP or DynamoDB tableprotect_durable_stateSee Teardown.

Teardown

  • Remove what you added. Delete your own routes to the GWLB endpoints, firewall rules and test instances first; Terraform cannot delete networks that still hold resources it does not manage.
  • Production order. Destroy the pipe root before the controller root. Removing cloudspectra_pipe_config only removes it from Terraform state -- the controller has no delete for it, so the stored configuration stays until the controller itself is destroyed, and a later apply adopts it.
  • The controller's durable state. With protect_durable_state = true (the default) the Elastic IP cannot be destroyed and the DynamoDB table has deletion protection, and the setting cannot be switched off on a live deployment. To destroy a protected controller, remove the address from Terraform state, disable the table's deletion protection, destroy, and then release the address yourself -- the commands are in the Quick Start's teardown step.
  • Billing. Destroying infrastructure does not cancel your Cloud Spectra subscription.

Cost considerations

  • Licence. Per relay vCPU, both ends, hourly; the second vCPU of each instance is free (see Pricing). The controller is not licensed.
  • Inter-cloud data. Traffic between the clouds crosses the internet, so each cloud bills the bytes leaving it as internet egress, in both directions. This is usually the largest cost of a busy pipe; where a private interconnect with cheaper transfer already exists, compare.
  • AWS. The relay instances, the Gateway Load Balancer and its endpoints, the controller instance, its Elastic IP and its on-demand DynamoDB table. Cross-zone load balancing is off and each zone's subnets should route to their own zone's endpoint, so no inter-zone transfer is added.
  • Google Cloud. The relay VMs, the internal load balancer, and Cloud NAT where the root creates it.
  • Public addresses. Both clouds bill public IPv4 addresses per hour; that is why Google Cloud relays default to IPv6.
  • Right-sizing. Relays are sized at 3 Gbps each; size by target_gbps rather than by instance size alone, and remember one conversation never uses more than one relay.