This walkthrough builds CloudConnector's evaluation shape from one Terraform root: a new AWS VPC and a new Google Cloud network, a relay fleet in each, and the controller that joins them. You then send test traffic through the pipe, confirm that the source address survives, and remove everything. Nothing you already run is touched. Most of the time is spent waiting for instances to boot.
What you will build
| Where | Resources |
|---|---|
AWS, the pipe's end (aws_region, default us-east-1) | A VPC (10.60.0.0/16) with one public subnet in zone <region>a and an internet gateway; the relay fleet -- a launch template, one Auto Scaling group per zone, a Gateway Load Balancer with a GENEVE target group, a VPC endpoint service and one GWLB endpoint per zone, and a security group; an IAM role and instance profile for the relays. |
| AWS, the controller | A separate VPC (10.240.0.0/16) with two public subnets; an Auto Scaling group of one instance; an Elastic IP; a DynamoDB table; an IAM role and a security group. |
Google Cloud (gcp_region, default us-central1) | A network and a dual-stack subnetwork (10.70.0.0/16) with Cloud Router and Cloud NAT; the relay fleet -- an instance template, a regional managed instance group in zone <region>-a, a health check, an internal passthrough load balancer, firewall rules; and a route that sends the AWS range to the load balancer for VMs carrying the client tag. |
| The controller's store | The pipe configuration: side aws advertises 10.60.0.0/16, side gcp advertises 10.70.0.0/16. |
Before you begin
| You need | Details |
|---|---|
| An AWS account | Credentials that can create VPC networking, launch templates and Auto Scaling groups, a Gateway Load Balancer and target group, a VPC endpoint service and endpoints, IAM roles and instance profiles, an Elastic IP and a DynamoDB table. |
| A Google Cloud project | The Compute Engine API enabled, and credentials that can create networks, subnetworks, Cloud Router and Cloud NAT, firewall rules, instance templates, regional managed instance groups, health checks, internal load balancers and routes. The relays run as the project's default Compute Engine service account (<project-number>-compute@developer.gserviceaccount.com), which must exist. |
| A Cloud Spectra licence key | Both fleets run under it. This walkthrough runs one relay per end: 2 vCPUs on AWS and 4 on Google Cloud, 6 raw vCPUs in all, inside the Developer lane's 16-vCPU account limit. Its 2-vCPU AWS relay is below the Enterprise lane's 4-vCPU minimum, so use a Developer-lane licence for it. |
| Terraform 1.5 or later | terraform version to check. terraform init installs the cloudspectra/gateway, hashicorp/aws, hashicorp/google and hashicorp/random providers. |
| The AWS CLI and the Google Cloud CLI | For the routing and verification steps. |
| The CloudConnector configuration | See Step 1. |
Step 1 -- Get the configuration
CloudConnector is deployed from the cloudconnector example root, which calls the cloudspectra-relay-deploy composition by relative path (../../terraform/cloudspectra-relay-deploy). Keep the directory layout exactly as delivered, or the module path breaks:
<workdir>/
examples/cloudconnector/ main.tf variables.tf outputs.tf plan-only/
terraform/cloudspectra-relay-deploy/
terraform/cloudspectra-relay-controller/
terraform/cloudspectra-relay-pipe/
terraform/cloudspectra-relay/modules/
To obtain the configuration for your release, contact support@cloudspectra.ai.
Step 2 -- Provide credentials
The deployment needs three credentials: AWS, Google Cloud and your licence. Authenticate the two clouds the standard way, so that every module in the configuration picks them up:
# AWS: a named profile, environment variables or SSO -- whatever the AWS CLI uses
export AWS_PROFILE=my-profile
aws sts get-caller-identity
# Google Cloud: application-default credentials, and the project to build in
gcloud auth application-default login
export GOOGLE_PROJECT=my-project-id
The root also reads ~/.secrets/.aws.credentials (an AWS shared-credentials file) and ~/.secrets/.gcp.credentials (a service-account key, whose own project it then uses) when they exist, for its own lookups. The modules it calls authenticate through the standard chains, so configure those in any case.
Place the licence key where the deployment reads it:
mkdir -p ~/.secrets && chmod 700 ~/.secrets
printf '%s\n' '<your licence key>' > ~/.secrets/.cloudspectra.relay.credentials
chmod 600 ~/.secrets/.cloudspectra.relay.credentials
Step 3 -- Choose the inputs
Every input of the example has a default. Create examples/cloudconnector/terraform.tfvars:
name = "cloudconnector" # prefixes every resource; also the fleet id
release = "1.0.0" # the release whose relay images are deployed
aws_region = "us-east-1"
gcp_region = "us-central1"
target_gbps = 3 # capacity to size each end for
target_gbps decides the number of relays per end: one relay per 3 Gbps, rounded up, never fewer than one. The value 3 used here gives one relay per end -- the cheapest pipe, with no redundancy: the plan says so in a note, and losing either relay stops the pipe until it is replaced. The example's own default of 10 builds four relays per end, which is 24 raw vCPUs and more than the Developer lane allows.
So that you can query the controller in Step 7, add two outputs to examples/cloudconnector/outputs.tf:
output "fleet_id" {
value = module.cloudconnector.fleet_id
}
output "read_token" {
value = module.cloudconnector.credentials.read_token
sensitive = true
}
Step 4 -- Initialize and plan
cd examples/cloudconnector
terraform init
terraform plan
Read the plan's warnings before you apply. Terraform reports them but still lets the apply go ahead:
| Warning | What it means |
|---|---|
About this pipe | Notes from the product's own sizing rules: that this mode runs a controller in your account and, at one relay per end, that the pipe has no redundancy. |
No licence. Looked in: ... | No licence was found in the files from Step 2. Relays without a licence come up and forward nothing. |
No AMI for release ... / No GCP image for release ... | The image manifest for that release and architecture is empty. (If the manifest does not exist at all, the plan fails while reading it.) |
tunnel_members (2) is below what the AWS autoscaler may reach | The AWS Auto Scaling groups may grow to four relays per zone, beyond the two-port WireGuard band opened by default. Harmless while each end runs one or two relays; see The bond ceiling before you grow. |
Step 5 -- Apply
terraform apply
When the apply finishes it prints:
| Output | What it is |
|---|---|
controller_url | The controller both ends register with, http://<elastic-ip>:8080. |
aws_endpoint_service | The Gateway Load Balancer endpoint service on the AWS end. Its endpoints are the targets of your AWS routes. |
gcp_ilb_ip | The internal load balancer address on the Google Cloud end. |
wg_port_range | The UDP band both ends listen on, 28302-28303 by default. Anything between the two sides must admit it. |
relay_target_per_end, relay_count_aws, relay_count_gcp, relay_count | Relays per end that target_gbps asked for, what each end actually runs, and the total that is metered. |
The relays need a few minutes after the apply to boot, fetch their datapath, register with the controller and bond with each other.
Step 6 -- Route traffic into the pipe
Nothing is steered into the pipe until you say so. The steps below create one test host on each side; for your own workloads, apply the same routes and firewall rules to their subnets and VMs.
On AWS: a workload subnet and a route to the GWLB endpoint
Put workloads in their own subnet with their own route table. Do not add the steering route to the relays' route table: the relays must not route the far side's range back into the load balancer.
REGION=us-east-1
VPC=$(aws ec2 describe-vpcs --region $REGION \
--filters Name=tag:Name,Values=cloudconnector-pipe --query 'Vpcs[0].VpcId' --output text)
IGW=$(aws ec2 describe-internet-gateways --region $REGION \
--filters Name=attachment.vpc-id,Values=$VPC --query 'InternetGateways[0].InternetGatewayId' --output text)
GWLBE=$(aws ec2 describe-vpc-endpoints --region $REGION \
--filters Name=service-name,Values=$(terraform output -raw aws_endpoint_service) \
--query 'VpcEndpoints[0].VpcEndpointId' --output text)
# A subnet for workloads, in the relays' zone, with its own route table
SUBNET=$(aws ec2 create-subnet --region $REGION --vpc-id $VPC \
--cidr-block 10.60.128.0/24 --availability-zone ${REGION}a --query Subnet.SubnetId --output text)
RTB=$(aws ec2 create-route-table --region $REGION --vpc-id $VPC --query RouteTable.RouteTableId --output text)
aws ec2 associate-route-table --region $REGION --route-table-id $RTB --subnet-id $SUBNET
aws ec2 create-route --region $REGION --route-table-id $RTB \
--destination-cidr-block 0.0.0.0/0 --gateway-id $IGW
# The steering route: the Google Cloud range goes to this zone's GWLB endpoint
aws ec2 create-route --region $REGION --route-table-id $RTB \
--destination-cidr-block 10.70.0.0/16 --vpc-endpoint-id $GWLBE
A route can only target the endpoint, never the load balancer or the endpoint service. With relays in several zones, route each workload subnet to the endpoint in its own zone.
Now a test host. Its security group must admit the Google Cloud range, because packets from there arrive with their original source:
SG=$(aws ec2 create-security-group --region $REGION --vpc-id $VPC \
--group-name cc-test --description "CloudConnector test host" --query GroupId --output text)
aws ec2 authorize-security-group-ingress --region $REGION --group-id $SG \
--protocol tcp --port 22 --cidr <your-public-ip>/32
aws ec2 authorize-security-group-ingress --region $REGION --group-id $SG \
--protocol -1 --cidr 10.70.0.0/16
aws ec2 run-instances --region $REGION --instance-type t3.micro \
--image-id resolve:ssm:/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
--subnet-id $SUBNET --security-group-ids $SG --associate-public-ip-address \
--key-name <your-key-pair> \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=cc-test-aws}]'
On Google Cloud: the client tag and two firewall rules
The deployment already created the route that sends 10.60.0.0/16 to the internal load balancer. It applies only to VMs with the network tag <name>-client -- here cloudconnector-client -- and never to the relays themselves (tag cloudconnector-gcp-relay). Add firewall rules so that the AWS range can reach your VMs, and your VMs can reach the relays:
gcloud compute firewall-rules create cc-test-from-aws --network cloudconnector-pipe \
--source-ranges 10.60.0.0/16 --allow all --target-tags cloudconnector-client
gcloud compute firewall-rules create cc-test-to-relays --network cloudconnector-pipe \
--source-ranges 10.70.0.0/16 --allow all --target-tags cloudconnector-gcp-relay
gcloud compute firewall-rules create cc-test-ssh --network cloudconnector-pipe \
--source-ranges <your-public-ip>/32 --allow tcp:22 --target-tags cloudconnector-client
gcloud compute instances create cc-test-gcp --zone us-central1-a --machine-type e2-small \
--subnet cloudconnector-pipe --tags cloudconnector-client \
--image-family debian-12 --image-project debian-cloud
To put an existing VM on the pipe, add the tag: gcloud compute instances add-tags <vm> --tags cloudconnector-client --zone <zone>.
Step 7 -- Verify end to end
1Traffic crosses
Note the private address of each test host (aws ec2 describe-instances, gcloud compute instances list). Log in to the AWS host and ping the Google Cloud host's private address:
ssh ec2-user@<aws-test-public-ip>
ping -c 5 <gcp-test-private-ip>
2The source survives
On the Google Cloud host, capture the arriving packets while the ping runs. The source must be the AWS host's private address -- not a relay's, not a NAT address:
gcloud compute ssh cc-test-gcp --zone us-central1-a
sudo tcpdump -ni any icmp and src host <aws-test-private-ip>
3Bulk TCP crosses
Ping proves reachability but not the tunnel MTU path. A TCP transfer does: install iperf3 on both hosts, run iperf3 -s on the Google Cloud host and iperf3 -c <gcp-test-private-ip> -t 10 on the AWS host. Traffic between the clouds is billed as internet egress, so keep test transfers short.
4The controller agrees
From a machine the controller's security group admits, read the pipe's status:
curl -s -H "Authorization: Bearer $(terraform output -raw read_token)" \
"$(terraform output -raw controller_url)/fleet/v1/pipe/status?fleet=$(terraform output -raw fleet_id)"
A healthy pipe reports "health": "up", at least one live relay on each side, and no unplaced, stranded or problems entries. A relay that has carried nothing for 270 seconds counts as idle, so a pipe with no traffic reads degraded. The fields are described under Monitoring.
Step 8 -- Tear down
Remove what you created by hand first: Terraform cannot delete a VPC or network that still holds resources it does not manage.
# AWS
aws ec2 terminate-instances --region $REGION --instance-ids <cc-test-aws-instance-id>
aws ec2 wait instance-terminated --region $REGION --instance-ids <cc-test-aws-instance-id>
aws ec2 delete-security-group --region $REGION --group-id $SG
aws ec2 delete-subnet --region $REGION --subnet-id $SUBNET
aws ec2 delete-route-table --region $REGION --route-table-id $RTB
# Google Cloud
gcloud compute instances delete cc-test-gcp --zone us-central1-a --quiet
gcloud compute firewall-rules delete cc-test-from-aws cc-test-to-relays cc-test-ssh --quiet
# Everything Terraform built
terraform destroy
protect_durable_state input). If terraform destroy refuses to remove them, take the address out of Terraform's management and lift the table's protection, destroy again, then release the address yourself:terraform state rm 'module.cloudconnector.module.controller.aws_eip.controller[0]'
aws dynamodb update-table --region $REGION --table-name cloudconnector-controller-fleet \
--no-deletion-protection-enabled
terraform destroy
aws ec2 release-address --region $REGION --allocation-id <controller-eip-allocation-id>
Destroying the deployment does not cancel your Cloud Spectra subscription. Relays are metered for the clock hours they hold, so a relay's last hour bills in full; a relay that stops checking in without a goodbye stops accruing time within 30 minutes.
Next steps
- Join networks you already own, with credentials you supply: Topology and the configuration reference.
- Size the pipe for your traffic: Sizing and scaling.
- Watch it: Monitoring and alerts.
- When something does not cross: Troubleshooting.