How to connect services across VPCs privately using PSC, with Shared VPC and subnet design.
The Goal
Allow ArkCI dev runners (GitHub Actions self-hosted runners) to connect to Octopus servers (lab and prod) privately via internal networking β no public internet exposure.
Core Networking Concepts
VPC (Virtual Private Cloud)
A VPC is an isolated private network in Google Cloud. Resources in different VPCs cannot communicate by default.
βββββββββββββββββββββββ βββββββββββββββββββββββ
β VPC-A β β β VPC-B β
β 10.0.0.0/16 βββββββββββΊβ 10.1.0.0/16 β
β β Can't β β
β Server A β talk β Server B β
βββββββββββββββββββββββ βββββββββββββββββββββββ
Shared VPC
A Shared VPC allows multiple GCP projects to share a single VPC network. Thereβs one host project that owns the VPC, and multiple service projects that use it.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SHARED VPC HOST: k-shared-vpc-host-dev β
β β
β Network: shared-vpc-network-default β
β β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β
β β Subnet: β β Subnet: β β Subnet: β β
β β citadel-dev- β β github-actions- β β github-actions- β β
β β tokyo β β dev-virginia β β dev-tokyo [NEW] β β
β β 10.32.x.x/xx β β 10.39.x.x/xx β β 10.36.200.0/24 β β
β βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β
βΌ βΌ βΌ
βββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β SERVICE β β SERVICE β β SERVICE β
β PROJECT: β β PROJECT: β β PROJECT: β
β m-jp- β β k-github- β β k-github- β
β citadel-dev β β actions-dev β β actions-dev β
β β β (runners) β β (PSC endpoints) β
βββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
Subnets
A subnet is a range of IP addresses within a VPC, tied to a specific region. Resources must be created in a subnet that exists in their target region.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SHARED VPC NETWORK β
β β
β VIRGINIA (us-east4) TOKYO (asia-northeast1) β
β βββββββββββββββββββββββ βββββββββββββββββββββββ β
β β k-github- β β k-github- β β
β β actions-dev-virginiaβ β actions-dev-tokyo β β
β β β β β β
β β β’ ArkCI runners β β β’ PSC endpoints β β
β β run here β β (to reach Tokyo β β
β β β β Octopus servers) β β
β βββββββββββββββββββββββ βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Why two subnets?
- ArkCI runners are in Virginia (closer to GitHub, better performance)
- Octopus servers are in Tokyo (where most infrastructure lives)
- PSC endpoints must be in the same region as the service they connect to
Private Service Connect (PSC)
PSC creates a private tunnel between VPCs without exposing services to the internet. It operates on a Consumer-Producer model over the Google Cloud backbone β the producer publishes a service via a Service Attachment, and the consumer creates a PSC Endpoint (an internal IP) in their own VPC that tunnels traffic privately to the producer.
What PSC Solves (vs VPC Peering)
Before PSC, connecting VPCs required VPC Peering, which has three major limitations PSC resolves:
- IP Address Overlaps β PSC works even if consumer and producer use the same IP ranges (e.g., both use
10.0.0.0/24). Peering fails in this scenario. - Security Radius β PSC connects to a specific service, whereas peering opens connectivity to the entire network.
- Operational Complexity β no need to coordinate IP ranges and firewall rules between teams.
| Feature | PSC | VPC Peering |
|---|---|---|
| Connectivity Scope | One specific service | Entire networks |
| IP Overlap | Allowed | Not allowed |
| Traffic Flow | Unidirectional (Consumer β Producer) | Bidirectional |
| Transitivity | Accessible from on-prem/peered networks | Not transitive |
| Administration | Independent, no coordination needed | Requires mutual agreement on IP ranges |
Primary Use Cases
- Google APIs β access BigQuery, Cloud Storage, Vertex AI via private internal IPs
- SaaS consumption β connect to MongoDB Atlas, Snowflake, Confluent within Googleβs network
- Cross-organization access β share services across teams/acquisitions without merging networks or refactoring IP addresses
Components of PSC
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β SERVICE PROVIDER β
β (k-octopus-lab) β
β β
β βββββββββββββββ βββββββββββββββββββ ββββββββββββββββββββ β
β β Octopus β β Internal Load β β SERVICE β β
β β Pods βββββββΊβ Balancer (ILB) βββββββΊβ ATTACHMENT β β
β β β β β β β β
β βββββββββββββββ βββββββββββββββββββ β "I'm publishing β β
β β this service" β β
β β β β
β β Allowed: β β
β β β’ citadel-dev β β
β β β’ citadel-lab β β
β β β’ github-actionsβ β
β β -dev [NEW] β β
β ββββββββββ¬ββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββ
β
PSC Connectionβ(private)
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌββββββββββββββ
β SERVICE CONSUMER β β
β (k-github-actions-dev) β β
β βΌ β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β PSC ENDPOINT β β
β β β β
β β βββββββββββββββββββββββ βββββββββββββββββββββββ β β
β β β COMPUTE ADDRESS β β FORWARDING RULE β β β
β β β β β β β β
β β β Internal IP: βββββββΊβ Target: Service β β β
β β β 10.36.200.x β β Attachment URI β β β
β β β β β β β β
β β β "Traffic to this β β "Route traffic to β β β
β β β IP goes to PSC" β β the provider" β β β
β β βββββββββββββββββββββββ βββββββββββββββββββββββ β β
β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
How PSC Works
- Provider creates a Service Attachment that wraps an Internal Load Balancer
- Provider whitelists allowed consumer projects (
consumer_accept_lists) - Consumer creates a PSC Endpoint (address + forwarding rule)
- The endpoint gets a private IP in the consumerβs VPC
- Traffic to that IP is tunneled to the providerβs service
DNS Configuration
For services to connect using a hostname instead of IP, we need DNS. See DNS Zones & Forwarding Rules for full details.
Private DNS Zone
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β PRIVATE DNS ZONE: psc.internal β
β (k-github-actions-dev) β
β β
β Visibility: Private (only visible to the dev shared VPC) β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β DNS RECORDS β β
β β β β
β β octopus.lab.psc.internal βββΊ 10.36.200.x (PSC endpoint IP) β β
β β octopus.prod.psc.internal βββΊ 10.36.200.y (PSC endpoint IP) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Complete Data Flow
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β ArkCI Dev Runner (Virginia) β
β β
β curl https://octopus.lab.psc.internal/api β
β β β
β β Step 1: DNS Lookup β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β Private DNS Zone (psc.internal) β β
β β octopus.lab.psc.internal β 10.36.200.x β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β β Step 2: Connect to IP β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β PSC Endpoint (Tokyo subnet) β β
β β 10.36.200.x β β
β β β β
β β Forwarding Rule targets: β β
β β octopus-lab service attachment β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β β Step 3: PSC Tunnel (private, cross-VPC) β
β βΌ β
βββββββββΌββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Octopus Lab Server (Tokyo) β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β Service Attachment β β
β β octopus-server-psc β β
β β β β
β β Accepts: k-github-actions-dev β β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
β β Internal Load Balancer β Octopus Pods β β
β βββββββββββββββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Why Not Just Use Public URLs?
| Approach | Pros | Cons |
|---|---|---|
Public URL (octopus.lab.citadelapps.com) | Simple, no setup | Exposed to internet, requires firewall rules |
PSC (octopus.lab.psc.internal) | Private, secure, no internet exposure | More complex setup |
PSC is preferred for internal services because:
- Traffic never leaves Googleβs network
- No public IP exposure
- Fine-grained access control via
consumer_accept_lists
See also
- PSC Terraform Resources β Terraform
google_compute_addressandgoogle_compute_forwarding_rulefor PSC endpoints - DNS Zones & Forwarding Rules β private DNS zones for PSC endpoint hostname resolution
- Shared VPC Knowledge β host/service project attachment and subnet sharing
- Cloud NAT & VPC Networking β NAT gateway configuration for VPC egress
Interview Prep
Q: What is Private Service Connect and how does it differ from VPC Peering?
A: PSC is a networking capability that allows VPCs to access specific services privately without peering entire networks. Unlike VPC Peering β which connects entire networks, requires non-overlapping IP ranges, and is bidirectional β PSC connects to a single service, allows overlapping IPs, and is unidirectional (consumer β producer). PSC uses a Consumer-Producer model: the producer publishes a Service Attachment wrapping an Internal Load Balancer, and the consumer creates a PSC Endpoint (internal IP + forwarding rule) that tunnels traffic privately to that attachment.
Q: Can PSC work when consumer and producer VPCs have overlapping IP ranges?
A: Yes. This is one of PSCβs key advantages over VPC Peering. PSC creates a private tunnel at the SDN level β the consumer accesses the service via a local internal IP in their own VPC, and Googleβs network fabric routes it to the producerβs service attachment. The two VPCs never merge routing tables, so overlapping CIDRs are not a problem.
Q: Why must a PSC endpoint be in the same region as the service it connects to?
A: PSC endpoints connect to Service Attachments, which are regional resources backed by regional Internal Load Balancers. The forwarding rule and compute address that form the PSC endpoint must be in the same region as the target service attachment. If your runners are in Virginia but the service is in Tokyo, you create the PSC endpoint in a Tokyo subnet β cross-region routing within the same VPC handles the rest.
Q: Walk through the complete data flow when a runner in Virginia connects to an Octopus server in Tokyo via PSC.
A: (1) The runner calls octopus.lab.psc.internal. (2) Cloud DNS resolves this via a private zone to the PSC endpointβs internal IP (e.g., 10.36.200.x) in the Tokyo subnet. (3) The VPC routes the packet to Tokyo (same VPC, cross-region). (4) The forwarding rule on that IP matches and tunnels the packet to the producerβs Service Attachment URI. (5) The Service Attachment checks the consumer project against its consumer_accept_lists. (6) If allowed, traffic reaches the producerβs Internal Load Balancer, which forwards to the Octopus pods. The return path reverses through the same tunnel.
Q: What is the network field in a PSC forwarding rule, and why is it required even when the address already specifies a subnetwork?
A: Internal IPs like 10.x.x.x are only unique within a VPC. The network field tells the forwarding rule which VPCβs routing table to register in. In a Shared VPC, the network is managed by the host project while the forwarding rule lives in a service project β the explicit network field ensures the endpoint is visible to the entire shared VPC, not just the service projectβs local scope.